数据可视化:D3 基础入门学习笔记(一)

16 Apr 2018

D3 : Data-Driven Documents

D3 的全称是 Data-Driven Documents,就是数据驱动文档,What? 其实就是一个 JavaScript 的函数库,主要用来做数据可视化。D3 Gallery 展示了使用 D3 做出来的各种数据可视化的效果。

D3 vs HighCharts vs Echarts

在数据可视化方面,除了 D3 之外,还有一些比较流行的工具,比如 HighCharts、百度的ECharts 等等。👇这里简单地对这三个可视化的库做了一下对比。

  • 渲染引擎上,可视化库通常会选择 SVG 或者 Canvas 进行渲染。D3 和 HighCharts 主要是基于 SVG, ECharts 主要基于 Canvas;

( D3 4.0 开始也支持 Canvas 渲染, 但是对于 Canvas 的支持程度远不及 SVG, 大部分 D3 项目使用的是 SVG; Echarts 4.0 开始提供了 SVG 渲染器,但 Echarts 默认使用的是 Canvas 渲染。另外,对于低版本 IE, HighCharts 和 Echarts 使用 VML 进行渲染。)

  • 浏览器兼容性上,三者都支持主流的现代浏览器(Chrome / Firefox / Safari ...),对于低版本浏览器,D3 支持到 IE9+, HighCharts 支持到 IE6+, Echarts 支持到 IE8+;

  • 使用方法上,HighCharts 和 Echarts 用法比较相似,通过简单的配置语法就可以生成对应的图表,可能并不需要我们对 SVG 或 Canvas 预先有太多了解; D3 虽然是做数据可视化的,但其实并不是一个真正意义上的图形绘制的库,绘制图表的时候需要我们自己结合使用 SVG 或者 Canvas, D3 的功能主要是集中在数据处理方面,将数据映射到图形,中间有很多琐碎复杂的过程,D3 提供了一系列封装好的函数,让我们能够方便地实现从数据到图形的转换。

总的来看,这三个可视化的库,各有优缺,D3的相对优势是更加灵活,拓展性更好一些。

D3 基础概念 & 用法

Selection 选择集

D3 对 DOM 操作进行了封装。和 jQuery 类似,D3 依赖于选择符选中一组元素,建立选择集,然后使用选择集对象的方法操作 DOM,比如设置属性、样式、文本内容以及监听 DOM 事件等,并且, D3 也支持链式调用的写法。

  • 选择元素
    • select():使用第一个匹配的元素创建选择集;
    • selectAll():使用全部匹配的元素创建选择集;
var body = d3.select("body");
var container = body.select("#container");

