Improve indicators and fix hover card age calculation
Draw ancestor/descendant indicators behind card, make them clickable (navigates to person), use sharper box corners (rx=1). Fix age calculation to use full birth/death dates instead of just years, correctly accounting for whether the birthday has passed.
This commit is contained in:
File diff suppressed because one or more lines are too long
2
resources/js/full-diagram.min.js
vendored
2
resources/js/full-diagram.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -35,6 +35,78 @@ export function renderPersonCard(parent, person, config, onClick, containerSelec
|
||||
onClick({ id: person.id, data });
|
||||
});
|
||||
|
||||
// "More ancestors" indicator — drawn first so card renders on top
|
||||
if (data.hasMoreAncestors) {
|
||||
const ig = g.append("g")
|
||||
.attr("class", "more-ancestors-indicator")
|
||||
.style("cursor", "pointer")
|
||||
.on("click", (event) => {
|
||||
event.stopPropagation();
|
||||
onClick({ id: person.id, data });
|
||||
});
|
||||
|
||||
const bw = 10, bh = 7, gap = 4;
|
||||
const cx = w - 25;
|
||||
const topY = -14;
|
||||
const leftX = cx - gap / 2 - bw;
|
||||
const rightX = cx + gap / 2;
|
||||
const barY = topY + bh;
|
||||
|
||||
// Lines first (behind boxes)
|
||||
ig.append("line")
|
||||
.attr("x1", leftX + bw / 2).attr("y1", barY)
|
||||
.attr("x2", rightX + bw / 2).attr("y2", barY);
|
||||
ig.append("line")
|
||||
.attr("x1", cx).attr("y1", barY)
|
||||
.attr("x2", cx).attr("y2", 0);
|
||||
|
||||
// Boxes on top of lines
|
||||
ig.append("rect")
|
||||
.attr("x", leftX).attr("y", topY)
|
||||
.attr("width", bw).attr("height", bh)
|
||||
.attr("rx", 1).attr("ry", 1);
|
||||
ig.append("rect")
|
||||
.attr("x", rightX).attr("y", topY)
|
||||
.attr("width", bw).attr("height", bh)
|
||||
.attr("rx", 1).attr("ry", 1);
|
||||
}
|
||||
|
||||
// "More descendants" indicator — drawn first so card renders on top
|
||||
if (data.hasMoreDescendants) {
|
||||
const ig = g.append("g")
|
||||
.attr("class", "more-descendants-indicator")
|
||||
.style("cursor", "pointer")
|
||||
.on("click", (event) => {
|
||||
event.stopPropagation();
|
||||
onClick({ id: person.id, data });
|
||||
});
|
||||
|
||||
const bw = 10, bh = 7, gap = 4;
|
||||
const cx = w - 25;
|
||||
const boxTop = h + 7;
|
||||
const leftX = cx - gap / 2 - bw;
|
||||
const rightX = cx + gap / 2;
|
||||
const barY = boxTop;
|
||||
|
||||
// Lines first (behind boxes)
|
||||
ig.append("line")
|
||||
.attr("x1", cx).attr("y1", h)
|
||||
.attr("x2", cx).attr("y2", barY);
|
||||
ig.append("line")
|
||||
.attr("x1", leftX + bw / 2).attr("y1", barY)
|
||||
.attr("x2", rightX + bw / 2).attr("y2", barY);
|
||||
|
||||
// Boxes on top of lines
|
||||
ig.append("rect")
|
||||
.attr("x", leftX).attr("y", boxTop)
|
||||
.attr("width", bw).attr("height", bh)
|
||||
.attr("rx", 1).attr("ry", 1);
|
||||
ig.append("rect")
|
||||
.attr("x", rightX).attr("y", boxTop)
|
||||
.attr("width", bw).attr("height", bh)
|
||||
.attr("rx", 1).attr("ry", 1);
|
||||
}
|
||||
|
||||
// Card background
|
||||
g.append("rect")
|
||||
.attr("width", w)
|
||||
@@ -125,66 +197,6 @@ export function renderPersonCard(parent, person, config, onClick, containerSelec
|
||||
.text(truncateText(subtitle, maxTextWidth));
|
||||
}
|
||||
|
||||
// "More ancestors" indicator — two small parent boxes at top-right
|
||||
if (data.hasMoreAncestors) {
|
||||
const ig = g.append("g").attr("class", "more-ancestors-indicator");
|
||||
|
||||
const bw = 10, bh = 7, gap = 4;
|
||||
const cx = w - 25;
|
||||
const topY = -14;
|
||||
const leftX = cx - gap / 2 - bw;
|
||||
const rightX = cx + gap / 2;
|
||||
const barY = topY + bh;
|
||||
|
||||
// Lines first (behind boxes)
|
||||
ig.append("line")
|
||||
.attr("x1", leftX + bw / 2).attr("y1", barY)
|
||||
.attr("x2", rightX + bw / 2).attr("y2", barY);
|
||||
ig.append("line")
|
||||
.attr("x1", cx).attr("y1", barY)
|
||||
.attr("x2", cx).attr("y2", 0);
|
||||
|
||||
// Boxes on top
|
||||
ig.append("rect")
|
||||
.attr("x", leftX).attr("y", topY)
|
||||
.attr("width", bw).attr("height", bh)
|
||||
.attr("rx", 2).attr("ry", 2);
|
||||
ig.append("rect")
|
||||
.attr("x", rightX).attr("y", topY)
|
||||
.attr("width", bw).attr("height", bh)
|
||||
.attr("rx", 2).attr("ry", 2);
|
||||
}
|
||||
|
||||
// "More descendants" indicator — two small child boxes at bottom-right
|
||||
if (data.hasMoreDescendants) {
|
||||
const ig = g.append("g").attr("class", "more-descendants-indicator");
|
||||
|
||||
const bw = 10, bh = 7, gap = 4;
|
||||
const cx = w - 25;
|
||||
const boxTop = h + 7; // below card bottom edge
|
||||
const leftX = cx - gap / 2 - bw;
|
||||
const rightX = cx + gap / 2;
|
||||
const barY = boxTop; // bar at top of boxes
|
||||
|
||||
// Lines first (behind boxes)
|
||||
ig.append("line")
|
||||
.attr("x1", cx).attr("y1", h)
|
||||
.attr("x2", cx).attr("y2", barY);
|
||||
ig.append("line")
|
||||
.attr("x1", leftX + bw / 2).attr("y1", barY)
|
||||
.attr("x2", rightX + bw / 2).attr("y2", barY);
|
||||
|
||||
// Boxes on top
|
||||
ig.append("rect")
|
||||
.attr("x", leftX).attr("y", boxTop)
|
||||
.attr("width", bw).attr("height", bh)
|
||||
.attr("rx", 2).attr("ry", 2);
|
||||
ig.append("rect")
|
||||
.attr("x", rightX).attr("y", boxTop)
|
||||
.attr("width", bw).attr("height", bh)
|
||||
.attr("rx", 2).attr("ry", 2);
|
||||
}
|
||||
|
||||
// Attach hover bio card
|
||||
if (containerSelector) {
|
||||
attachHoverBioCard(g, data, containerSelector);
|
||||
|
||||
@@ -101,6 +101,17 @@ function addFact(container, label, value, place) {
|
||||
row.append("span").attr("class", "bio-fact-value").text(display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse a webtrees display date string into a Date object.
|
||||
* Handles formats like "15 March 1985", "March 1985", "1985".
|
||||
*/
|
||||
function parseDate(dateStr) {
|
||||
if (!dateStr) return null;
|
||||
const d = new Date(dateStr);
|
||||
if (!isNaN(d.getTime())) return d;
|
||||
return null;
|
||||
}
|
||||
|
||||
function computeAge(data) {
|
||||
if (!data.birthYear) return "";
|
||||
|
||||
@@ -108,18 +119,41 @@ function computeAge(data) {
|
||||
if (isNaN(birthYear)) return "";
|
||||
|
||||
if (data.isDead) {
|
||||
// Try precise calculation from full dates
|
||||
const birthDate = parseDate(data.birthDate);
|
||||
const deathDate = parseDate(data.deathDate);
|
||||
if (birthDate && deathDate) {
|
||||
let age = deathDate.getFullYear() - birthDate.getFullYear();
|
||||
const monthDiff = deathDate.getMonth() - birthDate.getMonth();
|
||||
if (monthDiff < 0 || (monthDiff === 0 && deathDate.getDate() < birthDate.getDate())) {
|
||||
age--;
|
||||
}
|
||||
return t("Died at age %s", age);
|
||||
}
|
||||
// Fallback to year-based
|
||||
if (data.deathYear) {
|
||||
const deathYear = parseInt(data.deathYear, 10);
|
||||
if (!isNaN(deathYear)) {
|
||||
const age = deathYear - birthYear;
|
||||
return t("Died at age %s", age);
|
||||
return t("Died at age %s", deathYear - birthYear);
|
||||
}
|
||||
}
|
||||
return t("Deceased");
|
||||
}
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const age = currentYear - birthYear;
|
||||
// Living person — try precise calculation
|
||||
const birthDate = parseDate(data.birthDate);
|
||||
const now = new Date();
|
||||
if (birthDate) {
|
||||
let age = now.getFullYear() - birthDate.getFullYear();
|
||||
const monthDiff = now.getMonth() - birthDate.getMonth();
|
||||
if (monthDiff < 0 || (monthDiff === 0 && now.getDate() < birthDate.getDate())) {
|
||||
age--;
|
||||
}
|
||||
return t("Age ~%s", age);
|
||||
}
|
||||
|
||||
// Fallback to year-based approximation
|
||||
const age = now.getFullYear() - birthYear;
|
||||
return t("Age ~%s", age);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user