跳转至

选择器与数据绑定 — D3 的心脏

这一章讲什么: D3 和其他 DOM 库的分水岭是数据绑定:不是手动增删元素,而是把数据数组绑到一批元素上, 让数据自己决定哪些元素该出现、该消失、该更新。 本章把这条机制的每一个零件拆开:选择器对象内部长什么样、data() 之后元素和数据怎么对账、 join() 替你做了什么、以及数据顺序变了该怎么办。 原书的说法不夸张:本章是「掌握 D3 的核心」1——读懂它,任何 D3 代码都拦不住你。

1. 顶层全景:一次数据更新的完整对账流程

  已有元素(页面上 5 个圆)          新数据数组(3 个值)
      │                                │
      └──────── selectAll("circle") ───┘
                    │  data([19,15,26])
                    ▼
            ┌─── 对账(three-way)───┐
            │ _groups  → 对得上的(更新) │
            │ _enter   → 数据多出      │ → join() 新建元素补上
            │ _exit    → 元素多出      │ → join() 删除元素
            └─────────────────────────┘
                    │  join("circle")
                    ▼
            合并后的选择(每个元素都绑着新数据)
                    │  attr / style / transition…
                    ▼
                  画面更新

  图说:join 的核心是「三方对账」——数据与元素按默认规则(数组下标)配对,
  多退少补,最后合并成一个全体可用的选择。

本章的主线例子就是这张流程图本身:一个空 SVG、一组圆、三次数组变化, 我们在浏览器控制台里一步步观察选择器对象的内部数组如何变化—— 每个数都是真实运行结果(原书在控制台里逐段演示)2。

2. 选择器对象:先看清你手里拿的是什么

2.1 全局选择函数

一切从顶层两个函数开始(第 01 章已经用过)3:

函数 返回
d3.select(选择器或节点) 选中第一个匹配元素的选择器(单元素)
d3.selectAll(选择器或节点列表) 选中全部匹配元素的选择器(多元素)

关键性质:即使一个元素都没匹配到,也照样返回选择器对象。 d3.selectAll("div.klm") 找不到任何元素,返回的选择器里装着一个空列表—— 但它是合法对象,绑上数据之后就能往里造元素4。这是「先选空的、再用 join 创建」这招能成立的原因。

选择器对象有两个内部属性,值得在控制台里展开看一眼:

  • _groups:选中的元素,按组存放;
  • _parents:当前的分组上下文(「这些元素是谁的孩子」)。

它们不是给你在代码里直接用的(原书专门加了「重要提示」:内部属性不属于公开 API, 调试时可以看,代码里只许用方法访问)5,但看懂它们,后面 join 的行为就不再是黑箱。

从节点反着走:事件处理函数里拿到的是原生 DOM 节点,d3.select(node) 把它包回选择器, 就能继续用 D3 方法;反向操作是 .nodes()(返回节点数组)和 .node()(返回第一个节点)—— 比如选中 <canvas> 后要调原生方法 getContext("2d") 画图,就得先 .node() 取出真身6。

2.2 嵌套选择:select 与 selectAll 的分水岭

在已有选择器上继续选择,有两个方法,差别致命7:

方法 数据 分组(_parents)
selection.select("p") 继承父选择器的数据 不保留(父是文档根)
selection.selectAll("p") 不继承(子元素用自己的数据) 保留(父是各分组元素)

后果:往 selectAll 的结果里 append,新元素会追加到对应父元素下; 往 select 的结果里 append,新元素可能挂到 <html> 底下。 原书给的 console 实验:d3.select("div").selectAll("p:first-child") 和 d3.select("div").select("p") 选的是同一个 <p>,但只有前者的 _parents 是 div8。

「数据继承」正是第 03 章柱状图更新能写成 entries.select(".bar") 的原因: 组上绑着数据,组里的柱子是 selectAll 进来的子元素——按上表,selectAll 不传播数据…… 那柱状图里子元素的 d 是哪来的?答案是那个场景的 select 路径(单元素选择传播数据), 以及子元素在创建时由 append 继承了当时父级的数据。这条继承链是 D3 里最容易绕晕、 也最常被面试问到的细节,原书用两张控制台截图把两种 _parents 摆在一起对比9。

