chart = {
const width = 1200;
const height = 800;
const hierarchyData = data;
const root = d3.hierarchy(hierarchyData);
const treeLayout = d3
.tree()
.size([width - 200, height - 300])
.nodeSize([220, 220])
.separation((a, b) => (a.parent === b.parent ? 1.5 : 2.5));
treeLayout(root);
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.style("font", "14px Arial, sans-serif");
const g = svg.append("g").attr("transform", `translate(${width / 2}, 50)`);
svg.call(
d3
.zoom()
.extent([
[0, 0],
[width, height]
])
.scaleExtent([0.3, 3])
.on("zoom", (event) => {
g.attr("transform", event.transform);
})
);
g.append("g")
.attr("fill", "none")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.5)
.attr("stroke-width", 2)
.selectAll("path")
.data(root.links())
.join("path")
.attr(
"d",
d3
.linkVertical()
.x((d) => d.x)
.y((d) => d.y)
);
const createPersonNode = (group, d) => {
const container = group.append("g").attr("class", "person-node");
container
.append("rect")
.attr("x", -110)
.attr("y", -60)
.attr("width", 220)
.attr("height", 120)
.attr("rx", 10)
.attr("ry", 10)
.attr("fill", d.data.spouse ? "#f0f0f0" : "#e6f2ff")
.attr("stroke", "#666")
.attr("stroke-width", 1.5);
const formatName = (name) => {
return name
.split(" ")
.map(
(part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
)
.join(" ");
};
const truncateName = (name, maxLength = 20) => {
return name.length > maxLength
? name.substring(0, maxLength) + "..."
: name;
};
if (d.data.spouse) {
container
.append("text")
.attr("x", -100)
.attr("y", -30)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.attr("font-size", "0.9em")
.text(truncateName(formatName(d.data.name)))
.clone(true)
.attr("y", -10)
.attr("font-size", "0.8em")
.attr("fill", "#666")
.text(`Age: ${d.data.age}`);
container
.append("text")
.attr("x", 100)
.attr("y", -30)
.attr("text-anchor", "end")
.attr("font-weight", "bold")
.attr("font-size", "0.9em")
.text(truncateName(formatName(d.data.spouse.name)))
.clone(true)
.attr("y", -10)
.attr("font-size", "0.8em")
.attr("fill", "#666")
.text(`Age: ${d.data.spouse.age}`);
container
.append("line")
.attr("x1", -110)
.attr("y1", 0)
.attr("x2", 110)
.attr("y2", 0)
.attr("stroke", "#666")
.attr("stroke-dasharray", "4,4");
} else {
container
.append("text")
.attr("x", 0)
.attr("y", -30)
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("font-size", "0.9em")
.text(truncateName(formatName(d.data.name)))
.clone(true)
.attr("y", -10)
.attr("font-size", "0.8em")
.attr("fill", "#666")
.text(`Age: ${d.data.age}`);
}
container
.append("text")
.attr("x", -100)
.attr("y", -45)
.attr("font-size", "0.8em")
.attr("fill", "#666")
.text(`Gen: ${d.data.generation || 0}`);
};
const node = g
.append("g")
.selectAll("g")
.data(root.descendants())
.join("g")
.attr("transform", (d) => `translate(${d.x},${d.y})`)
.each(function (d) {
createPersonNode(d3.select(this), d);
});
node
.append("title")
.text(
(d) =>
`Name: ${d.data.name}\nAge: ${d.data.age}\nGeneration: ${
d.data.generation || 0
}`
);
return svg.node();
}