<script lang="ts"> //@ts-nocheck import { line, curveLinear, Delaunay, range, scaleLinear } from 'd3'; export let data; const marginTop = 30; // the top margin, in pixels const marginRight = 30; // the right margin, in pixels const marginBottom = 30; // the bottom margin, in pixels const marginLeft = 100; // the left margin, in pixels const inset = 0; // inset the default range, in pixels const width = 600; // the outer width of the chart, in pixels const height = 400; // the outer height of the chart, in pixels const xLabel = ''; // a label for the y-axis const yLabel = 'Résultat net'; // a label for the y-axis const xFormat = ''; // a format specifier string for the y-axis const yFormat = '€'; // a format specifier string for the y-axis const horizontalGrid = true; // show horizontal grid lines const verticalGrid = true; // show vertical grid lines const colors = ["#b81111", "#ffbf00"]; // fill color for dots && number of colors in fill array MUST match number of subsets in data const showDots = false; // whether dots should be displayed const dotsFilled = true; // whether dots should be filled or outlined const r = 0; // (fixed) radius of dots, in pixels const strokeWidth = 1; // stroke width of line, in pixels const strokeOpacity = [1]; // stroke opacity of line const tooltipBackground = 'white'; // background color of tooltip const tooltipTextColor = 'black'; // text color of tooltip const strokeLinecap = 'round'; // stroke line cap of the line const strokeLinejoin = 'round'; // stroke line join of the line const xScalefactor = width / 180; //y-axis number of values const yScalefactor = height / 40; //y-axis number of values const curve = curveLinear; // method of interpolation between points const xType = scaleLinear; // type of x-scale const insetTop = 50; // inset from top const insetRight = inset; // inset from right const insetBottom = inset; // inset fro bottom const insetLeft = inset; // inset from left const xRange = [marginLeft + insetLeft, width - marginRight - insetRight]; // [left, right] const yType = scaleLinear; // type of y-scale const yRange = [height - marginBottom - insetBottom, marginTop + insetTop]; // [bottom, top] let x: string, y:string, dotInfo, lines, xVals = [], yVals = [], points = [], subsets = [], colorVals = []; let I, xScale, gaps, cleanData, xDomain, yDomain, yScale, niceY, chartLine, pointsScaled, delaunayGrid, voronoiGrid, xTicks, xTicksFormatted, yTicks $: { // For a single set of data xVals = [] yVals = [] if (!('data' in data[0])) { x = Object.keys(data[0])[0]; y = Object.keys(data[0])[1]; xVals = data.map((el: Record<string, any>) => el[x]); yVals = data.map((el: Record<string, any>) => el[y]); colorVals = data.map((_) => 0); points = data.map((el) => ({ x: el[x], y: el[y], color: 0 })); } // For data with subsets (NOTE: expects 'id' and 'data' keys) else { x = Object.keys(data[0]?.data[0])[0]; y = Object.keys(data[0]?.data[0])[1]; data.forEach((subset, i) => { subset.data.forEach((coordinate) => { xVals.push(coordinate[x]); yVals.push(coordinate[y]); colorVals.push(i); points.push( { x: coordinate[x], y: coordinate[y], color: i }); }); subsets.push(subset.id); }); } I = range(xVals.length); gaps = (_d, i) => !isNaN(xVals[i]) && !isNaN(yVals[i]); cleanData = points.map(gaps); xDomain = [xVals[0], xVals[xVals.length - 1]]; yDomain = [Math.min(...yVals), Math.max(...yVals)]; xScale = xType(xDomain, xRange); yScale = yType(yDomain, yRange); niceY = scaleLinear().domain([Math.min(...yVals), Math.max(...yVals)]).nice(); chartLine = line() .defined(i => cleanData[i]) .curve(curve) .x(i => xScale(xVals[i])) .y(i => yScale(yVals[i])); lines = []; colors.forEach((_color, j) => { const filteredI = I.filter((_el, i) => colorVals[i] === j); lines.push(chartLine(filteredI)); }); console.log("lines", lines) pointsScaled = points.map((el) => [xScale(el.x), yScale(el.y), el.color]); delaunayGrid = Delaunay.from(pointsScaled); voronoiGrid = delaunayGrid.voronoi([0, 0, width, height]); xTicks = xScale.ticks(xScalefactor); xTicksFormatted = xTicks.map((el) => el) yTicks = niceY.ticks(yScalefactor); } </script> <div class="chart-container"> <svg {width} {height} viewBox="0 0 {width} {height}" cursor='crosshair' on:mouseout="{() => dotInfo = null}" on:blur="{() => dotInfo = null}" > <!-- Dots (if enabled) --> {#if showDots && !dotInfo} {#each I as i} <g class='dot' pointer-events='none'> <circle cx={xScale(xVals[i])} cy={yScale(yVals[i])} r={r} stroke={colors[colorVals[i]]} fill={dotsFilled ? colors[colorVals[i]] : 'none'} /> </g> {/each} {/if} <!-- Chart lines --> {#each lines as subsetLine, i} <g class='chartlines' pointer-events='none'> {#if dotInfo && false} <path class="line" fill='none' stroke-opacity={points[dotInfo[1]].color === i ? '1' : '0.1'} stroke={colors[i]} d={subsetLine} stroke-width={strokeWidth} stroke-linecap={strokeLinecap} stroke-linejoin={strokeLinejoin}/> <circle cx={xScale(points[dotInfo[1]].x)} cy={yScale(points[dotInfo[1]].y)} r={r} stroke={colors[points[dotInfo[1]].color]} fill={dotsFilled} /> {:else} <path class="line" fill='none' stroke={colors[i]} d={subsetLine} stroke-opacity={strokeOpacity[i]} stroke-width={strokeWidth} stroke-linecap={strokeLinecap} stroke-linejoin={strokeLinejoin} /> {/if} </g> {/each} <!-- Y-axis and horizontal grid lines --> <g class="y-axis" transform="translate({marginLeft}, 0)" pointer-events='none'> <path class="domain" stroke="black" d="M{insetLeft}, {marginTop} V{height - marginBottom + 6}"/> {#each yTicks as tick} <g class="tick" transform="translate(0, {yScale(tick)})"> <line class="tick-start" x1={insetLeft - 6} x2={insetLeft}/> {#if horizontalGrid} <line class="tick-grid" x1={insetLeft} x2={width - marginLeft - marginRight}/> {/if} <text x="-10" y="5">{tick + yFormat}</text> </g> {/each} <text text-anchor="middle" x="0" y={marginTop - 10}>{yLabel}</text> </g> <!-- X-axis and vertical grid lines --> <g class="x-axis" transform="translate(0,{height - marginBottom - insetBottom})" pointer-events='none'> <path class="domain" stroke="black" d="M{marginLeft},0.5 H{width - marginRight}"/> {#each xTicks as tick, i} <g class="tick" transform="translate({xScale(tick)}, 0)"> <line class="tick-start" stroke='black' y2='6' /> {#if verticalGrid} <line class="tick-grid" y2={-height + insetTop+ marginTop} /> {/if} <text font-size='8px' x={-marginLeft/4} y="20">{xTicksFormatted[i] + xFormat}</text> </g> {/each} <text x={width - marginLeft - marginRight - 40} y={marginBottom}>{xLabel}</text> </g> {#each pointsScaled as point, i} <path stroke="none" fill-opacity="0" class="voronoi-cell" d={voronoiGrid.renderCell(i)} on:mouseover="{(e) => dotInfo = [point, i, e] }" on:focus="{(e) => dotInfo = [point, i, e] }" ></path> {/each} </svg> </div> <!-- Tooltip --> {#if dotInfo} <div class="tooltip" style='position:absolute; left:{dotInfo[2].pageX + 12}px; top:{dotInfo[2].pageY + 12}px; pointer-events:none; background-color:{tooltipBackground}; color:{tooltipTextColor}'> {points[dotInfo[1]].x}: {points[dotInfo[1]].y.toFixed(2)}{yFormat} </div> {/if} <style> .chart-container { justify-content: center; align-items: center; margin-top: 50px; margin-left: 8 0px; } svg { max-width: 100%; height: auto; height: "intrinsic"; margin: auto; } path { fill: "green" } .y-axis { font-size: "10px"; font-family: sans-serif; text-anchor: "end"; } .x-axis { font-size: "10px"; font-family: sans-serif; text-anchor: "end"; } .tick text { font-size: 80%; fill: white !important } .tick { opacity: 1; } .tick-start { stroke: black; stroke-opacity: 1; } .tick-grid { stroke: black; stroke-opacity: 0.2; font-size: "11px"; color: black; } .tick text { fill: black; } .y-axis .tick text { text-anchor: end !important; } @media (prefers-color-scheme: dark) { .y-axis text { fill: white; } .x-axis text { fill: white; } .tick-start { stroke: white; } .tick-grid { stroke: white; } .tick text { fill: white; } .domain { stroke: white } } .tooltip{ border-radius: 5px; padding: 5px; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; opacity: 1; } </style>