Time-Series Chart, W Svelte, Pancake, TypeScript
February 6, 2023•553 words
<script lang="ts">
import * as Pancake from '@sveltejs/pancake';
interface Point {
x: number,
y: number,
};
interface IndividualPoint extends Point {
langInfo: ChartItem,
}
interface ChartItem {
name: string,
data: Point[],
color?: string,
}
export let programmingLanguages: ChartItem[] = [];
// Axis for chart
let x1 = +Infinity;
let x2 = -Infinity
let y1 = +Infinity;
let y2 = -Infinity;
// Set axis based on data
programmingLanguages.forEach(progLang => {
progLang.data.forEach(d => {
x1 = d.x < x1 ? d.x : x1;
x2 = d.x > x2 ? d.x : x2;
y1 = d.y < y1 ? d.y : y1;
y2 = d.y > y2 ? d.y : y2;
});
});
// Highlighted point, closest to mouse
let closest: IndividualPoint | undefined;
// Value of filter search field
let filterValue = '';
// Filter programming languages based on search field
const filterNodes = (progLang: ChartItem) => progLang.name.toLowerCase().includes(filterValue.toLowerCase());
// Data to display, filter if search filed has value
$: filtered = filterValue ? programmingLanguages.filter(filterNodes) : programmingLanguages;
// Points to display, used for quadtree
// $:points = [];
$: points = filtered.reduce((points: ChartItem[] | any, langInfo: ChartItem) => {
const reduced = langInfo.data.map(d => ({ x: d.x, y: d.y, langInfo}));
return [...points, ...reduced];
}, []);
</script>
<input placeholder="Type to filter" bind:value={filterValue}>
<div class="chart">
<Pancake.Chart {x1} {x2} y1={y1} y2={y2}>
<!-- Horizontal lines -->
<Pancake.Grid horizontal count={5} let:value>
<div class="grid-line horizontal"><span>{value}</span></div>
</Pancake.Grid>
<!-- Vertical Lines -->
<Pancake.Grid vertical count={5} let:value>
<span class="x-label">{value}</span>
</Pancake.Grid>
<!-- The actual charts -->
<Pancake.Svg>
{#each filtered as progLang}
<Pancake.SvgLine data={progLang.data} let:d>
<path class="data" style={`--langColor: ${progLang.color}a8;`} {d}></path>
</Pancake.SvgLine>
{/each}
<!-- Highlighted line (nearest mouse pointer) -->
{#if closest?.langInfo}
<Pancake.SvgLine data={closest.langInfo.data} let:d>
<path class="highlight" style={`--langColor: ${closest.langInfo.color};`} {d}></path>
</Pancake.SvgLine>
{/if}
</Pancake.Svg>
<!-- Shows circular dot, on data point nearest mouse pointer -->
{#if closest?.langInfo}
<Pancake.Point x={closest.x} y={closest.y}>
<span class="annotation-point"></span>
<div class="annotation" style="transform: translate(-{100 * ((closest.x - x1) / (x2 - x1))}%,0)">
<strong>{closest.langInfo.name}</strong>
<span>{closest.x}: {closest.y} years</span>
</div>
</Pancake.Point>
{/if}
<Pancake.Quadtree data={points} bind:closest/>
</Pancake.Chart>
</div>
<style lang="scss">
.chart {
height: 400px;
padding: 2rem 1rem;
margin: 0 auto;
}
input {
font-size: 0.75rem;
font-family: inherit;
padding: 0.5em;
color: var(--accent);
background: var(--background);
border: var(--card-border);
border-radius: var(--curve-factor);
&:focus, &:hover {
outline: none;
}
}
.grid-line {
position: relative;
display: block;
}
.grid-line.horizontal {
width: calc(100% + 2em);
left: -2em;
border-bottom: 1px dashed var(--dimmed-text);
}
.grid-line span {
position: absolute;
left: 0;
bottom: 0.2rem;
color: var(--foreground);
opacity: 0.8;
}
.x-label {
position: absolute;
width: 4rem;
left: -2rem;
bottom: -2rem;
color: var(--foreground);
opacity: 0.8;
text-align: center;
}
path.data {
stroke: var(--langColor);
stroke-linejoin: round;
stroke-linecap: round;
stroke-width: 1px;
fill: none;
}
.highlight {
stroke: var(--langColor);
fill: none;
stroke-width: 2;
}
.annotation {
position: absolute;
white-space: nowrap;
bottom: 1rem;
background-color: var(--card-background);
border: var(--card-border);
border-radius: var(--curve-factor);
padding: 0.2rem 0.4rem;
}
.annotation-point {
position: absolute;
width: 0.75rem;
height: 0.75rem;
background-color: var(--foreground);
border-radius: 50%;
transform: translate(-50%,-50%);
}
.annotation strong {
display: block;
font-size: 20px;
}
.annotation span {
display: block;
font-size: 14px;
}
</style>
Usage:
<script lang="ts">
import TimeLines from '$src/components/charts/TimeLines.svelte';
const programmingLanguages = [
{ name: 'United States', color: '#88ff88', data: [
{ x: 2020, y: 1 },
{ x: 2021, y: 2 },
{ x: 2022, y: 3 },
]},
{ name: 'Somewhere else', color: '#ff0099', data: [
{ x: 2020, y: 0 },
{ x: 2021, y: 4 },
{ x: 2022, y: 6 },
]},
];
</script>
<TimeLines programmingLanguages={programmingLanguages} />