fitness: use time-scale x-axis for weight chart to handle date gaps
All checks were successful
CI / update (push) Successful in 2m23s
All checks were successful
CI / update (push) Successful in 2m23s
Weight chart now spaces data points proportionally to actual dates instead of evenly. Days without a weight log no longer compress adjacent points together. Uses Chart.js time scale with chartjs-adapter-date-fns.
This commit is contained in:
@@ -50,6 +50,8 @@
|
||||
"@sveltejs/adapter-node": "^5.0.0",
|
||||
"@tauri-apps/plugin-geolocation": "^2.3.2",
|
||||
"chart.js": "^4.5.0",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"file-type": "^19.0.0",
|
||||
"ioredis": "^5.9.0",
|
||||
"leaflet": "^1.9.4",
|
||||
|
||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@@ -20,6 +20,12 @@ importers:
|
||||
chart.js:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
chartjs-adapter-date-fns:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0(chart.js@4.5.0)(date-fns@4.1.0)
|
||||
date-fns:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
file-type:
|
||||
specifier: ^19.0.0
|
||||
version: 19.6.0
|
||||
@@ -1086,6 +1092,12 @@ packages:
|
||||
resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==}
|
||||
engines: {pnpm: '>=8'}
|
||||
|
||||
chartjs-adapter-date-fns@3.0.0:
|
||||
resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==}
|
||||
peerDependencies:
|
||||
chart.js: '>=2.8.0'
|
||||
date-fns: '>=2.0.0'
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
@@ -1137,6 +1149,9 @@ packages:
|
||||
resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
|
||||
debug@4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -2613,6 +2628,11 @@ snapshots:
|
||||
dependencies:
|
||||
'@kurkle/color': 0.3.4
|
||||
|
||||
chartjs-adapter-date-fns@3.0.0(chart.js@4.5.0)(date-fns@4.1.0):
|
||||
dependencies:
|
||||
chart.js: 4.5.0
|
||||
date-fns: 4.1.0
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
@@ -2661,6 +2681,8 @@ snapshots:
|
||||
whatwg-mimetype: 4.0.0
|
||||
whatwg-url: 15.1.0
|
||||
|
||||
date-fns@4.1.0: {}
|
||||
|
||||
debug@4.3.4:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { Chart, registerables } from 'chart.js';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* type?: 'line' | 'bar',
|
||||
* data: { labels: string[], datasets: Array<{ label: string, data: (number|null)[], borderColor?: string, backgroundColor?: string, borderWidth?: number, pointRadius?: number, pointBackgroundColor?: string, tension?: number, fill?: boolean|string, order?: number, type?: string, borderDash?: number[], [key: string]: any }> },
|
||||
* data: { labels: string[], dates?: string[], datasets: Array<{ label: string, data: (number|null)[], borderColor?: string, backgroundColor?: string, borderWidth?: number, pointRadius?: number, pointBackgroundColor?: string, tension?: number, fill?: boolean|string, order?: number, type?: string, borderDash?: number[], [key: string]: any }> },
|
||||
* title?: string,
|
||||
* height?: string,
|
||||
* yUnit?: string,
|
||||
@@ -47,13 +48,20 @@
|
||||
const textColor = dark ? '#D8DEE9' : '#2E3440';
|
||||
const gridColor = dark ? 'rgba(216,222,233,0.1)' : 'rgba(46,52,64,0.08)';
|
||||
|
||||
const plainLabels = [...(data.labels || [])];
|
||||
const useTimeAxis = !!(data.dates && data.dates.length > 0);
|
||||
const dates = data.dates || [];
|
||||
const plainLabels = useTimeAxis ? [] : [...(data.labels || [])];
|
||||
const plainDatasets = (data.datasets || []).map((ds, i) => {
|
||||
const isLine = ds.type === 'line' || (type === 'line' && !ds.type);
|
||||
const isBar = ds.type === 'bar' || (type === 'bar' && !ds.type);
|
||||
const rawData = [...(ds.data || [])];
|
||||
// When using time axis, pair each value with its date
|
||||
const chartData = useTimeAxis
|
||||
? rawData.map((v, j) => ({ x: dates[j], y: v }))
|
||||
: rawData;
|
||||
return {
|
||||
...ds,
|
||||
data: [...(ds.data || [])],
|
||||
data: chartData,
|
||||
borderColor: ds.borderColor || nordColors[i % nordColors.length],
|
||||
backgroundColor: ds.backgroundColor ?? (isBar
|
||||
? (nordColors[i % nordColors.length])
|
||||
@@ -93,7 +101,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
chart = new Chart(ctx, {
|
||||
chart = new Chart(ctx, /** @type {any} */ ({
|
||||
type,
|
||||
data: { labels: plainLabels, datasets: plainDatasets },
|
||||
plugins,
|
||||
@@ -102,7 +110,13 @@
|
||||
maintainAspectRatio: false,
|
||||
animation: { duration: 0 },
|
||||
scales: {
|
||||
x: {
|
||||
x: useTimeAxis ? {
|
||||
type: 'time',
|
||||
time: { unit: 'day', tooltipFormat: 'MMM d' },
|
||||
grid: { display: false },
|
||||
border: { display: false },
|
||||
ticks: { color: textColor, font: { size: 11 }, maxTicksLimit: 8 }
|
||||
} : {
|
||||
grid: { display: false },
|
||||
border: { display: false },
|
||||
ticks: { color: textColor, font: { size: 11 }, maxTicksLimit: 8 }
|
||||
@@ -146,7 +160,7 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -155,17 +155,19 @@ export const GET: RequestHandler = async ({ locals }) => {
|
||||
// Build chart-ready weight data with SMA ± 1 std dev confidence band
|
||||
const weightChart: {
|
||||
labels: string[];
|
||||
dates: string[];
|
||||
data: number[];
|
||||
sma: (number | null)[];
|
||||
upper: (number | null)[];
|
||||
lower: (number | null)[];
|
||||
} = { labels: [], data: [], sma: [], upper: [], lower: [] };
|
||||
} = { labels: [], dates: [], data: [], sma: [], upper: [], lower: [] };
|
||||
const weights: number[] = [];
|
||||
for (const m of weightMeasurements) {
|
||||
const d = new Date(m.date);
|
||||
weightChart.labels.push(
|
||||
d.toLocaleDateString('en', { month: 'short', day: 'numeric' })
|
||||
);
|
||||
weightChart.dates.push(d.toISOString());
|
||||
weightChart.data.push(m.weight!);
|
||||
weights.push(m.weight!);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
|
||||
const weightChartData = $derived({
|
||||
labels: stats.weightChart?.labels ?? [],
|
||||
dates: stats.weightChart?.dates,
|
||||
datasets: [
|
||||
...(hasSma ? [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user