D3.js Enter/Exit/Update机制异常导致旭日图更新错误及路径属性报错问题
D3.js Enter/Exit/Update机制异常导致旭日图更新错误及路径属性报错问题
我仔细看了你的代码和问题描述,这两个问题其实都和D3的Enter/Exit绑定逻辑、DOM元素选择的细节处理有关,咱们一步步来解决:
一、旭日图更新时错误删除元素的问题
问题根源
你当前的Update阶段代码存在选择器模糊的问题:当你用slice.select('path')时,每个.slice容器里有两个<path>元素(main-arc和hidden-arc),这个选择器只会选中第一个<path>,然后强制修改它的class为main-arc,导致DOM结构混乱,进而让D3的Exit机制无法正确识别需要移除的元素。另外,用索引生成hiddenArc的ID,会随着数据更新时元素索引变化,造成文本路径引用混乱。
修复方案
- 精准选择DOM元素:在Update阶段分别选择
.main-arc和.hidden-arc,避免混淆两个路径元素:
// 更新主弧(main-arc) slice.select('.main-arc') .style('fill', d => (d.data.color == undefined) ? color((d.children ? d : d.parent).data.name) : d.data.color) .transition().duration(500) .attr("d", arc); // 更新隐藏弧(hidden-arc) slice.select('.hidden-arc') .transition().duration(500) .attr('d', middleArcLine);
- 用唯一标识替代索引生成ID:不要用动态变化的索引生成
hiddenArc的ID,改用节点的name属性(你的数据中name是唯一的),保证文本路径的引用始终正确:
// Enter阶段生成hidden-arc时 newSlice.append('path') .attr('class', 'hidden-arc') .attr('id', d => `hiddenArc-${d.data.name}`) // 用name作为唯一标识 .attr('d', middleArcLine); // Enter阶段生成textPath时 text.append('textPath') .attr('startOffset', '50%') .attr('xlink:href', d => `#hiddenArc-${d.data.name}`) // 绑定固定ID .text(d => d.data.name) .attr('fill', d => 'black'); // Update阶段无需修改textPath的href,仅更新文本内容即可 slice.select('textPath') .transition().duration(500) .text(d => d.data.name);
二、路径属性d报错的问题
问题根源
当旭日图缩放过渡、或者节点角度差极小时,middleArcLine函数可能生成无效的路径命令(比如半径为0、角度差为0的情况),导致浏览器解析d属性时报错。另外,之前的索引ID冲突也会间接引发路径引用错误。
修复方案
在middleArcLine函数中增加无效情况的判断,避免生成错误的路径:
middleArcLine = d => { const halfPi = Math.PI / 2; const angles = [x(d.x0) - halfPi, x(d.x1) - halfPi]; const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2); // 当半径为0或角度无变化时,返回空路径避免报错 if (r === 0 || Math.abs(angles[1] - angles[0]) < 0.001) { return ''; } const middleAngle = (angles[1] + angles[0]) / 2; const invertDirection = middleAngle > 0 && middleAngle < Math.PI; if (invertDirection) { angles.reverse(); } const path = d3.path(); path.arc(0, 0, r, angles[0], angles[1], invertDirection); return path.toString(); }
完整修改后的核心代码片段
整合以上修复后的sun函数关键部分:
function sun(pData) { root = d3.hierarchy(pData); root.sum(d => (d.children == undefined) ? ((d.size == undefined) ? 1 : d.size) : 0); const slice = svg.selectAll('g.slice') .data(partition(root).descendants(), d => d.data.name); // Enter Section const newSlice = slice.enter() .append('g').attr('class', 'slice') .attr('display', d => d.depth < 2 ? null : 'none') .on('dblclick', d => {focusOn(d);}) newSlice.append('path') .attr('class', 'main-arc') .style('fill', d => (d.data.color == undefined) ? color((d.children ? d : d.parent).data.name) : d.data.color) .attr('d', arc); newSlice.append('path') .attr('class', 'hidden-arc') .attr('id', d => `hiddenArc-${d.data.name}`) .attr('d', middleArcLine); const text = newSlice.append('text') .attr('display', d => textFits(d) ? null : 'none'); text.append('textPath') .attr('startOffset', '50%') .attr('xlink:href', d => `#hiddenArc-${d.data.name}`) .text(d => d.data.name) .attr('fill', 'black'); // Update Section slice.select('.main-arc') .style('fill', d => (d.data.color == undefined) ? color((d.children ? d : d.parent).data.name) : d.data.color) .transition().duration(500) .attr("d", arc); slice.select('.hidden-arc') .transition().duration(500) .attr('d', middleArcLine); slice.select('text') .attr('display', d => textFits(d) ? null : 'none'); slice.select('textPath') .transition().duration(500) .text(d => d.data.name); // Delete Section slice.exit().transition().duration(500).style("fill-opacity", 0.2).remove(); }
备注:内容来源于stack exchange,提问作者Jegor Wieler
相关产品推荐
相关产品推荐

