ActivityCharts: add avg/P20/P80 reference lines to speed, cadence, and power line charts

This commit is contained in:
Davide Scaini
2026-05-12 23:24:33 +02:00
parent 0b266d208c
commit 3231fdb4b7
+29
View File
@@ -147,6 +147,17 @@
};
}
// ── Reference lines (avg, P20, P80) — speed, cadence, and power ────────
function refStats(tab: Tab, vals: number[]) {
if (tab !== 'speed' && tab !== 'cadence' && tab !== 'power') return null;
const moving = vals.filter(v => v > 0);
if (moving.length < 5) return null;
const avg = moving.reduce((a, b) => a + b, 0) / moving.length;
const sorted = [...moving].sort((a, b) => a - b);
const q = (p: number) => sorted[Math.max(0, Math.floor((sorted.length - 1) * p))];
return { avg, p20: q(0.2), p80: q(0.8) };
}
// ── Rendering ────────────────────────────────────────────────────────────
let themeObserver: MutationObserver | null = null;
@@ -212,6 +223,24 @@
);
}
// ── Reference lines (avg, P20, P80) ─────────────────────────────────────
const ref = refStats(activeTab, metricValues);
if (ref) {
const xEnd = (data[data.length - 1] as any)?.[x];
marks.push(
Plot.ruleY([ref.avg], { stroke: color, strokeWidth: 1.5, strokeDasharray: '6,4', strokeOpacity: 0.65 }),
Plot.ruleY([ref.p20, ref.p80], { stroke: tc.axis, strokeWidth: 1, strokeDasharray: '3,4', strokeOpacity: 0.55 }),
Plot.text(
[
{ xv: xEnd, yv: ref.avg, label: `avg ${Math.round(ref.avg)}` },
{ xv: xEnd, yv: ref.p20, label: `P20 ${Math.round(ref.p20)}` },
{ xv: xEnd, yv: ref.p80, label: `P80 ${Math.round(ref.p80)}` },
],
{ x: 'xv', y: 'yv', text: 'label', textAnchor: 'end', dx: -4, dy: -6, fill: tc.axis, fontSize: 10 },
),
);
}
marks.push(
Plot.ruleX(data, Plot.pointerX({ x, stroke: tc.rule, strokeWidth: 1, strokeDasharray: '4,4' })),
Plot.dot(data, Plot.pointerX({ x, y: yKey, r: 4, fill: color, stroke: tc.tooltipBg, strokeWidth: 1.5 })),