[譯] D3.js 嵌套選擇集 (Nested Selection)

[譯] D3.js 嵌套選擇集 (Nested Selection)

譯者注:

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

譯者: ssthouse

本文講解的是關(guān)于 D3.js 中 d3-selection 的使用. d3-selection 是 d3 的核心所在, 它提供了一種和以往 Dom 操作數(shù)據(jù)操作 完全不同的思路, 讓我們能非常優(yōu)雅的進(jìn)行數(shù)據(jù)可視化工作.
本文是 d3 作者對(duì)于 d3-selection 中 嵌套選擇集 的講解, 本人閱讀后覺得很有啟發(fā), 所以翻譯成中文, 希望對(duì)讀者也能有所幫助.

譯文

D3 的 選擇集是分層級(jí)的, 就像 Dom 元素和數(shù)據(jù)集是可以有層級(jí)的一樣. 比如說 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, 你會(huì)如何操作? 直接使用 d3.selectAll('td') 顯然會(huì)選中所有的 td 元素(包括 thead 和 tbody). 如果想只選中存在與 B 元素中的 A 元素, 你需要這樣操作:

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

除了上面那種方法, 你還可以先選中 tbody, 再選中 td 元素, 像這樣:

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

因?yàn)?selectAll 會(huì)對(duì)當(dāng)前選擇集中的每個(gè)元素(如: tbody)選中其符合條件的子元素(如: td). 這種方法會(huì)得到和 d3.selectAll('tbody td') 相同的結(jié)果. 但如果你之后想要對(duì)同一父元素的選擇集引申出更多的選擇集的話 (比如區(qū)分選中 table 中的奇數(shù)行選擇集和偶數(shù)行選擇集), 這種嵌套選擇集方式會(huì)方便的多.

嵌套中索引的使用

接著上面的例子, 如果你使用 d3.selectAll('td') , 你會(huì)得到一個(gè)平展的選擇集, 像這樣:

[圖片上傳失敗...(image-6493d1-1530010072875)]

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

平展的選擇集缺少了層級(jí)結(jié)構(gòu): 不論是 thead 中的 td 還是 tbody 中的 td 全都被展開成了一個(gè)數(shù)組, 而不是以父元素進(jìn)行分組. 這讓我們想對(duì)每一行或是每一列的 td 進(jìn)行操作變得很困難. 與此相反的, D3 的嵌套選擇集能保存層級(jí)關(guān)系. 比如我們想以行的方式對(duì)選擇集分組, 我們首先選中 tr 元素. 然后選中 td 元素.

[圖片上傳失敗...(image-16b4f6-1530010072875)]

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

現(xiàn)在, 如果你想讓第一列的所有元素變紅, 你可以利用 index 參數(shù) i:

td.style('color', function(d, i) {
  return i ? null : 'red'
})

上面的參數(shù) i 是 td 元素在 tr 中的索引, 你還可以通過添加一個(gè) j 參數(shù)來獲得當(dāng)前的行數(shù)的索引. 像這樣:

td.style('color', function(d, i, j) {
  console.log(`current row: ${j}`)
  return i ? null : 'red'
})

嵌套和數(shù)據(jù)間的關(guān)聯(lián)

層級(jí)結(jié)構(gòu)的 Dom 元素常常是由層級(jí)結(jié)構(gòu)的數(shù)據(jù)來驅(qū)動(dòng)的. 而層級(jí)的選擇集能更方便的處理數(shù)據(jù)綁定.
繼續(xù)上面的例子, 你可以把 table 的數(shù)據(jù)表示為一個(gè)矩陣:

var matrix = [
  [0, 1, 2, 3], 
  [4, 5, 6, 7], 
  [8, 9, 10, 11], 
  [12, 13, 14, 15]
]

為了讓這些數(shù)據(jù)綁定上對(duì)應(yīng)的 td 元素, 我們首先將矩陣的每一行和 tr 綁定對(duì)應(yīng)起來, 然后再將矩陣中一行的每一個(gè)元素和 tr 中的每一個(gè) td 綁定起來:

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

需要注意的是, data() 不僅可以傳入一個(gè)數(shù)據(jù), 它還可以傳入一個(gè) 返回一個(gè)數(shù)組的 function. 平展的選擇集通常對(duì)應(yīng)的是單個(gè)數(shù)組, 是因?yàn)槠秸沟倪x擇集只有一個(gè) group.

上面的 row 選擇集是一個(gè)平展的選擇集, 因?yàn)樗侵苯佑?d3.selectAll 創(chuàng)建的:

