D3.js 核心概念——数据绑定(七)绑定数据之一

762 阅读6分钟

原文(持续更新):datavis-note.benbinbin.com/article/d3/…


系列文章可以查看《数据可视化》专栏


当我们获得了选择集后,下一步一般是将元素与数据进行绑定,这样数据就会添加到 DOM 元素的 __data__ 属性中,即特定的数据就和特定的元素「结合」在一起了(即使清空了选择集,再次重新选择元素,D3 也可以读取到原来绑定的数据)

使用方法 selection.data([data[, key]]) 为选择集中的元素绑定数据,根据选择集中分组 group 的数量不同,该方法的参数 data 要求也不同:

  • 如果选择集中含有多个分组(使用方法 selection.selectAll() 嵌套选择生成的选择集可能含有多个分组),则入参 data 应该是一个返回数组的函数,每一个分组都会调用该方法,并依次传入三个参数:

    • 当前所遍历的分组的父节点所绑定的数据 datum d
    • 当前所遍历的分组的索引 index i
    • 选择集的所有父节点 parent nodes nodes

    其中函数内的 this 指向当前所遍历的分组的父节点,即与 nodes[i] 相同

    最后该函数返回一个数组,然后该数组的元素就用于与该分组的元素进行绑定

    例如为一个 4x4 的表格的各单元格绑定数据,其中选择集是具有多个分组,每一个分组就是一行,每一个分组内的元素就是每一个行相应的单元格

    selection-data.png

  • 如果选择集中只有一个分组,则入参 data 应该是一个数组,该数组会与选择集中的元素进行绑定。

    💡 需要绑定的数组里的元素可以是任意类型的数据,可以是一个数值,或是一个对象都行。

默认基于索引来对选择集中的元素和数组中的数据进行匹配 join-by-index,即选择集的第一个元素和数组的第一个数据进行绑定,并依次类推,但我们可以通过第二个入参 key 来自定义匹配规则

第二个参数 key 是一个返回一个字符串作为标识符的函数,该函数会被元素数据依次调用,以分别计算出表示各元素的键/标识符,和表示数据的键/标识符,如果两者的键匹配,则它们就会配对绑定。

💡 如果有多个元素具有同一个键,则多出来的元素会被放到 exiting 选择集中;如果多个数据具有同一个键,则多出来的数据所对应的「虚拟节点」会被放到 entering 选择集中

key 函数被元素调用时,会依次传入三个参数:

💡 此时函数内的 this 指向当前所遍历的元素,即与 node[i] 相同。

💡 最后函数返回的字符串作为该元素的标识符/键

  • 当前所遍历的元素(在之前)已绑定的数据 datum d
  • 当前所遍历的元素在分组中的索引 index i
  • 当前所遍历的元素所在的分组中所有元素 nodes

key 函数被数据调用时,也会依次传入三个参数:

💡 此时函数内部的 this 指向的是该分组的父节点 parent node

💡 最后函数返回的字符串作为该数据的标识符/键

  • 当前所遍历的数据 datum d
  • 当前所遍历的数据在数组中的索引 index i
  • 整个数组 arr
<!-- 页面当前已有的元素 -->
<div id="Ford"></div>
<div id="Jarrah"></div>
<div id="Kwon"></div>
<div id="Locke"></div>
<div id="Reyes"></div>
<div id="Shephard"></div>
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) {
    // key 函数,如果传入的 d 存在,则取其属性 name 作为键
    // 否则取元素的 id 属性(这是一个回退操作,因为在初始时,元素还没有绑定数据,所以 d 是 undefined)
    return d ? d.name : this.id;
  })
    .text(d => d.number);

以上示例的 key 函数会依次被元素和数据调用,共执行 10 次。

元素在绑定数据时,并不一定是一一对应的,可能会出现节点和数据元素个数不匹配的问题,针对这个问题,D3 提出三个概念:

  • 如果 DOM 节点多出来,则未绑定数据的节点会进入名为 ==exiting 选择集==(准备从页面「离去」的节点,一般在后续操作中删除)
  • 如果数据元素多出来了,则对应多出来的「虚拟节点」会进入名为 ==entering 选择集==(一般会在后续操作中实例化这些 DOM 节点,并插入在页面的相应位置)
  • 可与数据对应上的 DOM 节点,进入名为 ==updating 选择集==,它是默认选择集,即 selection.data() 方法返回的对象就是 update 选择集(而 enter 选择集和 exit 选择集需要调用该对象的 enter()exit() 方法才能获得

💡 在绑定数据后,D3 没有立即更新(增删)页面节点,而是生成 3 个选择集,这样为数据可视化提供了更大的灵活度和可定制性,例如对于 exiting 选择集的节点,可以在删除时设置一些淡出的动效;对于 entering 选择集的节点可以设置不一样的颜色,高亮出来它们是新增到页面上的

💡 选择集绑定数据后,返回的三个选择集 entering、exiting、updating 其中的元素次序会有所不同

  • 其中选择集 entering 和 updating 中元素会根据其绑定的数据在新数组中的索引顺序进行排列,而且会在相应的位置「留空」(使用 null 作为占位符),便于后续两者合并 merge
  • 而 exiting 选择集会根据其原来绑定的数据在原来的数组中的位置来排序,也是在相应的位置「留空」,以便保持元素原有的索引值。

enter-exit-update.png

然后将 entering 选择集的「虚拟节点」添加 append 到页面时,元素会基于索引顺序「混入」页面的 updating 选择集的元素中(作为兄弟元素),这样可以确保最后页面的元素顺序与它们所绑定的数据在数组顺序一致

⚠️ 但是由于 D3 会复用 updating 选择集的元素(以便提高性能),对于 updating 选择集中的元素,如果在新绑定的数组中,它们所对应的数据的索引发生了变化时,页面的相应元素的次序并不会更新,如果不进行「重排」,可能造成最后页面的元素无法与它们所绑定的数据在数组顺序一致。因此一般在选择集绑定新数据后调用 selection.order() 方法,以更新 updating 选择集在页面的元素的顺序