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__ 中也有 select 和 selectAll,这个时候大家是不是有所联想,没错,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() 得到的 selection 用 datum(),selectAll() 得到的 selection 用 data(),有种一对一,多对多的感觉,但是实际使用中并没有这样的限制。
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()--- 修改元素文本,类似修改 innerHTMLselection.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()--- 在元素内部末尾添加。该函数返回值为添加的元素的selectionselection.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');
结果: