Add metric checkboxes to the daily counts chart

The Daily counts legend is now a Pees/Poos/Meals checkbox group, so you
can hide metrics and focus on just the ones you care about. The choice is
persisted per device, at least one metric is always kept visible, and the
y-axis and bar widths adapt to the selected metrics.
This commit is contained in:
Alexander Heldt
2026-08-02 13:26:38 +00:00
parent a44c75d2d4
commit 09d9d38c12
4 changed files with 55 additions and 12 deletions
+41 -8
View File
@@ -927,6 +927,27 @@
render();
}
// Which metrics the daily-counts chart shows. Device-global, toggled via the
// checkboxes under the chart. At least one is always kept on so the chart is
// never empty; an invalid/empty stored value falls back to all three.
const COUNTS_METRICS_KEY = "puppy-tracker:counts-metrics:v1";
const COUNTS_METRIC_KEYS = ["pees", "poos", "meals"];
function countsMetrics() {
let stored;
try { stored = JSON.parse(localStorage.getItem(COUNTS_METRICS_KEY)); } catch { /* ignore */ }
const on = Array.isArray(stored)
? COUNTS_METRIC_KEYS.filter(k => stored.includes(k))
: [];
return on.length ? on : COUNTS_METRIC_KEYS.slice();
}
function setCountsMetrics(keys) {
// Never let the user hide everything — keep at least one metric visible.
const on = COUNTS_METRIC_KEYS.filter(k => keys.includes(k));
if (!on.length) { renderChartWindow(); return; } // restore the checkbox we just rejected
try { localStorage.setItem(COUNTS_METRICS_KEY, JSON.stringify(on)); } catch { /* ignore */ }
render();
}
// Sync every "(last N days)" header and the picker's active button.
function renderChartWindow() {
const n = chartDays();
@@ -938,6 +959,10 @@
document.querySelectorAll(".chart-days-picker button").forEach(b => {
b.classList.toggle("active", Number(b.dataset.days) === n);
});
const on = countsMetrics();
document.querySelectorAll("#counts-metrics input[data-metric]").forEach(cb => {
cb.checked = on.includes(cb.dataset.metric);
});
}
// ---------- daily charts ----------
@@ -1094,13 +1119,19 @@
const innerW = W - ML - MR;
const innerH = H - MT - MB;
const rawMax = Math.max(...days.flatMap(d => [d.pees, d.poos, d.meals]));
const series = [
{ key: "pees", label: "Pees", cls: "bar-pee" },
{ key: "poos", label: "Poos", cls: "bar-poo" },
{ key: "meals", label: "Meals", cls: "bar-eat" },
].filter(s => countsMetrics().includes(s.key));
const rawMax = Math.max(0, ...days.flatMap(d => series.map(s => d[s.key])));
const { yMax, steps: ySteps } = niceAxis(rawMax);
const groupGap = days.length > 14 ? 2 : 4;
const innerBarGap = days.length > 14 ? 0.5 : 1.5;
const groupW = (innerW - (days.length - 1) * groupGap) / days.length;
const barW = (groupW - 2 * innerBarGap) / 3;
const barW = (groupW - (series.length - 1) * innerBarGap) / series.length;
const parts = [];
for (let i = 0; i <= ySteps; i++) {
@@ -1110,12 +1141,6 @@
parts.push(`<text x="${ML - 4}" y="${y + 3}" text-anchor="end">${v}</text>`);
}
const series = [
{ key: "pees", label: "Pees", cls: "bar-pee" },
{ key: "poos", label: "Poos", cls: "bar-poo" },
{ key: "meals", label: "Meals", cls: "bar-eat" },
];
const selYmd = ymd(selectedDay());
days.forEach((d, i) => {
const isToday = i === days.length - 1;
@@ -3296,6 +3321,14 @@
b.addEventListener("click", () => setChartDays(Number(b.dataset.days)));
});
document.querySelectorAll("#counts-metrics input[data-metric]").forEach(cb => {
cb.addEventListener("change", () => {
const on = [...document.querySelectorAll("#counts-metrics input[data-metric]:checked")]
.map(el => el.dataset.metric);
setCountsMetrics(on);
});
});
// Swap the timer pill in/out of the frozen bar as the big card scrolls
// past. rAF-throttled: scroll events fire far more often than we can paint.
{