Interactive SVG family tree visualization using ELK (Sugiyama) for layout and D3 for rendering. Shows ancestors, descendants, and siblings in a single diagram with orthogonal bus-line connectors. Features: - Bidirectional tree traversal (ancestors + descendants + siblings) - Generation-aligned layout with post-processing Y-snap - Person cards with photos, names, dates, and hover bio cards - "More ancestors" indicator for persons with hidden parents - Pan/zoom navigation - Docker dev environment
84 lines
2.5 KiB
JavaScript
84 lines
2.5 KiB
JavaScript
/**
|
|
* Family connector utilities.
|
|
*
|
|
* Draws special connectors between spouses and from couples to children.
|
|
*/
|
|
|
|
/**
|
|
* Draw a family connector: horizontal line between spouses,
|
|
* then vertical drop to children.
|
|
*
|
|
* @param {d3.Selection} group
|
|
* @param {object} parent1 - First parent position {x, y}
|
|
* @param {object} parent2 - Second parent position {x, y}
|
|
* @param {Array} children - Child positions [{x, y}, ...]
|
|
* @param {Configuration} config
|
|
*/
|
|
export function drawFamilyConnector(group, parent1, parent2, children, config) {
|
|
const halfHeight = config.cardHeight / 2;
|
|
|
|
// Horizontal line between spouses
|
|
if (parent2) {
|
|
group
|
|
.append("line")
|
|
.attr("class", "link spouse-link")
|
|
.attr("x1", parent1.x)
|
|
.attr("y1", parent1.y)
|
|
.attr("x2", parent2.x)
|
|
.attr("y2", parent2.y)
|
|
.attr("stroke", "#d4a87b")
|
|
.attr("stroke-width", 2);
|
|
}
|
|
|
|
if (children.length === 0) return;
|
|
|
|
// Midpoint between parents (or just parent1 if single parent)
|
|
const coupleX = parent2 ? (parent1.x + parent2.x) / 2 : parent1.x;
|
|
const coupleY = parent1.y + halfHeight;
|
|
|
|
// Vertical drop from couple midpoint
|
|
const childrenY = children[0].y - halfHeight;
|
|
const midY = (coupleY + childrenY) / 2;
|
|
|
|
group
|
|
.append("line")
|
|
.attr("class", "link descendant-link")
|
|
.attr("x1", coupleX)
|
|
.attr("y1", coupleY)
|
|
.attr("x2", coupleX)
|
|
.attr("y2", midY);
|
|
|
|
if (children.length === 1) {
|
|
// Single child — straight line down
|
|
group
|
|
.append("line")
|
|
.attr("class", "link descendant-link")
|
|
.attr("x1", coupleX)
|
|
.attr("y1", midY)
|
|
.attr("x2", children[0].x)
|
|
.attr("y2", childrenY);
|
|
} else {
|
|
// Multiple children — horizontal rail
|
|
const minX = Math.min(...children.map((c) => c.x));
|
|
const maxX = Math.max(...children.map((c) => c.x));
|
|
|
|
group
|
|
.append("line")
|
|
.attr("class", "link descendant-link")
|
|
.attr("x1", minX)
|
|
.attr("y1", midY)
|
|
.attr("x2", maxX)
|
|
.attr("y2", midY);
|
|
|
|
for (const child of children) {
|
|
group
|
|
.append("line")
|
|
.attr("class", "link descendant-link")
|
|
.attr("x1", child.x)
|
|
.attr("y1", midY)
|
|
.attr("x2", child.x)
|
|
.attr("y2", childrenY);
|
|
}
|
|
}
|
|
}
|