d3.js 学习笔记1

133 阅读8分钟

d3.js 给我的感觉和 jQuery 很像,多数方法都是在 d3 命名空间下,也都可以轻松的操作 DOM 来选择目标元素。

它之所以对数据可视化操作很有利,是因为它有对 <svg> 的操作进行优化。

在自己的学习过程中主要依赖下面的资源:

我的学习笔记是按着中文教程文档的节奏来的,大家完全可以去看中文教程,写的很详细了,这个学习笔记主要补充了一些自己实验的时候得出的结论。

以下为正文。

选择元素

d3.js 主要的选择元素的方法有两个:

  • d3.select() --- 选中满足条件的第一个内容
  • d3.selectAll() --- 选中所有满足条件的内容

它们的参数都只有一个,selector,为字符串。

它们的返回值并不是 element,而是 selection,后面会提到这个返回值的作用。

接下来我们通过实例来感受一下它们的使用方式。

通过标签名进行查找

const p = d3.select('p');

const pList = d3.selectAll('p');

上述例子中,我们把字符串形式的标签名作为 select()selectAll() 的参数,结果是,select() 会找到HTML 中第一个 <p> 作为 selection 并返回,selectAll 会找到 HTML 中所有的 <p> 作为 selection 并返回。

通过 ID 进行查找

const p = d3.select('#pTag');

const pList = d3.selectAll('#pTag');

上述例子中,我们把字符串形式的 ID 作为 select()selectAll() 的参数,结果是,select()selectAll 都只会找到 ID 为 pTag 的标签作为 selection 并返回。

通过类名进行查找

const p = d3.select('.pTag');

const pList = d3.selectAll('.pTag');

上述例子中,我们把字符串形式的类名作为 select()selectAll() 的参数,结果是,select() 只会找到第一个类名为 pTag 的标签作为 selection 并返回,selectAll() 会找到所有类名为 pTag 的标签作为 selection 并返回。

关于返回值

和 jQuery 不一样的是,d3.js 的返回值不是 element,而是 selection,这是官方文档自己的叫法,它是一个 Object,包含了相关信息,通过 console.log 的方式我们大致可以看出它的长相:

第一个是 selection,第二个是 element。

大家可能有注意到 selection__proto__ 中也有 selectselectAll,这个时候大家是不是有所联想,没错,selection 的一个很重要的作用和优点,就是可以让我们连续不断的调用函数,编写链式语法

// 链式语法, 拿到 body 中所有的 p
const pList = d3.select('body').selectAll('p'); 

其次是想让大家理解一下 select()selectAll() 返回的 selection 有类似单数和复数的差别,select() 返回的就当成是单个的,只指代一个元素,selectAll() 返回的就当成是复数个的,会指代多个元素,这样有助于后续调用函数时理解。

给元素绑定数据

d3.js 提供了以下方式给 selection 绑定数据:

  • selection.datum() --- 绑定一个数据
  • selection.data() --- 绑定一个数组

基本的使用方式如下所示:

const data1 = 1;
const data2 = [2, 3, 4];

const p1 = d3.select('p').datum(data1);
const p2 = d3.selectAll('h1').data(data);

上述例子中,data1 是数字 1,data2 是一个数组,成员为数字。例子达成的结果是,我们将 data1 绑定给了 HTML 中第一个 <p> ,将 data2 数组的成员,依次绑定给了 HTML 中所有的 <h1>

大家可能发现了,案例中,我特意让 select() 得到的 selectiondatum()selectAll() 得到的 selectiondata(),有种一对一,多对多的感觉,但是实际使用中并没有这样的限制。

datum 和 data 使用中需要注意

所有 selection 都可以调用 datum()data(),但是含义不同:

  • 如果是通过 select() 得到的 selection 调用
    • 调用 datum() 指把作为参数的数据绑定给它
    • 调用 data() 指把作为参数的数组中的第一个成员绑定给它
      • 如果此时参数不是数组,那么视为绑定操作失败,不会报错
  • 如果是通过 selectAll() 得到的 selection 调用
    • 调用 datum() 指把作为参数的数据绑定给 selection 中指定的各个元素
    • 调用 data() 指把作为参数的数组中的成员依次绑定给 selection 中指代的各个元素
      • 如果此时参数不是数组,那么视为绑定操作失败,不会报错

因为可以写成链式语法,一个 selection 连续呼叫了 data()datum() 也是可以的,只需要记住一句:后成功调用的数据绑定操作会覆盖前面绑定的数据。

上述结论大家可以自己做简单的 demo 来验证,这边我就不上代码了。之所以会留意这些奇奇怪怪的地方,实在是因为这几天面试的缘故,被问怕了,23333。

绑定了数据有什么用?

当然是为了使用啦!那怎么用呢?

