以 Join 的方式來(lái)思考D3.js

打個(gè)小廣告:
如果你想獲取更多前端干貨、鵝廠工程師的前端面試指南,
歡迎關(guān)注我的個(gè)人微信公眾號(hào):
前端夜談

Join 的方式來(lái)思考D3.js

聲明

原文鏈接: 來(lái)自 D3作者 Mike Bostock https://bost.ocks.org/mike/join/

譯文地址: github repository

如果覺(jué)得還不錯(cuò), 不妨去github給個(gè)star ?

Content

打個(gè)比方, 你想用D3畫一個(gè) 散點(diǎn)圖 , 用每一個(gè)svg的circle元素來(lái)可視化你的數(shù)據(jù). 你會(huì)驚訝的發(fā)現(xiàn): D3居然沒(méi)有直接創(chuàng)建多個(gè)DOM元素的方法! 怎么回事?

當(dāng)然, D3有 append 方法, 你可以用來(lái)創(chuàng)建單個(gè)元素. 比如:

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

但這只是一個(gè)圓, 如果你想要?jiǎng)?chuàng)建很多個(gè)圓(每一個(gè)圓代表一個(gè)數(shù)據(jù)點(diǎn)). 你可能會(huì)想到用一個(gè)for循環(huán)來(lái)實(shí)現(xiàn) ? 這是非常直觀的想法, 這個(gè)想法并沒(méi)有什么錯(cuò), 但是在這之前不妨看看D3中是如何實(shí)現(xiàn)創(chuàng)建多個(gè)元素的:

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);

上面這段代碼完美的實(shí)現(xiàn)了你想要的效果: 為每一個(gè)數(shù)據(jù)點(diǎn)創(chuàng)建了一個(gè) circle, 用數(shù)據(jù)點(diǎn)的 xy 屬性作為circle的坐標(biāo). 但這段代碼里面的 selectAll("circle") 是什么意思? 我們?yōu)槭裁匆?select 我們知道當(dāng)前并不存在的 circle, 還用這個(gè)方法的返回值去創(chuàng)建新的元素?

這段代碼的思想是: 不要告訴D3如何去做, 而是告訴D3你想要的效果. 你想要circle元素和數(shù)據(jù)一一對(duì)應(yīng), 那么你就不應(yīng)該告訴D3去創(chuàng)建circle元素, 而是告訴D3: .selectAll("circle") 得到的circle集合應(yīng)該和 .data(data) 一一對(duì)應(yīng). 這個(gè)思想就叫做 Join.

Join 模型

從上圖中可以看到:

  • 數(shù)據(jù)集合DOM元素集合 相交產(chǎn)生了中間的 update 集合
  • 沒(méi)有DOM元素與之對(duì)應(yīng)的Data產(chǎn)生了左邊的 enter 集合 (也就是缺失DOM元素)
  • 同樣的, 所有沒(méi)有數(shù)據(jù)與之對(duì)應(yīng)的DOM元素產(chǎn)生了右邊的 exit 集合 (也就意味著這些DOM元素將被移除)

現(xiàn)在我們可以再來(lái)看看上面那段使用 enter-append 模型的代碼了:

  1. 首先, svg.selectAll("circle") 返回的是一個(gè)空的集合, 因?yàn)楫?dāng)前 svg 容器還是空的. 這里的 svg 是所有后續(xù)創(chuàng)建的 circle元素的父節(jié)點(diǎn).
  2. svg.selectAll("circle") 返回的集合接下來(lái)和 data 進(jìn)行 Join 操作, 得到的就是我們上面提到的三個(gè)集合: update 集合 , enter 集合 , exit 集合. 因?yàn)槌跏紩r(shí) Elements集合(也就是circle集合)是空的, 所以 updateexit 集合為空, 而 enter 集合會(huì)自動(dòng)為每一個(gè)新的data元素生成一個(gè)占位符.
  3. 默認(rèn) .data(data) 返回的是 update 集合, 因?yàn)?update 集合為空, 所以我們不對(duì)其進(jìn)行操作, 這里我們調(diào)用 .enter() 得到 enter 集合.
  4. 接下來(lái), 對(duì)于 enter 集合中的每一個(gè)元素, 我們使用 selection.append('circle') (值得注意的是, 對(duì)集合的操作會(huì)被應(yīng)用到集合中的每一個(gè)元素上去). 這樣就為每一個(gè)數(shù)據(jù)點(diǎn)創(chuàng)建了一個(gè) circle (這些circle都在他們的父節(jié)點(diǎn) svg 中)

Join 的方式來(lái)思考意味著, 我們要做的事情僅僅是聲明 DOM集合(比如這里的 circle 集合) 和數(shù)據(jù)集合之間的關(guān)系, 并且通過(guò)處理三個(gè)不同狀態(tài)的集合 enter, update , exit 來(lái)描述這種關(guān)系.

