Stretch the Sleep trend y-axis above 10h

The interesting detail sits near the goal, in the upper teens of hours,
so the scale is piecewise: 0–10h shares a compressed 26% of the height
with 2h gridlines, and everything above gets the rest with 1h
gridlines. Falls back to linear while the axis is too short to split.
The caption notes the stretched axis.
This commit is contained in:
Alexander Heldt
2026-07-15 19:06:16 +00:00
parent 897311465c
commit 0ebaa11c93
3 changed files with 30 additions and 12 deletions
+28 -11
View File
@@ -1379,14 +1379,33 @@
target ? target.hi : 0, target ? target.hi : 0,
...series.flatMap(s => s.pts.map(p => p.y)), ...series.flatMap(s => s.pts.map(p => p.y)),
); );
const { yMax, steps: ySteps } = niceAxisSleepHours(rawMax); const { yMax } = niceAxisSleepHours(rawMax);
// The interesting detail sits near the goal, in the upper teens of hours,
// so the y scale is piecewise: everything up to BREAK shares a compressed
// slice of the height and the hours above it get all the rest. Linear
// again while the axis is still too short for a split to mean anything.
const BREAK = 10, LOW_FRAC = 0.26;
const split = yMax > BREAK + 2;
const frac = (v) => !split
? v / yMax
: v <= BREAK
? (v / BREAK) * LOW_FRAC
: LOW_FRAC + ((v - BREAK) / (yMax - BREAK)) * (1 - LOW_FRAC);
const xOf = (hour) => ML + (hour / 24) * innerW; const xOf = (hour) => ML + (hour / 24) * innerW;
const yOf = (v) => MT + innerH * (1 - v / yMax); const yOf = (v) => MT + innerH * (1 - frac(v));
// Label every 1h gridline; smaller text once the axis is dense so the // Gridlines: every 1h in the stretched region above the break, every 2h
// labels stay apart. // in the compressed region below it (1h everywhere when linear).
const yLabelCls = ySteps > 16 ? "y-dense" : ""; const yTicks = [];
if (split) {
for (let v = 0; v < BREAK; v += 2) yTicks.push(v);
for (let v = BREAK; v <= yMax; v++) yTicks.push(v);
} else {
for (let v = 0; v <= yMax; v++) yTicks.push(v);
}
// Smaller text once the axis is dense so the labels stay apart.
const yLabelCls = yTicks.length > 16 ? "y-dense" : "";
const parts = []; const parts = [];
// Age-based goal band, behind everything: today's projection should land // Age-based goal band, behind everything: today's projection should land
@@ -1400,12 +1419,10 @@
`<title>${escapeText(`Goal ${target.lo}${target.hi}h (${target.label})`)}</title></rect>` `<title>${escapeText(`Goal ${target.lo}${target.hi}h (${target.label})`)}</title></rect>`
); );
} }
for (let i = 0; i <= ySteps; i++) { for (const v of yTicks) {
const y = MT + innerH * (1 - i / ySteps); const y = yOf(v);
const v = Math.round(yMax * i / ySteps * 10) / 10; parts.push(`<line class="grid" x1="${ML}" y1="${y.toFixed(1)}" x2="${W - MR}" y2="${y.toFixed(1)}"/>`);
const vText = v % 1 === 0 ? v : v.toFixed(1); parts.push(`<text class="${yLabelCls}" x="${ML - 4}" y="${(y + 3).toFixed(1)}" text-anchor="end">${v}h</text>`);
parts.push(`<line class="grid" x1="${ML}" y1="${y}" x2="${W - MR}" y2="${y}"/>`);
parts.push(`<text class="${yLabelCls}" x="${ML - 4}" y="${y + 3}" text-anchor="end">${vText}h</text>`);
} }
for (const hr of [0, 6, 12, 18, 24]) { for (const hr of [0, 6, 12, 18, 24]) {
const x = xOf(hr); const x = xOf(hr);
+1
View File
@@ -1,4 +1,5 @@
[ [
{ "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)" },
{ "date": "2026-07-15", "text": "The Sleep trend yesterday line is orange instead of gray, which was hard to see" }, { "date": "2026-07-15", "text": "The Sleep trend yesterday line is orange instead of gray, which was hard to see" },
+1 -1
View File
@@ -230,7 +230,7 @@
<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>
<span class="lg trend-goal" id="legend-trend-goal" hidden><span class="sw"></span><span id="legend-trend-goal-text">Goal</span></span> <span class="lg trend-goal" id="legend-trend-goal" hidden><span class="sw"></span><span id="legend-trend-goal-text">Goal</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. The dashed tail continues today's line the way the average day usually plays out.</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. The axis is stretched above 10h to give the hours around the goal more room.</p>
</section> </section>
<section class="patterns" data-panel="hour-heatmap"> <section class="patterns" data-panel="hour-heatmap">