Project today's end-of-day sleep total on the Sleep trend chart
A dashed tail continues today's line from now to midnight by adding the increments the N-day average curve makes over the same stretch, so the projection follows the usual daily rhythm instead of extrapolating the current rate (which overshoots right after a long night). The legend shows the projected total; with no history there's no average and no projection.
This commit is contained in:
+29
-2
@@ -1318,7 +1318,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { today, yesterday, avg, avgDays };
|
// Where today is likely to end up: continue today's line by adding what
|
||||||
|
// 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.
|
||||||
|
let projected = null;
|
||||||
|
if (avg) {
|
||||||
|
const nowPt = today[today.length - 1];
|
||||||
|
const avgAt = (x) => {
|
||||||
|
const lo = Math.floor(x);
|
||||||
|
const hi = Math.min(24, lo + 1);
|
||||||
|
return avg[lo].y + (avg[hi].y - avg[lo].y) * (x - lo);
|
||||||
|
};
|
||||||
|
projected = [{ x: nowPt.x, y: nowPt.y }];
|
||||||
|
for (let h = Math.ceil(nowPt.x); h <= 24; h++) {
|
||||||
|
if (h <= nowPt.x) continue; // now landing exactly on an hour boundary
|
||||||
|
projected.push({ x: h, y: nowPt.y + avgAt(h) - avgAt(nowPt.x) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { today, yesterday, avg, avgDays, projected };
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSleepTrendChart(curves) {
|
function drawSleepTrendChart(curves) {
|
||||||
@@ -1330,9 +1350,10 @@
|
|||||||
const innerH = H - MT - MB;
|
const innerH = H - MT - MB;
|
||||||
|
|
||||||
const series = [
|
const series = [
|
||||||
// Reference lines first so today draws on top of them.
|
// 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: "Yesterday" },
|
||||||
|
{ 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: "Today" },
|
||||||
].filter(s => s.pts && s.pts.length > 1);
|
].filter(s => s.pts && s.pts.length > 1);
|
||||||
|
|
||||||
@@ -1377,9 +1398,15 @@
|
|||||||
const yLegend = document.getElementById("legend-trend-yesterday");
|
const yLegend = document.getElementById("legend-trend-yesterday");
|
||||||
const aLegend = document.getElementById("legend-trend-avg");
|
const aLegend = document.getElementById("legend-trend-avg");
|
||||||
const aText = document.getElementById("legend-trend-avg-text");
|
const aText = document.getElementById("legend-trend-avg-text");
|
||||||
|
const pLegend = document.getElementById("legend-trend-projected");
|
||||||
|
const pText = document.getElementById("legend-trend-projected-text");
|
||||||
if (yLegend) yLegend.hidden = !curves.yesterday;
|
if (yLegend) yLegend.hidden = !curves.yesterday;
|
||||||
if (aLegend) aLegend.hidden = !curves.avg;
|
if (aLegend) aLegend.hidden = !curves.avg;
|
||||||
if (aText) aText.textContent = `${curves.avgDays}-day avg`;
|
if (aText) aText.textContent = `${curves.avgDays}-day avg`;
|
||||||
|
if (pLegend) pLegend.hidden = !curves.projected;
|
||||||
|
if (pText && curves.projected) {
|
||||||
|
pText.textContent = `Projected ~${curves.projected[curves.projected.length - 1].y.toFixed(1)}h`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- weight ----------
|
// ---------- weight ----------
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
|
{ "date": "2026-07-15", "text": "The Sleep trend chart projects where today will land by midnight — a dashed tail continues today's line following the average day's rhythm" },
|
||||||
{ "date": "2026-07-15", "text": "The charts highlight the selected day, so it's easy to spot which bars, rows and cells you're looking at" },
|
{ "date": "2026-07-15", "text": "The charts highlight the selected day, so it's easy to spot which bars, rows and cells you're looking at" },
|
||||||
{ "date": "2026-07-15", "text": "New Sleep trend chart: today's running sleep total through the day, against yesterday and the average over your chart window (7/14/30 days)" },
|
{ "date": "2026-07-15", "text": "New Sleep trend chart: today's running sleep total through the day, against yesterday and the average over your chart window (7/14/30 days)" },
|
||||||
{ "date": "2026-07-15", "text": "Quick actions that don't fit right now are dimmed (asleep → everything but Sleep end; awake → Sleep end) — still tappable for corrections" },
|
{ "date": "2026-07-15", "text": "Quick actions that don't fit right now are dimmed (asleep → everything but Sleep end; awake → Sleep end) — still tappable for corrections" },
|
||||||
|
|||||||
+3
-2
@@ -222,13 +222,14 @@
|
|||||||
|
|
||||||
<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 160" role="img" aria-label="Cumulative sleep hours through the day: today, yesterday and the recent average"></svg>
|
<svg id="chart-sleep-trend" class="chart-svg" viewBox="0 0 320 160" role="img" aria-label="Cumulative sleep hours through the day: today, yesterday, the recent average and the projected end-of-day total"></svg>
|
||||||
<div class="legend">
|
<div class="legend">
|
||||||
<span class="lg trend-today"><span class="sw"></span>Today</span>
|
<span class="lg trend-today"><span class="sw"></span>Today</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-yesterday" id="legend-trend-yesterday"><span class="sw"></span>Yesterday</span>
|
<span class="lg trend-yesterday" id="legend-trend-yesterday"><span class="sw"></span>Yesterday</span>
|
||||||
<span class="lg trend-avg" id="legend-trend-avg"><span class="sw"></span><span id="legend-trend-avg-text">7-day avg</span></span>
|
<span class="lg trend-avg" id="legend-trend-avg"><span class="sw"></span><span id="legend-trend-avg-text">7-day avg</span></span>
|
||||||
</div>
|
</div>
|
||||||
<p class="muted-note">Hours slept so far at each point of the day, against yesterday and the average over the picked chart window.</p>
|
<p class="muted-note">Hours slept so far at each point of the day, against yesterday and the average over the picked chart window. The dashed tail continues today's line the way the average day usually plays out.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="patterns" data-panel="hour-heatmap">
|
<section class="patterns" data-panel="hour-heatmap">
|
||||||
|
|||||||
@@ -939,7 +939,19 @@ input.switch:checked::after { transform: translateX(18px); }
|
|||||||
fill: none;
|
fill: none;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
/* Today's projected tail: same weight/color as today so it reads as its
|
||||||
|
continuation, dashed + faded because it hasn't happened yet. */
|
||||||
|
.chart-svg .trend-projected {
|
||||||
|
stroke: var(--sleep);
|
||||||
|
stroke-width: 2.5;
|
||||||
|
stroke-dasharray: 2 4;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
fill: none;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
.lg.trend-today .sw { background: var(--sleep); }
|
.lg.trend-today .sw { background: var(--sleep); }
|
||||||
|
.lg.trend-projected .sw { background: color-mix(in srgb, var(--sleep) 40%, var(--surface)); }
|
||||||
.lg.trend-yesterday .sw { background: var(--muted); }
|
.lg.trend-yesterday .sw { background: var(--muted); }
|
||||||
.lg.trend-avg .sw { background: color-mix(in srgb, var(--sleep) 55%, var(--surface)); }
|
.lg.trend-avg .sw { background: color-mix(in srgb, var(--sleep) 55%, var(--surface)); }
|
||||||
.lg[hidden] { display: none; }
|
.lg[hidden] { display: none; }
|
||||||
|
|||||||
Reference in New Issue
Block a user