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:
+41
-8
@@ -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.
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user