你也許會(huì)問(wèn), 為什么要用這種方式來(lái)進(jìn)行我的數(shù)據(jù)可視化工作呢? 好處在哪? 為什么我不直接用for循環(huán)創(chuàng)建所有我想要的元素? 答案是這個(gè)思想確實(shí)是非常有好處的, 它的優(yōu)美之處在于它的概括性. 現(xiàn)在我們的代碼還只是處理了 enter 的部分, 這部分對(duì)于展示靜態(tài)的數(shù)據(jù)已經(jīng)足夠了, 但如果你想進(jìn)行動(dòng)態(tài)的數(shù)據(jù)展示, 這種 Join 的方式將大大簡(jiǎn)化你的工作, 你只需要對(duì) updateexit 進(jìn)行很少的操作就能得到你想要的效果. 這也意味著你可以輕松的展示實(shí)時(shí)數(shù)據(jù), 能夠?yàn)橛脩籼砑觿?dòng)態(tài)的交互, 能平滑的切換不同的展示數(shù)據(jù)集.

下面這段代碼展示了對(duì)于 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; });

無(wú)論什么時(shí)候上面的這段代碼被執(zhí)行, 它都將重新計(jì)算 Join 并且維護(hù)好 DOM元素集合 和 數(shù)據(jù)集合 之間的對(duì)應(yīng)關(guān)系. 如果你的新數(shù)據(jù)集比之前老的數(shù)據(jù)集要小, 多余的DOM元素就會(huì)進(jìn)入 exit 集合, 然后被 remove掉. 如果新的數(shù)據(jù)集比老的大, 那么新的數(shù)據(jù)就將進(jìn)入 enter 集合, 并創(chuàng)建出新的DOM元素. 如果新的數(shù)據(jù)集和老的數(shù)目相同, 那么只有 update 集合會(huì)被更新坐標(biāo).

使用 Join 的思想能讓我們的代碼更加直觀. 你只需要處理好這三種狀態(tài)的集合, 而不需要 iffor 來(lái)進(jìn)行復(fù)雜的邏輯判斷. 你只需要描述好你的數(shù)據(jù)集合和DOM集合想要有怎樣的對(duì)應(yīng)關(guān)系.

Join 還讓你可以對(duì)不同狀態(tài)的DOM元素進(jìn)行不同的操作. 比如, 你可以只對(duì) enter 集合進(jìn)行操作, 這樣就不會(huì)每次都對(duì)所有的 DOM元素進(jìn)行更新, 這能顯著的提升你的數(shù)據(jù)可視化作品的渲染效率.
同樣的, 你也可以給指定集合的元素添加動(dòng)畫效果, 比如給 enter 的元素添加放大進(jìn)入的效果:

circle.enter().append("circle")
    .attr("r", 0)
  .transition()
    .attr("r", 2.5);

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

circle.exit().transition()
    .attr("r", 0)
    .remove();

譯者注:

這里有一個(gè)非常好的實(shí)踐 Join 思想的例子(同樣來(lái)自D3作者), 不妨看看:

Mike Bostock 的實(shí)現(xiàn)

join example

這里是我對(duì)這個(gè)例子的實(shí)現(xiàn)(也包括一些其他的案例):

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

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

D3-blog

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

github主頁(yè)

知乎專欄

掘金

最后編輯于
?著作權(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)容

  • 對(duì)集合的操作 關(guān)于d3.attr 一個(gè)可以處理很多情況的函數(shù),當(dāng)只傳入一個(gè)參數(shù)時(shí),如果是string,則返回該屬性...
    陳堅(jiān)生閱讀 2,766評(píng)論 0 2
  • d3 (核心部分)選擇集d3.select - 從當(dāng)前文檔中選擇一系列元素。d3.selectAll - 從當(dāng)前文...
    謝大見(jiàn)閱讀 3,574評(píng)論 1 4
  • 1.發(fā)現(xiàn)故事 本課講述可視化用到的:敘事結(jié)構(gòu)數(shù)據(jù)收集過(guò)程數(shù)據(jù)處理 2.新聞方法 給可視化添加語(yǔ)境圍繞數(shù)據(jù)進(jìn)行敘事 ...
    esskeetit閱讀 3,089評(píng)論 0 2
  • 有些遇見(jiàn),不需要理由;有些追求,不需要刻意。生活中最常有的遇見(jiàn)最是無(wú)意,而最美的遇見(jiàn),也許就像是寒冬里的一股暖流,...
    聽(tīng)雨小咖閱讀 326評(píng)論 0 2
  • 我的工資不高(男),本身也不太會(huì)搭配衣服,總想著掙到錢了,要先買一套讓人眼前一亮的衣服,好讓很多人都對(duì)自己熱情(這...
    金光點(diǎn)點(diǎn)閱讀 224評(píng)論 0 1

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