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
All checks were successful
Deploy production / deploy (push) Successful in 2m36s
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user