宣告
原文連結: 來自 D3作者 Mike Bostock bost.ocks.org/mike/join/
譯文地址: github repository
如果覺得還不錯, 不妨去github給個star ?
Content
打個比方, 你想用D3畫一個 散點圖
, 用每一個svg的circle元素來視覺化你的資料. 你會驚訝的發現: D3居然沒有直接建立多個DOM元素的方法! 怎麼回事?
當然, D3有 append
方法, 你可以用來建立單個元素. 比如:
svg.append("circle")
.attr("cx", d.x)
.attr("cy", d.y)
.attr("r", 2.5);
複製程式碼
但這只是一個圓, 如果你想要建立很多個圓(每一個圓代表一個資料點). 你可能會想到用一個for迴圈來實現 ? 這是非常直觀的想法, 這個想法並沒有什麼錯, 但是在這之前不妨看看D3中是如何實現建立多個元素的:
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, 用資料點的 x
和 y
屬性作為circle的座標. 但這段程式碼裡面的 selectAll("circle")
是什麼意思? 我們為什麼要 select
我們知道當前並不存在的 circle, 還用這個方法的返回值去建立新的元素?
這段程式碼的思想是: 不要告訴D3如何去做, 而是告訴D3你想要的效果. 你想要circle元素和資料一一對應, 那麼你就不應該告訴D3去建立circle元素, 而是告訴D3: .selectAll("circle")
得到的circle集合應該和 .data(data)
一一對應. 這個思想就叫做 Join.
從上圖中可以看到:
資料集合
和DOM元素集合
相交產生了中間的update
集合- 沒有DOM元素與之對應的Data產生了左邊的
enter
集合 (也就是缺失DOM元素) - 同樣的, 所有沒有資料與之對應的DOM元素產生了右邊的
exit
集合 (也就意味著這些DOM元素將被移除)
現在我們可以再來看看上面那段使用 enter-append
模型的程式碼了:
- 首先,
svg.selectAll("circle")
返回的是一個空的集合, 因為當前 svg 容器還是空的. 這裡的 svg 是所有後續建立的 circle元素的父節點. svg.selectAll("circle")
返回的集合接下來和data
進行 Join 操作, 得到的就是我們上面提到的三個集合:update
集合 ,enter
集合 ,exit
集合. 因為初始時 Elements集合(也就是circle集合)是空的, 所以update
和exit
集合為空, 而enter
集合會自動為每一個新的data元素生成一個佔位符.- 預設
.data(data)
返回的是update
集合, 因為update
集合為空, 所以我們不對其進行操作, 這裡我們呼叫.enter()
得到enter
集合. - 接下來, 對於
enter
集合中的每一個元素, 我們使用selection.append('circle')
(值得注意的是, 對集合的操作會被應用到集合中的每一個元素上去). 這樣就為每一個資料點建立了一個circle
(這些circle都在他們的父節點 svg 中)
用 Join 的方式來思考意味著, 我們要做的事情僅僅是宣告 DOM集合(比如這裡的 circle 集合) 和資料集合之間的關係, 並且通過處理三個不同狀態的集合 enter, update , exit 來描述這種關係.
你也許會問, 為什麼要用這種方式來進行我的資料視覺化工作呢? 好處在哪? 為什麼我不直接用for迴圈建立所有我想要的元素? 答案是這個思想確實是非常有好處的, 它的優美之處在於它的概括性. 現在我們的程式碼還只是處理了 enter
的部分, 這部分對於展示靜態的資料已經足夠了, 但如果你想進行動態的資料展示, 這種 Join 的方式將大大簡化你的工作, 你只需要對 update
和 exit
進行很少的操作就能得到你想要的效果. 這也意味著你可以輕鬆的展示實時資料, 能夠為使用者新增動態的互動, 能平滑的切換不同的展示資料集.
下面這段程式碼展示了對於 exit
和 update
集合的處理:
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 的思想能讓我們的程式碼更加直觀. 你只需要處理好這三種狀態的集合, 而不需要 if
和 for
來進行復雜的邏輯判斷. 你只需要描述好你的資料集合和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作者), 不妨看看:
這裡是我對這個例子的實現(也包括一些其他的案例):
想繼續瞭解 D3.js ?
這裡是我的 D3.js 、 資料視覺化 的github 地址, 歡迎 start & fork :tada: