iT邦幫忙

2021 iThome 鐵人賽

DAY 5
0
Modern Web

三十天成為D3.js v7 好手系列 第 5

Day5-D3 資料綁定 Data Binding:data( ) 跟 datum( )

  • 分享至 

  • xImage
  •  

本篇大綱:Joining Data、綁定 DOM 元素跟資料的方法、data 跟 datum 的比較、何時該用 data / datum

上一章講解完 D3 的 Selection,今天要來看資料綁定的部分啦!大家請把零食、飲料準備好,因為這篇的概念太重要了,所以將會是非常長篇幅的文章!等不及的人可以按照大綱去找自己想看的段落~話不多說,我們趕快開始吧!

Joining Data

我們先來看到官方文件提供的 API 們
https://ithelp.ithome.com.tw/upload/images/20210917/201349302RqE4uSM2x.jpg

綁定資料的這些 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,建議直接先去官方文件查查,減少自己困在程式碼的時間~

兩類方法:綁定資料、增減 DOM 元素

話說遠了趕快拉回來~我們先前看到D3.js提供了五個綁定資料的方法,我覺得這些方法可以分成兩類:

  1. 綁定DOM元素跟資料的方法

    • selection.data( )
    • selection.datum( )
  2. 增減資料數量與DOM元素不匹配的方法

    • selection.join( )
    • selection.enter( )
    • selection.exit( )

接著就來講講我為何會這樣分類,以及這些 API 該怎麼使用吧!


綁定DOM元素跟資料的方法

D3 提供了兩個把資料跟元素綁定的方法,分別是

  • selection.data
  • selection.datum

這兩個方法會把資料陣列跟 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「黏」在一起的效果
https://ithelp.ithome.com.tw/upload/images/20210917/20134930r8PyJkNkke.jpg

  1. 舉例而言:目前我的畫面上有一個 < p > 的 DOM 元素,選定這個 DOM 元素後,將設定好的資料 (必須是陣列) 用 data () 方法帶進去,接著把 bindData 印到 console 上看看
const bindData = d3.select('p')
				   .data(['綁定資料'])

console.log(bindData)
console.log(bindData['_groups'][0][0]['__data__'])

此時你會看到一個大物件,展開後裡面長這樣
https://ithelp.ithome.com.tw/upload/images/20210917/20134930BOH6CCg6to.jpg

我們的 _data_ 就藏在 _groups 裡面。展開 _groups 之後,再點開 0 這個陣列,並且一路滑到最下面,就能找到 _data_ 了
https://i.imgur.com/QXO7ivj.gif

  1. 如果想綁定多個元素跟數值的話也可以,但就要改成secectAll 來選定 DOM 元素
const multiData = ['綁', '定', '資', '料']
const bindMultiData = d3.selectAll('.multiData')
						.data(multiData)

console.log(bindMultiData)
console.log(bindMultiData['_groups'][0])

這樣一來,把 bindMultiData 印出來時,就會看到資料會按照 index 一個個被綁到相對應的node節點上
https://i.imgur.com/n6X9pKF.gif

  1. 上面說的是 data 跟 DOM 元素的數量都一樣多的情況,但如果兩者的數量不匹配時,就會看到 console 的物件中,_enter 跟 _exit 的數量變了,而且點開 _group 會發現 DOM 元素也只綁到第一個資料
const multiData = ["綁", "定", "資", "料"];
const bindUnmatchData = d3.select(".unmatchData").data(multiData);
console.log("bindUnmatchData", bindUnmatchData);

https://ithelp.ithome.com.tw/upload/images/20210917/201349309bamBYZYcT.jpg

https://ithelp.ithome.com.tw/upload/images/20210917/201349304OaMvHExx2.jpg

這種情況很常見,因為我們的資料不一定會跟 DOM 元素數量一樣;或是當資料有變動時,也會讓兩者的匹配度產生改變,這時就要用到增減資料數量與DOM元素不匹配的方法來處理。但這個晚點再說,我們先來看到綁定資料的另一個方法- selection.datum( )