d3.js 提供了很多修改 selection 指代元素属性的方法,例如 :

  • selection.text() --- 修改元素文本,类似修改 innerHTML
  • selection.attr() --- 修改元素属性
  • selection.style() --- 修改元素样式

这些函数会在之后的学习过程中使用到,大家也可以去官方文档上去看它们的具体用法。

这些函数虽然参数不同,但是都有一个参数来指代 value,这个 value 可以有两种数据类型,其中一个类型就是函数,直接上示例:

<p></p>
<p></p>
<p></p>
const p = d3.selectAll('p')
	.data([1, 2, 3]) // 数组成员依次绑定给各个 p 标签
	.text((d, i) => {
    return `第 ${i} 个 p 的值为 ${d}`
 })
运行结果:
第 0 个 p 的值为 1
第 1 个 p 的值为 2
第 2 个 p 的值为 3

上述以 text()为例,我们可以看出函数有两个参数,d 指代绑定的数据,i 指代序号,也就是 index。在案例中,我们先找到所有的 <p>,然后再将 [1, 2, 3] 中的成员依次绑定给各个 <p>,然后我们通过 text() 修改各个 <p> 的文本。

此时 text() 的参数为一个匿名函数,我们可以看出函数有两个参数,d 指代元素绑定的数据,i 指代序号,也就是 index,因为 selection 包含所有 <p>,我们可以通过 i 来判断这是第几个 <p>。这个函数的返回值是一个字符串,这个字符串就是将要显示在对应 <p> 的文本内容。

添加和删除

有两种添加元素的方式:

  • selection.append() --- 在元素内部末尾添加。该函数返回值为添加的元素的 selection
  • selection.insert() --- 在元素内部指定位置插入。该函数返回值为添加的元素的 selection

不多bb,直接上示例:

<div></div>
<div id="another"></div>
const div1 = d3.select('div').append('p').text('新增的内容');
const div2 = d3.select('#another').insert('div');
<!--结果-->
<div>
  <p>新增内容</p>
</div>
<div id="another">
  <div></div>
</div>

上面这个例子不难,我们通过 append()insert() 分别在两个 <div> 中添加了新的元素。上例中,两个函数的作用给人的感觉是一样的,因为我们没有给 insert() 设定第二个参数。

insert() 有第二个可选参数,表明需要在那个元素前插入。我们基于上面的例子进行补充:

const div3 = d3.select('#another').insert('p', 'div').text('嘿嘿嘿');
<!--结果-->
<div id="another">
  <p>嘿嘿嘿</p>
  <div></div>
</div>

可以看到,我们设置了 insert() 的第二个参数,在 <div> 的前面插入了 <p> 并设定其文本内容。

最后来看看删除:selection.remove(),上示例

<div id="another">
  <p>嘿嘿嘿</p>
  <div></div>
</div>
const div3 = d3.select('#another').select('p').remove();
<!--结果-->
<div id="another">
  <div></div>
</div>

可以看到,我们用 remove() 删除了指定的元素。

exter 和 exit

这两个东西很神奇,因为他们的用法和他们的名字没有半毛钱关系,2333。

上面我们有提到 selection.data() 这个函数,可以将自己的成员绑定到对应的元素上,那你有没有想过如果元素不够呢?如下示例:

<p></p>
const data = [1, 2, 3];
d3.selectAll('p').data(data);

此时只有一个 <p>,如果我们想更加 data 的长度补全 <p>,这时我们就会用到 selection.exter()

selection.exter() 的作用是在绑定数据后,选中不够的元素,如果这个时候我们接着调用 selection.append(),就会达到补全效果,并得到绑定 data 成员的所有元素。

我们来修改上面的例子:

const data = [1, 2, 3];
d3.selectAll('p').data(data).enter().append('p').text((d, i) = >d);
<!--结果-->
<p>1</p>
<p>2</p>
<p>3</p>

selection.exit() 刚好反过来,是选中多出来的元素,我们可以接着调用 selection.remove() 来删除多余的内容,示例如下

<p></p>
<p></p>
<p></p> 
const data = [1, 2];
d3.selectAll('p').data(data).exit().remove;
<!--结果-->
<p>1</p>
<p>2</p>

做个简单的图吧

学 d3.js 的目的就是想要画图表,实现数据可视化,这边直接上一下练习代码,根据上面我整理的内容,应该能看懂全部代码意图。

Html:

    <svg id="rect">
        <rect></rect>
    </svg>

javascript:

const rectData = [250, 210, 170, 130, 90];
const rectHeight = 25;
const rectSVG = d3.select('#rect')
    .selectAll('rect')
    .data(rectData)
    .enter()
    .append('rect')
    .attr('x', 20)
    .attr('y', (d, i) => i * rectHeight)
    .attr('width', (d, i) => d)
    .attr('height', rectHeight - 2)
    .style('fill', 'green');

结果: