读书笔记:《数据可视化实战:使用D3设计交互式图表》 – [美] Scott Murray 著 / 李松峰 译

读完时间:2021 年 11 月 4 日

出版时间:2020 年 9 月

5.2 绑定数据

数据可视化说到底就是把数据映射成图形——输入数据,输出图形。

D3 绑定的数据没有出现在 DOM 中,而是作为该元素的 data 属性保存在内存里。

可以把 d 想象成一个寂寞的小占位值,它需要一点温暖,包括来自和蔼可亲的函数圆括号的拥抱。

6.1 绘制 DIV

严格来说,柱形图(column chart)指的是沿垂直方向度量的矩形,而沿水平方向度量的矩形叫条形图(bar chart)。不过,大多数人不区分这两个概念,而统一称其为条形图


D3 的另一个方法 classed(),用于快速地添加或删除元素的类。比如,前面那行代码可以重写成下面这样:

.classed("bar", true)

这行代码会为调用该方法的选集添加类 bar。如果第二个参数是 false,则会把元素的类 bar 删除:

.classed("bar", false)

6.4 绘制条形图

可以使用 SVG 的 text-anchor 属性,让文本在指定的 x 值上水平居中。

.attr("text-anchor", "middle")

第 7 章 比例尺

听到比例尺这个词,有人可能会不由自主地想到最终图表中的一系列刻度线,对应一系列值。不要搞错,这些刻度线是坐标轴的一部分,而坐标轴只是比例尺的一种形象表示。比例尺实际上代表着一种数学关系,没有直接的视觉呈现。大家可以把比例尺和坐标轴想象成两样不同但相关的东西。

7.1 苹果和像素

到目前为止,我们一直都在直接显示数据值,忽略了单位的差异。

7.3 归一化

归一化就是根据可能的最小值和最大值,把某个数值映射为介于 0 和 1 之间的一个新值的过程。比如,一年 365 天,那么 310 天映射过来就是 0.85,即一整年的 85%。

7.4 创建比例尺

这些步骤可以独立完成,也可以用一行代码全部连起来:

var scale = d3.scaleLinear()
    .domain([100, 500])
    .range([10, 350]);

8.3 定位数轴

如果你在给 SVG 元素应用样式时,发现 CSS 代码根本不起作用,我劝你不要心急。深呼吸,稍等一下,再仔细瞧瞧那些属性名,一定要保证是 SVG 的,而不是 CSS 的。

9.2 更新数据

什么是事件监听器呢?其实也是匿名函数,可以监听(一个或多个)特定元素上发生的事件。D3 的 on() 方法是添加事件监听器的简便方法,它接受两个参数:事件类型("click")和监听器(匿名函数)。

9.3 过渡动画

根据经验,对于细微的界面反馈(比如鼠标悬停在元素上),过渡时间大约 150 毫秒比较合适,而更显著的视觉过渡(比如整个数据视图的变化)持续 1000 毫秒比较理想。1000 毫秒(1 秒)不算长,也不算短。

过渡只能用在已经存在的值上;如果你对不存在的值应用了过渡,会触发意料之外或者你不希望得到的行为。比方说,你想让前面例子中的矩形的不透明度慢慢降低。可以执行这段代码 svg.selectAll("rect").transition().attr("opacity", 0)。这会让矩形立刻变得透明(0% 的不透明度),没有平滑的过渡效果。如果你一开始设置了 attr("opacity", 1),那么过渡才会按预期发生:从开始值(1)过渡到最终值(0)。要让新值产生过渡效果,一定要记得先设置初始值哦。

静态延迟时间只是一种延迟方式,更有意思的是可以动态计算延迟时间。动态延迟的一个常见用途就是创建交错延迟的效果,让某些元素的过渡在其他元素之前发生。交错延迟对人的感知有利,因为当相邻元素的变化不那么同步时,人眼更容易注意到每个元素的变化。

在默认情况下,任何元素在任意时刻都只能有一个过渡效果。新过渡效果会打断并覆盖原来的过渡效果。不过,可以用 on("end", ...) 指定另一个过渡,因为执行 on("end", ...) 时,之前的过渡已经结束了,因此再执行新过渡不会产生任何副作用。

