feat(fitness/stats): 4 cm minimum y-axis range on body-part history charts
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.
This commit is contained in:
@@ -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/<part> 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/<part> 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
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.46.8",
|
||||
"version": "1.46.9",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
+19
-1
@@ -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 @@
|
||||
</section>
|
||||
|
||||
<section class="chart-wrap">
|
||||
<FitnessChart data={chartData} yUnit=" cm" height="320px" />
|
||||
<FitnessChart data={chartData} yUnit=" cm" height="320px" yMin={yRange.min} yMax={yRange.max} />
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user