<script>
import { line, curveLinear, Delaunay, range, scaleLinear, scaleUtc } from 'd3';
import data from './line-data';
const marginTop = $ChartDocs[0].value;
const marginRight = $ChartDocs[1].value;
const marginBottom = $ChartDocs[2].value;
const marginLeft = $ChartDocs[3].value;
const inset = $ChartDocs[4].value;
const width = $ChartDocs[5].value;
const height = $ChartDocs[6].value;
const xLabel = $ChartDocs[7].value;
const yLabel = $ChartDocs[8].value;
const xFormat = $ChartDocs[9].value;
const yFormat = $ChartDocs[10].value;
const horizontalGrid = $ChartDocs[11].value;
const verticalGrid = $ChartDocs[12].value;
const colors = $ChartDocs[13].value;
const showDots = $ChartDocs[14].value;
const dotsFilled = $ChartDocs[15].value;
const r = $ChartDocs[16].value;
const strokeWidth = $ChartDocs[17].value;
const strokeOpacity = $ChartDocs[18].value;
const tooltipBackground = $ChartDocs[19].value;
const tooltipTextColor = $ChartDocs[20].value;
const strokeLinecap = 'round';
const strokeLinejoin = 'round';
const xScalefactor = width / 80;
const yScalefactor = height / 40;
const curve = curveLinear;
const xType = scaleUtc;
const insetTop = inset;
const insetRight = inset;
const insetBottom = inset;
const insetLeft = inset;
const xRange = [marginLeft + insetLeft, width - marginRight - insetRight];
const yType = scaleLinear;
const yRange = [height - marginBottom - insetBottom, marginTop + insetTop];
let x, y, dotInfo, lines, xVals = [], yVals = [], points = [], subsets = [], colorVals = [];
if (!('data' in data[0])) {
x = Object.keys(data[0])[0];
y = Object.keys(data[0])[1];
xVals = data.map((el) => el[x]);
yVals = data.map((el) => el[y]);
colorVals = data.map((el) => 0);
points = data.map((el) => ({
x: el[x],
y: el[y],
color: 0
}));
}
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);
});
}
const I = range(xVals.length);
const gaps = (d, i) => !isNaN(xVals[i]) && !isNaN(yVals[i]);
const cleanData = points.map(gaps);
const xDomain = [xVals[0], xVals[xVals.length - 1]];
const yDomain = [0, Math.max(...yVals)];
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const niceY = scaleLinear().domain([0, Math.max(...yVals)]).nice();
const 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));
});
}
const pointsScaled = points.map((el) => [xScale(el.x), yScale(el.y), el.color]);
const delaunayGrid = Delaunay.from(pointsScaled);
const voronoiGrid = delaunayGrid.voronoi([0, 0, width, height]);
const xTicks = xScale.ticks(xScalefactor);
const xTicksFormatted = xTicks.map((el) => el.getFullYear());
const 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}
<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} 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, i}
<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="-{marginLeft}" y="5">{tick + yFormat}</text>
</g>
{/each}
<text x="-{marginLeft}" 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 + 70} />
{/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].clientX + 12}px; top:{dotInfo[2].clientY + 12}px; pointer-events:none; background-color:{tooltipBackground}; color:{tooltipTextColor}'>
{subsets ? subsets[points[dotInfo[1]].color] : ''}:
{points[dotInfo[1]].x.getFullYear()}: {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 {
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;
text-anchor: start;
}
.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;
}
</style>
const csvNortheast = `Year,Population
1920,29.662053
1930,34.427091
1940,35.976777
1950,39.477986
1960,44.677819
1970,49.040703
1980,49.135283
1990,50.809229
2000,53.594378
2010,55.317240
2020,57.609148`;
const csvMidwest = `Year,Population
1920,34.019792
1930,38.594100
1940,40.143332
1950,44.460762
1960,51.619139
1970,56.571663
1980,58.865670
1990,59.668632
2000,64.392776
2010,66.927001
2020,68.985454`;
const csvSouth = `Year,Population
1920,33.125803
1930,37.857633
1940,41.665901
1950,47.197088
1960,54.973113
1970,62.795367
1980,75.372362
1990,85.445930
2000,100.236820
2010,114.555744
2020,126.266107`;
const csvWest = `Year,Population
1920,9.213920
1930,12.323836
1940,14.379119
1950,20.189962
1960,28.053104
1970,34.804193
1980,43.172490
1990,52.786082
2000,63.197932
2010,71.945553
2020,78.588572`;
function csvConvert(csv) {
return csv.split('\n').slice(1).map(str => {
const [date, population] = str.split(',')
.map((el) => (el > 1900 ? new Date(el, 0) : parseFloat(el)));
return { date, population };
});
}
const northeast = csvConvert(csvNortheast);
const midwest = csvConvert(csvMidwest);
const south = csvConvert(csvSouth);
const west = csvConvert(csvWest);
export default [
{
id: 'Northeast',
data: northeast
},
{
id: 'Midwest',
data: midwest
},
{
id: 'South',
data: south
},
{
id: 'West',
data: west
}
]
const firstDataSet =
"title_X,title_Y
x-value, y-value
x-value, y-value
x-value, y-value
const secondDataSet =
"title_X,title_Y
x-value, y-value
x-value, y-value
x-value, y-value
const thirdDataSet =
"title_X,title_Y
x-value, y-value
x-value, y-value
x-value, y-value
export { firstDataSet, secondDataSet, thirdDataSet };