d3这个工具包的内容非常丰富,这里只写一入门级别的东西,代你理解一下d3的数据机制。包含主下内容,基础了解的同学就不用看了。
- Selection 及基础操作
- 数据绑定
- merge / filter
d3的API设计风格很符合现在流式编程的范式,简单点说,就是很省代码行数。
Selection 选择对象
通过 d3.select()
或 d3.selectAll()
可以返回一个 Selection 对象。
除data/datus外,Selection对象可以使用的方法如下 : 如果玩过 JQuery 的同学应该会相当熟悉,所以就不多解释了。
另外,我认为在SVG编程中,频率使用最高的方法标了红,分别是:
方法 | 说明 |
---|---|
selection.select | 从每个被选中的元素中选择一个后代元素. |
selection.selectAll | 从每个被选中的元素中选择多个后代元素. |
selection.attr | 设置或获取属性. |
selection.classed | 获取,添加或者移除 CSS 类. |
selection.style | 获取或设置样式属性. |
selection.property | 获取或设置一个特殊属性. |
selection.text | 设置或获取文本内容. |
selection.html | 设置或获取 HTML 内容. |
selection.append | 创建、添加并返回一个新的元素. |
selection.insert | 创建、插入并返回一个新的元素. |
selection.remove | 从文档中移除元素. |
selection.clone | 插入选中元素的克隆. |
selection.sort | 基于数据对文档中的元素进行排序. |
selection.order | 在文档中重新排列元素. |
selection.raise | 将每个选中的元素重新排列为其对应父节点的最后一个子元素. |
selection.lower | 将每个选中的元素重新排列为其对应父节点的第一个子元素. |
selection.on | 添加或移除事件监听器. |
selection.dispatch | 分发一个自定义事件. |
selection.each | 为每个选中的元素执行相应的函数. |
selection.call | 为当前选择集指向相应的函数. |
selection.empty | 判断当前选择集是否为空. |
selection.nodes | 以数组的形式返回当前选择集中的所有被选中的元素. |
selection.node | 返回当前选择集中第一个非空元素. |
selection.size | 返回选择集中元素选中的数量. |
selection.transition | 为指定的选择集指定一个过渡. |
selection.interrupt | 中断并且取消选中元素的过渡 |
数据绑定
data && datum
数据绑定主要是两个API,d3.data()
d3.datum
对于新入门者会有一点点晦涩难懂。
通常来说,我们使用data就够了,很少有场景需要使用到 datum,那么,到底是什么场景呢:
下面这个HTML和JS文件,建议直接粘贴成文本本地测试一下,十分钟左右的时间保证能让你理解两者的关系。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TEST</title>
<script src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<a id="id1"></a>
<a id="id2"></a>
<a id="id3"></a>
<a id="id4"></a>
<br>
<a class="link1"></a>
<a class="link1"></a>
<a class="link1"></a>
<br>
<a class="link2"></a>
<a class="link2"></a>
<a class="link2"></a>
<br>
<a class="link3"></a>
<a class="link3"></a>
<a class="link3"></a>
<br>
<div class="link4">
<a></a>
<a></a>
<a></a>
</div>
</body>
<script>
</script>
</html>
这里我把脚本单独拿出来看一下,根据注释应该好理解。
const data1 = {title:'link1',href:'https://www.xdnote.com/'};
const data2 = {title:'link2',href:'https://www.xdnote.com/'};
const data3 = {title:'link3',href:'https://www.xdnote.com/'};
const data4 = {title:'link4',href:'https://www.xdnote.com/'};
// 以下五条,正常,请注意一下我的参数里面是否加了中括号
d3.select('#id1').data([data1]).text(d=>d.title).attr('href',d=>d.href);
d3.select('#id2').datum(data2).text(d=>d.title).attr('href',d=>d.href);
d3.selectAll('#id3').data([data3]).text(d=>d.title).attr('href',d=>d.href);
d3.selectAll('#id4').datum(data4).text(d=>d.title).attr('href',d=>d.href);
d3.selectAll('.link1').data([data1,data2,data3]).text(d=>d.title).attr('href',d=>d.href);
// 这里出现异常情况: 由于 select 只选中了一条,所以,你看到,只有第一个a标签正常转化
d3.select('.link2').data([data1,data2]).text(d=>d.title).attr('href',d=>d.href);
// 这里出了小情况,我所有的 link3,都赋予了 data1 的属性,而实际上就是API的正常设计
d3.selectAll('.link3').datum(data1).text(d=>d.title).attr('href',d=>d.href);
// 这里先用 datum 把一个数组给了 div,再用selectAll把这个数组又给了 a 元素
d3.select('div.link4').datum([data2,data3,data4]).selectAll('a').data(d=>d).text(d=>d.title).attr('href',d=>d.href);
data应该好说(data方法的参数必须是数组),而对于datum 通常是结一个固定元素绑定数据,或是一群元素绑定同一条数据的功能。
enter,exit 及 update
selection.enter()
和 selection.exit()
分别代表新进入的和应退出的元素。
update实际上并没有这个方法,因为 selection
进行了 data()
操作后,自己本身就是自己的 update了,不需要额外的方法,标题上加个update是便于理解,因为这三个部分都需要你后续写代码来进行操作!
举个栗子:你手有一批 [1,2,3,4,5]
的货 ,现在要换用 [4,5,6,7,8]
的这批货,那需要进行以下三步操作
- 新enter进来 : [6,7,8]
- 没用的exit出去 : [1,2,3]
- 翻新update : [4,5]
操作完成,实际上,这三个方法并没有顺序之分,如果你确定你的数据没有某一项操作,不执行那一项操作也没有问题
它的代码如下:(为了简单性,我就拿HTML来做实验了,SVG也是差不多的)
你可以把上面的HTML,
标签里面的内容替换为<div id="root"></div>
<script>
标签里放入以下内容:
const data1 = [{id:1,name:'111'},{id:2,name:'222'},{id:3,name:'333'},{id:4,name:'444'},{id:5,name:'555'}];
// 请注意,我在data2里面,把货物3的名字变了,而且顺序上也做了变化
const data2 = [{id:4,name:'444'},{id:3,name:'三三三'},{id:5,name:'555'},{id:6,name:'666'},{id:7,name:'777'}];
d3.select('#root')
.selectAll('p')
.data(data1)
.enter()
.append('p')
.text(d=>d.name);
// 说明,以上代码为刚说的:“手上有一批 [1,2,3,4,5] 的货” 做了先决条件
// 我们的代码将从这个方法开始
function gg(){
var ROOT = d3.select('#root');
// 0. 分别取出 update, enter, exit
var updates = ROOT.selectAll('p') //选择元素
.data(data2,d=>d.id); //更新数据
// .data(data2);
//说明:第二个参数,需要设置 d=>d.id,主键就可以对上
//当然对不上也没问题,留个作业给你:自行尝试
var enters = updates.enter(); //新加入的 enter
var exits = updates.exit(); //将要退出的 exit
// 新enter进来 : [6,7,8]
enters.append('p')
.text(d=>d.name);
// 没用的exit出去 : [1,2,3]
exits.remove();
// 翻新update : [4,5]
updates.text(d=>d.name)
}
window.setTimeout(()=>gg(),2000)
个人认为理解了 data()
exit()
enter()
三个方法,就是正式跨入d3的第一大步,而且是最难最重要的一步,后面的基本上都非常简单了。
merge & filter
这里还要说下两个方法:
Selection.merge()
这个也是使用频率最高的方法之一,直接拿代码来演示一下:
回到刚才的代码里面,我们单看以下三行代码
// 新enter进来 : [6,7,8]
enters.append('p')
.text(d=>d.name);
// 没用的exit出去 : [1,2,3]
exits.remove();
// 翻新update : [4,5]
updates.text(d=>d.name)
可以看到 enter 以及 update 都是作了相同的操作,即 text(d=>d.name)。在实际项目中也是这样,而且操作的内容行数会更多。
这里提供的 merge 方法,主要功能就是来帮你省代码行数的,上面三句话,通过merge,就可以写成两句话了:
// 新enter进来 : [6,7,8] and 翻新update : [4,5]
enters.append('p')
.merge(updates)
.text(d=>d.name);
// 没用的exit出去 : [1,2,3]
exits.remove();
当然要是不实际使用的话,肯定会觉得:“这有什么了不起的”,所以建议实践中使用,肯定会让你有所收获。
Selection.filter()
这个实际中使用的并不是很多了,所以实用性并没之前说的api那么强,但还是很有用的一个API。
假设我有这样一个需求(还是上面的demo): 对于名字中不包含的,标红显示,只需要在方法后面加一行代码:
// 找出name中不包含id的
updates.filter(d=>!d.name.includes(d.id))
.style('color','red')
小结
对于新手,本小节所有的代码,建个html文件,实际跑一下,每行代码能够理解就OK了,相信我,自己动手并不耗时,比去网上找各种入门的文章来看,更省时间,理解更深。
再次安利一下,d3是一个值得一学的工具,不管你使不使用SVG编程,看一看D3的源码及其设计思路,会对编程的素质有所帮助。