D3.js 使用 DOM 元素创建数据可视化的 JavaScript 库

发布于 2020-01-05 11:38:43 字数 9102 浏览 1811 评论 0

D3js 是一个可以基于数据来操作文档的 JvaScript 库。可以帮助你使用 HTML, CSS, SVG 以及 Canvas 来展示数据。D3 遵循现有的 Web 标准,可以不需要其他任何框架独立运行在现代浏览器中,它结合强大的可视化组件来驱动 DOM 操作。

最新的版本(5.9.2):d3.zip

或者插入如下代码来在线使用:

<script src="https://d3js.org/d3.v5.min.js"></script>

介绍

D3 可以将数据绑定到 DOM 上,然后根据数据来计算对应 DOM 的属性值。例如你可以根据一组数据生成一个表格。或者也可以生成一个可以过渡和交互的 SVG 图形。

D3 不是一个框架,因此也没有操作上的限制。没有框架的限制带来的好处就是你可以完全按照自己的意愿来表达数据,而不是受限于条条框框,非常灵活。D3 的运行速度很快,支持大数据集和动态交互以及动画。

浏览器支持

D3js 和 ECharts 一样都是实现数据可视化的 JS 库,比较 D3js 和 ECharts

  • D3支持的主流浏览器不包括IE8及以前的版本。D3测试了Firefox、Chrome、Safari、Opera和IE9。D3的大部分组件可以在旧的浏览器运行。
  • ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖轻量级的矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。

Selections 选择集

通过原生的 W3C DOM API 来修改文档是很无聊的:方法名冗余,并且对多个元素进行操作时需要循环操作,例如改变 p 元素的颜色:

var paragraphs = document.getElementsByTagName("p");
for (var i = 0; i < paragraphs.length; i++) {
  var paragraph = paragraphs.item(i);
  paragraph.style.setProperty("color", "white", null);
}

D3 采用声明式方法,可以对任意节点以及节点集合进行操作,则上述例子可以修改为:

d3.selectAll("p").style("color", "white");

同时,可以操作其他节点,而不相互影响:

d3.select("body").style("background-color", "black");

选择器是 W3C所定义的选择集, 并且支持现代浏览器。如果浏览器比较老旧,则可以借助 Sizzle 来向后兼容。上述例子中 "p" 和 "body" 都是选择器中的一种。也可以使用其他的合法选择器:类选择器、ID 选择器属性值选择器等。

关于选择器的更多资料参考 Selection选择集

动态属性

熟悉 jQuery 或者 Prototype 的同学可能会立即发现 D3 与这些框架的相似之处。但是不同的是,样式、属性以及其他属性的值在 D3 中可以是函数形式,而不仅仅是常量。

这个功能十分强大,它可以支持动态设置属性、样式等值。比如随机的为每个段落设置颜色:

d3.selectAll("p").style("color", function() {
  return "hsl(" + Math.random() * 360 + ",100%,50%)";
});

或者为奇偶段落设置不同的颜色:

d3.selectAll("p").style("color", function(d, i) {
  return i % 2 ? "#fff" : "#eee";
});

这种通过匿名函数动态设置属性、样式值的方法常用来绑定数据。数据被定义在一个数组中,并且每一个数据值可以作为这个函数的参数,此外还有索引等参数,比如根据数据值设置不同的字体大小:

d3.selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
  .style("font-size", function(d) { return d + "px"; });

一旦为元素绑定数据之后,后续如果需要操作就不需要重新绑定,D3会搜索已经绑定的数据。也就是可以一次绑定,多次使用。

enter 和 exit 操作

数据绑定的时候可能出现 DOM 元素与数据元素个数不匹配的问题,那么 enterexit 就是用来处理这个问题的。enter 操作用来添加新的 DOM 元素,exit 操作用来移除多余的 DOM 元素。

如果数据元素多于 DOM 个数时用 enter,如果数据元素少于 DOM元素,则用 exit

在数据绑定时候存在三种情形:

  • 数据元素个数多于 DOM 元素个数
  • 数据元素与 DOM 元素个数一样
  • 数据元素个数少于 DOM 元素个数

情形 1:数据元素个数多于 DOM 元素个数

如以下例子,如果文档中p标签的个数少于数组个数(6个),则使用 enterappend 操作来补齐 DOM 元素:

d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
  .enter().append("p")
  .text(function(d) { return "I’m number " + d + "!"; });

情形 2:数据元素与 DOM 元素个数一样

如果这种情况再使用 data 来绑定数据,相当于是更新了每个 DOM 元素所对应的数据,此时不需要加入新节点也不需要删除多余的节点:

//update
var p = d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
  .text(function(d) { return d; });

情形 3:数据元素个数少于 DOM 元素个数

假设p元素的个数多于 6 个,数据元素个数为 6,则:

var p = d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
  .text(function(d) { return d; });

p.exit().remove()  //移除多余的元素

