Render the pedigree top-down as a family tree

Replace the indented ancestry list with a top-down chart: the dog on top,
sire (left) and dam (right) branching below, connected by lines drawn with
each node's CSS ::before/::after (a nested <ul>/<li> tree). It shows three
generations by default and each dog expands to trace the line further back;
deeper levels get wide, so the view scrolls horizontally.
This commit is contained in:
Alexander Heldt
2026-07-26 10:39:28 +00:00
parent 86e51bb851
commit 8157e95066
3 changed files with 97 additions and 41 deletions
+16 -13
View File
@@ -2459,7 +2459,7 @@
const pedBtn = document.getElementById("pedigree-btn");
const pedRefresh = document.getElementById("pedigree-refresh");
const PED_OPEN_DEPTH = 4; // generations shown expanded by default; deeper collapse
const PED_OPEN_DEPTH = 3; // show 3 generations expanded by default; deeper collapses
let pedPollTimer = null;
let pedNodes = {}; // latest ancestry map, for the progress count
@@ -2646,12 +2646,18 @@
}
// The tree is ahnentafel-indexed: the dog is position 1, its sire 2n and dam
// 2n+1. We build recursively (sire above dam) and collapse below PED_OPEN_DEPTH.
// 2n+1. We render it top-down like a family tree — the dog on top, parents
// branching below — as a nested <ul>/<li> so CSS can draw the connectors.
// Built recursively (sire left, dam right) and collapsed below PED_OPEN_DEPTH.
function renderTree(nodes) {
pedNodes = nodes;
pedTree.textContent = "";
const root = buildPedNode(nodes, 1, 0);
if (root) pedTree.append(root);
if (!root) return;
const ul = document.createElement("ul");
ul.className = "ped-tree-h";
ul.append(root);
pedTree.append(ul);
}
function buildPedNode(nodes, pos, depth) {
@@ -2660,8 +2666,7 @@
const hasDam = !!nodes[String(pos * 2 + 1)];
if (!n && !hasSire && !hasDam) return null;
const wrap = document.createElement("div");
wrap.className = "ped-node";
const li = document.createElement("li");
const card = document.createElement("div");
card.className = "ped-card";
@@ -2682,32 +2687,30 @@
r.textContent = n.reg;
card.append(r);
}
li.append(card);
if (hasSire || hasDam) {
const kids = document.createElement("div");
kids.className = "ped-children";
const kids = document.createElement("ul");
const s = buildPedNode(nodes, pos * 2, depth + 1);
const d = buildPedNode(nodes, pos * 2 + 1, depth + 1);
if (s) { s.classList.add("ped-sire"); kids.append(s); }
if (d) { d.classList.add("ped-dam"); kids.append(d); }
const collapsed = depth >= PED_OPEN_DEPTH;
if (collapsed) wrap.classList.add("collapsed");
if (collapsed) li.classList.add("collapsed");
const toggle = document.createElement("button");
toggle.type = "button";
toggle.className = "ped-toggle";
toggle.setAttribute("aria-label", "Toggle ancestors");
toggle.textContent = collapsed ? "+" : "";
toggle.addEventListener("click", () => {
const nowCollapsed = wrap.classList.toggle("collapsed");
const nowCollapsed = li.classList.toggle("collapsed");
toggle.textContent = nowCollapsed ? "+" : "";
});
card.prepend(toggle);
wrap.append(card, kids);
} else {
wrap.append(card);
li.append(kids);
}
return wrap;
return li;
}
// ---------- changelog dialog ----------