d3.js selection机制——示例详解

前言

d3中经过selection来操做元素,但selection并不能简单地被认为是“一个装了元素的数组”。本文学习自d3做者Mike Bostock的文章How Selections Work,同时添加了个人代码实践,代码均会放在CodePen上给你们自行调试

但愿能起到理解selection的做用,不足或错误之处还请指出

javascript

selection是数组的子类

那什么是selection呢?
selection并非一个DOM数组,它是数组的子类
与数组不一样点有以下:html

  1. 提供了操做被选中DOM元素的方法
  2. selection数组的元素是group

接下来的两个小标题将解释这两点java

提供操做被选中DOM元素的方法

selection提供了操做被选中DOM元素的方法,好比selection.attr、selection.style等等,同时也继承了数组的一些方法,好比array.forEach、array.map。固然,咱们通常不会使用这些原生方法,而是使用d3提供的更加方便的方法,好比selection.eachgit

元素为group的数组

selection是一个元素为group的数组,每个group是一个DOM数组
用图表能够这样表示,椭圆表示数组元素,中括号表示数组:
github


只有 selection.selectAll可以得到有多个group的selection。接下来咱们以 图表 + 代码 + 控制台打印来讲明group,再说明group有什么做用

一个group的selection

<h1></h1>
<h1></h1>
<h1></h1>
<h1></h1>
<script> const selection = d3.selectAll('h1'); <script> 复制代码


对应控制台咱们能够看见只有一个group

若是是数组

d3.select('h1')
复制代码

则会选择一个h1标签
bash


CodePen打开

多个group的selection

上文咱们提到过只有selection.selectAll可以得到有多个group的selectionapp

<h1>
  <p></p>
  <p></p>
</h1>

<h1>
  <p></p>
  <p></p>
</h1>

<script>
  const h1Selection = d3.selectAll('h1');
  const groupSelection = h1Selection.selectAll('p');
<script>
复制代码


对应控制台,咱们能够看见有多个group

每个旧元素(此例中为h1)会变成一个group,group中包含旧元素下匹配的元素(此例中为p);同时每个group会有对应的 父元素,此例中则为h1
另外,d3.select与d3.selectAll的父元素均为html根元素
CodePen打开

group有什么做用

  1. 操做元素时,将相同层次结构的DOM元素以组划分,方便多组DOM元素的操做
  2. 数据绑定时,以group划分,方便多层次结构的数据绑定

这里咱们先举操做元素的例子,数据绑定的例子在后文

group将相同层次结构的DOM元素以组划分,在进行多组DOM元素操做时更加方便,所以,selction.attr与selection.style所调用函数的第二个参数是在group范围的下标,而不是selection范围的下标函数

groupSelection.attr('some-attr', (data, index)=>{
  console.log('元素在group范围内的下标:' + index);
})
复制代码


CodePen打开


至此,咱们能够看出selection并不能简单地认为是一个DOM元素数组,而是数组的子类,数组元素为group,同时提供了操做被选中DOM元素的方法

接下来咱们进一步探索selection的相关操做学习

不会生成group的操做

只有selection.selectAll方法会生成含多个group的selection
由于select方法只会选中每一个group一个元素,因此select方法会保存已有的group,同时传递已绑定的数据

const exampleData = [1, 2];
const h1Selection = d3.selectAll('h1');
const groupSelection = h1Selection.selectAll('p');
console.log(groupSelection);

groupSelection.data(exampleData)

const spanSelection = groupSelection.select('span');
console.log(spanSelection);
console.log(spanSelection.data());
复制代码


CodePen打开
经过控制台打印,咱们能够看见select方法 保存了原先存在的group,同时传递了数据

同时,因为selection.append和selection.insert方法是在select之上进行封装的,因此他们也有保存group和传递数据的特性,这就很是方便了咱们使子元素根据父元素的数据进行相对应的构建,例如:

咱们把数据绑定在已有的div上,在div中插入p,就能够直接使用对应的数据进行操做了

数据绑定与selection

那么数据绑定是“绑”到哪里的呢?d3把数据绑定到DOM元素的__data__属性上而非selection的属性上。

const data = [10,11,12];
const selection = d3.selectAll('div').data(data);
console.log(selection);
const DOMList = document.querySelectorAll('div');
console.log(DOMList);
复制代码

CodePen打开
咱们能够看到DOM元素中即有对应绑定的数据


若是被selection选中的DOM元素没有数据,则会返回undefined

<div></div>
<div></div>
<div></div>
<div></div>
  ......