2.3 读写元素:attr / style / text / classed / property

五个最常用的读写方法,规则统一:只传名字是读(返回第一个元素的值),带值是写(返回选择器本身,可链式)10:

方法 管什么 例子
attr(name, value) 标签属性(x、r、transform) .attr("r", 40)
style(name, value) CSS 样式(fill、font-size) .style("fill", "green")
text(value) 文本内容 .text(d => d)
classed(names, bool) 加减类名(只写不读) .classed("even", d => d%2==0)
property(name, bool) DOM 属性(disabled、checked、hidden) .property("hidden", d => d<50)

attr 和 style 的分工在 SVG 里有一个实际差别:attr("stroke", …) 生成 XML 属性, style("stroke", …) 生成 style 属性里的 CSS——而 CSS 的优先级高于 XML 属性, 样式表里一句 circle { stroke: blue } 会把 attr 写的黑边盖成蓝的。 所以原书建议:fill、stroke、字体这类,用 style();静态样式多的时候,干脆写样式表, 动态逐个变的才用回调 + style11。例外:transform 用 CSS 写要带单位(translate(25px,25px)), 用 XML 属性写不用(translate(25,25))——transform 建议走 attr12。

2.4 回调函数的三个参数

所有写方法的值都可以是回调,回调按元素逐个执行,最多收三个参数13:

  (d, i, nodes) =>
   d       —— 当前元素绑的数据(datum);没绑数据时是 undefined
   i       —— 当前元素在组内的下标(index)
   nodes   —— 包含当前元素的节点数组;当前元素 = nodes[i]

  图说:d、i、nodes 三个参数,加上普通函数里的 this(也指向当前元素)。

两个坑原书专门点过:

  • 箭头函数没有自己的 this。想要当前元素,要么用普通函数(用 this), 要么用 nodes[i]:(d,i,n) => n[i]14。
  • classed 和 property 配合布尔回调是交互利器:数据更新后类名自动加减, CSS 跟着变(例:偶数染黄底),property("hidden", d => d < 50) 按数据隐藏元素15。

2.5 filter:压缩选择器,不保序 vs 保序

filter(回调) 返回只含「回调返回 true」的元素的新选择器。 原书的 console 实验把七段文字绑上 [9,6,5,4,3,2,1],filter(d => d % 3 == 0) 留下第 1、2、5 段16。

filter 与 select(回调) 的细微差别在内部数组上:filter 把不通过的抠掉(数组变短), select 用 null 占位(保留下标)。下标在合并、排序、删除等按位置操作时重要; 只读的静态场景,filter 更省17。

3. 主线例子:一次完整的 bind-join-update

现在走本章的核心。场景:一个 600×100 的空 SVG,我们绑三轮数据,每轮都在控制台里摊开选择器对象看。

第 0 轮:空选择

  sel1 = d3.select("svg").selectAll("circle")   // sel1.size() == 0

<svg> 里还没有圆,选到的是空选择——但注意它是合法对象,_parents 指向那个 <svg>。 这个「空 + 有父级上下文」的状态,是 join 的起点18。

第 1 轮:数据比元素多(enter)

绑 5 个数据:upd1 = sel1.data([5,10,15,20,25])。

data() 不碰 DOM——页面上还是空白;它返回一个新的更新选择(update selection), 内部三个数组此刻的状态是:

  _groups: [ 空, 空, 空, 空, 空 ]          ← 5 个空位(还没有元素可更新)
  _enter:  [ 占位, 占位, 占位, 占位, 占位 ] ← 5 个 EnterNode 占位对象,各存一项数据
  _exit:   [ 空, 空, 空, 空, 空 ]

  图说:三个数组等大、互补——这是 D3 按下标管理更新的数据结构基础。
  展开 EnterNode 能看到它存数据的 __data__ 属性。

