ActivityCharts: smoothing toggle (Raw/10s/20s) for all line chart metrics
This commit is contained in:
@@ -14,10 +14,13 @@
|
||||
type Tab = 'elevation' | 'speed' | 'hr' | 'cadence' | 'power';
|
||||
type XMode = 'time' | 'distance';
|
||||
type ChartType = 'line' | 'histogram';
|
||||
type SmoothMode = 'raw' | '10s' | '20s';
|
||||
const SMOOTH_HALF: Record<SmoothMode, number> = { raw: 0, '10s': 5, '20s': 10 };
|
||||
|
||||
let activeTab: Tab = 'elevation';
|
||||
let xMode: XMode = 'time';
|
||||
let chartType: ChartType = 'line';
|
||||
let smoothMode: SmoothMode = 'raw';
|
||||
let chartEl: HTMLDivElement;
|
||||
let chart: SVGElement | null = null;
|
||||
|
||||
@@ -183,7 +186,7 @@
|
||||
onDestroy(() => { chart?.remove(); chart = null; themeObserver?.disconnect(); });
|
||||
|
||||
$: if (chartEl) {
|
||||
activeTab; xMode; chartType; histData; histThresholds; alignZones;
|
||||
activeTab; xMode; chartType; histData; histThresholds; alignZones; smoothMode;
|
||||
renderChart();
|
||||
}
|
||||
|
||||
@@ -228,13 +231,14 @@
|
||||
// control points and visual artifacts — use linear instead.
|
||||
const curve = xMode === 'distance' ? 'linear' : 'monotone-x';
|
||||
|
||||
// Smooth cadence and power for visual rendering only (±5 s centred window = 11 s).
|
||||
// Raw data is still used for the histogram, reference-line stats, and tooltip.
|
||||
const needsSmooth = activeTab === 'cadence' || activeTab === 'power';
|
||||
const smoothed = needsSmooth
|
||||
? rollingMean(data.map(d => (d as any)[yKey] as number | null), 5)
|
||||
: null;
|
||||
const lineData = smoothed ? data.map((d, i) => ({ ...d, [yKey]: smoothed[i] })) : data;
|
||||
// Apply smoothing for visual rendering only — raw data still used for stats/histogram.
|
||||
const halfWin = SMOOTH_HALF[smoothMode];
|
||||
const lineData = halfWin > 0
|
||||
? (() => {
|
||||
const s = rollingMean(data.map(d => (d as any)[yKey] as number | null), halfWin);
|
||||
return data.map((d, i) => ({ ...d, [yKey]: s[i] }));
|
||||
})()
|
||||
: data;
|
||||
|
||||
if (activeTab === 'cadence') {
|
||||
marks.push(Plot.lineY(lineData, { x, y: yKey, stroke: color, strokeWidth: 1.5, curve }));
|
||||
@@ -265,9 +269,9 @@
|
||||
}
|
||||
|
||||
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 })),
|
||||
Plot.text(data, Plot.pointerX({
|
||||
Plot.ruleX(lineData, Plot.pointerX({ x, stroke: tc.rule, strokeWidth: 1, strokeDasharray: '4,4' })),
|
||||
Plot.dot(lineData, Plot.pointerX({ x, y: yKey, r: 4, fill: color, stroke: tc.tooltipBg, strokeWidth: 1.5 })),
|
||||
Plot.text(lineData, Plot.pointerX({
|
||||
x, y: yKey,
|
||||
text: (d: any) => d[yKey] != null ? `${Math.round(d[yKey])}` : '',
|
||||
dy: -12,
|
||||
@@ -426,6 +430,21 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if chartType === 'line'}
|
||||
<div class="flex items-center gap-1">
|
||||
{#each (['raw', '10s', '20s'] as SmoothMode[]) as sm}
|
||||
<button
|
||||
class="px-2 py-1 rounded transition-colors"
|
||||
class:bg-zinc-800={smoothMode === sm}
|
||||
class:text-white={smoothMode === sm}
|
||||
class:hover:text-zinc-300={smoothMode !== sm}
|
||||
on:click={() => smoothMode = sm}
|
||||
title="Smoothing window"
|
||||
>{sm === 'raw' ? '~ Raw' : sm}</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
{#each (['line', 'histogram'] as ChartType[]) as type}
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user