Money tracker: vertical month/year gridlines on wealth chart, keep range in ?range= URL param
All checks were successful
Deploy production / deploy (push) Successful in 2m36s

This commit is contained in:
2026-10-24 02:25:12 +00:00
parent faeb76a7e7
commit 2af279e8a9
4 changed files with 140 additions and 6 deletions

View File

@@ -1,16 +1,19 @@
<script lang="ts">
import type { ChartPoint, ChartSeries } from './charts';
import { timeGrid, yearLabels, type ChartPoint, type ChartSeries, type GridMode } from './charts';
let {
series,
title,
unit = '€',
marker
marker,
grid
}: {
series: ChartSeries[];
title: string;
unit?: string;
marker?: { date: string; label: string };
/** Vertical gridlines: every month (1 January drawn stronger) or every year. */
grid?: GridMode;
} = $props();
let clientWidth = $state(800);
let width = $derived(Math.max(280, clientWidth));
@@ -26,6 +29,14 @@
let from = $derived(Math.min(...times));
let until = $derived(Math.max(...times));
let dateSpan = $derived(until - from || 1);
let gridLines = $derived(
grid && values.length ? timeGrid(isoDate(from), isoDate(until), grid) : []
);
let axisTicks = $derived(
grid === 'years'
? yearLabels(gridLines, Math.max(2, Math.floor((width - plot.left - plot.right) / 44)))
: ticks()
);
let snapDates = $derived(
[...new Set(series.flatMap((item) => item.points.map((point) => point.date)))].sort()
);
@@ -59,6 +70,9 @@
return { min, max, ticks };
}
function isoDate(ms: number) {
return new Date(ms).toISOString().slice(0, 10);
}
function time(date: string) {
return Date.parse(`${date}T12:00:00Z`);
}
@@ -108,6 +122,9 @@
}
return result;
}
function tickLabel(date: string) {
return grid === 'years' ? date.slice(0, 4) : dateLabel(date);
}
function dateLabel(date: string, withDay = false) {
return new Date(time(date)).toLocaleDateString('en-NL', {
day: withDay ? 'numeric' : undefined,
@@ -145,6 +162,16 @@
onpointerdown={hover}
onpointerleave={() => (hoverDate = null)}
>
{#each gridLines as line (line.date)}
<line
class="grid-x"
class:year={line.year && grid === 'months'}
x1={x(line.date)}
x2={x(line.date)}
y1={plot.top}
y2={height - plot.bottom}
/>
{/each}
{#each scale.ticks as value (value)}
<line
x1={plot.left}
@@ -162,13 +189,13 @@
font-size="11">{format(value)}</text
>
{/each}
{#each ticks() as tick (tick)}
{#each axisTicks as tick (tick)}
<text
x={x(tick)}
y={height - 8}
text-anchor="middle"
fill="var(--site-muted)"
font-size="11">{dateLabel(tick)}</text
font-size="11">{tickLabel(tick)}</text
>
{/each}
{#if marker && time(marker.date) >= from && time(marker.date) <= until}
@@ -294,6 +321,18 @@
overflow: visible;
user-select: none;
}
/* Vertical time gridlines: quieter than the value gridlines so the data stays in front. */
.grid-x {
stroke: var(--site-border);
stroke-width: 1;
stroke-opacity: 0.55;
shape-rendering: crispEdges;
}
.grid-x.year {
stroke: var(--site-muted);
stroke-width: 1.5;
stroke-opacity: 0.4;
}
.tooltip {
position: absolute;
top: 4px;