本篇大綱:Joining Data、綁定 DOM 元素跟資料的方法、data 跟 datum 的比較、何時該用 data / datum
上一章講解完 D3 的 Selection,今天要來看資料綁定
的部分啦!大家請把零食、飲料準備好,因為這篇的概念太重要了,所以將會是非常長篇幅的文章!等不及的人可以按照大綱去找自己想看的段落~話不多說,我們趕快開始吧!
我們先來看到官方文件提供的 API 們
綁定資料的這些 API 是 D3.js 很重要、也非常方便的功能,它能讓透過資料的增減去新增或刪除 DOM 元素,這樣我們就只要專注在資料的變化上就好,是個非常方便的功能~
要特別注意的是,這些綁定資料的 API 歸類於 selection 分類之下,這是因為我們要先用 d3.select 的方法選定DOM節點後,才能將資料綁定到 d3.select 回傳的 selection 實體上,並對資料跟元素的配對與增減進行對應處理
。
上面說的看起來可能很難懂,但說白了其實就是如果你不先用 d3.selection 選定節點,就不能用.data()去綁定資料!所以你看所有的程式碼,這兩個方法都是一起出現的,沒有單獨使用 d3.data() 的情況
d3.select('div').data()
另外,我個人認為確定哪個方法是歸類在哪邊蠻重要的,因為 D3 實在有太多 API
有些名字一模一樣
例如 ⇒ Scale 中的 Continuous Scales 跟 Ordinal Scales 中都有 .domain( )、.range( )的方法
有些卻是只有它獨有
例如 ⇒ Scale 中只有 Continuous Scales 有 .invert( ) 的方法,因此其他 scale 無法使用.invert()
如果 API 使用錯誤,就會發現圖片出不來、console一直報錯,除錯半天卻不明白到底哪裡有問題。更常遇到的情況是,你看了某篇文章的圖表不錯,作者也有分享程式碼,於是你想直接拿來用並多加一些功能上去,但卻發現加上新功能後一直出錯,查半天找不出問題。所以如果有不太確定的 API,建議直接先去官方文件查查,減少自己困在程式碼的時間~
話說遠了趕快拉回來~我們先前看到D3.js提供了五個綁定資料的方法,我覺得這些方法可以分成兩類:
綁定DOM元素跟資料的方法
增減資料數量與DOM元素不匹配的方法
接著就來講講我為何會這樣分類,以及這些 API 該怎麼使用吧!
D3 提供了兩個把資料跟元素綁定的方法,分別是
這兩個方法會把資料陣列跟 DOM 元素們綁定在一起,並返還一個更新後與資料綁定的 selection 物件,之後就能針對這個 selection 物件去決定要用 enter/exit 去增加或刪減 DOM 元素。
要注意的是,這兩個方法只會綁定數量一樣的DOM元素與資料陣列。一旦資料比較多、DOM 元素比較少,亦或是反過來的情況,多餘的資料或 DOM 元素就會落入 enter 或是 exit 的區塊,我們便能使用增減資料數量與 DOM 元素不匹配的方法( enter/exit )的方法去增加或刪除相對應的 DOM 元素。
selection.data
我們首先來看到官方文件對於 selection.data( ) 的闡述~
當資料陣列跟 DOM 元素綁定時,會按照 index 的順序將資料一一綁定到DOM元素上。這時單一個 data 就會被存在匹配的單一個DOM元素 _data_ 這個屬性中,並達成讓元素與 DOM「黏」在一起的效果
const bindData = d3.select('p')
.data(['綁定資料'])
console.log(bindData)
console.log(bindData['_groups'][0][0]['__data__'])
此時你會看到一個大物件,展開後裡面長這樣
我們的 _data_ 就藏在 _groups 裡面。展開 _groups 之後,再點開 0 這個陣列,並且一路滑到最下面,就能找到 _data_ 了
const multiData = ['綁', '定', '資', '料']
const bindMultiData = d3.selectAll('.multiData')
.data(multiData)
console.log(bindMultiData)
console.log(bindMultiData['_groups'][0])
這樣一來,把 bindMultiData 印出來時,就會看到資料會按照 index 一個個被綁到相對應的node節點上
const multiData = ["綁", "定", "資", "料"];
const bindUnmatchData = d3.select(".unmatchData").data(multiData);
console.log("bindUnmatchData", bindUnmatchData);
這種情況很常見,因為我們的資料不一定會跟 DOM 元素數量一樣;或是當資料有變動時,也會讓兩者的匹配度產生改變,這時就要用到增減資料數量與DOM元素不匹配的方法來處理
。但這個晚點再說,我們先來看到綁定資料的另一個方法- selection.datum( )
selection.datum( )
一樣先看到官方文件對這個 API 的解釋
官方文件上說的是,datum( ) 跟 data( )的不同在於,datum 無法合併資料、不能改變資料的順序,也不能去增減綁定的DOM API。這到底是什麼意思呢? 我們直接看範例比較清楚
現在我們的畫面上只有一個 calss="join" 的 DOM 元素,但 joinData 資料包含兩個物件。因此當我們使用 selection.data( )
將資料跟DOM 元素綁定時,因為selection.data 會將資料按照index的順序一一綁定到DOM元素上,但由於DOM元素不夠,所以實際上只能夠綁定到第一個物件 {name:'jin'},剩下的資料則會被歸類的 enter 內
// html
<div class="join"></div>
// JS
const joinData = [{name:'jin'}, {name:'JIN'}]
const jData = d3.select('.join').data(joinData)
console.log('jData',jData)
如果你想將這兩個元素一起綁定到這個DOM的話,可以使用合併資料的方式
// html
<div class="join"></div>
// JS
const joinData = [{name:'jin'}, {name:'JIN'}]
const jData = d3.select('.join').data([joinData])
console.log('jData',jData)
這樣一來就能看到這個 DOM 元素把整包資料都綁進來了。
但如果我們改用 selection.datum( )
這個方法的話,它並不會按照陣列的順序去綁定 DOM 元素,而是直接把整包物件都綁定到 DOM 上面。既然每次都是整包資料綁定元素,這樣一來自然就沒有資料跟元素數量不匹配的問題,也就沒有 join、enter、exit 這些增減 DOM 元素的方法可以使用
// html
<div class="join"></div>
// JS
const joinDatum = [{name:'jin'}, {name:'JIN'}]
const jDatum = d3.select('.join').datum(joinDatum)
console.log('joinDatum',joinDatum)
如果還想了解更多,可以直接看這篇作者親自在Stackoverflow上的回答
如果你看完上面的解說後,仍然搞不懂這兩個方法差在哪裡的話⋯⋯⋯⋯別擔心!下面就是簡單的統整:
.data( )
將傳入的資料陣列,按照索引值一個一個綁定到DOM元素上.datum( )
將傳入的資料陣列,整包綁訂到 一個DOM 元素上舉例而言,假如我們的DOM上面有五個 < rect >
<svg>
<rect />
<rect />
<rect />
<rect />
<rect />
</svg>
要綁定的資料則是 [10, 20, 30, 40],分別用 data( ) 跟 datum( ) 去綁定
const data = [10, 20, 30, 40];
// data()
const d3Data = d3.select(".chartContainer").selectAll("rect").data(data);
// datum()
const d3Datum = d3.select(".chartContainer").selectAll("rect").datum(data);
console.log("d3Data", d3Data);
console.log("d3Datum", d3Datum);
接著再觀察印出來的console,會看到 data( ) 只綁訂了四個 < rect >,datum( ) 卻綁定了五個 < rect >:
點開每個DOM元素,找到綁定的 _data_ 資料,會發現
整理比較
DOM 元素 | 使用 data 綁定得到的 _data_ | 使用 datum 綁定得到的 _data_ |
---|---|---|
第一個 < rect > | 10 | ['10', '20', '30', '40'] |
第二個 < rect > | 20 | ['10', '20', '30', '40'] |
第三個 < rect > | 30 | ['10', '20', '30', '40'] |
第四個 < rect > | 40 | ['10', '20', '30', '40'] |
第五個 < rect > | 沒綁到資料,歸到exit selection 內 | ['10', '20', '30', '40'] |
瞭解這兩者的差異後,大家心裡一定會出現一個疑惑:那到底什麼時候要用data( )、什麼時候要用datum( )呢?
基本上就是:
適合使用 datum
適合使用 data()
以上就是綁定DOM元素跟資料的方法
!
本來想一口氣把接下來的增減資料數量與DOM元素不匹配的方法
也寫完,但發現如果繼續寫下去,可能就要變成萬言書了(登愣)~~為了方便讀者閱讀與查找,最後決定還是拆成上下兩集吧!今天就先講完 data( ) 跟 datum( ) 這兩個綁定資料的方法與差異,明天我們再來看看 enter( )、exit( )、join( ) 這幾個 API 又是幹嘛的~
最後,一樣附上本章的程式碼與圖表 Github 、 Github Page,這次基本上都是看 console.log 出來的內容,所以看到畫面一片白不要太震驚哦~
請問關於 datum 的示例是不是寫的不太對? 感覺像是筆誤..
// html
<div class="join"></div>
// JS
const joinDatum = [{name:'jin'}, {name:'JIN'}]
// 下面這邊是不是應該要改成.datum() ?
const jDatum = d3.select('.join').data(joinDatum)
//然後這邊是不是應該要console.log(jDatum) ?
console.log('joinDatum',joinDatum)
對耶!這邊不小心打錯了~感謝糾錯!我來改改
"現在我們的畫面上只有一個 calss="join" 的 DOM 元素"
應該是class嗎?
受益良多