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

D3v4三层嵌套数据更新模式问题:层级渲染与报错修复

解决D3.js三层嵌套数据更新与矩形渲染问题

咱们先来拆解你遇到的两个核心问题:矩形无法正常渲染和**this.setAttribute is not a function报错**,这俩问题本质都是三层嵌套的Enter/Update/Exit流程逻辑混乱导致的,尤其是中层(segments)的处理步骤出了错。

错误原因分析

  1. segments层的流程顺序颠倒:你先处理了bits层的数据绑定,再回头给segments的enter选择集添加rect元素,这导致update阶段的segments元素没被正确关联,后续的merge操作也失效了。
  2. 错误的选择集合并对象:你原本用segmentsEnter.merge(segmentsAll),但segmentsEnter只是仅包含新创建元素的enter选择集,正确的做法应该是先给enter选择集添加元素,再和包含已有元素的segmentsAll合并成完整的选择集。
  3. bits层的绑定上下文错误:你从segmentsEnter(仅新元素)里选择bits,导致已有segments里的bits无法被更新,进而引发DOM操作错误。

修正后的完整代码

let width = 0.9 * window.innerWidth, 
    height = 0.9 * window.innerHeight, 
    colors = ['darkviolet', 'steelblue', 'coral', 'Turquoise', 'firebrick', 'mediumslateblue', 'palevioletred', 'green', 'aqua']; 

let data1 = [{"group":"A","segment":"1","item":"1"}, {"group":"A","segment":"1","item":"2"}, {"group":"A","segment":"1","item":"3"}, {"group":"A","segment":"2","item":"4"}, {"group":"A","segment":"2","item":"5"}, {"group":"A","segment":"2","item":"6"}, {"group":"A","segment":"3","item":"7"}, {"group":"A","segment":"3","item":"8"}, {"group":"A","segment":"3","item":"9"}, {"group":"B","segment":"4","item":"1"}, {"group":"B","segment":"4","item":"2"}, {"group":"B","segment":"4","item":"3"}, {"group":"B","segment":"5","item":"4"}, {"group":"B","segment":"5","item":"5"}, {"group":"B","segment":"5","item":"6"}, {"group":"B","segment":"6","item":"7"}, {"group":"B","segment":"6","item":"8"}, {"group":"B","segment":"6","item":"9"}, {"group":"C","segment":"7","item":"1"}, {"group":"C","segment":"7","item":"2"}, {"group":"C","segment":"7","item":"3"}, {"group":"C","segment":"8","item":"4"}, {"group":"C","segment":"8","item":"5"}, {"group":"C","segment":"8","item":"6"}, {"group":"C","segment":"9","item":"7"}, {"group":"C","segment":"9","item":"8"}, {"group":"C","segment":"9","item":"9"}], 
    data2 = [{"group":"A","segment":"1","item":"1"}, {"group":"A","segment":"8","item":"2"}, {"group":"A","segment":"9","item":"3"}, {"group":"A","segment":"2","item":"4"}, {"group":"A","segment":"2","item":"5"}, {"group":"A","segment":"2","item":"6"}, {"group":"A","segment":"5","item":"7"}, {"group":"A","segment":"3","item":"8"}, {"group":"A","segment":"3","item":"9"}, {"group":"B","segment":"4","item":"1"}, {"group":"B","segment":"4","item":"2"}, {"group":"B","segment":"7","item":"3"}, {"group":"B","segment":"5","item":"4"}, {"group":"B","segment":"5","item":"5"}, {"group":"B","segment":"5","item":"6"}, {"group":"B","segment":"5","item":"7"}, {"group":"B","segment":"6","item":"8"}, {"group":"B","segment":"6","item":"9"}, {"group":"C","segment":"7","item":"1"}, {"group":"C","segment":"7","item":"2"}, {"group":"C","segment":"3","item":"3"}, {"group":"C","segment":"8","item":"4"}, {"group":"C","segment":"8","item":"5"}, {"group":"C","segment":"8","item":"6"}, {"group":"C","segment":"9","item":"7"}, {"group":"C","segment":"6","item":"8"}, {"group":"C","segment":"1","item":"9"}]; 

