const treeData = { name: "根节点", children: [ { name: "子节点1" }, { name: "子节点2", children: [{ name: "孙节点" }] } ] };
const width = 800; const height = 600; const treeLayout = d3.tree() .size([height, width - 200]); const root = d3.hierarchy(treeData); treeLayout(root);
const svg = d3.select("#container") .append("svg") .attr("width", width) .attr("height", height); // 绘制连线 const links = svg.selectAll(".link") .data(root.links()) .join("path") .attr("class", "link") .attr("d", d3.linkHorizontal() .x(d => d.y) .y(d => d.x)); // 绘制节点 const nodes = svg.selectAll(".node") .data(root.descendants()) .join("g") .attr("class", "node") .attr("transform", d => `translate(${d.y},${d.x})`);
function addChildNode(parentNode) { if (!parentNode.data.children) { parentNode.data.children = []; } const newChild = { name: `新节点_${new Date().getTime()}`, parent: parentNode }; parentNode.data.children.push(newChild); }
function updateTree() { // 重建层级数据 const newRoot = d3.hierarchy(treeData); // 重新计算布局 treeLayout(newRoot); // 获取最新节点和连线数据 const nodes = newRoot.descendants(); const links = newRoot.links(); }
// 节点更新 const nodeGroups = svg.selectAll(".node") .data(nodes, d => d.data.name); // 新增节点处理 const enterNodes = nodeGroups.enter() .append("g") .attr("class", "node") .attr("transform", d => `translate(${d.parent.y},${d.parent.x})`); enterNodes.transition() .duration(500) .attr("transform", d => `translate(${d.y},${d.x})`); // 已有节点更新 nodeGroups.transition() .duration(500) .attr("transform", d => `translate(${d.y},${d.x})`); // 删除节点处理 nodeGroups.exit() .transition() .duration(500) .style("opacity", 0) .remove();
const linkPaths = svg.selectAll(".link") .data(links, d => `${d.source.data.name}-${d.target.data.name}`); linkPaths.enter() .append("path") .attr("class", "link") .attr("d", d => { const source = { x: d.source.y, y: d.source.x }; const target = { x: d.source.y, y: d.source.x }; // 初始位置与父节点重叠 return d3.linkHorizontal()(source, target); }) .transition() .duration(500) .attr("d", d3.linkHorizontal() .x(d => d.y) .y(d => d.x)); linkPaths.exit() .transition() .duration(500) .style("opacity", 0) .remove();
nodes.append("circle") .attr("r", 20) .on("click", function(event, d) { addChildNode(d); updateVisualization(); }); nodes.append("text") .text(d => d.data.name) .attr("dy", 5);
function updateVisualization() { updateTree(); // 应用过渡动画 svg.selectAll(".link") .transition() .duration(800) .ease(d3.easeCubicOut) .attr("d", d3.linkHorizontal() .x(d => d.y) .y(d => d.x)); svg.selectAll(".node") .transition() .duration(800) .ease(d3.easeElasticOut) .attr("transform", d => `translate(${d.y},${d.x})`); }
const uuid = () => Math.random().toString(36).substr(2, 9);
treeLayout.maxDepth(6);
引用说明
本文实现方案参考D3.js官方文档(https://d3js.org/)及经典案例库Observable(https://observablehq.com/@d3/tree),核心算法遵循层级布局规范,关键方法引用自《Interactive Data Visualization for the Web》技术专著。