SVG 支持剪切路径(clipping path),也就是 Sketch、Photoshop 和 Illustrator 这类绘图工具中的蒙版。剪切路径就是一个 SVG 元素,可以包含可见的元素,并与这个可见元素一起构成可以应用到其他元素的剪切路径或蒙版。元素应用了蒙版后,只有落在该蒙版形状内部的像素才会显示。

9.4 其他数据更新方式

维护对象恒定性的关键在哪儿?就在键(key)上。(顺便告诉大家,Mike Bostock 关于对象值的恒定性写过一篇很精彩的文章“Object Constancy”,推荐大家读一读。)

只要每个数据都有独一无二的 key,我们就可以灵活地随意添加或删除数据值(和元素)。

这一章讲的内容太多啦!好吧,咱们简单回顾一下。

  • data() 把数据绑定到元素,但也会返回更新选集。
  • 更新选集可能包含加入和退出选集,这两个选集可以通过 enter()exit() 方法得到。
  • merge() 一般用来合并加入选集和更新选集,以便同时给两者应用某些改动。
  • 数据值比元素多的情况下,加入选集会引用尚不存在的占位元素。
  • 元素比数据值多的情况下,退出选集会引用没有对应数据的元素。
  • 数据聚合决定数据值怎么与元素匹配。
  • 默认情况下,数据聚合按照索引进行,也就是数据集里值出现的顺序。
  • 想更细致地控制数据聚合,可以指定键函数。

10.1 绑定事件监听器

大多数情况下,事件一直在不同的位置被触发,只是没人监听,它们会自生自灭。

10.2 什么是行为

有些人之所以不喜欢 JavaScript,一个主要原因就是这个关键字 this。在其他语言中,this 的含义非常明确,不像 JavaScript 中那么多变。

很多时候,我们希望在某些元素上(比如在值标签上)忽略鼠标事件。幸好,只要给要忽略的元素添加一行 CSS 代码即可:

pointer-events: none;

这行神奇的代码会通知浏览器:“嘿,这个元素不要触发任何指针事件(比如 click、mouseover 或 mouseout),就当这个元素不在那儿。”这样事件就可以穿透这个元素,在它下面的元素上触发。

10.4 提示条

只要把鼠标放在任何条形上,过几秒钟就能看到提示条出来。这段相对长的延迟时间是浏览器决定的,不受 D3 和 JavaScript 的控制,所以我们无可奈何。

11.1 折线图

defined() 最简单的用法就是判断给定值是否存在:

.defined(function(d){ return d; })

如上所示,如果 d 存在,函数就返回 true。

然而在我们的例子中,-99.99 确实是个存在的值,只不过我们认为它不是有效的测量值。为了排除它们,可以借助比较操作符,返回逻辑判断的结果(true 或 false):

// 定义线条生成器
var line = d3.line()
.defined(function(d){ return d.average >= 0; })
.x(function(d){ return xScale(d.date); })
.y(function(d){ return yScale(d.average); });

11.2 区域图

二氧化碳

我不知道你看了现在的图会有什么感受,但它让我觉得大气中二氧化碳实在太多了。(突然之间,我希望你看的是本书的电子版,因为电子版是用可再生能源来驱动的。如果你读的是砍树造出来的纸质版,也不要太难过,利用从本书学到的知识多做好事就行啦。)

12.2 选择更具体的元素

为了避免这类混淆,Mike Bostock 推荐了一种代码缩进约定:选集没变的时候缩进四个空格,返回新选集时只要两个空格。Mike 在 d3-selection 的文档中提供了一个示例:

提供了一个示例

select()append() 方法都会返回新选集。attr() 不会,它只是把刚刚修改的选集往后传递。注意,这个示例要想生效,这些元素的父子关系必须满足:body 是 svg 的父元素,svg 是 g 的父元素,g 是 rect 的父元素。

13.2 堆叠布局

d3.stack() 能够把二维数据转换成“堆叠”数据,它会计算每个数据点的基线值,以便把数据层相互堆叠起来。这个布局方法可用于创建堆叠条形图、堆叠面积图,甚至河流图(stream graph,就是没有严格零起点基线值的堆叠面积图)。

15.1 准备数据

  • HEV(hybrid electric vehicle,即混合电动汽车,主要用汽油做燃料,也配备有电池和电动机。)
  • PHEV(plug-in hybrid electric vehicle,即插电式混合电动汽车,供能方式既可以是电力,也可以是汽油。)
  • BEV(battery electric vehicle,即电池电动车,也就是我们说的“纯”电动车。)
  • FCEV(fuel-cell electric vehicle,即燃料电池电动车,通常是氢动力燃料电池车。)