let button = d3.select('body') 
    .append('button') 
    .attr('type', 'button') 
    .style('display', 'block') 
    .text('Update') 
    .on('click', function() { update(data2) }); 

var svg = d3.select('body').append('svg') 
    .attr('width', width) 
    .attr('height', height) 
    .append('g'); 

let color = d3.scaleOrdinal().range(colors); 

update(data1); 

function getxy(data) { 
    let grouped = Array.from(d3.group(data, d=> d.group, d=> d.segment), ([key, value]) => ({key, value})); 
    grouped.forEach(function(s) { 
        s.value = Array.from(s.value, ([key, value]) => ({key, value})); 
        s.value.forEach(function(d) { 
            d.start = d3.min(d.value, function(t) { 
                t.segment = +t.segment; 
                t.item = +t.item; 
                return +t.item 
            }); 
            d.end = d3.max(d.value, function(t) { return t.item }); 
            d.key = +d.key; 
            d.group = s.key; 
        }) 
    }) 
    let x1 = d3.scaleBand() 
        .domain([1, 2, 3, 4, 5, 6, 7, 8, 9]) 
        .range([width*0.05, width]) 
        .padding(0.0); 
    let y1 = d3.scaleBand() 
        .domain(['A', 'B', 'C']) 
        .range([10, height]) 
        .padding(0.1); 
    return [x1, y1, grouped]; 
} 

function update(data) { 
    let xy = getxy(data); 
    let x = xy[0], y = xy[1], groupedData = xy[2]; 

    // 第一层:处理groups(bars容器)
    let barsAll = svg.selectAll('.bars').data(groupedData);
    barsAll.exit().remove();
    let barsEnter = barsAll.enter().append('g').attr('class', 'bars');
    let bars = barsEnter.merge(barsAll); // 合并新老容器选择集

    // 第二层:处理segments(矩形)
    let segmentsAll = bars.selectAll('.segments').data(function(d) { return d.value });
    segmentsAll.exit().remove();
    // 给新创建的segments添加rect元素
    let segmentsEnter = segmentsAll.enter().append('rect')
        .attr('stroke', 'black')
        .attr('class', 'segments')
        .style('fill-opacity', 0.2);
    // 合并新老矩形选择集
    let segments = segmentsEnter.merge(segmentsAll);

    // 更新矩形的属性(新老元素统一处理)
    segments
        .attr('fill', function(d) { return color(d.key) })
        .attr('height', y.bandwidth()*0.75)
        .attr('x', function(d) { return x(d.start) - width*0.05 })
        .attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 })
        .attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 });

    // 第三层:处理bits(圆形)
    let bitsAll = segments.selectAll('.bits').data(function(d) { return d.value });
    bitsAll.exit().remove();
    // 给新创建的bits添加circle元素
    let bitsEnter = bitsAll.enter().append('circle')
        .attr('class', 'bits')
        .attr('r', width*0.05)
        .attr('stroke', 'none');
    // 合并新老圆形选择集
    let bits = bitsEnter.merge(bitsAll);

    // 更新圆形的属性(新老元素统一处理)
    bits
        .attr('cx', function(d) { return x(d.item) })
        .attr('cy', function(d) { return y(d.group) + y.bandwidth()/2 })
        .attr('fill', function(d) { return color(d.segment) });
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-array.v2.min.js"></script>

关键修正点说明

  1. 分层清晰的Enter/Update/Exit流程:把三层元素(bars容器→segments矩形→bits圆形)的处理逻辑拆分,每一层都先清理废弃元素、创建新元素、合并新老选择集,最后统一更新属性,避免流程混乱。
  2. 正确的元素创建时机:在segments的enter选择集上直接创建rect元素,确保DOM元素存在后再处理下层的bits,避免数据绑定到不存在的元素上。
  3. 基于合并选择集的子元素操作:所有下层元素的选择都基于合并后的上层选择集(比如从segments而不是segmentsEnter里选bits),确保新老元素都能被正确更新。

修改后,矩形和圆形都能正常渲染,点击Update按钮时数据也能动态更新,不会再出现this.setAttribute is not a function的错误。

内容的提问来源于stack exchange,提问作者callmedeeray

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:05:26