<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>