但是在实际应用中,不可能先去统计一下元素个数,因此这三种情形可以一起使用:

// Update 情形2
var p = d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
  .text(function(d) { return d; });    

// Enter 情形1
p.enter().append("p")
  .text(function(d) { return d; });

// Exit 情形3
  p.exit().remove();

有了这三种操作,就可以自由的根据数据元素动态的修改 DOM 元素了。

D3不是一种新的表示

D3 不引入新的视觉表示方法,而是借助于现有的 Web 元素: HTML, CSS, SVG 等。例如,可以使用 D3 创建 SVG 元素,并使用外部样式表进行样式化。也可以使用复合过滤器效果,虚线和裁剪。这样在调试的时候可以方便的使用浏览器内置的调试工具来进行调试。

过渡

D3 支持动画效果,这种动画效果可以通过对样式属性的过渡实现。其补间插值支持多种方式,比如线性、弹性等。此外 D3 内置了多种插值方式,比如对数值类型、字符类型路径数据以及颜色等。

比如对元素的背景颜色进行过渡:

d3.select("body").transition()
  .style("background-color", "black");

此外还可以为一组元素设置不同的延迟:

d3.selectAll("circle").transition()
  .duration(750)
  .delay(function(d, i) { return i * 10; })
  .attr("r", function(d) { return Math.sqrt(d * scale); });

除了 D3 提供的过渡之外,你也可以通过 CSS 动画来实现对元素的过渡效果。

数据处理

数据处理就很简单了,就是对于数组和集合以及时间的一些处理方法, 比如数组求中位数方差等等,和lodash的一些方法有重合,但是还是偏向数学方面,方法有点多这里不一一列出了:

// array的方法
d3.min([1, 2, 3, 4]) 
// 1 不同于Math,min忽略NaN undefined等
d3.range(1, 10) // [1, 2 ... 10]

// collection的方法
d3.entries({foo: 42, bar: true});
// [{key: "foo", value: 42}, {key: "bar", value: true}]
var map = d3.map([{name: "foo"}, {name: "bar"}], function(d) {
  return d.name;
});
map.get("foo"); // {"name": "foo"}
map.get("bar"); // {"name": "bar"}
map.get("baz"); // undefined

// time的方法
d3.timeDays(new Date('2014-01-11'), new Date('2014-02-12'))
// 获取2014-01-11 到2014-02-12的日期数组

上面是单纯的数据处理也就是工具类,但是d3的强大不仅仅在于此,d3提供了一个强大算法库,比如力导向图的碰撞检测以及tick等等,这里的功能也属于数据处理但是又跟插入dom密不可分。

d3的数据不仅仅是这些有些跟dom耦合极深没办法完全拎出来说, 而且d3的api极多, 这些东西很多时候也只能边看文档边做。好在d3的示例很多,基本需求都能满足。

Dom 处理

关于dom操作d3也提供了一系列方便的接口,比如d3.selectd3.append等等, 这部分的接口相当多,个人也没法一一说明, 只能说用法都是一样的,和jQuery相当类似:

svg.selectAll("circle")
  .data(data)
  .enter().append("circle")
  .attr("cx", function(d) { return d.x; })
  .attr("cy", function(d) { return d.y; })
  .attr("r", 2.5);

上面的代码是把circledata进行数据绑定并插入对应的dom节点:

  1. 首先,svg.selectAll("circle") 返回一个空选集,因为当前 SVG 还没有任何子元素,该选集的父节点是这个 SVG 容器。
  2. 然后将该选集与数据绑定,产生三个新的子选集,分别代表三种可能的状态:enter、update 和 exit。由于当前选集为空,所以 update 和 exit 子选集也为空,enter 子选集就包含了每条数据对应的元素的占位符。
  3. update 子选集直接通过 selection.data 返回,enter 和 exit 子选集分别通过 selection.enterselection.exit 返回。
  4. 那些缺少的元素通过对 enter 子选集调用 selection.append 方法来添加到 SVG 中,这样就为每条数据添加了一个新的圆点到 SVG 中。

如上都是链式操作。

事件

不同于canvas这里可以直接触发原生事件,让人亲切很多。

事件是指基于dom的一些交互操作,包括但不限于click等原生事件,类似 jQuery,事件是通过on进行绑定的:

selection.on('click', function (d) {})
// this指向事件元素, d是绑定的数据可以直接使用

同时,d3提供了很多自定义事件诸如drag, zoom,brush等等,这时候就是通过call调用了:

const brush = d3.brushX()
  .extent([[50, 50], [1100, 150]])
  .on('start brush', brushed)
  .on('end', brushended)

svg.append("g")
  .call(brush)

上面是调用brush事件,同时调用相应的回调, 都是字面意思,至于还有很多有意思的事件,都隐藏在文档中。

相关链接

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

沧笙踏歌

文章 0 评论 0

山田美奈子

文章 0 评论 0

佚名

文章 0 评论 0

岁月无声

文章 0 评论 0

暗藏城府

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文