执行 merged1 = upd1.join("circle") 之后:占位对象变成 5 个真 <circle> 挂到 <svg> 下, 三个内部数组合流,_enter/_exit 消失,_groups 里是 5 个圆。 此时这 5 个圆还没有任何属性——全部叠在原点 (0,0),半径 0,仍然不可见19。

加上属性,圆才出现:

  function update() {
    d3.select("svg").selectAll("circle")
      .attr("cy", 50)
      .transition()
        .attr("cx", (d,i) => i * 100 + 50)   // 横向排开:50, 150, 250, 350, 450
        .attr("r", d => d);                  // 半径 = 数据值:5, 10, 15, 20, 25
  }
  update();

五个圆亮出来:圆心 y 全是 50,横坐标按序排开,半径正好是各自的数20。

第 2 轮:数据比元素少(exit)

重选现有元素:sel2 = d3.select("svg").selectAll("circle")(5 个)。 绑更小的数组:upd2 = sel2.data([19,15,26])。内部对账:

  _groups: [ 圆1(绑19), 圆2(绑15), 圆3(绑26) ]   ← 前 3 个对上了
  _enter:  [ 空, 空, 空 ]
  _exit:   [ 空, 空, 圆4, 圆5, 空 ]              ← 后 2 个元素没人要

  图说:默认按下标配对——新数组的第 0 项接管旧第 0 个元素,不管值对不对。
  所以 19、15、26 分别绑到了原来半径 5、10、15 的圆上。

upd2.join("circle"):把 _exit 里的两个圆删掉(等价于 upd2.exit().remove()), 返回剩 3 个圆的合并选择。再调 update(),三个圆动画到新值: 原来的圆 4(半径 20)和圆 5(半径 25)消失,剩下的缩放到 19、15、2621。

第 3 轮:数据又变多(enter + update 同场)

现在 3 个圆在页面上,绑 6 个数据:upd3 = sel.data([12,15,9,3,15,18]):

  _groups: [ 圆1(绑12), 圆2(绑15), 圆3(绑9), 空, 空, 空 ]
  _enter:  [ 空, 空, 空, 占位(3), 占位(15), 占位(18) ]
  _exit:   [ 空 × 6 ]

  图说:前 3 个对上是「更新」,后 3 个数据没有元素是「新增」。

upd3.join("circle"):3 个占位对象变成新圆(追加在最后),与 3 个旧圆合并成 6 个圆的 _groups。 注意 join 返回的是合并选择,不是只含新圆的 enter 选择—— upd3.enter().append("circle") 只返回新圆,join() 是全体22。 再调 update(),六个圆排开:半径 12、15、9、3、15、18。

收敛成惯用写法

拆开看是为了理解,写代码时一次链式调用就够(三轮数据共用的就是这一段):

  d3.select("svg").selectAll("circle")
    .data(data)                // 依次喂 [5,10,15,20,25] → [19,15,26] → [12,15,9,3,15,18]
    .join("circle")
      .attr("cy", 50)
      .transition()
        .attr("cx", (d,i) => i*100+50)
        .attr("r", d => d);

data() 管对账,join() 管增删合并,后面的 attr/style 作用于合并后的全体—— 第 01 章的那个简单写法,内部就是这整套流程23。

3.1 拆开 join:enter / update / exit 三个回调

join("circle") 是简写,等价于显式传入三个回调24:

  .join(
    enter  => enter.append("circle"),   // 新增:造元素(必须返回选择器)
    update => update,                    // 更新:原样返回(也可加过渡)
    exit   => exit.remove()              // 删除(返回值被忽略)
  )

为什么要拆开?三个阶段的待遇可以不同。原书的演示:新圆绿色进场、更新的圆变蓝、 要删的圆先变红、停 ¼ 秒再删除:

  .join(
    enter  => enter.append("circle").attr("fill","green").attr("cy",50),
    update => update.transition().attr("fill","blue"),
    exit   => exit.transition().attr("fill","red").transition().remove()
  ).transition()                          // 合并后全体再做位置/半径动画
   .attr("cx", (d,i) => i*100+50)
   .attr("r", d => d);

