以 Join 的方式來思考D3.js

聲明

原文連接: 來自 D3做者 Mike Bostock bost.ocks.org/mike/join/javascript

譯文地址: github repositoryjava

若是以爲還不錯, 不妨去github給個star ?git

Content

打個比方, 你想用D3畫一個 散點圖 , 用每個svg的circle元素來可視化你的數據. 你會驚訝的發現: D3竟然沒有直接建立多個DOM元素的方法! 怎麼回事?github

固然, D3有 append 方法, 你能夠用來建立單個元素. 好比:app

svg.append("circle")
    .attr("cx", d.x)
    .attr("cy", d.y)
    .attr("r", 2.5);
複製代碼

但這只是一個圓, 若是你想要建立不少個圓(每個圓表明一個數據點). 你可能會想到用一個for循環來實現 ? 這是很是直觀的想法, 這個想法並無什麼錯, 可是在這以前不妨看看D3中是如何實現建立多個元素的:svg

svg.selectAll("circle")
  .data(data)
  .enter().append("circle")
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", 2.5);
複製代碼

上面這段代碼完美的實現了你想要的效果: 爲每個數據點建立了一個 circle, 用數據點的 xy 屬性做爲circle的座標. 但這段代碼裏面的 selectAll("circle") 是什麼意思? 咱們爲何要 select 咱們知道當前並不存在的 circle, 還用這個方法的返回值去建立新的元素?post

這段代碼的思想是: 不要告訴D3如何去作, 而是告訴D3你想要的效果. 你想要circle元素和數據一一對應, 那麼你就不該該告訴D3去建立circle元素, 而是告訴D3: .selectAll("circle") 獲得的circle集合應該和 .data(data) 一一對應. 這個思想就叫作 Join.動畫

Join 模型

從上圖中能夠看到:spa

  • 數據集合DOM元素集合 相交產生了中間的 update 集合
  • 沒有DOM元素與之對應的Data產生了左邊的 enter 集合 (也就是缺失DOM元素)
  • 一樣的, 全部沒有數據與之對應的DOM元素產生了右邊的 exit 集合 (也就意味着這些DOM元素將被移除)

如今咱們能夠再來看看上面那段使用 enter-append 模型的代碼了:3d

  1. 首先, svg.selectAll("circle") 返回的是一個空的集合, 由於當前 svg 容器仍是空的. 這裏的 svg 是全部後續建立的 circle元素的父節點.
  2. svg.selectAll("circle") 返回的集合接下來和 data 進行 Join 操做, 獲得的就是咱們上面提到的三個集合: update 集合 , enter 集合 , exit 集合. 由於初始時 Elements集合(也就是circle集合)是空的, 因此 updateexit 集合爲空, 而 enter 集合會自動爲每個新的data元素生成一個佔位符.
  3. 默認 .data(data) 返回的是 update 集合, 由於 update 集合爲空, 因此咱們不對其進行操做, 這裏咱們調用 .enter() 獲得 enter 集合.
  4. 接下來, 對於 enter 集合中的每個元素, 咱們使用 selection.append('circle') (值得注意的是, 對集合的操做會被應用到集合中的每個元素上去). 這樣就爲每個數據點建立了一個 circle (這些circle都在他們的父節點 svg 中)

Join 的方式來思考意味着, 咱們要作的事情僅僅是聲明 DOM集合(好比這裏的 circle 集合) 和數據集合之間的關係, 而且經過處理三個不一樣狀態的集合 enter, update , exit 來描述這種關係.

你也許會問, 爲何要用這種方式來進行個人數據可視化工做呢? 好處在哪? 爲何我不直接用for循環建立全部我想要的元素? 答案是這個思想確實是很是有好處的, 它的優美之處在於它的歸納性. 如今咱們的代碼還只是處理了 enter 的部分, 這部分對於展現靜態的數據已經足夠了, 但若是你想進行動態的數據展現, 這種 Join 的方式將大大簡化你的工做, 你只須要對 updateexit 進行不多的操做就能獲得你想要的效果. 這也意味着你能夠輕鬆的展現實時數據, 可以爲用戶添加動態的交互, 能平滑的切換不一樣的展現數據集.

下面這段代碼展現了對於 exitupdate 集合的處理:

var circle = svg.selectAll("circle")
  .data(data);

circle.exit().remove();

circle.enter().append("circle")
    .attr("r", 2.5)
  .merge(circle)
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });
複製代碼

不管何時上面的這段代碼被執行, 它都將從新計算 Join 而且維護好 DOM元素集合 和 數據集合 之間的對應關係. 若是你的新數據集比以前老的數據集要小, 多餘的DOM元素就會進入 exit 集合, 而後被 remove掉. 若是新的數據集比老的大, 那麼新的數據就將進入 enter 集合, 並建立出新的DOM元素. 若是新的數據集和老的數目相同, 那麼只有 update 集合會被更新座標.

使用 Join 的思想能讓咱們的代碼更加直觀. 你只須要處理好這三種狀態的集合, 而不須要 iffor 來進行復雜的邏輯判斷. 你只須要描述好你的數據集合和DOM集合想要有怎樣的對應關係.

Join 還讓你能夠對不一樣狀態的DOM元素進行不一樣的操做. 好比, 你能夠只對 enter 集合進行操做, 這樣就不會每次都對全部的 DOM元素進行更新, 這能顯著的提高你的數據可視化做品的渲染效率. 一樣的, 你也能夠給指定集合的元素添加動畫效果, 好比給 enter 的元素添加放大進入的效果:

circle.enter().append("circle")
    .attr("r", 0)
  .transition()
    .attr("r", 2.5);
複製代碼

或者給 exit 的集合添加 縮小隱藏 的效果:

circle.exit().transition()
    .attr("r", 0)
    .remove();
複製代碼

譯者注:

這裏有一個很是好的實踐 Join 思想的例子(一樣來自D3做者), 不妨看看:

Mike Bostock 的實現

join example

這裏是我對這個例子的實現(也包括一些其餘的案例):

想繼續瞭解 D3.js ?

這裏是個人 D3.js數據可視化 的github 地址, 歡迎 start & fork :tada:

D3-blog

若是以爲不錯的話, 不妨點擊下面的連接關注一下 : )

github主頁

知乎專欄

掘金

歡迎關注個人公衆號:

相關文章
相關標籤/搜索