[译] 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') , 你會得到一個平展的選擇集, 像這樣:
var td = d3.selectAll('tbody td')平展的選擇集缺少了層級結構: 不論是 thead 中的 td 還是 tbody 中的 td 全都被展開成了一個數組, 而不是以父元素進行分組. 這讓我們想對每一行或是每一列的 td 進行操作變得很困難. 與此相反的, D3 的嵌套選擇集能保存層級關系. 比如我們想以行的方式對選擇集分組, 我們首先選中 tr 元素. 然后選中 td 元素.
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 的數據表示為一個矩陣:
為了讓這些數據綁定上對應的 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 來完成你的數據可視化任務了.
如果覺得不錯的話, 不妨點下面的鏈接關注一下 : )
github 主頁
知乎專欄
掘金
總結
以上是生活随笔為你收集整理的[译] D3.js 嵌套选择集 (Nested Selection)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 还在手动更改SpringBoot的环境y
- 下一篇: 如何使用ISqlSugarClient进