D3.js作爲一門輕型的可視化類庫,非常便於將數據與web界面元素綁定,實現可視化。樂帝d3.js入門是大體看了一遍《d3js數據可視化實戰》這本書,D3操作非常類似於jquery的使用,具體體現在兩點:
- 選擇器模塊都採用CSS3標準
- 方法可以鏈式調用
有了jquery使用基礎,相信再加上以上書籍的例子,調試很容易上手使用D3.js,樂帝目前認爲D3.js與jquery區別在於:D3.js獨有的數據結構概念及對SVG操作方便的實現。而深入理解D3原理,以上皮毛的理解就不夠用了。
通過閱讀上述書籍樂帝將D3內容劃了幾大塊來分開理解:
DOM操作包括:
- 選擇器模塊(select、selectAll等方法)及如何實現鏈式調用。
- 節點模塊(append、remove等方法實現)
- 樣式模塊(attr、style等方法實現)
數據綁定相關方法:
- data方法
- enter、exit等方法
比例尺:
- 值域與輸出域的實現
更新、過渡、動畫方法:
- transition方法及連帶duration、ease、delay等方法
事件:
- 即綁定事件
而後通讀API發現對於理解D3.js實現機理有一些關鍵概念,這裏關鍵概念涉及selection、data join、group、transition等。而樂帝讀API最大心得在於,在沒有一個大體概念時,千萬不要去觸碰源碼,還是按部就班讀API吧,讀不懂把文檔翻譯一遍就懂了,樂帝是這麼做的。
這篇文章樂帝主要想討論data join,但在討論之前,還是需要補充下基本概念。
D3.js獨有關鍵概念是selection,樂帝將其翻譯成元素集。它表示從當前文檔獲取的元素數組。它定義了一種數據結構,或者說是對原有dom文檔數據結構的修改。有了元素集之後,就可以對元素執行常規操作了,諸如屬性、樣式、參數、文檔內容等。
selection存在的意義在於,是它將頁面元素與數據實現綁定連接,連接的數據又可以產生enter和exit子元素集,因此能夠反映數據的變化,用於添加或移除元素。
D3支持方法鏈,操作方法返回值是元素集,從這一句話,我們知道不管如何具體實現鏈式操作,我們知道返回的是元素集就夠了。
樂帝最近思考,整個web世界甚至整個世界,由兩種力量驅動:數據與人。而某些高深思想或許能把人也歸結爲數據。D3是這兩種力量的典型,數據驅動展現,人驅動交互。脫離人與數據,D3的代碼,只是一堆分離的函數而已。D3緊緊擁抱數據,這就是其最大特點。
selection介紹完了,我們開始介紹data join概念。
由上所述,D3是數據驅動,而元素集是溝通數據與元素的集合,而data join的概念又是代表數據與元素結合三種狀態的概念,由此看來,data join在D3中屬於核心概念。
《以data join概念思考》這篇文章給出了對data join比較簡明易懂的陳述。
在D3中任何時候,數據與頁面元素都有三種關係:數據與元素綁定時,即一一對應,這樣構成的selection狀態叫update selection;沒有元素與之對應的數據構成的selection叫enter selection;沒有數據與之對應的元素構成的selection叫exit selection,它代表將要被移除的元素集。
如下圖:
從字面上理解,D3對數據的推崇可謂毫無節操,有數據沒元素叫enter,有元素沒數據叫exit,代表要被踢出去的元素。
下面來看一段代碼例子:
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);
首先來看第一句svg.selectAll("circle"),它返回空的元素集,因爲SVG容器是空的。
然後上述空元素集與數據結合,構成新的元素集,包含enter、update、exit三種狀態。因爲元素集是空的,所以update和exit元素集爲空,而enter狀態的元素集,則包括五個佔位符元素。
selection.enter後,返回enter元素集,此時爲五個綁定數據的對象。
最後append("circle")一步,使得enter元素集實現與元素一一對應,沒錯構造成了上述的update元素集狀態。
由上述例子不難看出,給頁面對象添加子對象不用for循環,而是採取data join的概念,用意在於,在靜態展現的基礎上,對update及exit做微小改動,就可以使它實現動態展現。這就意味着你可以看實時數據,允許數據集合的交互行爲及溫和過渡效果展現。
任何時候運行代碼,都會重新計算data join,從而保證數據與元素預期的關係。
data join允許我們隊指定狀態進行操作,比如,可是設置常數值在enter上,而不是update上,通過重新選擇元素最小化改變原有dom,大大提升渲染效率。
下面樂帝展示一個對各個狀態data join操作的例子:
起始HTML:
<div>update</div>
<div>exit</div>
起始D3代碼:
var dataset =["enter", "hello"];
var key = function(d) {
return d || this.textContent;
}
var duration = 750;
var div = d3.select("body").selectAll("div")
.data(dataset,key);
此時data join 三個狀態:
如上圖,不難分析得到,此時data join三狀態:update元素集爲空,enter元素集已經有數據綁定,exit元素集有兩個div元素,在第二張圖中innerHTML屬性中,發現恰恰是初始化HTML的兩個div元素,這裏採用了鍵函數(key)方法,鍵函數被調用了四次,前兩次調用的是已有的div數據調用,後兩次則是enter狀態元素集數據調用。
接下來對exit元素集操作:
// // 1. exit
var exitTransition = d3.transition().duration(750).each(function() {
div.exit()
.style("background", "red")
.transition()
.style("opacity", 0)
.remove();//移除節點
});
不難得出結論,是原有兩個div將要被移除,在頁面及內存中清除。
接下來對enter的操作,這裏update爲空,故第二步update操作並沒有實際意義。
即dataset有一個元素與已存在的div文本內容相同,又由於鍵函數是處理的是dataset與div文本內容的集合,經過運行如下:
// 2. update
var updateTransition = exitTransition.transition().each(function() {
div.transition()
.style("background", "orange");
});
// 3. enter
var enterTransition = updateTransition.transition().each(function() {
div.enter().append("div")
.text(function(d) { return d; })
.style("opacity", 0)
.transition()
.style("background", "green")
.style("opacity", 1);
});
第三步對enter元素集的操作,使其與元素綁定,並設置成綠色。
樂帝在調試期間還發現了另外的情況:
一開始js代碼是這樣的:
var dataset =["enter", "update"];
var key = function(d) {
return d || this.textContent;
}
var duration = 750;
var div = d3.select("body").selectAll("div")
.data(dataset,key);
即dataset有一個元素與已存在的div文本內容相同,又由於鍵函數是處理的是dataset與div文本內容的集合,經過運行如下:
與之上比較不難發現,這裏update元素集有一個元素,enter元素集有一個enter狀態元素。另看exit元素集:
exit及enter都少了一個對應狀態的對象,這裏樂帝猜測d3應該是將每個鍵值函數返回數據進行了查重操作,將兩個“update”文本數據合併成一個update狀態元素了。
執行1.exit代碼,此時只有exit文本的div被移除。執行2.update代碼時,update文本的div被設置爲背景爲橘黃色。最後執行3.enter代碼時,背景色爲綠色的div被加入到文檔。