Show the age-based sleep goal as a band in the Sleep trend chart
The puppy's age (from the configured birthday) picks the daily goal — 0–8 weeks 20–22h, 8–16 weeks 18–20h, 16–18h to 6 months, 14–16h to 12 months — drawn as a shaded band behind the curves, with a legend chip naming the range. The Projected chip gains a ✓/▽ marker for whether today's projection reaches the goal's lower bound, and the y-axis always extends to cover the band. No birthday (or an adult dog) means no band, unchanged from before.
This commit is contained in:
+49
-4
@@ -257,6 +257,19 @@
|
||||
return `${wk} · ${mo} old`;
|
||||
}
|
||||
|
||||
// Rough age-based daily sleep goal (hours), for the trend chart's goal band:
|
||||
// 0–8 weeks 20–22h, 8–16 weeks 18–20h, then 16–18h to 6 months and 14–16h to
|
||||
// 12 months. No birthday (or an adult dog) → no goal.
|
||||
function sleepTargetFor(birthday) {
|
||||
const a = ageParts(birthday);
|
||||
if (!a) return null;
|
||||
if (a.weeks < 8) return { lo: 20, hi: 22, label: "0–8 weeks" };
|
||||
if (a.weeks < 16) return { lo: 18, hi: 20, label: "8–16 weeks" };
|
||||
if (a.months < 6) return { lo: 16, hi: 18, label: "4–6 months" };
|
||||
if (a.months < 12) return { lo: 14, hi: 16, label: "6–12 months" };
|
||||
return null;
|
||||
}
|
||||
|
||||
function addEvent(type, note, at, { photoId, weight, grams, exerciseId } = {}) {
|
||||
const events = loadAll();
|
||||
const now = Date.now();
|
||||
@@ -1345,7 +1358,7 @@
|
||||
return { today, yesterday, avg, avgDays, projected };
|
||||
}
|
||||
|
||||
function drawSleepTrendChart(curves) {
|
||||
function drawSleepTrendChart(curves, target) {
|
||||
const svg = document.getElementById("chart-sleep-trend");
|
||||
if (!svg) return;
|
||||
const W = 320, H = 220;
|
||||
@@ -1361,7 +1374,11 @@
|
||||
{ pts: curves.today, cls: "trend-today", label: "Today" },
|
||||
].filter(s => s.pts && s.pts.length > 1);
|
||||
|
||||
const rawMax = Math.max(...series.flatMap(s => s.pts.map(p => p.y)));
|
||||
// The axis must reach the goal band's top even when the data doesn't yet.
|
||||
const rawMax = Math.max(
|
||||
target ? target.hi : 0,
|
||||
...series.flatMap(s => s.pts.map(p => p.y)),
|
||||
);
|
||||
const { yMax, steps: ySteps } = niceAxisSleepHours(rawMax);
|
||||
|
||||
const xOf = (hour) => ML + (hour / 24) * innerW;
|
||||
@@ -1371,6 +1388,18 @@
|
||||
// labels stay apart.
|
||||
const yLabelCls = ySteps > 16 ? "y-dense" : "";
|
||||
const parts = [];
|
||||
|
||||
// Age-based goal band, behind everything: today's projection should land
|
||||
// inside it by midnight.
|
||||
if (target) {
|
||||
const yTop = yOf(target.hi);
|
||||
const yBot = yOf(target.lo);
|
||||
parts.push(
|
||||
`<rect class="trend-goal" x="${ML}" y="${yTop.toFixed(1)}" ` +
|
||||
`width="${innerW}" height="${(yBot - yTop).toFixed(1)}">` +
|
||||
`<title>${escapeText(`Goal ${target.lo}–${target.hi}h (${target.label})`)}</title></rect>`
|
||||
);
|
||||
}
|
||||
for (let i = 0; i <= ySteps; i++) {
|
||||
const y = MT + innerH * (1 - i / ySteps);
|
||||
const v = Math.round(yMax * i / ySteps * 10) / 10;
|
||||
@@ -1399,7 +1428,8 @@
|
||||
|
||||
function renderSleepTrend(events) {
|
||||
const curves = sleepTrendCurves(events);
|
||||
drawSleepTrendChart(curves);
|
||||
const target = sleepTargetFor(loadConfig().birthday);
|
||||
drawSleepTrendChart(curves, target);
|
||||
// Drop legend chips for missing reference lines (no data for them yet),
|
||||
// keep the average chip's label in step with the chart-days picker, and
|
||||
// write each curve's slept-hours total into its chip.
|
||||
@@ -1419,7 +1449,22 @@
|
||||
const pLegend = chip("legend-trend-projected");
|
||||
pLegend.hidden = !curves.projected;
|
||||
if (curves.projected) {
|
||||
chip("legend-trend-projected-text").textContent = `Projected ~${hrs(curves.projected)}`;
|
||||
// ✓ when the projection reaches at least the goal's lower bound.
|
||||
let mark = "";
|
||||
if (target) {
|
||||
const end = curves.projected[curves.projected.length - 1].y;
|
||||
mark = end >= target.lo ? " ✓" : " ▽";
|
||||
pLegend.title = end >= target.lo
|
||||
? "On track for the sleep goal"
|
||||
: "Projected below the sleep goal";
|
||||
}
|
||||
chip("legend-trend-projected-text").textContent = `Projected ~${hrs(curves.projected)}${mark}`;
|
||||
}
|
||||
const gLegend = chip("legend-trend-goal");
|
||||
gLegend.hidden = !target;
|
||||
if (target) {
|
||||
chip("legend-trend-goal-text").textContent =
|
||||
`Goal ${target.lo}–${target.hi}h (${target.label})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user