如果新旧元素待遇相同,官方建议:enter 里设置一次,update 原样返回, 修改统一放在 join 之后对合并选择做(上面代码 join 后的 transition 就是)25。

3.2 key 函数:不按位置,按身份对账

默认对账按下标——新数组第 i 项永远接管旧第 i 个元素,所以「更新」总是前排元素, 「删除」总是末尾元素。但很多场景数据有身份:国家、用户、商品…… 换一批数据,你希望对得上的是同一个国家的元素,而不是「位置相同」的元素。

key 函数就是给每个数据项发身份证,作为 data() 的第二参数:

  sel.data(objects, d => d.id)     // 用对象的 id 属性当钥匙
  sel.data([19,15,26], d => d)     // 恒等钥匙:数值本身就是身份

回到主线例子,加恒等 key 后同样三轮更新,内部数组完全不同26。看第 2 轮 (5 个圆 [5,10,15,20,25] → 3 个数据 [19,15,26],按值对账):

  按下标(默认):                   按 key(值即身份):
  _groups: [圆(19), 圆(15), 圆(26)]   _groups: [ 空, 圆(15), 空 ]
  _exit:   [ 空, 空, 圆4, 圆5 ]       _exit:   [ 圆(5), 空, 圆(20), 圆(25), 空 ]
  → 删末尾两个                      _enter:  [ 占位(19), 空, 占位(26), 空, 空 ]
                                      → 删 5、20、25 三个;新建 19、26 两个;
                                        只有原来绑 15 的那个圆是「更新」

  图说:identity key 下,只有值 15 同时存在于两批数据,所以它独占 update。
  视觉后果:蓝色(更新)不再总是前排,红色(删除)不再总是末尾——元素跟着「身份」走。

key 不要求唯一:重复的 key 只有第一个生效,重复的元素进 _exit、重复的数据进 _enter27。 key 函数是原书 exercise 6.6 的考点:给两组线加 key,新线会插进旧线中间而不是追加在末尾。

3.3 datum():一对一的单点绑定

datum(value) 把一个值直接绑给选择里所有元素,不参与 join,不动下标,返回原选择28:

  const dataset = [20,40,60];
  dataset.forEach(d => d3.select("svg").append("rect").datum(d));
  svg.selectAll("rect").attr("height", d => d).attr("width", d => d)

适用场景原书列了四类:数据本身是单个对象(不是数组)、只给一个元素绑值、 给 Canvas 路径绑数据(Canvas 没有 DOM 元素可绑)、从 HTML 的 data-* 属性里取数据。 其他情况,「用 data join 通常更简单也更高效」29。datum() 不带参数是读,datum(null) 解绑。

4. 其他方法:让链条更顺手的工具

4.1 each 与 call:把函数接进链里

链条写到一半,逻辑复杂起来怎么办?两个方法让函数调用不打断链30:

方法 执行次数 典型用途
each(回调) 每个元素一次 在回调里 d3.select(this) 取当前元素,往它里面追加子结构
call(函数, …参数) 整个选择一次(选择器作第一参数) 复用同一段配置代码到多个选择

each 的经典场景:圆 + 标签要放进同一个组。链式 append 到圆之后就断了(当前选择变成圆, 而 SVG 的 <text> 不能塞进 <circle>);用 each,在每个组的回调里各自 append:

  d3.select("svg").selectAll("g")
    .data([3,5,9,15,24])
    .join("g")
      .attr("transform", d => `translate(${d*10}, 200)`)
      .each(function(d) {                      // 普通函数:this 是当前 <g>
        const group = d3.select(this);
        group.append("text").text(d).attr("y",100);
        group.append("circle").attr("r", d*2);
      });

