
D3 数据连接实战selection.data、join 与 enter/update/exit 三段式 DOM 同步机制详解【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3本篇基于 d3 官方文档 Joining data 展开深入讲解 d3-selection 的数据连接data join机制如何把任意数组数据绑定到已选中的 DOM 元素上、如何用 key 函数控制数据—元素的对应关系以及 enter、update、exit 三个子选择如何配合完成 DOM 与数据的同步。读完本文你将能够熟练运用selection.data、selection.join、selection.enter、selection.exit、selection.datum和selection.merge六个 API掌握 d3 数据可视化中最核心的 General Update Pattern通用更新模式并能对照仓库内真实示例组件验证其用法。一、为什么需要数据连接d3 的核心工作方式是数据驱动的 DOM 变换通过 d3-selection 选中元素后设置 attributes、styles 等内容。当数据是动态变化的DOM 中的元素数量往往与数据长度不一致——多了的元素要删掉少了的元素要创建一致的元素要更新。数据连接data join正是解决这一问题的机制update 选择成功绑定到数据的现有元素enter 选择数据中新增、但 DOM 中尚无对应元素的占位符exit 选择DOM 中残留、但新数据中已无对应数据项的多余元素。在 d3 7 中这套模式被selection.join收敛为一行调用仓库中 package.json 声明的版本为 d3 7.9.0d3-selection: ^3.0.0selection.join是该版本的标准做法。二、selection.data(data, key)绑定数据并派生 enter/exitselection.data(data, key)将指定的数据数组绑定到选中的元素上返回代表 update 选择的新选择同时在返回的选择上定义 enter 和 exit 子选择供后续添加或删除元素。关键行为数据粘性sticky数据被赋给元素后会存储在该元素的__data__属性上因此重新选中同一元素时仍可用d访问其数据data 是按组per group指定的若选择包含多个组例如 d3.selectAll 之后又调用了selection.selectAlldata通常应写成函数该函数按组依次求值参数为组父级数据d可能为 undefined、组索引i和选择父节点数组nodesthis为组父元素默认按索引连接若未提供key函数则数据数组第i项绑定到第i个选中元素key 函数控制对应关系key 函数先对每个选中元素求值参数d、i、nodesthis为当前 DOM 元素nodes[i]此时d是元素当前已绑定的数据首次连接时为 null再对每个新数据项求值this为组父元素两次返回的字符串相同即视为同一对象。若多个元素返回同一个 key重复元素进入 exit 选择若多个数据项返回同一个 key重复数据进入 enter 选择顺序规则update 与 enter 选择按数据顺序返回而 exit 选择保持连接前的选择顺序。指定 key 函数后选择中元素的顺序可能不再与文档顺序一致需要时用selection.order 或selection.sort 调整其他约束不传data时该方法返回选中元素当前绑定的数据数组此方法不能用于清除已绑定数据清除须用selection.datum。2.1 嵌套组从数字矩阵构建 HTML 表格下面的经典示例展示了两层数据连接外层按行数据连接tr内层在每一行内按单元格数据连接tdconst matrix [ [11975, 5871, 8916, 2868], [ 1951, 10048, 2060, 6171], [ 8010, 16145, 8090, 8045], [ 1013, 990, 940, 6907] ]; d3.select(body) .append(table) .selectAll(tr) .data(matrix) // 第一层连接每个组body绑一个二维数组 .join(tr) .selectAll(td) // 第二层连接以每个 tr 为父组 .data(d d) // 数据函数即恒等函数每行返回对应的行数组 .join(td) .text(d d);这里内层的data函数是恒等函数对每个表格行返回其对应的数据矩阵行。这一步正体现了多组选择时 data 应写成函数的规则——selection.selectAll(td)产生了 4 个组每个tr一个组因此必须用d d为每个组分别提供该行自己的数据。2.2 key 函数让元素对号入座给定如下文档6 个带 id 的 divdiv idFord/div div idJarrah/div div idKwon/div div idLocke/div div idReyes/div div idShephard/div按 key 连接数据的写法const data [ {name: Locke, number: 4}, {name: Reyes, number: 8}, {name: Ford, number: 15}, {name: Jarrah, number: 16}, {name: Shephard, number: 23}, {name: Kwon, number: 42} ]; d3.selectAll(div) .data(data, function(d) { return d ? d.name : this.id; }) .text(d d.number);这个 key 函数的技巧在于若数据项d存在就用d.name否则回退到元素自身的id属性。由于这些元素此前从未绑定过数据key 函数在已选中元素上求值时d为 null走this.id分支而在新数据上求值时d非 null走d.name分支。最终{name: Locke}绑定到div idLocke依此类推实现按名称而非按位置匹配。引入 key 函数的收益有两点其一数据重新排序或增删时d3 能准确区分移动新增删除DOM 变动最小化有利于性能其二元素与数据的稳定对应保证了对象恒常性object constancy是动画中同一数据项平滑移动的前提。三、selection.join(enter, update, exit)一步完成增删改selection.join(enter, update, exit)对先前由selection.data绑定的数据做必要的追加、删除与重排返回enter 与 update 选择合并后的选择。它是显式通用更新模式的便捷替代一次性替代了 enter、exit、selection.append、selection.remove 和selection.order 的组合。3.1 字符串简写与等价展开svg.selectAll(circle) .data(data) .join(circle) .attr(fill, none) .attr(stroke, black);enter参数可以写成字符串简写等价于用该元素名调用selection.appendupdate与exit为可选函数默认分别为恒等函数和调用selection.remove。上式展开即svg.selectAll(circle) .data(data) .join( enter enter.append(circle), update update, exit exit.remove() ) .attr(fill, none) .attr(stroke, black);传入三个分支函数后后续的.attr(...)会同时作用于 enter 与 update 两部分元素两者的选择被 merge 合并后返回。3.2 差异化处理 enter 与 update通过为 enter、update 指定不同的函数可以精细控制不同阶段的行为。例如入场元素绿色、更新元素蓝色svg.selectAll(circle) .data(data) .join( enter enter.append(circle).attr(fill, green), update update.attr(fill, blue) ) .attr(stroke, black);再结合给selection.data指定 key 函数可以把 DOM 的改动压缩到最小从而优化频繁更新的场景性能。3.3 在 join 中驱动过渡动画在enter、update、exit函数内部创建 transitions 即可为三个阶段分别加动画。若enter和update函数返回的是过渡transition它们底层的选择会被合并后由selection.join返回exit函数的返回值则不被使用。3.4 仓库内的真实用法d3 官方文档站的示例组件大量采用这一写法。例如力导向图示例 ExampleDisjointForce.vue 中边与节点分别连接svg.selectAll(.link) .data(links) .join(line) .attr(class, link); svg.selectAll(.node) .data(nodes) .join(circle) .attr(class, node);而 ExampleArcs.vue 则展示了多层嵌套 join 的完整形态——先用.data([null]).join(svg)为容器元素走了一遍以 null 为数据保证单一实例的连接模式再对pie(data)的结果分别.data(pie(data)).join(path)绘制弧与标签组。这类用[null]单元素数组连接容器的写法是selection.data/join组合的一个实用技巧把确保存在一个容器也统一纳入数据连接范式。四、selection.enter()为缺失元素创建占位符selection.enter()返回 enter 选择为每个没有对应 DOM 元素的数据项提供一个占位节点对于非selection.data返回的选择enter 选择为空。占位符在概念上是指向父元素的指针因此 enter 选择通常只是临时性地用于 append 元素之后常与 update 选择 merge 起来使后续修改同时作用于进入中和更新中的元素。例如从数字数组创建 DIVconst div d3.select(body) .selectAll(div) .data([4, 8, 15, 16, 23, 42]) .enter().append(div) .text(d d);若 body 初始为空这段代码会按顺序创建 6 个新 DIV 并挂到 body 下文本内容为对应数字经字符串强制转换div4/div div8/div div15/div div16/div div23/div div42/div五、selection.exit()移除多余元素selection.exit()返回 exit 选择对每个找不到新数据的现有 DOM 元素对于非selection.data返回的选择exit 选择为空。exit 选择通常用于删除与旧数据对应的多余元素。延续上一节的 6 个 DIV用新的数据数组重新绑定并显式指定 key 函数恒等函数div div.data([1, 2, 4, 8, 16, 32], d d);由于指定了 key 函数且新数据中的 [4, 8, 16] 与文档中现有元素匹配update 选择包含 3 个 DIV。保留它们不动用 enter 选择为 [1, 2, 32] 追加新元素div.enter().append(div).text(d d);同样地删除退出中的元素 [15, 23, 42]div.exit().remove();此时 body 变为div1/div div2/div div4/div div8/div div16/div div32/div此处 DOM 元素顺序与数据顺序一致是因为旧数据与新数据的顺序恰好相容。若新数据顺序不同例如数据重新排序需要调用selection.order 按数据顺序重排 DOM 元素——这也是selection.join内部自动替你做的事之一。六、selection.datum(value)单个数据的读写selection.datum(value)读写每个选中元素绑定的数据。与selection.data不同它不计算连接也不影响索引及 enter/exit 选择。若指定value把所有选中元素的绑定数据设为该值。value是常量时所有元素共享同一数据项是函数时按元素顺序逐个求值参数d、i、nodesthis为当前 DOM 元素以函数返回值设置各元素的新数据传 null 可删除绑定数据这也是清除数据绑定的正确方式若未指定value返回选择中第一个非 null元素的绑定数据通常仅在选择恰好包含一个元素时才有意义。一个典型应用是暴露 HTML5 自定义数据属性。给定ul idlist li>selection.datum(function() { return this.dataset; })这样后续回调中d.username就能直接读到data-username的值。七、selection.merge(other)按索引合并两个选择selection.merge(other)返回一个新选择把当前选择与给定的other选择或过渡合并返回选择与当前选择具有相同的组数与相同的父元素当前选择中缺失null的元素用other选择中对应位置若存在且非 null的元素补齐若other选择有额外的组或父元素则被忽略这是按元素索引合并的当两个选择在相同索引处都有非 null 元素时取当前选择的元素other的选择在该索引的元素被忽略。因此它不适合拼接任意两个选择。selection.join内部正是用它来合并 data 绑定后的 enter 与 update 选择。你也可以显式使用 merge但要注意由于合并基于索引应使用保持索引的操作如selection.select而不要用selection.filterfilter 会重排索引导致错位合并。示例const odd selection.select(function(d, i) { return i 1 ? this : null; }); const even selection.select(function(d, i) { return i 1 ? null : this; }); const merged odd.merge(even);这里用select函数版返回this或 null把选择拆成奇偶两个索引保持的选择再 merge 回完整选择。若改用filter两个子选择的索引都会坍缩合并结果就会错位。八、小结三个 API 的选择策略场景推荐 API说明常规增删改同步selection.data(...).join(...)字符串简写即可覆盖 90% 的场景需差异化/动画化 enter、update、exitselection.join(enter, update, exit)传函数各阶段可分别 append、attr、transition需要精细控制或教学理解机制dataenter()/exit()/append/remove/order显式通用更新模式join 的展开形式单元素读/写数据、清除绑定selection.datum(value)不参与连接null 可清除合并保持索引的两个子选择selection.merge(other)按索引补齐 null 位勿与 filter 搭配以上机制共同构成了 d3 数据可视化的骨架select/selectAll划定作用域见 Selecting elementsdata完成数据与元素的匹配join/enter/exit 完成 DOM 同步再配合 Modifying elements 中的attr/style/text等设置视觉属性。掌握这套流程后动态图表、流式数据更新与过渡动画都有了统一的实现基础。【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考