chart = {
const width = 1500;
const height = 900;
const marginTop = 60;
const marginRight = 260;
const marginBottom = 60;
const marginLeft = 260;
const root = d3.hierarchy(data);
const dx = 40;
const dy = (width - marginRight - marginLeft) / (root.height + 0.5);
const tree = d3.tree()
.nodeSize([dx, dy])
.separation((a, b) => a.parent === b.parent ? 1.5 : 2.5);
const diagonal = d3.linkHorizontal().x(d => d.y).y(d => d.x);
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-marginLeft, -marginTop, width, height])
.attr("style", "max-width: 100%; height: auto; font-family: Arial, sans-serif; user-select: none;");
svg.append("rect")
.attr("x", -marginLeft)
.attr("y", -marginTop)
.attr("width", width)
.attr("height", height)
.attr("fill", "#f9f9f9");
const gLink = svg.append("g")
.attr("fill", "none")
.attr("stroke-opacity", 0.7)
.attr("stroke-width", 2.5);
const gNode = svg.append("g")
.attr("cursor", "pointer")
.attr("pointer-events", "all");
const levelColors = [
"#0B1E3A",
"#1F2A44",
"#3A5F8A",
"#2AA6B1",
"#68C3A3",
"#B8E1D3",
"#EAF2F8",
"#FFFFFF",
"#F0F4F8",
"#DDE2E7"
];
function getFontSize(depth) {
const baseFontSize = 20;
return Math.max(baseFontSize - (depth * 1.2), 12) + "px";
}
function update(event, source) {
const nodes = root.descendants();
const links = root.links();
tree(root);
let left = root;
let right = root;
root.eachBefore(node => {
if (node.x < left.x) left = node;
if (node.x > right.x) right = node;
});
const height = Math.max(900, right.x - left.x + marginTop + marginBottom + 100);
svg.attr("height", height)
.attr("viewBox", [-marginLeft, left.x - marginTop, width, height]);
const node = gNode.selectAll("g")
.data(nodes, d => d.id);
node.exit().remove();
const nodeEnter = node.enter().append("g")
.attr("transform", d => `translate(${d.y},${d.x})`)
.on("click", (event, d) => {
d.children = d.children ? null : d._children;
update(event, d);
});
nodeEnter.append("circle")
.attr("r", d => 8 - d.depth * 0.5)
.attr("fill", d => d._children ? levelColors[d.depth % levelColors.length] : "#fff")
.attr("stroke", d => levelColors[d.depth % levelColors.length])
.attr("stroke-width", 2);
nodeEnter.append("text")
.attr("dy", "0.31em")
.attr("x", d => d._children ? -12 : 12)
.attr("text-anchor", d => d._children ? "end" : "start")
.text(d => d.data.name)
.style("font-size", d => getFontSize(d.depth))
.style("font-weight", d => d.depth < 2 ? "bold" : "normal")
.style("fill", d => levelColors[d.depth % levelColors.length])
.clone(true).lower()
.attr("stroke", "white")
.attr("stroke-width", 4);
node.attr("transform", d => `translate(${d.y},${d.x})`);
node.select("circle")
.attr("fill", d => d._children ? levelColors[d.depth % levelColors.length] : "#fff");
const link = gLink.selectAll("path")
.data(links, d => d.target.id);
link.exit().remove();
link.enter().append("path")
.attr("d", diagonal)
.attr("stroke", d => levelColors[d.source.depth % levelColors.length])
.attr("stroke-width", d => Math.max(3.5 - d.source.depth * 0.3, 1.8));
link.attr("d", diagonal);
root.eachBefore(d => {
d.x0 = d.x;
d.y0 = d.y;
});
}
root.x0 = dy / 2;
root.y0 = 0;
root.descendants().forEach((d, i) => {
d.id = i;
d._children = d.children;
if (d.depth > 1) d.children = null;
});
update(null, root);
return svg.node();
}