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 ----------
// Cumulative hours slept as the day progresses: today's running total (up to
// now) against yesterday's full curve and the mean of the last N full days,
// where N follows the 7/14/30 chart-days picker.
// Cumulative hours slept as the selected day progresses: that day's running
// total (up to now when it's today, else the full 24h) against the previous
// 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 }.
function sleepTrendCurves(events) {
const HOUR = 3_600_000;
@@ -1294,8 +1295,10 @@
}
return total;
};
const day = selectedDay();
const isToday = ymd(day) === ymd(new Date());
const dayStartTs = (daysAgo) => {
const d = startOfDay(new Date());
const d = startOfDay(day);
d.setDate(d.getDate() - daysAgo);
return d.getTime();
};
@@ -1314,7 +1317,8 @@
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 yesterday = yesterdayCurve[24].y > 0 ? yesterdayCurve : null;
@@ -1339,9 +1343,10 @@
// the average day typically adds between now and midnight. That respects
// the time-of-day rhythm (night sleep, nap clusters), unlike a linear
// 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;
if (avg) {
if (avg && isToday) {
const nowPt = today[today.length - 1];
const avgAt = (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) {
@@ -1369,9 +1380,9 @@
const series = [
// 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.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.today, cls: "trend-today", label: "Today" },
{ pts: curves.today, cls: "trend-today", label: curves.dayLabel },
].filter(s => s.pts && s.pts.length > 1);
// 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.
const chip = (id) => document.getElementById(id);
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");
yLegend.hidden = !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");
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 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)" },
+1 -1
View File
@@ -222,7 +222,7 @@
<section class="patterns" data-panel="sleep-trend">
<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">
<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>