[圖片上傳失敗...(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 傳入的 操作函數(shù)給每一個(gè) group 綁定了一個(gè)數(shù)組數(shù)據(jù). d3 會(huì)對(duì)每一行 tr 調(diào)用操作函數(shù). 因?yàn)楦冈財(cái)?shù)據(jù)是矩陣, 所以操作函數(shù)在每次被調(diào)用時(shí)只是簡(jiǎn)單的返回矩陣中當(dāng)前行的數(shù)據(jù), 來和 tr 進(jìn)行綁定.

嵌套中父節(jié)點(diǎn)作用

嵌套選擇集有一個(gè)微妙但可能造成嚴(yán)重影響的副作用: 它會(huì)給每個(gè) group 設(shè)置父節(jié)點(diǎn). 父節(jié)點(diǎn)是選擇集的一個(gè)隱藏屬性, 它會(huì)在被調(diào)用 append 方法時(shí)使用, 將子元素添加到父節(jié)點(diǎn)的 Dom 元素當(dāng)中. 比如: 如果你想通過下面的方式進(jìn)行數(shù)據(jù)綁定操作, 你會(huì)得到一個(gè) error:

[圖片上傳失敗...(image-61adb-1530010072875)]

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

上面的代碼之所以會(huì)報(bào)錯(cuò), 是因?yàn)槟J(rèn)的父節(jié)點(diǎn)是 html 元素, 你不能直接將 tr 元素添加到 html 元素中. 所以, 我們應(yīng)該在進(jìn)行數(shù)據(jù)綁定前, 先選擇好父節(jié)點(diǎn):

[圖片上傳失敗...(image-56bf04-1530010072875)]

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

這種方式可以用來選擇任意層級(jí)的嵌套選擇集. 比如你想從頭創(chuàng)建一個(gè) table, 并填入上面矩陣中的數(shù)據(jù), 你可以首選選中 body 元素:

[圖片上傳失敗...(image-5b5d98-1530010072875)]

var body = d3.select('body')

接下來在父節(jié)點(diǎn) body 中添加一個(gè) table:

[圖片上傳失敗...(image-691dac-1530010072875)]

var table = body.append('table')

接下來綁定矩陣數(shù)據(jù), 創(chuàng)建 tr 元素. 因?yàn)?selectAll 是在 table 元素上進(jìn)行調(diào)用的, 所以父節(jié)點(diǎn)是 table:

[圖片上傳失敗...(image-34a1f4-1530010072875)]

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

最后我們以 tr 作為父節(jié)點(diǎn), 創(chuàng)建 td 元素:

[圖片上傳失敗...(image-add8e2-1530010072875)]

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

要不要使用嵌套選擇集 ?

在 D3 中, select 和 selectAll 有一個(gè)很重要的區(qū)別: select 會(huì)繼續(xù)使用當(dāng)前存在的 group, 而 selectAll 總是會(huì)創(chuàng)建新的 group. 因此調(diào)用 select 能保存原有 selection 的數(shù)據(jù), 索引位置, 甚至父節(jié)點(diǎn).

比如, 下面的平展的選擇集, 它的父節(jié)點(diǎn)仍然是 html 節(jié)點(diǎn):

[圖片上傳失敗...(image-d0df1-1530010072875)]

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

想要得到嵌套的選擇集, 唯一的方法就是在已有的選擇集的基礎(chǔ)上, 調(diào)用 selectAll 方法. 這就是為什么數(shù)據(jù)綁定總是出現(xiàn)在 selectAll 之后, 而不是 select 之后.

這篇文章使用 table 作為例子僅僅是為了方便講解層級(jí)結(jié)構(gòu). 其實(shí) table 的使用并不是特別具有典型性. 其實(shí)還有許多其它 嵌套選擇集的例子(點(diǎn)我查看, 點(diǎn)我查看)

就像 用 join 的方式思考 一樣, 嵌套選擇集同樣使用了一種思想上完全不同的處理 Dom 元素數(shù)據(jù) 的思路. 這種思路剛開始可能很難理解, 但你一旦掌握了, 你就能駕輕就熟的使用 D3.js 來完成你的數(shù)據(jù)可視化任務(wù)了.

想繼續(xù)了解 D3.js ?

這里是我的 D3.js 、 數(shù)據(jù)可視化 的github 地址, 歡迎 start & fork :tada:

D3-blog

如果覺得不錯(cuò)的話, 不妨點(diǎn)擊下面的鏈接關(guān)注一下 : )

github主頁

知乎專欄

掘金

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容