var p = body.selectAll("p");
var btns = body.selectAll(".btn”);
  • 插入元素
    • append(type) :在选择集末尾插入元素;
    • insert(type[, before]) : 在选择集匹配 before 选择器的第一个元素前插入元素;
// 当不指定 before 时,insert 和 append 一样,都是插入在末尾
// 下面两个写法效果一样👇
p.append("div");
p.insert("div");

// 要让 insert 插入在选择集第一个位置,写法如下:
p.insert("div”, ":first-child");
  • 删除元素

    • remove()
  • 其他

    • selection.attr - get or set an attribute.
    • selection.classed - get, add or remove CSS classes.
    • selection.style - get or set a style property.
    • selection.property - get or set a (raw) property.
    • selection.text - get or set the text content.
    • selection.html - get or set the inner HTML.

。。。

d3.selectAll("circle")
  .style("fill", "blue")
  .attr("r", 30)
  .attr("cy", function() { return Math.random() * 400 });
  • 事件监听

    • selection.on(typenames[, listener[, capture]])

    跟一般的事件监听,区别在于,D3 的事件监听函数在触发时,传入的参数中没有 DOM 事件对象,D3 提供一个全局变量来提供 DOM 事件对象:d3.event。这个对象仅在监听函数中有效。每当事件触发时,D3 就将 DOM 事件对象赋给 d3.event, 并在监听器处理完之后将其清理。

    利用事件监听可以添加交互效果,比如添加 mousemove 效果等。虽然可以通过 DOM 事件对象的 pageX 和 pageY 获取鼠标位置,但是 D3 提供了更好的方法:d3.mouse(container),参数是一个 html 元素,d3.mouse() 返回鼠标位置相对于容器元素的 x,y 坐标。

🏷 官方文档D3 Selection

Data Join 数据绑定

D3 将数据可视化抽象为数据与可视化元素的匹配,一个数据对应一个可视化元素。

  • datum():绑定一个数据到选择集上;
  • data():绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定;
var data = [10, 20, 30];
d3.selectAll("circle")
  .data(data)
  .style("fill", "pink")
  .attr("r", function(d, i) { return d; }) // d 和 i 分别对应数据和索引
  .attr("cx", function(d, i) { return i * 100 + 50; });

Update & Enter & Exit

这是 D3 中比较重要的三个概念,用来处理选择集和数据的数量关系不确定的情况。

默认情况下,数据绑定按照索引进行匹配,第一个元素和数组中第一个数据绑定,以此类推。在数据绑定时,会产生三个选择集用来代表三种不同的状态: Update、Enter、 Exit。 其中,与数据相匹配的元素属于 Update 选择集, 当数据比元素多时,多出来的数据缺少相应的元素与之匹配,这部分缺失的元素属于 Enter 选择集,当数据比元素少时,多余的元素属于 Exit 选择集。

看下面的小栗子🌰

有三个圆 ~ 圈圈圆圆圈圈 ~

<svg width="600" height="400">
    <circle cx="100" cy="200" r="50"></circle>
    <circle cx="300" cy="200" r="50"></circle>
    <circle cx="500" cy="200" r="50"></circle>
</svg>

使用 D3 的数据绑定,示例如下:

var data = [10, 20, 30, 40];
var svg = d3.select("svg");

// DATA JOIN
var circle = svg.selectAll("circle").data(data);

// Update
circle
 .style("fill", "pink")
 .attr("r", function(d, i) { return d; })
 .attr("cx", function(d, i) { return i * 100 + 50; })
 .attr("cy", function(d, i) { return i * 100 + 50; });

// Enter
circle.enter().append("circle")
 .style("fill", "blue")
 .attr("r", function(d, i) { return d; })
 .attr("cx", function(d, i) { return i * 100 + 50; })
 .attr("cy", function(d, i) { return i * 100 + 50; });

👉 selection.data() 返回的是 update selection,而 enter selection 和 exit selection 是挂在 update 选择集上的,分别通过 selection.enter() 和 selection.exit() 来获得。

上面栗子中,对于 update 选择集和 enter 选择集的圆分别添加不同颜色,但是对于半径和圆心位置的操作是一样的。可以将 Enter 部分改成下面的写法👇,减少一些重复:

// Update + Enter
// After merging the entered elements with the update selection,
// apply operations to both

circle.enter().append("circle")
  .style("fill", "blue")
  .merge(circle)
  .attr("r", function(d, i) { return d; })
  .attr("cx", function(d, i) { return i * 100 + 50; })
  .attr("cy", function(d, i) { return i * 100 + 50; });

再看一个小栗子🌰 此时一开始一个圆也没有 ~ ~

<svg width="600" height="400"></svg>

但是依然可以通过 selectAll 来选择并不存在的 circle 元素创建选择集,并与数组绑定,此时 update 选择集为空。

var data = [10, 20, 30, 40];
var circle = d3.select('svg')
 .selectAll("circle")
 .data(data);

circle.enter()
 .append("circle")
 .style("fill", 'blue')
 .attr("r", function(d, i) { return d; })
 .attr("cx", function(d, i) { return i * 100 + 50; })
 .attr("cy", function(d, i) { return i * 100 + 50; });

👉 enter 选择集一开始所包含的元素只是一些占位符而不是 DOM 元素。这些占位符其实只是带有__data__属性的简单对象,而且占位符只是临时的,当调用 append 或 insert 后就被新创建的 DOM 元素替代。

最后,对于 Exit 选择集的操作比较简单,通常直接把多余的元素删除就可以。假设一开始还是三个圆,绑定的数组有两个数据,把多余的一个元素删除,示例如下:

// Exit  
circle.exit().remove();

参考 & 推荐