feat(fitness/stats): body-fat trend chart as Δ from baseline
Mirrors the weight chart pipeline (SMA + ±1σ confidence band) for body-fat %, but emits deltas from the first displayed measurement so the y-axis shows change instead of raw numbers. Title surfaces the baseline (e.g. "Body Fat · Δ from 18.2%"), y-unit is "pp" (percentage points), colours are purple trend on top of an orange raw-data line so it reads differently from weight's green+blue at a glance. FitnessChart gained two shared upgrades: `interaction.mode = 'index'` on line charts so hovering the x-axis shows tooltips for every dataset (including the trend line whose pointRadius is 0), and a `σ` dataset filter so the confidence band doesn't clutter the tooltip. A new optional `tooltipFormatter` prop lets callers format the hover label; the BF chart uses it to show the signed delta + reconstructed absolute % for raw points and additionally the ±1σ window for trend points (e.g. "+0.30 ±0.45 pp · 18.5% (18.0–18.9%)").
This commit is contained in:
@@ -10,10 +10,11 @@
|
||||
* title?: string,
|
||||
* height?: string,
|
||||
* yUnit?: string,
|
||||
* goalLine?: number
|
||||
* goalLine?: number,
|
||||
* tooltipFormatter?: (value: number, datasetIndex: number, dataIndex: number, label: string) => string
|
||||
* }}
|
||||
*/
|
||||
let { type = 'line', data, title = '', height = '250px', yUnit = '', goalLine = undefined } = $props();
|
||||
let { type = 'line', data, title = '', height = '250px', yUnit = '', goalLine = undefined, tooltipFormatter = undefined } = $props();
|
||||
|
||||
/** @type {HTMLCanvasElement | undefined} */
|
||||
let canvas = $state(undefined);
|
||||
@@ -109,6 +110,7 @@
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: { duration: 0 },
|
||||
interaction: type === 'line' ? { mode: 'index', intersect: false } : undefined,
|
||||
scales: {
|
||||
x: useTimeAxis ? {
|
||||
type: 'time',
|
||||
@@ -156,7 +158,19 @@
|
||||
bodyColor: dark ? '#D8DEE9' : '#3B4252',
|
||||
borderWidth: 0,
|
||||
cornerRadius: 8,
|
||||
padding: 10
|
||||
padding: 10,
|
||||
filter: (/** @type {any} */ ctx) => !(ctx.dataset?.label ?? '').includes('σ'),
|
||||
...(tooltipFormatter ? {
|
||||
callbacks: {
|
||||
label: (/** @type {any} */ ctx) => {
|
||||
const v = ctx.parsed.y;
|
||||
const label = ctx.dataset.label ?? '';
|
||||
if (v == null) return label;
|
||||
const formatted = tooltipFormatter(v, ctx.datasetIndex, ctx.dataIndex, label);
|
||||
return `${label}: ${formatted}`;
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user