call 的经典场景:同一段「配置函数」喂给多组图表(坐标轴、缩放行为内部也大量用 call)31:

  function config(group, scale, pos) {
    group.attr("transform", d => `translate(${pos + d*scale*5}, 200)`);
    group.append("text").text(d => d).attr("y",100);
    group.append("circle").attr("r", d => d*scale);
  }
  d3.select("svg").selectAll("g#one")  .data([1,2,4,7,12]).join("g").attr("id","one")  .call(config, 2, 0);
  d3.select("svg").selectAll("g#two")  .data([3,5,9,15,24]).join("g").attr("id","two")  .call(config, 2, 180);
  d3.select("svg").selectAll("g#three").data([3,5,9])     .join("g").attr("id","three").call(config, 1, 500);

4.2 raise / lower / sort / insert / create:元素排序与游离子树

SVG 没有 z-index,谁后画谁在上——元素在 DOM 里的顺序就是叠放顺序。四个方法管这件事32:

方法 干什么
raise() / lower() 把元素挪成父元素的最后一个/第一个孩子(置顶/置底)
sort(比较函数) 按绑的数据重排元素(DOM 顺序跟着变);比较器可用 d3.ascending/descending
insert(tag, 选择器) 像 append,但插到匹配元素之前(可用 circle[r='35'] 这类属性选择器定位)
d3.create("svg:circle") 造一棵游离的元素子树(不进页面),做好了再挂

raise 的典型交互:一堆叠在一起的圆,点哪个哪个 .raise() 到最上层33。 d3.create 的注意点:游离元素默认进 HTML 命名空间,SVG 元素要加前缀 d3.create("svg:circle"); 挂回页面时 append 不认选择器,要传函数返回节点:svg.append(() => circle.node())34。

5. 调试:两个「代码明明执行了、图却不动」的经典 bug

这一节的 bug 都很阴险:join 流程完全正常、没有报错,元素就是不渲染。原书给的排查路径只有一句话: 看生成的代码(元素检查器)35。

Bug 1:类名断链

  d3.select("svg").selectAll(".dot")      // 选 .dot 类
    .data([18,15,9,3,15,18])
    .join("circle");                      // 造出来的是 <circle>,没有 class="dot"!

页面原有 3 个 .dot 圆,join 补了 3 个新圆——但新圆没有 dot 类。 更新函数 d3.selectAll(".dot") 只能找到旧 3 个,新 3 个永远停在原点、半径 0,隐形36。

修法:join 之后立刻补类(官方推荐做法,类选择器配合 join 是原书认可的最佳实践):

  .join("circle").attr("class", "dot")    // ← 加这一行

Bug 2:元素挂错地方

  d3.selectAll(".dot")      // 全局选!没有先 d3.select("svg") 建立上下文
    .data(data)
    .join("circle")

d3.selectAll(全局函数)不保留父级上下文:新元素追加到了 <html> 根下, 不在 <svg> 里,继承不到 SVG 命名空间——浏览器不认 HTML 里的 <circle>,全部不渲染37。

修法:先选容器再选内容,父级上下文就对了:

  d3.select("svg")           // 先建立正确的上下文
    .selectAll(".dot")
    .data(data)
    .join("circle").attr("class", "dot")

两个 bug 合起来正好是本章两条铁律的反面:(1) join 出来的元素要带上选择时用的类名; (2) 更新用的选择必须和创建用的一样,在同样的父级上下文里。

6. 在线教程:卫星尺寸图(第二部分)

接着第 04 章末的教程:静态图升级成交互应用——控制面板选行星、换比例尺、悬浮提示, 用到本章的高级 join 和 key 函数。成品同样在 Observable 上可跑38。

7. 作者的判断与证据

判断 性质 依据
「join 优于手写 enter/exit/merge」 接口演进事实 老代码三段式被明确称为 legacy,join 是 v5+ 的官方写法39
「类选择器 + join 是推荐做法,但必须在 enter 时补类」 最佳实践 用 Bug 1 反证:类名断链导致元素隐形36
「内部属性(_groups 等)不是公开 API」 设计声明 原书「Important note」框明确警告5

8. 边界与局限

  • on()(事件)和 transition()(动画)两个选择器方法本章故意没讲——分别在第 14、13 章40。
  • 选择器性能(大列表的 join 开销、key 函数的哈希成本)全书没有定量讨论。
  • _enter/_exit 的内部结构属于实现细节,D3 版本升级可能变;本章的「看内部数组学机制」是教学手段,不是依赖契约。
  • 老代码里的 enter().append() + exit().remove() + merge() 三段式只提到名字,迁移细节在仓库 Appendix/Migration41。

