From 653db2428f35e4a0a97f7b5fa44b61485ba16df5 Mon Sep 17 00:00:00 2001 From: Davide Scaini Date: Thu, 14 May 2026 18:43:05 +0200 Subject: [PATCH] nerd corner: add cumulative plot below the per-period chart --- site/src/components/NerdCorner.svelte | 66 +++++++++++++++++++++------ 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/site/src/components/NerdCorner.svelte b/site/src/components/NerdCorner.svelte index 45ffc80..b6093ee 100644 --- a/site/src/components/NerdCorner.svelte +++ b/site/src/components/NerdCorner.svelte @@ -76,31 +76,48 @@ $: ({ rows, colorDomain, colorRange } = buildData(activities, metric, granularity)); - let chartEl: HTMLElement; + let chartEl: HTMLElement; + let chartCumEl: HTMLElement; - function renderChart( + function renderChartInto( + el: HTMLElement, rows: { year: string; period: number; value: number }[], colorDomain: string[], colorRange: string[], m: Metric, g: Granularity, + cumulative: boolean, ) { - if (!chartEl) return; - chartEl.innerHTML = ''; + if (!el) return; + el.innerHTML = ''; if (!rows.length) return; + // For the cumulative chart, convert per-period rows to running sums. + // rows are ordered: for each year (sorted asc), periods 1..limit in order. + let displayRows = rows; + if (cumulative) { + const acc = new Map(); + displayRows = rows.map(r => { + const prev = acc.get(r.year) ?? 0; + const cum = prev + r.value; + acc.set(r.year, cum); + return { ...r, value: cum }; + }); + } + const maxPer = g === 'week' ? 52 : 12; const xLabel = g === 'week' ? 'Week' : 'Month'; const axColor = document.documentElement.getAttribute('data-theme') === 'light' ? '#52525b' : '#a1a1aa'; const fmt = METRIC_FMT[m]; const curYear = String(_currentYear); - const pastRows = rows.filter(r => r.year !== curYear); - const curRows = rows.filter(r => r.year === curYear); + const pastRows = displayRows.filter(r => r.year !== curYear); + const curRows = displayRows.filter(r => r.year === curYear); + const yLabel = cumulative ? `Cumulative ${METRIC_LABEL[m]}` : METRIC_LABEL[m]; const MONTH_LABELS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; const chart = Plot.plot({ - width: chartEl.clientWidth || 700, + width: el.clientWidth || 700, height: 320, marginLeft: 60, marginBottom: 40, @@ -113,8 +130,8 @@ ? (d: number) => MONTH_LABELS[d - 1] : (d: number) => String(d), }, - y: { label: METRIC_LABEL[m], grid: true, zero: true }, - color: { domain: colorDomain, range: colorRange, legend: true }, + y: { label: yLabel, grid: true, zero: true }, + color: { domain: colorDomain, range: colorRange, legend: !cumulative }, marks: [ ...(pastRows.length ? [ Plot.line(pastRows, { @@ -140,19 +157,31 @@ ] : []), ], }); - chartEl.appendChild(chart); + el.appendChild(chart); } - $: renderChart(rows, colorDomain, colorRange, metric, granularity); + function renderBoth( + rows: { year: string; period: number; value: number }[], + colorDomain: string[], + colorRange: string[], + m: Metric, + g: Granularity, + ) { + renderChartInto(chartEl, rows, colorDomain, colorRange, m, g, false); + renderChartInto(chartCumEl, rows, colorDomain, colorRange, m, g, true); + } + + $: renderBoth(rows, colorDomain, colorRange, metric, granularity); // Keep current values for resize / theme callbacks let _r = rows, _cd = colorDomain, _cr = colorRange, _m = metric, _g = granularity; $: _r = rows; $: _cd = colorDomain; $: _cr = colorRange; $: _m = metric; $: _g = granularity; onMount(() => { - const ro = new ResizeObserver(() => renderChart(_r, _cd, _cr, _m, _g)); + const redraw = () => renderBoth(_r, _cd, _cr, _m, _g); + const ro = new ResizeObserver(redraw); ro.observe(chartEl); - const mo = new MutationObserver(() => renderChart(_r, _cd, _cr, _m, _g)); + const mo = new MutationObserver(redraw); mo.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); return () => { ro.disconnect(); mo.disconnect(); }; }); @@ -175,6 +204,14 @@ } .pill.active { background: #1d4ed822; border-color: #60a5fa; color: #60a5fa; } .pill:hover:not(.active) { border-color: #a1a1aa; color: #d4d4d8; } + .section-label { + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #52525b; + margin: 2rem 0 0.5rem; + }
@@ -191,6 +228,9 @@
+ +
+ {#if !rows.length}

No activity data to display.

{/if}