[译] D3.js 嵌套选择集 (Nested Selection)

[译] D3.js 嵌套选择集 (Nested Selection)

译者注:

原文: Mike Bostock (D3.js 作者) -- Nested Selections

译者: ssthouse

本文讲解的是关于 D3.js 中 d3-selection 的使用. d3-selection 是 d3 的核心所在, 它提供了一种和以往 Dom 操作数据操作 完全不同的思路, 让我们能非常优雅的进行数据可视化工作.
本文是 d3 作者对于 d3-selection 中 嵌套选择集 的讲解, 本人阅读后觉得很有启发, 所以翻译成中文, 希望对读者也能有所帮助.

译文

D3 的 选择集是分层级的, 就像 Dom 元素和数据集是可以有层级的一样. 比如说 Table:

<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 元素, 你需要这样操作:

var td = d3.selectAll('tbody td')

除了上面那种方法, 你还可以先选中 tbody, 再选中 td 元素, 像这样:

var td = d3.select('tbody').selectAll('td')

因为 selectAll 会对当前选择集中的每个元素(如: tbody)选中其符合条件的子元素(如: td). 这种方法会得到和 d3.selectAll('tbody td') 相同的结果. 但如果你之后想要对同一父元素的选择集引申出更多的选择集的话 (比如区分选中 table 中的奇数行选择集和偶数行选择集), 这种嵌套选择集方式会方便的多.

嵌套中索引的使用

接着上面的例子, 如果你使用 d3.selectAll('td') , 你会得到一个平展的选择集, 像这样:

[图片上传失败...(image-6493d1-1530010072875)]

var td = d3.selectAll('tbody td')

平展的选择集缺少了层级结构: 不论是 thead 中的 td 还是 tbody 中的 td 全都被展开成了一个数组, 而不是以父元素进行分组. 这让我们想对每一行或是每一列的 td 进行操作变得很困难. 与此相反的, D3 的嵌套选择集能保存层级关系. 比如我们想以行的方式对选择集分组, 我们首先选中 tr 元素. 然后选中 td 元素.

[图片上传失败...(image-16b4f6-1530010072875)]

var td = d3.selectAll('tbody tr').selectAll('td')

现在, 如果你想让第一列的所有元素变红, 你可以利用 index 参数 i:

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 创建的:

[图片上传失败...(image-9b5f49-1530010072875)]

var tr = d3.selectAll('tbody tr').data(matrix)

而 td 的选择集是嵌套的:

[图片上传失败...(image-b51862-1530010072875)]

var td = tr.selectAll('td').data(function(d) {
  return d
}) // matrix[i]

data 传入的 操作函数给每一个 group 绑定了一个数组数据. d3 会对每一行 tr 调用操作函数. 因为父元素数据是矩阵, 所以操作函数在每次被调用时只是简单的返回矩阵中当前行的数据, 来和 tr 进行绑定.

嵌套中父节点作用

嵌套选择集有一个微妙但可能造成严重影响的副作用: 它会给每个 group 设置父节点. 父节点是选择集的一个隐藏属性, 它会在被调用 append 方法时使用, 将子元素添加到父节点的 Dom 元素当中. 比如: 如果你想通过下面的方式进行数据绑定操作, 你会得到一个 error:

[图片上传失败...(image-61adb-1530010072875)]

d3.selectAll('table tr')
  .data(matrix)
  .enter()
  .append('tr') // error!

上面的代码之所以会报错, 是因为默认的父节点是 html 元素, 你不能直接将 tr 元素添加到 html 元素中. 所以, 我们应该在进行数据绑定前, 先选择好父节点:

[图片上传失败...(image-56bf04-1530010072875)]

d3.select('table')
  .selectAll('tr')
  .data(matrix)
  .enter()
  .append('tr') // success

这种方式可以用来选择任意层级的嵌套选择集. 比如你想从头创建一个 table, 并填入上面矩阵中的数据, 你可以首选选中 body 元素:

[图片上传失败...(image-5b5d98-1530010072875)]

var body = d3.select('body')

接下来在父节点 body 中添加一个 table:

[图片上传失败...(image-691dac-1530010072875)]

var table = body.append('table')

接下来绑定矩阵数据, 创建 tr 元素. 因为 selectAll 是在 table 元素上进行调用的, 所以父节点是 table:

[图片上传失败...(image-34a1f4-1530010072875)]

var tr = table
  .selectAll('tr')
  .data(matrix)
  .enter()
  .append('tr')

最后我们以 tr 作为父节点, 创建 td 元素:

[图片上传失败...(image-add8e2-1530010072875)]

var td = tr
  .selectAll('td')
  .data(function(d) {
    return d
  })
  .enter()
  .append('td')

要不要使用嵌套选择集 ?

在 D3 中, select 和 selectAll 有一个很重要的区别: select 会继续使用当前存在的 group, 而 selectAll 总是会创建新的 group. 因此调用 select 能保存原有 selection 的数据, 索引位置, 甚至父节点.

比如, 下面的平展的选择集, 它的父节点仍然是 html 节点:

[图片上传失败...(image-d0df1-1530010072875)]

var td = d3.selectAll('tbody tr').select('td')

想要得到嵌套的选择集, 唯一的方法就是在已有的选择集的基础上, 调用 selectAll 方法. 这就是为什么数据绑定总是出现在 selectAll 之后, 而不是 select 之后.

这篇文章使用 table 作为例子仅仅是为了方便讲解层级结构. 其实 table 的使用并不是特别具有典型性. 其实还有许多其它 嵌套选择集的例子(点我查看, 点我查看)

就像 用 join 的方式思考 一样, 嵌套选择集同样使用了一种思想上完全不同的处理 Dom 元素数据 的思路. 这种思路刚开始可能很难理解, 但你一旦掌握了, 你就能驾轻就熟的使用 D3.js 来完成你的数据可视化任务了.

想继续了解 D3.js ?

这里是我的 D3.js数据可视化 的github 地址, 欢迎 start & fork :tada:

D3-blog

如果觉得不错的话, 不妨点击下面的链接关注一下 : )

github主页

知乎专栏

掘金

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • HTML 5 HTML5概述 因特网上的信息是以网页的形式展示给用户的,因此网页是网络信息传递的载体。网页文件是用...
    阿啊阿吖丁阅读 3,875评论 0 0
  • 分享人:蔡永坚 1. 查看数据库操作记录 2. 弹窗问题 A页面通过AlertPage弹出高为200px的B页面,...
    胡諾阅读 672评论 0 1
  • d3 (核心部分)选择集d3.select - 从当前文档中选择一系列元素。d3.selectAll - 从当前文...
    谢大见阅读 3,437评论 1 4
  • 概述 在网易云课堂学习李南江老师的《从零玩转HTML5前端+跨平台开发》时,所整理的笔记。笔记内容为根据个人需求所...
    墨荀阅读 2,332评论 0 7
  • 2018年3月22日有幸来到了烟花三月的扬州,感谢学校提供的学习平台和机会,开始了为期两天的“全国班主任高峰论坛暨...
    张迪Z阅读 693评论 0 1