附录 A 案例研究

如果你在把玩自己可视化作品上花的时间比写代码的时间更多,那么你的方向就没错。如果你能从自己的作品中看出模式,那也说明你走对了方向。关于如何引起人们的兴趣,我认为如果你能够花一些精力做些调查,收获会很丰富(比如发现某种很有意思的模式)。在这个过程中找到恰当的平衡点,那么人们可能会留下来探索你的作品。

附录 B 4.0 版本新增特性

B.1 模块化

D3 不再是一个庞大、笨重的库了,现在它实现了模块化,也就是说它由许多小型库组成,每个小型库注重一个特定的领域。除非你的项目花样特别多,否则一般来说你只用默认的代码包即可,它包含了下列 D3 微型库和模块(对应的代码仓库都包含了各自的文档):

· d3-array
· d3-axis
· d3-brush
· d3-chord
· d3-collection
· d3-color
· d3-dispatch
· d3-drag
· d3-dsv
· d3-ease
· d3-force
· d3-format
· d3-geo
· d3-hierarchy
· d3-interpolate
· d3-path
· d3-polygon
· d3-quadtree
· d3-queue
· d3-random
· d3-request
· d3-scale
· d3-selection
· d3-shape
· d3-time
· d3-time-format
· d3-timer
· d3-transition
· d3-voronoi
· d3-zoom

附录 C 进阶学习

StackOverflow:当你被某个问题难住时,可以在 StackOverflow 上发布带有 d3.js 标签的问题。不过在你提问之前,请先阅读一份关于如何提一个好问题的重要清单。

(1)用清晰、简洁、明确的语言描述你遇到的情况。
(2)描述你期望达到的目的或者想看到的效果。
(3)先在网上、D3 Google Group 还有 StackOverflow 中搜索,看看有没有人问过相似的问题。
(4)搜索 D3.js 的作品库(D3.js Gallery),看看有没有示例实现了你要的类似效果,并研究这个示例找点头绪。
(5)用 Bl.ock 构造器的搜索工具寻找用到正在困扰你的方法的示例,研究这些示例找点头绪。
(6)如果问题还是没解决,把你的代码发布到能被公共访问到的地方。(查看附录 D 了解怎么快速做到这一点。)这是至关重要的一步,否则别人看不到你的代码,也就爱莫能助。提供别人需要的一切,他们才能更方便地帮你。
(7)最后,把你的问题发到 StackOverflow 上,带上 d3.js 标签,还有指向你的代码的链接。(查看附录 D 了解怎么做。)

D3 相关网站

❑ D3.js Gallery
Christophe Viau 创建的带搜索功能的作品库,可以作为官网的替代。强烈推荐!
❑ Mike Bostock's Blocks
这个网站上有更多的示例(全是 Mike Bostock 写的),每个示例往往会突出 D3 的某个特性。
❑ Bl.ock 构造器搜索工具(Bl.ock Builder)
Ian Johnson 开发的这个工具可以很方便地搜索任何人创建的所有 D3 代码块。(查看附录 D 了解 Bl.ock 构造器的详情。)试着搜一搜你想看示例的 API 方法(如 d3.geoPath())。
❑ tributary.io
Ian Johnson 开发的实时编码环境,用来测试 D3 代码。(Ian 怎么一直在做这些有用的工具,能不能停一停?)

最后,Nathan Yau 在他的文章“Getting started with visualization after getting started with visualization”里给出了大量明智的建议和宝贵的提示。

找一份工作动手实践

我的个人网站上有一份现成的列表:Where to Post and Find Data Visualization Jobs。不开玩笑:D3 技能的需求量很大。如果你吃透了本书,你可以找到工作。我还写了“Data Visualization and Art Process Blogs”。我是程序的狂热爱好者,我用这篇博客文章收集了那些才华横溢的人写的好文章链接,这些文章介绍了他们是怎样创作出令人惊叹而优美的作品的。向他们学习,并不断进步。

3 条评论

  1. reviewer
    高压锅
    2021年12月4日

    评论在审核吗?

    回复
  2. reviewer
    高压锅
    2021年12月4日

    我也要看看!

    回复
  3. reviewer
    高压锅
    2021年12月4日

    这本书挺好!

    回复

发表评论

您的电子邮箱地址不会被公开。