You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,会随着数据更新时元素索引变化,造成文本路径引用混乱。

修复方案

  1. 精准选择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);
  1. 用唯一标识替代索引生成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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.17 11:53:14