From 5638913b1d1dc16abd3e092b24301bda398b315f Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Thu, 23 Apr 2026 14:21:47 +0200 Subject: [PATCH] feat(fitness/stats): 4 cm minimum y-axis range on body-part history charts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Body-measurement variation of <4 cm used to stretch the full chart height, making normal weekly noise look dramatic. Now the y-axis enforces a 4 cm floor centered on the data's midpoint; wider swings render at their actual range as before. - FitnessChart: new optional `yMin` / `yMax` props mapped to Chart.js `suggestedMin` / `suggestedMax` — soft bounds, so data that exceeds them still widens the axis. - `/fitness/stats/history/[part]`: computes min/max across available values (both sides if paired), enforces the 4 cm floor, passes to FitnessChart. Tick distance stays on Chart.js auto — small ranges get 0.5 cm ticks, wider ones scale up naturally. --- TODO.md | 2 +- package.json | 2 +- .../components/fitness/FitnessChart.svelte | 8 ++++++-- .../[part]/+page.svelte | 20 ++++++++++++++++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index e1e88295..6ed80774 100644 --- a/TODO.md +++ b/TODO.md @@ -7,7 +7,7 @@ [x] add a button on /fitness/measure/body-parts for each measurement directly below to say "Same value", instead of having to hit +, then - to lock in same number [x] BF graph (with trend line like weight graph) on /fitness/stats page. Emphasize relative changes, not absolute numbers in design (as we cannot trust those) (e.g., use start day of overview as 0% and then show +/- x % on the graph) [x] Workshop better names than "Measure" for the /fitness/measure route. It's about body data points (i.e., non-food related). What's a better, short name than "Measure" to capture the logging of weight, body composition, body part measurements, and period tracking? -[ ] on /fitness/stats/histoy/ for body measurement graphs, make the range reasonable. e.g., if we have 1 cm change, do not fill the entire y-height with 1 cm. Use reasonable padding for low ranges (i think we do something like htis already on the weight graph?) +[x] on /fitness/stats/histoy/ for body measurement graphs, make the range reasonable. e.g., if we have 1 cm change, do not fill the entire y-height with 1 cm. Use reasonable padding for low ranges (i think we do something like htis already on the weight graph?) ## Refactor Recipe Search Component diff --git a/package.json b/package.json index f85bc53a..97bae306 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.46.8", + "version": "1.46.9", "private": true, "type": "module", "scripts": { diff --git a/src/lib/components/fitness/FitnessChart.svelte b/src/lib/components/fitness/FitnessChart.svelte index c915539c..006dea00 100644 --- a/src/lib/components/fitness/FitnessChart.svelte +++ b/src/lib/components/fitness/FitnessChart.svelte @@ -11,10 +11,12 @@ * height?: string, * yUnit?: string, * goalLine?: number, - * tooltipFormatter?: (value: number, datasetIndex: number, dataIndex: number, label: string) => string + * tooltipFormatter?: (value: number, datasetIndex: number, dataIndex: number, label: string) => string, + * yMin?: number, + * yMax?: number * }} */ - let { type = 'line', data, title = '', height = '250px', yUnit = '', goalLine = undefined, tooltipFormatter = undefined } = $props(); + let { type = 'line', data, title = '', height = '250px', yUnit = '', goalLine = undefined, tooltipFormatter = undefined, yMin = undefined, yMax = undefined } = $props(); /** @type {HTMLCanvasElement | undefined} */ let canvas = $state(undefined); @@ -125,6 +127,8 @@ }, y: { beginAtZero: type === 'bar', + suggestedMin: yMin, + suggestedMax: yMax, grid: { color: gridColor }, border: { display: false }, ticks: { diff --git a/src/routes/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]/+page.svelte b/src/routes/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]/+page.svelte index 43c0703e..327b9c4e 100644 --- a/src/routes/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]/+page.svelte +++ b/src/routes/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]/+page.svelte @@ -84,6 +84,24 @@ }; }); + /** + * Enforce a minimum y-axis range of 4 cm — prevents tiny variations + * from stretching across the full chart height. + */ + const Y_MIN_RANGE = 4; + const yRange = $derived.by(() => { + /** @type {number[]} */ + const vals = series.paired + ? [...series.left, ...series.right].filter((/** @type {number|null} */ v) => v != null) + : series.values.filter((/** @type {number|null} */ v) => v != null); + if (vals.length === 0) return { min: undefined, max: undefined }; + const dMin = Math.min(...vals); + const dMax = Math.max(...vals); + if (dMax - dMin >= Y_MIN_RANGE) return { min: dMin, max: dMax }; + const mid = (dMin + dMax) / 2; + return { min: mid - Y_MIN_RANGE / 2, max: mid + Y_MIN_RANGE / 2 }; + }); + /** * @typedef {{ paired: true, latest: { left: number|null, right: number|null }, first: { left: number|null, right: number|null }, count: number }} PairedStats * @typedef {{ paired: false, latest: number|null, first: number|null, count: number, min: number|null, max: number|null }} SingleStats @@ -230,7 +248,7 @@
- +
{/if}