From 01d64b682b17c3738038f4f0f6bef4be9c7e084b Mon Sep 17 00:00:00 2001 From: Alexander Heldt Date: Sun, 26 Jul 2026 10:39:51 +0000 Subject: [PATCH] Flag pedigree collapse with repeat badges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a dog fills more than one position in the pedigree (pedigree collapse), mark each copy with a ×N badge and a stable per-dog colour, and let tapping any copy highlight every place that dog appears. Identity is the registration number (falling back to name), so the same ancestor at different positions is treated as one. The summary line also reports how many ancestors recur. --- src/app.js | 65 ++++++++++++++++++++++++++++++++++++++++++---- src/changelog.json | 1 + src/index.html | 1 + src/style.css | 21 +++++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/app.js b/src/app.js index db4f777..1beee36 100644 --- a/src/app.js +++ b/src/app.js @@ -2458,6 +2458,7 @@ const pedTree = document.getElementById("pedigree-tree"); const pedBtn = document.getElementById("pedigree-btn"); const pedRefresh = document.getElementById("pedigree-refresh"); + const pedRepeatNote = document.getElementById("pedigree-repeat-note"); const PED_OPEN_DEPTH = 3; // show 3 generations expanded by default; deeper collapses let pedPollTimer = null; @@ -2580,20 +2581,24 @@ // fills many positions); "generations back" is the depth of the deepest // position (floor(log2(pos)), since sire = 2·pos and dam = 2·pos+1). function pedCounts() { - const seen = new Set(); + const counts = new Map(); let maxPos = 1; for (const k in pedNodes) { const n = pedNodes[k]; const key = n.reg || n.name; - if (key) seen.add(key); + if (key) counts.set(key, (counts.get(key) || 0) + 1); const p = Number(k); if (p > maxPos) maxPos = p; } - return { distinct: seen.size, gens: Math.floor(Math.log2(maxPos)) }; + let repeated = 0; + counts.forEach((c) => { if (c > 1) repeated++; }); + return { distinct: counts.size, repeated, gens: Math.floor(Math.log2(maxPos)) }; } function pedCountText() { - const { distinct: a, gens: g } = pedCounts(); - return `${a} ancestor${a === 1 ? "" : "s"} back ${g} generation${g === 1 ? "" : "s"}`; + const { distinct: a, repeated: r, gens: g } = pedCounts(); + let s = `${a} ancestor${a === 1 ? "" : "s"} back ${g} generation${g === 1 ? "" : "s"}`; + if (r > 0) s += `, ${r} appearing more than once`; + return s; } function setPedDone() { setPedStatus(`Traced ${pedCountText()}.`, "done"); } function setPedStatus(text, kind) { @@ -2649,9 +2654,29 @@ // 2n+1. We render it top-down like a family tree — the dog on top, parents // branching below — as a nested