diff --git a/src/app.js b/src/app.js
index 5b0dc20..fbc4480 100644
--- a/src/app.js
+++ b/src/app.js
@@ -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(`${v}`);
}
- 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.
{
diff --git a/src/changelog.json b/src/changelog.json
index 5aa63b9..a109b2f 100644
--- a/src/changelog.json
+++ b/src/changelog.json
@@ -1,4 +1,5 @@
[
+ { "date": "2026-08-02", "text": "The Daily counts chart now has Pees / Poos / Meals checkboxes so you can focus on just the metrics you care about โ untick the rest to see, say, only poos; your choice is remembered" },
{ "date": "2026-08-01", "text": "Logging a pee or poo now sets off ๐ง/๐ฉ fireworks that shoot up from the bottom of the screen โ a little celebration you can switch off in Settings (and it honours a reduced-motion preference)" },
{ "date": "2026-08-01", "text": "Tidied the header on long names and ages โ the name now truncates instead of shoving the buttons, and the age reads as a compact \"16 wk ยท 3 mo 3 wk\"; weight-log rows are a single line again (\"Aug 1 ยท 16 wk\")" },
{ "date": "2026-07-26", "text": "Added a fan-chart view of the pedigree (toggle it in the header): your dog at the centre with each generation fanning outward as a ring, so many generations fit at once without the tree sprawling sideways โ tap a wedge for that dog, and repeated ancestors keep their colour" },
diff --git a/src/index.html b/src/index.html
index 005f2ce..d57e449 100644
--- a/src/index.html
+++ b/src/index.html
@@ -203,10 +203,10 @@
Daily counts
-
- Pees
- Poos
- Meals
+
+
+
+
diff --git a/src/style.css b/src/style.css
index 2704d7c..454fa6c 100644
--- a/src/style.css
+++ b/src/style.css
@@ -685,6 +685,15 @@ dialog menu {
.lg.poo .sw { background: var(--poo); }
.lg.eat .sw { background: var(--eat); }
+/* Interactive legend: each item is a checkbox that toggles its metric. */
+.legend-toggle label.lg { cursor: pointer; user-select: none; }
+.legend-toggle input[type="checkbox"] { margin: 0; cursor: pointer; }
+.legend-toggle label.pee input { accent-color: var(--pee); }
+.legend-toggle label.poo input { accent-color: var(--poo); }
+.legend-toggle label.eat input { accent-color: var(--eat); }
+/* Dim an unchecked item so it's clear its bars are hidden. */
+.legend-toggle label.lg:has(input:not(:checked)) { opacity: 0.5; }
+
/* ---------- auth (login / register) ---------- */
.auth-screen {
position: fixed;