Highlight the selected day in the per-day charts

A soft accent band behind the selected day's bars/cells (sleep, counts,
food, training heatmap) — visible even when the day's values are zero —
plus an accent-colored day label, and an accent ring on the selected
row of the sleep timeline. The band tracks the day picker, so tapping
into a chart immediately shows which slice you landed on.
This commit is contained in:
Alexander Heldt
2026-07-15 10:24:48 +00:00
parent 8dc4fca4e2
commit cebe923d68
3 changed files with 52 additions and 8 deletions
+34 -8
View File
@@ -989,20 +989,27 @@
parts.push(`<text x="${ML - 4}" y="${y + 3}" text-anchor="end">${vText}h</text>`);
}
const selYmd = ymd(selectedDay());
days.forEach((d, i) => {
const isToday = i === days.length - 1;
const isSel = d.ymd === selYmd;
const x = ML + i * (barW + gap);
const h = (d.sleepHours / yMax) * innerH;
const y = MT + innerH - h;
const title = `${d.date.toLocaleDateString(undefined, { weekday: "long", month: "short", day: "numeric" })} — ${d.sleepHours.toFixed(1)}h`;
if (isSel) {
parts.push(`<rect class="day-highlight" x="${(x - gap / 2).toFixed(1)}" y="${MT}" width="${(barW + gap).toFixed(1)}" height="${innerH}" rx="3"/>`);
}
parts.push(
`<rect class="bar bar-sleep ${isToday ? "" : "bar-faded"}" data-day="${d.ymd}" ` +
`x="${x}" y="${y}" width="${barW}" height="${Math.max(0, h)}" rx="3">` +
`<title>${escapeText(title)}</title></rect>`
);
if (showDayLabel(i, days.length)) {
// The selected day always gets a label (accent-colored), even on wide
// windows that would otherwise skip it.
if (showDayLabel(i, days.length) || isSel) {
parts.push(
`<text x="${x + barW / 2}" y="${H - MB + 14}" text-anchor="middle">` +
`<text class="${isSel ? "day-label-sel" : ""}" x="${x + barW / 2}" y="${H - MB + 14}" text-anchor="middle">` +
`${escapeText(dayLabel(d.date, isToday))}</text>`
);
}
@@ -1040,10 +1047,15 @@
{ key: "meals", label: "Meals", cls: "bar-eat" },
];
const selYmd = ymd(selectedDay());
days.forEach((d, i) => {
const isToday = i === days.length - 1;
const isSel = d.ymd === selYmd;
const groupX = ML + i * (groupW + groupGap);
if (isSel) {
parts.push(`<rect class="day-highlight" x="${(groupX - groupGap / 2).toFixed(1)}" y="${MT}" width="${(groupW + groupGap).toFixed(1)}" height="${innerH}" rx="3"/>`);
}
series.forEach((s, j) => {
const val = d[s.key];
const x = groupX + j * (barW + innerBarGap);
@@ -1057,9 +1069,9 @@
);
});
if (showDayLabel(i, days.length)) {
if (showDayLabel(i, days.length) || isSel) {
parts.push(
`<text x="${groupX + groupW / 2}" y="${H - MB + 14}" text-anchor="middle">` +
`<text class="${isSel ? "day-label-sel" : ""}" x="${groupX + groupW / 2}" y="${H - MB + 14}" text-anchor="middle">` +
`${escapeText(dayLabel(d.date, isToday))}</text>`
);
}
@@ -1095,20 +1107,25 @@
parts.push(`<text x="${ML - 4}" y="${y + 3}" text-anchor="end">${vText}</text>`);
}
const selYmd = ymd(selectedDay());
days.forEach((d, i) => {
const isToday = i === days.length - 1;
const isSel = d.ymd === selYmd;
const x = ML + i * (barW + gap);
const h = (d.grams / yMax) * innerH;
const y = MT + innerH - h;
const title = `${d.date.toLocaleDateString(undefined, { weekday: "long", month: "short", day: "numeric" })} — ${Math.round(d.grams)} g`;
if (isSel) {
parts.push(`<rect class="day-highlight" x="${(x - gap / 2).toFixed(1)}" y="${MT}" width="${(barW + gap).toFixed(1)}" height="${innerH}" rx="3"/>`);
}
parts.push(
`<rect class="bar bar-eat ${isToday ? "" : "bar-faded"}" data-day="${d.ymd}" ` +
`x="${x}" y="${y}" width="${barW}" height="${Math.max(0, h)}" rx="3">` +
`<title>${escapeText(title)}</title></rect>`
);
if (showDayLabel(i, days.length)) {
if (showDayLabel(i, days.length) || isSel) {
parts.push(
`<text x="${x + barW / 2}" y="${H - MB + 14}" text-anchor="middle">` +
`<text class="${isSel ? "day-label-sel" : ""}" x="${x + barW / 2}" y="${H - MB + 14}" text-anchor="middle">` +
`${escapeText(dayLabel(d.date, isToday))}</text>`
);
}
@@ -1157,15 +1174,17 @@
parts.push(`<text x="${x.toFixed(1)}" y="${MT - 5}" text-anchor="${anchor}">${hr}h</text>`);
}
const selYmd = ymd(selectedDay());
for (let i = 0; i < N; i++) {
const day = new Date(today);
day.setDate(day.getDate() - (N - 1 - i)); // oldest at top, today at bottom
const dayStart = startOfDay(day).getTime();
const dayEnd = dayStart + dayMs;
const isToday = ymd(day) === ymd(new Date());
const isSel = ymd(day) === selYmd;
const y = MT + i * (rowH + rowGap);
parts.push(`<rect class="stl-track" x="${ML}" y="${y.toFixed(1)}" width="${innerW}" height="${rowH.toFixed(1)}" rx="2"/>`);
parts.push(`<rect class="stl-track${isSel ? " stl-selected" : ""}" x="${ML}" y="${y.toFixed(1)}" width="${innerW}" height="${rowH.toFixed(1)}" rx="2"/>`);
for (const wdw of windows) {
const s = Math.max(wdw.start, dayStart);
@@ -1177,7 +1196,7 @@
}
const label = isToday ? "Today" : `${day.toLocaleDateString(undefined, { weekday: "short" })} ${day.getDate()}`;
parts.push(`<text class="stl-day ${isToday ? "stl-today" : ""}" x="${ML - 6}" y="${(y + rowH / 2 + 3).toFixed(1)}" text-anchor="end">${escapeText(label)}</text>`);
parts.push(`<text class="stl-day ${isToday ? "stl-today" : ""}${isSel ? " stl-sel" : ""}" x="${ML - 6}" y="${(y + rowH / 2 + 3).toFixed(1)}" text-anchor="end">${escapeText(label)}</text>`);
const title = day.toLocaleDateString(undefined, { weekday: "long", month: "short", day: "numeric" });
parts.push(`<rect class="bar stl-hit" data-day="${ymd(day)}" x="${ML}" y="${y.toFixed(1)}" width="${innerW}" height="${rowH.toFixed(1)}"><title>${escapeText(title)}</title></rect>`);
@@ -1584,6 +1603,13 @@
const cellW = innerW / N;
const parts = [];
const selCol = dayIndex.get(ymd(selectedDay()));
if (selCol !== undefined) {
parts.push(
`<rect class="day-highlight" x="${(ML + selCol * cellW - 0.75).toFixed(1)}" y="${MT - 2}" ` +
`width="${cellW.toFixed(1)}" height="${(H - MT - MB + 4).toFixed(1)}" rx="3"/>`
);
}
exercises.forEach((x, r) => {
const y = MT + r * (rowH + rowGap);
const max = Math.max(1, ...counts[r]);
+1
View File
@@ -1,4 +1,5 @@
[
{ "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": "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": "Editing an event no longer pops the date picker over the whole screen on iPhone" },
+17
View File
@@ -603,6 +603,16 @@ dialog menu {
}
.chart-svg .bar { cursor: pointer; }
.chart-svg .bar-faded { opacity: 0.7; }
/* The selected day's slice in every per-day chart: a soft accent band behind
the bars/cells (visible even when the day's values are all zero) plus an
accent-colored day label. */
.chart-svg .day-highlight {
fill: var(--accent);
fill-opacity: 0.08;
stroke: var(--accent);
stroke-opacity: 0.45;
}
.chart-svg text.day-label-sel { fill: var(--accent); font-weight: 600; }
.chart-svg .bar-sleep { fill: var(--sleep); }
.chart-svg .bar-pee { fill: var(--pee); }
.chart-svg .bar-poo { fill: var(--poo); }
@@ -897,6 +907,13 @@ input.switch:checked::after { transform: translateX(18px); }
}
.chart-svg .stl-sleep { fill: var(--sleep); }
.chart-svg .stl-today { fill: var(--accent); font-weight: 600; }
/* Selected day: accent ring on the row's track + accent label. */
.chart-svg .stl-track.stl-selected {
stroke: var(--accent);
stroke-width: 1.2;
stroke-opacity: 0.8;
}
.chart-svg .stl-sel { fill: var(--accent); }
.chart-svg .stl-hit { fill: transparent; cursor: pointer; }
.chart-svg .stl-hit:hover { fill: var(--accent); fill-opacity: 0.08; }