基于选择框值更新D3树图时切回“全部”选项失效问题排查
D3树图切换回“All”选项无响应的问题修复
问题现象
- 选择“Level 2A”或“Level 2B”时,树图可正常更新显示对应子树
- 切换回“All”选项后,树图无变化,控制台抛出错误:
Uncaught TypeError: Cannot read properties of undefined (reading 'children')
错误根源
- 数据结构处理不一致:选择非“All”时,
selData是子节点数组;切回“All”时selData是完整顶层数据数组,虽然后续取selData[0]能拿到顶层节点,但更新逻辑未适配节点数量变化 - 元素更新逻辑不完整:仅使用
.join()而未处理元素的创建与移除,导致全量数据的节点(如顶层节点、未选中的子树节点)无法重新渲染 - 文本位置绑定错误:文本直接使用
d.y/d.x设置位置,未关联节点的transform属性,导致位置偏移
修复步骤
1. 统一数据处理逻辑
确保无论选择何种选项,传入d3.hierarchy的都是单个节点对象,保持层级结构一致性:
// 修正updateChart中的数据过滤部分 let selData; if(selection != "All"){ selData = { name: "Top Level", children: data[0].children.filter(d => d.name == selection) }; } else { selData = data[0]; // 直接取顶层节点对象 } const root = d3.hierarchy(selData, function(d) { return d.children;});
2. 完善元素的join生命周期
对链接、节点分别处理enter/update/exit阶段,确保新增元素能创建、多余元素能移除:
- 链接:新增路径元素,更新路径d属性,移除多余链接
- 节点:新增g元素,同时创建内部的圆圈和文本,更新transform和class,移除多余节点
3. 修正文本位置绑定
将文本附加到节点g元素上,继承节点的transform,仅设置相对于圆圈的偏移量,避免位置混乱:
// 初始化或新增节点时,文本附加到节点g内 g.append("text") .text(d => d.data.name) .attr("class", "label glow") .attr('text-anchor', "start") .attr("x", 10);
修正后的完整代码
//Load data const data = [ { "name": "Top Level", "parent": "null", "value": 10, "type": "black", "level": "red", "children": [ { "name": "Level 2A", "parent": "Top Level", "value": 15, "type": "grey", "level": "red", "children": [ { "name": "Son of A", "parent": "Level 2: A", "value": 5, "type": "steelblue", "level": "orange" }, { "name": "Daughter of A", "parent": "Level 2: A", "value": 8, "type": "steelblue", "level": "red" } ] }, { "name": "Level 2B", "parent": "Top Level", "value": 10, "type": "grey", "level": "green" } ] } ]; var margin = {top: 10, right: 10, bottom: 10, left: 50}, width = 500 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var tree = d3.cluster() .size([height-margin.top-margin.bottom,width-margin.left-margin.right]); //Create root var root = d3.hierarchy(data[0], function(d) { return d.children;}); //Attach canvas element var svg = d3.select("body") .append("svg") .attr("width", 1000) .attr("height", 1000); var g = svg .append("g") .attr('transform','translate('+ margin.left +','+ margin.right +')'); // 初始化链接 var link = g.selectAll(".link") .data(tree(root).links()) .enter() .append("path") .attr("class", "link") .attr("d", d3.linkHorizontal() .x(function(d) {return d.y;}) .y(function(d) {return d.x;})); // 初始化节点 var node = g.selectAll(".node") .data(root.descendants()) .enter() .append("g") .attr("class", d => { return "node" + (d.children ? " node--internal" : " node--leaf")}) .attr("transform", d => { return "translate(" + d.y + ","+ d.x + ")" ; }) // 初始化文本(附加到节点g元素) node.append("text") .text(d => d.data.name) .attr("class", "label glow") .attr('text-anchor', "start") .attr("x", 10); // 初始化圆圈 node.append("circle") .attr("r", 2.5) // 初始化选择框 var selections = ["All", "Level 2A", "Level 2B"] d3.select("#selectButton") .selectAll('myOptions') .data(selections) .enter() .append('option') .text(function (d) { return d; }) .attr("value", function (d) { return d; }) // 选择框事件监听 d3.select("#selectButton").on("change", function(){ const selectedGroup = this.value updateChart(selectedGroup, data) }) // 更新图表函数 function updateChart(selection, data){ // 统一数据处理逻辑 let selData; if(selection != "All"){ selData = { name: "Top Level", children: data[0].children.filter(d => d.name == selection) }; } else { selData = data[0]; } const root = d3.hierarchy(selData, function(d) { return d.children;}); const treeData = tree(root); // 更新链接 link = link.data(treeData.links()) .join( enter => enter.append("path") .attr("class", "link") .attr("d", d3.linkHorizontal() .x(d => d.y) .y(d => d.x)), update => update, exit => exit.remove() ) .transition() .duration(1000) .attr("d", d3.linkHorizontal() .x(d => d.y) .y(d => d.x)); // 更新节点 node = node.data(root.descendants()) .join( enter => enter.append("g") .attr("class", d => "node" + (d.children ? " node--internal" : " node--leaf")) .attr("transform", d => `translate(${d.y},${d.x})`) .call(g => { // 新增节点时添加圆圈和文本 g.append("circle").attr("r", 2.5); g.append("text") .text(d => d.data.name) .attr("class", "label glow") .attr('text-anchor', "start") .attr("x", 10); }), update => update, exit => exit.remove() ) .transition() .duration(1000) .attr("transform", d => `translate(${d.y},${d.x})`); };
内容的提问来源于stack exchange,提问作者Nivel
相关产品推荐
相关产品推荐