判断(我们的,不是书里的): 这一章最值得带走的心智模型是「对账」两个字: join 本质是把两个集合(数据、元素)做一次集合运算——默认按位置(类似数组下标连接), key 函数按身份(类似数据库主键连接)。第 2 轮里那种「更新只剩一个圆」的错配感, 正是从「位置连接」切到「身份连接」时的直觉落差;想通这个类比,join 就没有任何神秘的了。 如果错,会错在: 数据库连接的类比暗示 join 会「重排」已有元素——实际上 D3 从不为匹配 而移动 DOM 里的元素位置,新元素永远追加在末尾,顺序变化要靠 key + sort 显式完成。 类比只能用到「对账规则」这一层。

9. 可带走的

本章主线例子,一行走完: 空 SVG → data([5,10,15,20,25]).join("circle") 造 5 圆(半径 5/10/15/20/25, cx 50–450)→ 换绑 [19,15,26],末尾 2 圆进 _exit 被删,剩 3 圆动画到新半径 → 再绑 [12,15,9,3,15,18],后 3 项经 _enter 补 3 个新圆,6 圆排开。加 key d => d 后, 对账按值不按位:只有 15 是「更新」,19/26 是「新建」,5/20/25 是「删除」。

  1. d3.select / selectAll 永远返回选择器对象,空手也返回——空选择是 join 的起点;
  2. 选择器内部 _groups(元素)/_parents(父级上下文),调试可看,代码不碰;
  3. select 传数据不留分组;selectAll 留分组不传数据——append 前想清楚;
  4. 回调三参数 d, i, nodes;箭头函数没有 this,当前元素用 nodes[i];
  5. data() 做对账,产出 update 选择;_enter 是数据多出的占位,_exit 是元素多出的;
  6. join("tag") = enter 追加 + exit 删除 + 合并返回;拆成三个回调可分阶段定制(进场/更新/离场各待遇不同);
  7. key 函数 = 数据身份证;按身份对账后「更新」不再等于「前排元素」;
  8. datum() 单点绑定,适合非数组数据、Canvas、data-* 属性取值;
  9. each 逐元素、call 每选择一次,把函数接进链里不拆链;
  10. 叠放顺序 = DOM 顺序:raise/lower/sort/insert 管层叠;
  11. 隐形元素两大 bug:join 没补类名、全局 selectAll 丢了父级上下文——查元素检查器。

10. 原文地图

