Show age in weight graph
This commit is contained in:
+52
-20
@@ -222,26 +222,27 @@
|
||||
localStorage.setItem(CONFIG_KEY, JSON.stringify(cfg));
|
||||
}
|
||||
|
||||
// Age in whole days / weeks / calendar months from a "YYYY-MM-DD" birthday.
|
||||
// Returns null for a missing/invalid/future birthday.
|
||||
function ageParts(birthday) {
|
||||
// Age in whole days / weeks / calendar months from a "YYYY-MM-DD" birthday,
|
||||
// measured at `at` (defaults to now — pass a weigh-in's timestamp for its age
|
||||
// at that point). Returns null for a missing/invalid birthday or a date before it.
|
||||
function ageParts(birthday, at) {
|
||||
if (!birthday) return null;
|
||||
const [y, mo, d] = birthday.split("-").map(Number);
|
||||
if (!y || !mo || !d) return null;
|
||||
const birth = startOfDay(new Date(y, mo - 1, d));
|
||||
const now = startOfDay(new Date());
|
||||
if (birth > now) return null;
|
||||
const days = Math.floor((now - birth) / 86_400_000);
|
||||
const ref = startOfDay(new Date(Number.isFinite(at) ? at : Date.now()));
|
||||
if (birth > ref) return null;
|
||||
const days = Math.floor((ref - birth) / 86_400_000);
|
||||
const weeks = Math.floor(days / 7);
|
||||
let months = (now.getFullYear() - birth.getFullYear()) * 12 +
|
||||
(now.getMonth() - birth.getMonth());
|
||||
if (now.getDate() < birth.getDate()) months--;
|
||||
let months = (ref.getFullYear() - birth.getFullYear()) * 12 +
|
||||
(ref.getMonth() - birth.getMonth());
|
||||
if (ref.getDate() < birth.getDate()) months--;
|
||||
if (months < 0) months = 0;
|
||||
return { days, weeks, months };
|
||||
}
|
||||
|
||||
function formatAge(birthday) {
|
||||
const a = ageParts(birthday);
|
||||
function formatAge(birthday, at) {
|
||||
const a = ageParts(birthday, at);
|
||||
if (!a) return "";
|
||||
const wk = `${a.weeks} week${a.weeks === 1 ? "" : "s"}`;
|
||||
if (a.months < 1) return `${wk} old`;
|
||||
@@ -939,14 +940,23 @@
|
||||
return { lo, hi, steps: Math.max(1, Math.round((hi - lo) / step)) };
|
||||
}
|
||||
|
||||
function drawWeightChart(weights) {
|
||||
const svg = document.getElementById("chart-weight");
|
||||
// Human-readable detail for one weigh-in: date, weight, and the puppy's age
|
||||
// at that date (omitted if no birthday is configured).
|
||||
function weightPointInfo(w, birthday) {
|
||||
const dateStr = new Date(w.at).toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" });
|
||||
const age = formatAge(birthday, w.at);
|
||||
return `${dateStr} — ${formatWeight(w.weight)}${age ? ` · ${age}` : ""}`;
|
||||
}
|
||||
|
||||
function drawWeightChart(weights, birthday) {
|
||||
const svg = document.getElementById("chart-weight");
|
||||
const info = document.getElementById("weight-point-info");
|
||||
const W = 320, H = 180;
|
||||
const ML = 30, MR = 8, MT = 10, MB = 24;
|
||||
const innerW = W - ML - MR;
|
||||
const innerH = H - MT - MB;
|
||||
|
||||
if (weights.length === 0) { svg.innerHTML = ""; return; }
|
||||
if (weights.length === 0) { svg.innerHTML = ""; info.textContent = ""; return; }
|
||||
|
||||
const vals = weights.map(w => w.weight);
|
||||
const { lo, hi, steps } = niceWeightAxis(Math.min(...vals), Math.max(...vals));
|
||||
@@ -972,11 +982,16 @@
|
||||
parts.push(`<path class="weight-line" d="${d}"/>`);
|
||||
}
|
||||
|
||||
// Visible dots, then larger transparent hit targets on top (easier to tap
|
||||
// on touch, and they carry the tooltip + click detail).
|
||||
weights.forEach(w => {
|
||||
const title = `${new Date(w.at).toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" })} — ${formatWeight(w.weight)}`;
|
||||
parts.push(`<circle class="weight-dot" cx="${xOf(w.at).toFixed(1)}" cy="${yOf(w.weight).toFixed(1)}" r="3.5"/>`);
|
||||
});
|
||||
weights.forEach((w, i) => {
|
||||
const detail = weightPointInfo(w, birthday);
|
||||
parts.push(
|
||||
`<circle class="weight-dot" cx="${xOf(w.at).toFixed(1)}" cy="${yOf(w.weight).toFixed(1)}" r="3.5">` +
|
||||
`<title>${escapeText(title)}</title></circle>`
|
||||
`<circle class="weight-hit" data-i="${i}" cx="${xOf(w.at).toFixed(1)}" cy="${yOf(w.weight).toFixed(1)}" r="10">` +
|
||||
`<title>${escapeText(detail)}</title></circle>`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -987,6 +1002,19 @@
|
||||
}
|
||||
|
||||
svg.innerHTML = parts.join("");
|
||||
|
||||
// Default the caption to the most recent weigh-in; hover/tap focuses a point.
|
||||
const hits = svg.querySelectorAll(".weight-hit");
|
||||
const focus = (i) => {
|
||||
info.textContent = weightPointInfo(weights[i], birthday);
|
||||
hits.forEach(h => h.classList.toggle("active", Number(h.dataset.i) === i));
|
||||
};
|
||||
hits.forEach(h => {
|
||||
const i = Number(h.dataset.i);
|
||||
h.addEventListener("mouseenter", () => focus(i));
|
||||
h.addEventListener("click", () => focus(i));
|
||||
});
|
||||
info.textContent = weightPointInfo(weights[weights.length - 1], birthday);
|
||||
}
|
||||
|
||||
function renderWeight(events) {
|
||||
@@ -999,6 +1027,8 @@
|
||||
const changeEl = document.getElementById("weight-change");
|
||||
const list = document.getElementById("weight-list");
|
||||
|
||||
const birthday = loadConfig().birthday;
|
||||
|
||||
list.innerHTML = "";
|
||||
changeEl.classList.remove("up", "down");
|
||||
|
||||
@@ -1006,7 +1036,7 @@
|
||||
empty.hidden = false;
|
||||
latestEl.textContent = "—";
|
||||
changeEl.textContent = "—";
|
||||
drawWeightChart([]);
|
||||
drawWeightChart([], birthday);
|
||||
return;
|
||||
}
|
||||
empty.hidden = true;
|
||||
@@ -1031,7 +1061,9 @@
|
||||
li.className = "ww weight-ww";
|
||||
const date = document.createElement("span");
|
||||
date.className = "ww-range";
|
||||
date.textContent = new Date(w.at).toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
||||
const age = formatAge(birthday, w.at);
|
||||
const dateStr = new Date(w.at).toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
||||
date.textContent = age ? `${dateStr} · ${age}` : dateStr;
|
||||
const val = document.createElement("span");
|
||||
val.className = "ww-dur";
|
||||
val.textContent = formatWeight(w.weight);
|
||||
@@ -1041,7 +1073,7 @@
|
||||
list.appendChild(li);
|
||||
}
|
||||
|
||||
drawWeightChart(weights);
|
||||
drawWeightChart(weights, birthday);
|
||||
}
|
||||
|
||||
function renderDayBar() {
|
||||
|
||||
Reference in New Issue
Block a user