本篇大纲:Enter / Update / Exit 状态、增减资料数量与DOM元素不匹配的方法、神奇的d
今天要来讲资料绑定的第二个部分:根据资料与 DOM元素匹配的数量去增减DOM元素~
在讲解怎麽增加或减少DOM元素之前,我们要先来看看这张图
只要一提到 D3 的资料绑定,一定会解说到这张图,相信不少人也都看过~因为这个是D3 让我们能专注在资料上的核心概念。
由於使用selection.data
的时候,资料会跟DOM元素一一配对并绑定,因此也会出现资料较多或DOM元素较多的情况,因此如果两者的数量不匹配时,D3就把这些情况分成三种状态
update
资料。enter
资料。exit
资料。简单来说就是:
呼叫selection.data()时,会回传一个新的selection物件,里面包含成功系结到元素的资料 (update),同时也会填入enter()、exit(),里面包含多的资料或DOM
如果还是有点似懂非懂也没关系,下面一样直接上范例!
update 资料
⇒ 资料与DOM元素数量刚好// html
<p class="updateData"></p>
<p class="updateData"></p>
<p class="updateData"></p>
<p class="updateData"></p>
// js
const updateData = ["资", "料", "刚", "好"];
const upData = d3.selectAll('.updateData').data(updateData)
console.log('upData', upData)
把 upData 印到console後,我们会看到之前说过的 _enter
、 _exit
、_group
_enter
负责处理输入的资料_exit
负责处理搭配的DOM 元素_group
呈现DOM元素与绑定的资料这边可以看到,无论是 _enter、_exit 或是 _groups,它们阵列的数量都一样是四个
接着我们分别展开 _enter 跟 _exit,发现里面都是写 [empty X 4],代表所有的资料都跟 DOM 元素搭配好了,没有多余的资料或是多余的DOM元素
最後展开_group,会看到所有绑定资料的DOM元素。如果一一将元素展开,找到里面的 _data_,就知道该DOM元素绑定的是哪笔资料
而这些有与DOM元素绑定的资料,就会被归到 update 资料内
enter 资料
⇒ 资料多// html
<p class="enterData"></p>
<p class="enterData"></p>
// js
const enterData = ["资", "料", "比", "较", "多"];
const eData = d3.selectAll('.enterData').data(enterData)
console.log('eData', eData)
这边会看到 _enter 跟 _exit 的阵列内数量不相同。展开 _enter 可以看到阵列内虽然有五个值,但前面两个却是enpty。这是因为前两个数值已经让相对应的DOM元素匹配走了,剩下的三个就是没有DOM元素可以搭配的资料
继续将这个阵列展开後,可以看到是第3、4、5的资料没有搭配到,这几个资料的值分别是“比”、“较”、“多”,这些没跟DOM元素绑定的资料就会被归到 enter 资料。
exit 资料
⇒ DOM元素多// html
<p class="exitData"></p>
<p class="exitData"></p>
<p class="exitData"></p>
<p class="exitData"></p>
<p class="exitData"></p>
// js
const exitData = ["资", "料", "少"];
const exData = d3.selectAll('.exitData').data(exitData)
console.log('exData', exData);
再来我们把 exData 印到 console 上看,会发现这次是 _exit 的数值多了两个DOM元素。这代表着有两个DOM元素没有资料可以绑定
展开这个阵列後,发现是第3跟第4个DOM元素没有资料绑定,这些没跟资料绑定的DOM元素就是 exit 资料
看完了D3对资料与DOM元素搭配的状态後,接着就轮到增减资料数量与DOM元素不匹配的方法
上场啦!
这个分类的方法总共有三项
我们一样会先看官网解说,并搭配范例一一来讲解这三个方法
selection.enter( )
官方文件上写着:这个方法会返还一个 enter selection,这个 enter selection 用来抓出缺失的DOM元素,以搭配多余的data资料。
上面这段的意思是,刚刚我们已经先看到,当资料多余DOM元素时,在_enter
会呈现出没被绑定的资料,接着我们就要用 enter() 这个方法去创建DOM元素来搭配这些资料
一旦抓到缺少DOM元素搭配的资料後,我们就会用 .append( )的方法,把缺少的DOM元素加上去,如此一来每笔资料就都能搭配到对应的DOM元素了
const enterData = ["资", "料", "比", "较", "多"];
const eData = d3.selectAll('.enterData')
.data(enterData)
.enter()
.append('p')
.attr('class', 'enterData')
这麽一来,缺失的三个 DOM 元素就会被加上去了
selection.exit( )
接着我们看到 exit () 的官方解说:
这个方法会返还一个 exit selection,这个 exit selection 用来抓出多余的DOM元素,这些多的 DOM 元素没有资料能搭配。
这段话的意思是,刚刚我们已经先看到,当DOM元素比较多时,在_exit
会呈现出没绑定到资料的DOM元素,接着我们就要用 exit() 这个方法将这几个DOM元素抓出来并删除掉
一开始时,我们有五个DOM元素跟三笔资料
// html
<p class="exitData"></p>
<p class="exitData"></p>
<p class="exitData"></p>
<p class="exitData"></p>
<p class="exitData"></p>
const exitData = ["资", "料", "少"];
const exData = d3.selectAll('.exitData').data(exitData)
console.log('exData', exData);
画面上的 DOM元素
接着,用 exit() 抓出多余的DOM 元素後,搭配 remove() 的方法把多余的 DOM 元素删除
const exitData = ["资", "料", "少"];
const exData = d3.selectAll('.exitData').data(exitData).exit().remove()
console.log('exData', exData);
画面上就只剩下三个 DOM 元素了
selection.join( )
最後我们看到的是 join() 这个方法。这个方法其实是个更方便的方法,它结合了 exter()、exit() 跟其他方法,让我们能更快速简单的增减元素
先看到官方文件的解释:这个方法可以增加、移除或重新排列元素的顺序,藉以搭配资料
这个的意思就是,当你需要同时处理update、enter以及 exit 的资料时,之前需要分开来写,例如下面以update 跟 enter为例
const enterData = ["资", "料", "比", "较", "多"];
const eData = d3.selectAll('.enterData')
.data(enterData)
.text(d=>d) // 这边先处理 update 资料
.enter(). // 这边接着处理 enter 资料
.append('p')
.attr('class', 'enterData')
.text(d=>d)
但如果换成使用join的话,就可以一次并在一起写
const joinData = ['j', 'o', 'i', 'n']
d3.selectAll('.joinData')
.data(joinData)
.join() // 把update 跟 enter 一起处理
.append('p')
.attr('class','.joinData')
.text(d => d);
这样一来就节省了我们需要处理 DOM 元素的工序,也减少写重覆的 code,非常的方便~~
神奇的d
:绑定资料後的操作,API 回传 callback function前面我们已经将资料到DOM元素了,但这样还不算完成,我们接着还要处理想呈现的资料。处理之前,我们先来看看一个神奇的参数 —— d
。
在看别人写的D3图表程序码时,相信大家很常会看到一个神奇的参数 d
被带入呼叫的 API 中,例如:
const joinData = ['j', 'o', 'i', 'n']
d3.selectAll('.joinData')
.data(joinData)
.join(). // 把update 跟 enter 一起处理
.append('p')
.attr('class','.joinData')
.text(d => d); // 这个神奇的 d
这到底是什麽东西呢?
这边的d => d
其实是callback function 的缩写,以及它所带的参数,本来的写法是这样:
const joinData = ['j', 'o', 'i', 'n']
d3.selectAll('.joinData')
.data(joinData)
.join(). // 把update 跟 enter 一起处理
.append('p')
.attr('class','.joinData')
.text(function(d){return d });
我们一样来看看官网对这个方法的解释:selection.text() 这个方法里面可以带入参数,如果带入的参数是一个方法,它代表的就是 每一个绑定资料的 selection 实体,而且会按照顺序回传个别 selection 绑定的 data 跟 index
看上述的解释就很清楚啦~当我们使用callback function 去回传 d 时,就能把每个DOM元素绑定的资料选出来,并一一呈现在画面上,这样的方式有点类似map的用法,而且大多数的API 都能使用 callback function 去回传资料
。
除了一一回传资料之外,我们也可以利用 callback function 去设定一些条件
const joinData = ['j', 'o', 'i', 'n']
d3.selectAll('.joinData')
.data(joinData)
.join(). // 把update 跟 enter 一起处理
.append('p')
.attr('class','.joinData')
.text(function(d){
if(d ==='o'){
return '抓到你了'
} else {
return d
}
});
// 三元运算式的简化写法
.text(d => d === 'o'? '抓到你了' : d)
这样一来,就能把本来应该要呈现 o 的 DOM 元素,改成呈现我们自订的文字
这些只是 callback function 当参数的小应用,等到後面的篇章带到绘制图表後,就能看到更多更复杂的应用方式
上面讲了这麽多当资料数量不匹配时,要如何去增减DOM节点,这时的大家的心里是否会出现一个疑问:到底什麽时候资料会这样变动?上面的方法实际上要怎麽应用呢?
这边我们就来做个小范例吧,透过范例更能了解增减DOM元素的方法要怎麽实际应用~这个范例是滑动选取不同的范围时,资料会更新,并且呈现在下方的柱状图表也会更新
先来看到程序码:画面上有一个 input 范围条,我们先建立一个空阵列,当范围条改变时会把随机乱数推进阵列,这个阵列就会成为我的资料集
// html
<div>
<input class="dataChange" type="range" style="width:100% ; cursor:pointer" name="dataChange" min="0" max="10" value="0">
<p> data:<span class="showData"></span></p>
<div class="example">
</div>
</div>
// JS
const dataChange = document.querySelector('.dataChange')
const showData = document.querySelector('.showData')
let randomData = [] // 先建立空资料阵列
dataChange.addEventListener('change', function(){
randomData = [] // 每次重选range就清空阵列
for(i= 0; i< event.target.value; i++){
let random = Math.floor(Math.random() * 5)
randomData.push(random) // 塞入随机乱数资料
}
showData.innerHTML = randomData
drawDiagram() // 绘制图表的方法
});
设定好我们准备带入的资料阵列後,接着就撰写画图表的 drawDiagram()
方法吧!首先,一样先建立 svg 画布,接着设定画图表的方法
// 建立 svg 画布
const rangeSelect = d3.select('.example')
.append('svg')
.attr('width', 500)
.attr('height', 500)
// 制作图表
const drawDiagram =()=>{
}
选取页面上所有的 < rect > DOM元素之後,用.data()的方法把我们设定好的 randomData 与 DOM 元素绑定
// 制作图表
const drawDiagram =()=>{
// 绑定 update 资料
let rects = rangeSelect.selectAll('rect')
.data(randomData)
}
不过此时,我们的页面上并没有任何 < rect > 的DOM元素,因此这些绑定的资料就都会归到 enter 资料内。我们要使用 enter() 的方法去建立相对应的DOM元素,然後再将这些 DOM 元素加上必要的 style 跟 attr 标签内容
// 制作图表
const drawDiagram =()=>{
// 绑定 update 资料
let rects = rangeSelect.selectAll('rect')
.data(randomData)
// 用 enter 加上少的DOM元素
rects.enter()
.append('rect')
.attr('width', d => d * 60)
.attr('height', 50)
.style('fill', 'blue')
.attr('x', (d, index) => 0 ) // 设定x位置
.attr('y', (d, index) => index * 60) // 设定y轴位置
}
这样一来,我们就能顺利呈现绑定资料的图表了
太好了完成!结束掰掰 (想得美)! 乍看之下一切都没问题,但如果你开始移动 range bar,就会发现:奇怪,怎麽资料明明更新了,但我的图表却没有更新呢?
这是因为,一旦DOM元素跟原本的资料绑定後,虽然你更新了资料,更新的资料也已经跟DOM元素绑定的(绑到_data_上),但却没有更新DOM元素一开始绑定的高度设定呀!因此我们要重新设定 rect 的 width,让它绑定新的资料,才能正确呈现更新後的图表
// 制作图表
const drawDiagram =()=>{
// 绑定 update 资料
let rects = rangeSelect.selectAll('rect')
.data(randomData)
// update 更新绑定的资料
rects.attr('width', d => d * 60)
// 用 enter 加上少的DOM元素
rects.enter()
.append('rect')
.attr('width', d => d * 60)
.attr('height', 50)
.style('fill', 'blue')
.attr('x', (d, index) => 0 ) // 设定x位置
.attr('y', (d, index) => index * 60) // 设定y轴位置
}
这样一来图表就能随着新的资料正确更新了
但是!又出现一个问题(有完没完啊~),如果我们把范围缩小,就会惊讶的发现DOM元素并没有减少,而且还绑定着原本的资料!
这是因为我们并没有把没被新资料绑定的DOM元素删除,因此我们要用 exit() 的方法来移除没绑定新资料的DOM元素
// 制作图表
const drawDiagram =()=>{
// 绑定 update 资料
let rects = rangeSelect.selectAll('rect')
.data(randomData)
// update 更新绑定的资料
rects.attr('width', d => d * 60)
// 用 enter 加上少的DOM元素
rects.enter()
.append('rect')
.attr('width', d => d * 60)
.attr('height', 50)
.style('fill', 'blue')
.attr('x', (d, index) => 0 ) // 设定x位置
.attr('y', (d, index) => index * 60) // 设定y轴位置
// 用 exit 移除多的 DOM 元素
rects.exit().remove()
}
这样一来,我们的图表才算是大功告成!
除了用 enter、update、exit 这些方法外,我们也可以用之前提过的 join() 方法,一次处理完新增、更新、删除
let rects = rangeSelect.selectAll('rect')
.data(randomData)
.join(
enter => enter.append("rect")
.attr('width', d => d * 60)
.attr('height', 50)
.style('fill', 'blue')
.attr('x', (d, index) => 0 ) // 设定x位置
.attr('y', (d, index) => index * 60), // 设定y轴位置,
update => update.attr('width', d => d * 60),
exit => exit.remove()
)
透过D3的这些API,我们就可以很方便的绘制资料变化後的图表~
以上,就是D3.js重要的资料绑定概念!老天爷啊终於写完了,差点以为今天就是挑战失败的那一日了XD 。这边花如此长篇幅详细讲解,是因为这是d3.js很重要的观念,如果看完後还有不太懂的地方(或是我哪里写错),都欢迎在下面留言一起讨论~
最後,一样附上本章的程序码与图表 Github 、 Github Page,可以实际操作一次看看资料跟图表的变化哦~
大家好~ 今天来实作如何用 Google Calendar API 建立 Google Meet 会...
目前我们完成档案上传的功能,接下来就要进行档案下载 写一个专门下载档案的Action,接受ID参数,...
事情来自某天我在找资料的过程中,看到有些大大提供了事件纪录档的文本说明,所以今天要来试着阅读.evt...
来到了铁人赛的29天,扣除掉最後一集的心得,今天算是最後一个主题。 今天的影片和以往不太一样,我事...
在规模较大的企业网路中,为了避免单点故障会采用 LACP 的方式将多条线路聚合在一起使用,除了增加...