原文: Mike Bostock (D3.js 做者) -- Nested Selectionsjavascript
译者: ssthousehtml
本文讲解的是关于 D3.js 中 d3-selection 的使用. d3-selection 是 d3 的核心所在, 它提供了一种和以往 Dom 操做 和 数据操做 彻底不一样的思路, 让咱们能很是优雅的进行数据可视化工做. 本文是 d3 做者对于 d3-selection 中 嵌套选择集 的讲解, 本人阅读后以为颇有启发, 因此翻译成中文, 但愿对读者也能有所帮助.java
D3 的 选择集是分层级的, 就像 Dom 元素和数据集是能够有层级的同样. 好比说 Table:node
<table>
<thead>
<tr><td> A</td><td> B</td><td> C</td><td> D</td></tr>
</thead>
<tbody>
<tr><td> 0</td><td> 1</td><td> 2</td><td> 3</td></tr>
<tr><td> 4</td><td> 5</td><td> 6</td><td> 7</td></tr>
<tr><td> 8</td><td> 9</td><td> 10</td><td> 11</td></tr>
<tr><td> 12</td><td> 13</td><td> 14</td><td> 15</td></tr>
</tbody>
</table>
复制代码
若是让你只选中 tbody 元素中的 td, 你会如何操做? 直接使用 d3.selectAll('td')
显然会选中全部的 td 元素(包括 thead 和 tbody). 若是想只选中存在与 B 元素中的 A 元素, 你须要这样操做:git
var td = d3.selectAll('tbody td')
复制代码
除了上面那种方法, 你还能够先选中 tbody, 再选中 td 元素, 像这样:github
var td = d3.select('tbody').selectAll('td')
复制代码
由于 selectAll 会对当前选择集中的每一个元素(如: tbody)选中其符合条件的子元素(如: td). 这种方法会获得和 d3.selectAll('tbody td')
相同的结果. 但若是你以后想要对同一父元素的选择集引伸出更多的选择集的话 (好比区分选中 table 中的奇数行选择集和偶数行选择集), 这种嵌套选择集方式会方便的多.数组
接着上面的例子, 若是你使用 d3.selectAll('td')
, 你会获得一个平展的选择集, 像这样:app
var td = d3.selectAll('tbody td')
复制代码
平展的选择集缺乏了层级结构: 不管是 thead 中的 td 仍是 tbody 中的 td 全都被展开成了一个数组, 而不是以父元素进行分组. 这让咱们想对每一行或是每一列的 td 进行操做变得很困难. 与此相反的, D3 的嵌套选择集能保存层级关系. 好比咱们想以行的方式对选择集分组, 咱们首先选中 tr 元素. 而后选中 td 元素.函数
var td = d3.selectAll('tbody tr').selectAll('td')
复制代码
如今, 若是你想让第一列的全部元素变红, 你能够利用 index 参数 i:post
td.style('color', function(d, i) {
return i ? null : 'red'
})
复制代码
上面的参数 i 是 td 元素在 tr 中的索引, 你还能够经过添加一个 j 参数来得到当前的行数的索引. 像这样:
td.style('color', function(d, i, j) {
console.log(`current row: ${j}`)
return i ? null : 'red'
})
复制代码
层级结构的 Dom 元素经常是由层级结构的数据来驱动的. 而层级的选择集能更方便的处理数据绑定. 继续上面的例子, 你能够把 table 的数据表示为一个矩阵:
var matrix = [
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
]
复制代码
为了让这些数据绑定上对应的 td 元素, 咱们首先将矩阵的每一行和 tr 绑定对应起来, 而后再将矩阵中一行的每个元素和 tr 中的每个 td 绑定起来:
var td = d3
.selectAll('tbody tr')
.data(matrix)
.selectAll('td')
.data(function(d, i) {
return d
}) // d is matrix[i]
复制代码
须要注意的是, data()
不只能够传入一个数据, 它还能够传入一个 返回一个数组的 function. 平展的选择集一般对应的是单个数组, 是由于平展的选择集只有一个 group.
上面的 row 选择集是一个平展的选择集, 由于它是直接由 d3.selectAll 建立的:
var tr = d3.selectAll('tbody tr').data(matrix)
复制代码
而 td 的选择集是嵌套的:
var td = tr.selectAll('td').data(function(d) {
return d
}) // matrix[i]
复制代码
data 传入的 操做函数给每个 group 绑定了一个数组数据. d3 会对每一行 tr 调用操做函数. 由于父元素数据是矩阵, 因此操做函数在每次被调用时只是简单的返回矩阵中当前行的数据, 来和 tr 进行绑定.
嵌套选择集有一个微妙但可能形成严重影响的反作用: 它会给每一个 group 设置父节点. 父节点是选择集的一个隐藏属性, 它会在被调用 append 方法时使用, 将子元素添加到父节点的 Dom 元素当中. 好比: 若是你想经过下面的方式进行数据绑定操做, 你会获得一个 error:
d3.selectAll('table tr')
.data(matrix)
.enter()
.append('tr') // error!
复制代码
上面的代码之因此会报错, 是由于默认的父节点是 html 元素, 你不能直接将 tr 元素添加到 html 元素中. 因此, 咱们应该在进行数据绑定前, 先选择好父节点:
d3.select('table')
.selectAll('tr')
.data(matrix)
.enter()
.append('tr') // success
复制代码
这种方式能够用来选择任意层级的嵌套选择集. 好比你想从头建立一个 table, 并填入上面矩阵中的数据, 你能够首选选中 body 元素:
var body = d3.select('body')
复制代码
接下来在父节点 body 中添加一个 table:
var table = body.append('table')
复制代码
接下来绑定矩阵数据, 建立 tr 元素. 由于 selectAll 是在 table 元素上进行调用的, 因此父节点是 table:
var tr = table
.selectAll('tr')
.data(matrix)
.enter()
.append('tr')
复制代码
最后咱们以 tr 做为父节点, 建立 td 元素:
var td = tr
.selectAll('td')
.data(function(d) {
return d
})
.enter()
.append('td')
复制代码
在 D3 中, select 和 selectAll 有一个很重要的区别: select 会继续使用当前存在的 group, 而 selectAll 老是会建立新的 group. 所以调用 select 能保存原有 selection 的数据, 索引位置, 甚至父节点.
好比, 下面的平展的选择集, 它的父节点仍然是 html 节点:
var td = d3.selectAll('tbody tr').select('td')
复制代码
想要获得嵌套的选择集, 惟一的方法就是在已有的选择集的基础上, 调用 selectAll 方法. 这就是为何数据绑定老是出如今 selectAll 以后, 而不是 select 以后.
这篇文章使用 table 做为例子仅仅是为了方便讲解层级结构. 其实 table 的使用并非特别具备典型性. 其实还有许多其它 嵌套选择集的例子(点我查看, 点我查看)
就像 用 join 的方式思考 同样, 嵌套选择集一样使用了一种思想上彻底不一样的处理 Dom 元素 和 数据 的思路. 这种思路刚开始可能很难理解, 但你一旦掌握了, 你就能得心应手的使用 D3.js 来完成你的数据可视化任务了.