Make the Sleep trend chart follow the selected day

The curves were always computed relative to today regardless of the day
picker. Anchor them to selectedDay() instead: a past day shows its full
24h curve against the day before it and the average of the days leading
up to it, with no now-cap and no projection. Legend and tooltip labels
show the actual dates when viewing a past day.
This commit is contained in:
Alexander Heldt
2026-07-23 08:45:26 +00:00
parent 0ebaa11c93
commit 22a5ea76aa
3 changed files with 25 additions and 13 deletions
+23 -12
View File
@@ -1278,9 +1278,10 @@
} }
// ---------- sleep trend ---------- // ---------- sleep trend ----------
// Cumulative hours slept as the day progresses: today's running total (up to // Cumulative hours slept as the selected day progresses: that day's running
// now) against yesterday's full curve and the mean of the last N full days, // total (up to now when it's today, else the full 24h) against the previous
// where N follows the 7/14/30 chart-days picker. // day's full curve and the mean of the N full days before it, where N
// follows the 7/14/30 chart-days picker.
// Each curve is a list of { x: hour-of-day 0..24, y: cumulative hours }. // Each curve is a list of { x: hour-of-day 0..24, y: cumulative hours }.
function sleepTrendCurves(events) { function sleepTrendCurves(events) {
const HOUR = 3_600_000; const HOUR = 3_600_000;
@@ -1294,8 +1295,10 @@
} }
return total; return total;
}; };
const day = selectedDay();
const isToday = ymd(day) === ymd(new Date());
const dayStartTs = (daysAgo) => { const dayStartTs = (daysAgo) => {
const d = startOfDay(new Date()); const d = startOfDay(day);
d.setDate(d.getDate() - daysAgo); d.setDate(d.getDate() - daysAgo);
return d.getTime(); return d.getTime();
}; };
@@ -1314,7 +1317,8 @@
return pts; return pts;
}; };
const today = curveFor(dayStartTs(0), Date.now()); // A past day is complete, so its curve runs the full 24h uncapped.
const today = curveFor(dayStartTs(0), isToday ? Date.now() : null);
const yesterdayCurve = curveFor(dayStartTs(1)); const yesterdayCurve = curveFor(dayStartTs(1));
const yesterday = yesterdayCurve[24].y > 0 ? yesterdayCurve : null; const yesterday = yesterdayCurve[24].y > 0 ? yesterdayCurve : null;
@@ -1339,9 +1343,10 @@
// the average day typically adds between now and midnight. That respects // the average day typically adds between now and midnight. That respects
// the time-of-day rhythm (night sleep, nap clusters), unlike a linear // the time-of-day rhythm (night sleep, nap clusters), unlike a linear
// rate extrapolation, which overshoots wildly just after a long night. // rate extrapolation, which overshoots wildly just after a long night.
// No history → no average → no projection. // No history → no average → no projection. Past days are already complete,
// so there is nothing to project.
let projected = null; let projected = null;
if (avg) { if (avg && isToday) {
const nowPt = today[today.length - 1]; const nowPt = today[today.length - 1];
const avgAt = (x) => { const avgAt = (x) => {
const lo = Math.floor(x); const lo = Math.floor(x);
@@ -1355,7 +1360,13 @@
} }
} }
return { today, yesterday, avg, avgDays, projected }; // Labels follow the selection so a past day reads as its date, not "Today".
const fmtDay = (daysAgo) =>
new Date(dayStartTs(daysAgo)).toLocaleDateString(undefined, { month: "short", day: "numeric" });
const dayLabel = isToday ? "Today" : fmtDay(0);
const prevDayLabel = isToday ? "Yesterday" : fmtDay(1);
return { today, yesterday, avg, avgDays, projected, dayLabel, prevDayLabel };
} }
function drawSleepTrendChart(curves, target) { function drawSleepTrendChart(curves, target) {
@@ -1369,9 +1380,9 @@
const series = [ const series = [
// Reference lines first so today (and its projected tail) draw on top. // Reference lines first so today (and its projected tail) draw on top.
{ pts: curves.avg, cls: "trend-avg", label: `${curves.avgDays}-day average` }, { pts: curves.avg, cls: "trend-avg", label: `${curves.avgDays}-day average` },
{ pts: curves.yesterday, cls: "trend-yesterday", label: "Yesterday" }, { pts: curves.yesterday, cls: "trend-yesterday", label: curves.prevDayLabel },
{ pts: curves.projected, cls: "trend-projected", label: "Projected end of day" }, { pts: curves.projected, cls: "trend-projected", label: "Projected end of day" },
{ pts: curves.today, cls: "trend-today", label: "Today" }, { pts: curves.today, cls: "trend-today", label: curves.dayLabel },
].filter(s => s.pts && s.pts.length > 1); ].filter(s => s.pts && s.pts.length > 1);
// The axis must reach the goal band's top even when the data doesn't yet. // The axis must reach the goal band's top even when the data doesn't yet.
@@ -1452,11 +1463,11 @@
// write each curve's slept-hours total into its chip. // write each curve's slept-hours total into its chip.
const chip = (id) => document.getElementById(id); const chip = (id) => document.getElementById(id);
const hrs = (pts) => `${pts[pts.length - 1].y.toFixed(1)}h`; const hrs = (pts) => `${pts[pts.length - 1].y.toFixed(1)}h`;
chip("legend-trend-today-text").textContent = `Today ${hrs(curves.today)}`; chip("legend-trend-today-text").textContent = `${curves.dayLabel} ${hrs(curves.today)}`;
const yLegend = chip("legend-trend-yesterday"); const yLegend = chip("legend-trend-yesterday");
yLegend.hidden = !curves.yesterday; yLegend.hidden = !curves.yesterday;
if (curves.yesterday) { if (curves.yesterday) {
chip("legend-trend-yesterday-text").textContent = `Yesterday ${hrs(curves.yesterday)}`; chip("legend-trend-yesterday-text").textContent = `${curves.prevDayLabel} ${hrs(curves.yesterday)}`;
} }
const aLegend = chip("legend-trend-avg"); const aLegend = chip("legend-trend-avg");
aLegend.hidden = !curves.avg; aLegend.hidden = !curves.avg;
+1
View File
@@ -1,4 +1,5 @@
[ [
{ "date": "2026-07-17", "text": "The Sleep trend chart follows the selected day — pick a past day to see its full curve against the day before and the average leading up to it" },
{ "date": "2026-07-15", "text": "The Sleep trend y-axis is stretched above 10h, giving the hours around the sleep goal most of the chart" }, { "date": "2026-07-15", "text": "The Sleep trend y-axis is stretched above 10h, giving the hours around the sleep goal most of the chart" },
{ "date": "2026-07-15", "text": "The Sleep trend chart shows the sleep goal for your puppy's age as a shaded band — the projection chip gets a ✓ when today is on track" }, { "date": "2026-07-15", "text": "The Sleep trend chart shows the sleep goal for your puppy's age as a shaded band — the projection chip gets a ✓ when today is on track" },
{ "date": "2026-07-15", "text": "Logging a training session while viewing another day puts it on that day (the snackbar tells you where it went)" }, { "date": "2026-07-15", "text": "Logging a training session while viewing another day puts it on that day (the snackbar tells you where it went)" },
+1 -1
View File
@@ -222,7 +222,7 @@
<section class="patterns" data-panel="sleep-trend"> <section class="patterns" data-panel="sleep-trend">
<h2>Sleep trend</h2> <h2>Sleep trend</h2>
<svg id="chart-sleep-trend" class="chart-svg" viewBox="0 0 320 220" role="img" aria-label="Cumulative sleep hours through the day: today, yesterday, the recent average and the projected end-of-day total, with the age-based sleep goal band"></svg> <svg id="chart-sleep-trend" class="chart-svg" viewBox="0 0 320 220" role="img" aria-label="Cumulative sleep hours through the selected day, the day before it, the recent average and (for today) the projected end-of-day total, with the age-based sleep goal band"></svg>
<div class="legend"> <div class="legend">
<span class="lg trend-today"><span class="sw"></span><span id="legend-trend-today-text">Today</span></span> <span class="lg trend-today"><span class="sw"></span><span id="legend-trend-today-text">Today</span></span>
<span class="lg trend-projected" id="legend-trend-projected" hidden><span class="sw"></span><span id="legend-trend-projected-text">Projected</span></span> <span class="lg trend-projected" id="legend-trend-projected" hidden><span class="sw"></span><span id="legend-trend-projected-text">Projected</span></span>