D3v4三层嵌套数据更新模式问题:层级渲染与报错修复
解决D3.js三层嵌套数据更新与矩形渲染问题
咱们先来拆解你遇到的两个核心问题:矩形无法正常渲染和**this.setAttribute is not a function报错**,这俩问题本质都是三层嵌套的Enter/Update/Exit流程逻辑混乱导致的,尤其是中层(segments)的处理步骤出了错。
错误原因分析
- segments层的流程顺序颠倒:你先处理了bits层的数据绑定,再回头给segments的enter选择集添加rect元素,这导致update阶段的segments元素没被正确关联,后续的merge操作也失效了。
- 错误的选择集合并对象:你原本用
segmentsEnter.merge(segmentsAll),但segmentsEnter只是仅包含新创建元素的enter选择集,正确的做法应该是先给enter选择集添加元素,再和包含已有元素的segmentsAll合并成完整的选择集。 - 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>
关键修正点说明
- 分层清晰的Enter/Update/Exit流程:把三层元素(bars容器→segments矩形→bits圆形)的处理逻辑拆分,每一层都先清理废弃元素、创建新元素、合并新老选择集,最后统一更新属性,避免流程混乱。
- 正确的元素创建时机:在segments的enter选择集上直接创建rect元素,确保DOM元素存在后再处理下层的bits,避免数据绑定到不存在的元素上。
- 基于合并选择集的子元素操作:所有下层元素的选择都基于合并后的上层选择集(比如从
segments而不是segmentsEnter里选bits),确保新老元素都能被正确更新。
修改后,矩形和圆形都能正常渲染,点击Update按钮时数据也能动态更新,不会再出现this.setAttribute is not a function的错误。
内容的提问来源于stack exchange,提问作者callmedeeray
相关产品推荐
相关产品推荐