selection.datum( )

一樣先看到官方文件對這個 API 的解釋
https://ithelp.ithome.com.tw/upload/images/20210917/20134930PyCk30en11.jpg

官方文件上說的是,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)

https://i.imgur.com/lJFkrX5.gif

如果你想將這兩個元素一起綁定到這個DOM的話,可以使用合併資料的方式

// html
<div class="join"></div>

// JS
const joinData = [{name:'jin'}, {name:'JIN'}]
const jData = d3.select('.join').data([joinData])
console.log('jData',jData)

https://ithelp.ithome.com.tw/upload/images/20210917/20134930Bv8Z88yyTG.jpg

這樣一來就能看到這個 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)

https://ithelp.ithome.com.tw/upload/images/20210917/20134930oUgnMShwsq.jpg

如果還想了解更多,可以直接看這篇作者親自在Stackoverflow上的回答


data 跟 datum 的比較

如果你看完上面的解說後,仍然搞不懂這兩個方法差在哪裡的話⋯⋯⋯⋯別擔心!下面就是簡單的統整:

  • .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 >:

https://ithelp.ithome.com.tw/upload/images/20210917/20134930QNaKJpRtGs.jpg

點開每個DOM元素,找到綁定的 _data_ 資料,會發現

  • data( ) 每個 DOM 都綁定一個資料,剩下一個沒綁到資料的 DOM 則歸到 exit 那邊

https://ithelp.ithome.com.tw/upload/images/20210917/20134930hGUMbnPnAO.jpg

https://ithelp.ithome.com.tw/upload/images/20210917/20134930AcI7ky1TZc.jpg

  • datum() 則是每個 DOM 都綁定整組資料

https://ithelp.ithome.com.tw/upload/images/20210917/201349306gSWkSORK6.jpg

整理比較

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

瞭解這兩者的差異後,大家心裡一定會出現一個疑惑:那到底什麼時候要用data( )、什麼時候要用datum( )呢?
基本上就是:

  • 適合使用 datum
  1. 當你想把整組資料綁定到單獨的一個 DOM 元素上
  2. 當你的資料幾乎不會變動,因此不需要使用到 selection.enter/.exit 方法時
  • 適合使用 data()
    如果你想把資料一一綁定到一組 DOM 元素上、亦或是你的圖表資料常常會變動(例如:觀察每日搭乘捷運人次)

以上就是綁定DOM元素跟資料的方法

本來想一口氣把接下來的增減資料數量與DOM元素不匹配的方法也寫完,但發現如果繼續寫下去,可能就要變成萬言書了(登愣)~~為了方便讀者閱讀與查找,最後決定還是拆成上下兩集吧!今天就先講完 data( ) 跟 datum( ) 這兩個綁定資料的方法與差異,明天我們再來看看 enter( )、exit( )、join( ) 這幾個 API 又是幹嘛的~

Github Page 圖表與 Github 程式碼

最後,一樣附上本章的程式碼與圖表 GithubGithub Page,這次基本上都是看 console.log 出來的內容,所以看到畫面一片白不要太震驚哦~


上一篇
Day4- D3選取器:Selection
下一篇
Day6-D3 資料綁訂 Data Binding: 資料狀態enter、update、exit
系列文
三十天成為D3.js v7 好手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
Mizok
iT邦新手 3 級 ‧ 2022-01-25 11:47:17

請問關於 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)
金金 iT邦新手 1 級 ‧ 2022-01-27 18:27:31 檢舉

對耶!這邊不小心打錯了~感謝糾錯!我來改改

0
Snowmonkey
iT邦新手 5 級 ‧ 2024-01-08 15:19:06

"現在我們的畫面上只有一個 calss="join" 的 DOM 元素"
應該是class嗎?

受益良多

我要留言

立即登入留言