const data = [10,11,12];
const selection = d3.selectAll('div').data(data);
  ......
console.log(d3.selectAll('div').data());
复制代码


CodePen打开
所以数据是一直绑定在DOM元素上的,selection变量是能够销毁的,咱们彻底能够从新选择DOM元素,从新获取先前绑定的数据并进行操做

同时咱们列举下绑定数据的三种方法:

  • selection.data
  • sekectuib.datum
  • 经过append、insert、select从父节点继承

selection.data与group

selection.data是以group为单位进行数据绑定的,而不是以每个元素为单位
咱们来看看多个group的状况

以上图为例,当有多个group时,selection.data(argument1)传入的argument1为函数相比数组变量更为合适,同时这个函数的参数也会有对应的group下标
咱们仍是以代码为例更容易理解

<div>
  <p></p>
  <p></p>
  <p></p>
  <p></p>
</div>
<!-- ...... -->
<!-- ......一共有三个相似的div组 -->

const dataArray = ['a', 'b', 'c', 'd'];
const groupSelection = d3.selectAll('div').selectAll('p');
console.log(groupSelection);
groupSelection.data((parentData, groupIndex)=>{
  //console.log(parentData); //由于父元素无data绑定,因此返回undefined
  console.log('groupIndex:' + groupIndex);
  return dataArray;
});
console.log(groupSelection.data());
复制代码

CodePen打开
此处groupSelection.data()传入的为函数,此函数的第一个参数表示parentData,第二个参数就表示上文提到的groupIndex,由于有三个group,此例中会执行三次数据绑定,咱们也能够在控制台上看见按组划分的groupIndex


每次函数 return dataArray则对应绑定的数据

此例仅为每一个group都返回一样的一维数据,旨在说明的是groupIndex

这里让咱们回想上文提到的group的另外一个做用:数据绑定时,以group划分,方便多层次结构的数据绑定

当我赶上多层次数据对应多组group的时候,咱们就能够减轻层级DOM及层级data的绑定
这里举一个简单的应用:

const hierarchicalData = [
    ['a', 'b', 'c', 'd'],
    ['e', 'f', 'g', 'h'],
    ['i', 'j', 'k', 'l']
];
    .....
groupSelection.data((parentData, groupIndex)=>{
    return hierarchicalData[groupIndex];
});
复制代码

这样,就能够把对应的多层次数据分别绑定到group之中

当当selection只有一个group时咱们传值就没必要传函数了,直接传数组便可,通常只有在多个group的状况下才须要向selection.data()传函数

数据绑定匹配元素

当咱们把数据绑定到元素上时,是经过pairing keys(不知道咋翻译)匹配的,key是一个不可重复的字符串,用于为数据绑定进行匹配

最简单的key是取元素对应下标,这里我直接截取Mike Bostock文中内容


第0个元素匹配data[0],第1个元素匹配data[1]。。。
若是数据和元素的顺序没变的话,index为key是很方便的,当顺序改变时,index就没法匹配到原先的元素或是数据了,这时咱们须要第二个参数key function来指定key

咱们再举一个例子,假设某组元素绑定了一组数据,元素顺序改变,咱们但愿 保持原有元素并绑定数据,而不是销毁重建

咱们继续以代码说明

const oldData = [
    { name: "A", value: "I am A" },
    { name: "B", value: "I am B" },
    { name: "C", value: "I am C" },
    { name: "D", value: "I am D" },
    { name: "E", value: "I am E" },
];
const selection = d3.selectAll('div').data(oldData);

......

//模拟元素从新排序,此行代码后元素会倒序
const sortedSelection = selection.sort((a, b)=>{
  return -1;
});

//新数据
const newData = [
    { name: "B", value: "I am B!!!!!" },
    { name: "A", value: "I am A!!!!!" },
    { name: "C", value: "I am C!!!!!" },
    { name: "D", value: "I am D!!!!!" },
    { name: "E", value: "I am E!!!!!" },
];
//匹配绑定
sortedSelection.data(newData, (d)=>{
  return d.name;
});
复制代码

CodePen打开
咱们在绑定数据后对DOM元素进行重排,再次绑定新的数据时根据d.name匹配,能够发现元素顺序仍为重排后的顺序,而并未从新建立

结语

在学习How Selections Work以后,我对selection及group有了更深的理解,group数据传递key parser在数据绑定、多层级数据上着实提供了极大的便利,此文能够说是我的的学习总结,若有不足或错误之处还请大佬指出

参考资料

How Selections Work
d3.js API

相关文章
相关标签/搜索