主题 原书章 原文位置
章节定位:d3-selection 最重要 Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:5(搜「most important module」)
全局选择函数与空选择 Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:42(搜「d3.select(string)」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:96(搜「div.klm」)
node()/nodes() 与 canvas Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:127(搜「nodes()」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:142(搜「getContext」)
select 与 selectAll 的分组差别 Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:165(搜「propagates data」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:191(搜「only supported by selectAll()」)
attr/style/text/classed/property Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:209(搜「attr(name)」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:283(搜「style() is recommended」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:291(搜「transform CSS property」)
回调三参数与箭头函数 this Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:302(搜「up to three parameters」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:336(搜「arrow functions」)
filter vs select(保序) Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:385(搜「d % 3 == 0」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:394(搜「_groups: [p#n2」)
data()/datum()/join() 方法表 Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:479(搜「_groups, _enter, and _exit」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:500(搜「join(tagname)」)
内部属性非公开 API Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:589(搜「not part of the public API」)
主线例子三轮 join Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:667(搜「sel1.size() == 0」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:674(搜「data([5,10,15,20,25])」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:683(搜「join("circle")」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:724(搜「data([19,15,26])」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:746(搜「data([12,15,9,3,15,18])」)
update() 与 transition Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:701(搜「function update()」)
拆开的 join 三回调 Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:779(搜「Controlling separate join steps」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:819(搜「attr("fill", "green")」)
key 函数与按值对账 Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:855(搜「d => d.id」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:870(搜「identity key function」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:880(搜「Two new circles will be added」)
datum() Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:902(搜「one-to-one data binding」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:927(搜「typically used」)
each/call Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:968(搜「call(function,args)」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:1012(搜「each(function (d)」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:1034(搜「call(config, 2, 0)」)
raise/lower/sort/insert/create Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:1054(搜「bring to front」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:1085(搜「bound to data in array index order」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:1102(搜「insert("rect"」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:1110(搜「d3.create("svg")」)
两个 join bug Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:1139(搜「1-data-join-bug-1」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:1179(搜「add this」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:1197(搜「2-data-join-bug-2」) · text/13-ch06-chapter-6-selections-and-data-binding.txt:1209(搜「first get the right context」)
在线教程第二部分 Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:1221(搜「part 2」)
章小结 Selections and Data Binding text/13-ch06-chapter-6-selections-and-data-binding.txt:1235(搜「Summary」)

  1. 出处:「Selections and Data Binding」第 11–12 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:12,搜「essential to achieving proficiency」)。原文:「Mastering these subjects is essential to achieving proficiency in D3.」 ↩

  2. 出处:「Selections and Data Binding」第 532–534 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:533,搜「typing commands」)。本章大量示例通过浏览器控制台逐段演示,见原书各小节。 ↩

  3. 出处:「Selections and Data Binding」第 40–51 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:42,搜「d3.select(string)」)。函数表照原书 Table 6.1 转写。 ↩

  4. 出处:「Selections and Data Binding」第 94–100 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:96,搜「div.klm」;:99,搜「empty NodeList」)。原文:「A selection object that doesn't match any existing elements can be used to create and append new matching elements to the DOM tree after it is bound to a data array.」 ↩

  5. 出处:「Selections and Data Binding」第 586–590 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:589,搜「not part of the public API」)。原文:「While examining their contents may be useful for debugging purposes, they are not part of the public API and should never be accessed directly in your code.」 ↩↩

  6. 出处:「Selections and Data Binding」第 139–145 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:142,搜「getContext("2d")」)。原文:「Calling node() in the preceding example converts the selection into an HTMLCanvasElement object, from where you can call getContext().」 ↩

  7. 出处:「Selections and Data Binding」第 156–178 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:162,搜「selection.select(string)」)。方法表照原书 Table 6.3 转写。 ↩

  8. 出处:「Selections and Data Binding」第 191–195 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:191,搜「only supported by selectAll()」)。原文:「both of the following commands select the first <p> element in <div.abc>, but only the first sets it as parent」。 ↩

  9. 出处:「Selections and Data Binding」第 180–198 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:186,搜「_parents」)。Figure 6.2 的说明文字与截图对比见第 188 段。 ↩

  10. 出处:「Selections and Data Binding」第 202–247 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:209,搜「attr(name)」)。方法表照原书 Table 6.4 转写;「除 classed() 外都可以当 getter」见第 233 段(搜「used as getters」)。 ↩

  11. 出处:「Selections and Data Binding」第 283–299 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:283,搜「style() is recommended」)。原文:「When applying fills, strokes, and font styles, style() is recommended.」 ↩

  12. 出处:「Selections and Data Binding」第 290–295 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:294,搜「translate(25,25) rotate(45)」)。 ↩

  13. 出处:「Selections and Data Binding」第 301–309 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:302,搜「up to three parameters」)。 ↩

  14. 出处:「Selections and Data Binding」第 336–343 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:341,搜「nodes array and the current index」)。 ↩

  15. 出处:「Selections and Data Binding」第 356–375 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:361,搜「d % 2 == 0」;:375,搜「hidden」)。 ↩

  16. 出处:「Selections and Data Binding」第 379–389 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:383,搜「multiples of three」)。 ↩

  17. 出处:「Selections and Data Binding」第 391–402 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:401,搜「Indices are important」)。 ↩

  18. 出处:「Selections and Data Binding」第 660–670 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:667,搜「sel1.size() == 0」)。 ↩

  19. 出处:「Selections and Data Binding」第 672–697 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:683,搜「join("circle")」;:675,搜「EnterNode」)。 ↩

  20. 出处:「Selections and Data Binding」第 699–715 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:705,搜「i * 100 + 50」)。 ↩

  21. 出处:「Selections and Data Binding」第 717–739 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:724,搜「data([19,15,26])」;:732,搜「exit().remove()」)。 ↩

  22. 出处:「Selections and Data Binding」第 740–760 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:756,搜「not equivalent」)。原文:「The merged3 selection not equivalent to upd3.enter().append("circle"), as enter() returns a selection of the new circles, while join() returns the merged selection with all circles.」 ↩

  23. 出处:「Selections and Data Binding」第 762–773 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:763,搜「single chain」)。原文:「you don't have to break it down into separate selections and can write it all in a single chain」。 ↩

  24. 出处:「Selections and Data Binding」第 779–803 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:782,搜「shortcut for the following default」)。join("tag") 的等价展开式照原书第 784–800 段转写。 ↩

  25. 出处:「Selections and Data Binding」第 833–842 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:833,搜「apply the same styles」)。 ↩

  26. 出处:「Selections and Data Binding」第 849–900 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:869,搜「data([19,15,26], d => d)」;:880,搜「Two new circles will be added」)。 ↩

  27. 出处:「Selections and Data Binding」第 859–861 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:859,搜「not required to return unique values」)。原文:「Key functions are not required to return unique values, and if duplicates occur, only the initial value is considered.」 ↩

  28. 出处:「Selections and Data Binding」第 901–930 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:901,搜「One-to-one data binding」)。 ↩

  29. 出处:「Selections and Data Binding」第 927–930 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:930,搜「usually simpler and more efficient」)。 ↩

  30. 出处:「Selections and Data Binding」第 957–979 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:968,搜「call(function,args)」)。方法表照原书 Table 6.8 转写。 ↩

  31. 出处:「Selections and Data Binding」第 1018–1046 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1034,搜「call(config, 2, 0)」;:1045,搜「draw axes」)。 ↩

  32. 出处:「Selections and Data Binding」第 1048–1066 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1054,搜「bring to front」)。方法表照原书 Table 6.9 转写。 ↩

  33. 出处:「Selections and Data Binding」第 1068–1083 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1080,搜「circle.raise()」)。 ↩

  34. 出处:「Selections and Data Binding」第 1106–1125 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1110,搜「d3.create("svg")」;:1122,搜「circle.node()」)。 ↩

  35. 出处:「Selections and Data Binding」第 1131–1135 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1134,搜「common bugs」)。 ↩

  36. 出处:「Selections and Data Binding」第 1139–1180 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1151,搜「zero radius」;:1154,搜「class='dot'」;:1179,搜「attr("class", "dot")」)。 ↩↩

  37. 出处:「Selections and Data Binding」第 1184–1215 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1200,搜「SVG namespace」;:1212,搜「attr("class", "dot")」)。原文:「since HTML doesn't recognize the <circle> element, these circles won't be rendered」。 ↩

  38. 出处:「Selections and Data Binding」第 1221–1234 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1225,搜「control panel」;:1233,搜「Observable notebook」)。 ↩

  39. 出处:「Selections and Data Binding」第 844–845 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:844,搜「older D3 code」)。原文:「If you stumble upon older D3 code, instead of join(), it might use enter().append(), exit().remove(), and merge() to perform joins.」 ↩

  40. 出处:「Selections and Data Binding」第 1126–1128 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:1126,搜「selection methods were not covered」)。原文:「Two important selection methods were not covered in this chapter: on() for event handling and transition() for animations, since we already introduced their basic usage in Chapter 4.」 ↩

  41. 出处:「Selections and Data Binding」第 844–845 段(text/13-ch06-chapter-6-selections-and-data-binding.txt:844,搜「older D3 code」)与「References and Additional Resources」第 130–131 段(text/28-ch20-chapter-20-references-and-additional-resources.txt:129,搜「Migration」)。 ↩