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.
|
||||
{
|
||||
|
||||
@@ -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" },
|
||||
|
||||
+4
-4
@@ -203,10 +203,10 @@
|
||||
<div class="chart">
|
||||
<div class="chart-title">Daily counts</div>
|
||||
<svg id="chart-counts" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Pee, poo and meal counts per day"></svg>
|
||||
<div class="legend">
|
||||
<span class="lg pee"><span class="sw"></span>Pees</span>
|
||||
<span class="lg poo"><span class="sw"></span>Poos</span>
|
||||
<span class="lg eat"><span class="sw"></span>Meals</span>
|
||||
<div class="legend legend-toggle" id="counts-metrics" role="group" aria-label="Which counts to show">
|
||||
<label class="lg pee"><input type="checkbox" data-metric="pees" checked /><span class="sw"></span>Pees</label>
|
||||
<label class="lg poo"><input type="checkbox" data-metric="poos" checked /><span class="sw"></span>Poos</label>
|
||||
<label class="lg eat"><input type="checkbox" data-metric="meals" checked /><span class="sw"></span>Meals</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart" id="grams-chart-wrap" hidden>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user