Day5-D3 资料绑定 Data Binding:data( ) 跟 datum( )

本篇大纲: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 出来的内容,所以看到画面一片白不要太震惊哦~


<<:  意图下载微微软家的新OS,嚐鲜不成载到加好加满的谜包

>>:  Day 2 - 输出

Gulp 压缩优化程序码(1) DAY88

这里我们先介绍 gulp-clean-css(压缩css) 与 gulp-uglify(压缩js) ...

Day 22 资料宝石:【Lab】RDS架构 建立自己的第一台云端资料库 (中)

今天我们接续 RDS Lab 实作。 创建第一台 RDS instance 按下左边列表的 Dat...

15. 做对事是不够的,你还必须要有影响力。

前言 这篇演讲适合所有人听,特别是当你觉得「为什麽我明明在做对的事情,但大家都不接受我的意见」时,...

爬取instagram留言 - Selenium

这边我是打API爬的,所以先写了序列化: class IgCommentsSerializer(se...

[D08] placeholder

写在前面 placeholder placeholder placeholder placehold...