原文: 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')
, 你會獲得一個平展的選擇集, 像這樣:微信
var td = d3.selectAll('tbody td')
平展的選擇集缺乏了層級結構: 不管是 thead 中的 td 仍是 tbody 中的 td 全都被展開成了一個數組, 而不是以父元素進行分組. 這讓咱們想對每一行或是每一列的 td 進行操做變得很困難. 與此相反的, D3 的嵌套選擇集能保存層級關係. 好比咱們想以行的方式對選擇集分組, 咱們首先選中 tr 元素. 而後選中 td 元素.app
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 建立的:
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 來完成你的數據可視化任務了.
這裏是個人 D3.js 、 數據可視化 的github 地址, 歡迎 start & fork :tada:
郵箱: ssthouse@163.com
微信: