X.d 笔记

小Web,大世界

0%

SVG简单入门六:D3入门

d3这个工具包的内容非常丰富,这里只写一入门级别的东西,代你理解一下d3的数据机制。包含主下内容,基础了解的同学就不用看了。

  1. Selection 及基础操作
  2. 数据绑定
  3. 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] 的这批货,那需要进行以下三步操作

  1. 新enter进来  :  [6,7,8]
  2. 没用的exit出去 : [1,2,3]
  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的源码及其设计思路,会对编程的素质有所帮助。