group 元素
在初见 selection 不久时,会常识性的认为 selection 是 dom 元素的集合,但实际上,selection 是 group 的数组集合,而 group 才是 dom 元素的数组集合。
我们可以把一个selection实例打印下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<script src="../d3.js"></script>
<script>
const body = d3.select('table')
console.dir(body)
</script>
</body>
</html>
可以看到selection的结构分为 _groups 和 _parents。
_parents中其实就是 selection 的父级元素。使用
d3.select()d3.selectAll()得到的 selection 的父元素是 html。当使用
d3.selectAll('tr').selectAll('td')时,_parents就是每个 group 的父元素的数组集合。当对 enter 选择集调用
selection.append()方法时,d3 会进行特殊的处理,让新插入的元素插入到 group 的父节点中去,并且用新插入的元素取代占位符。
_groups中就是 dom 元素的集合 group 。使用
d3.select()d3.selectAll()得到的 selection 中 group 只有一个。
selection.selectAll()得到的 group 是多个,selection 原本的元素都将变为新 selection 的 group 。
select()不改变 group 。
空元素
group中可以保存空元素,来表示元素的缺失,空元素会在 selection.select 无法找到符合要求的子元素时被创建. 因为 select 方法会维护 group 的结构, 所以它会在缺失元素的地方填上 null. 比如下面这个例子, 四个 section 中只有两个有 aside 元素:
d3.selectAll('section').select('aside')
许多方法都会忽略空元素,但是 index 会计算空元素。
数据
使用 selection.data() 绑定数据时,数据是被保存在了 dom 元素的 __data__ 属性上,对于enter来说,则是一个对象的 __data__ 属性上,在对 enter 选择集添加元素后,d3会帮我们处理好。
selection.data() 是为每一个 group 定义数据, 而不是为每一个 dom 元素定义数据: 对于 group 来说, 数据应该是一个数组或者是一个返回数组的 function. 因此, 一个有多个 group 的 selection 其对应的数据也应该是一个包含多个子数组的数组.
d3 通过唯一的key值将元素和数组绑定到一起,最简单的便是索引。只需要保证key在group中是唯一的。当数据的索引会变化时,我们就需要指定一个key function。
selection.data(data, (data) => data.key)
因为我们用 key 值来匹配 DOM 元素和数据, 所以会有三种情况出现:
- update - 对于某一个数据, 有相同 key 值的 dom 元素想对应
- enter - 对于某一个数据, 没有相同 key 值的 dom 元素相对应
- exit - 对于某一个 DOM 元素, 没有相同 key 值的数据相对应
所以一般我们要指定各个情况下的处理。