【D3.js in Action 3 精译_039】4.3 D3 面积图的绘制方法及其边界标签的添加
当前内容所在位置:
- 第四章 直线、曲线与弧线的绘制 ✔️
- 4.1 坐标轴的创建(上篇)
- 4.1.1 D3 中的边距约定(中篇)
- 4.1.2 坐标轴的生成(中篇)
- 4.1.2.1 比例尺的声明(中篇)
- 4.1.2.2 坐标轴的添加(下篇)
- 4.1.2.3 轴标签的添加(下篇)
- 4.2 D3 折线图的绘制
- 4.2.1 直线生成工具的使用
- 4.2.2 对数据点作曲线插值处理
- 4.3 D3 面积图的绘制 ✔️
- 4.3.1 面积图生成工具的用法 ✔️
- 4.3.2 用标签提高图表的可读性 ✔️
- 4.4 D3 弧形图的绘制
文章目录
- 4.3 面积图的绘制 Drawing an area
- 4.3.1 面积图生成工具的用法 Using the area generator
- 4.3.2 用标签提高图表的可读性 Enhancing readability with labels
《D3.js in Action》全新第三版封面
译者按
由于 D3 的折线图和面积图在实现方式上相似度极高,这一篇就趁热打铁,将示例折线图剩下的面积图部分一并学完。看似复杂的面积图在作者精心绘制的示意图的帮助下变得非常简单(至少比我的其他专栏要容易很多),曾在 MSOffice 中盛行的“字不如表、表不如图”的说法用到 D3 数据可视化上依然适用。如果早几年出现这样的扫盲级 D3 参考书,如今国内数据可视化领域 ECharts 一手遮天的局面没准还真能被打破。一起学起来吧!
4.3 面积图的绘制 Drawing an area
本节将实现示例折线图后方的面积图部分,用于展示每个日期最低气温与最高气温的波动范围。D3 绘制面积图的方法与绘制折线图类似,也是通过 SVG 的路径元素来创建。D3 还专门提供了面积图生成工具函数 d3.area()
来处理路径 d
属性的复杂运算。
动手绘制前还需要注意一点:示例中的面积图位于折线图的 后方(behind the line chart)。鉴于屏幕元素的渲染顺序与添加到 SVG 父容器的元素顺序保持一致,因此该面积区域的实现代码应该添加到创建折线图的代码 前面。
4.3.1 面积图生成工具的用法 Using the area generator
首先声明一个面积图生成工具函数(area generator function),并赋给常量 areaGenerator
。根据以下示例代码,面积生成工具至少需要三个访问器函数(accessor functions)。一是 x()
,用于计算数据点的水平坐标,与直线生成工具完全相同;垂直方向上则略有不同,数据点从一组变为了两组,分别对应面积区域的上下边缘,因此需要设置 y0()
和 y1()
两个访问器函数。注意,示例面积图中的上下边缘数据点具有相同的水平坐标:
const areaGenerator = d3.area()
.x(d => xScale(d.date))
.y0(d => yScale(d.min_temp_F))
.y1(d => yScale(d.max_temp_F));
图 4.19 生动直观地展示了面积区域的下边界和上边界情况,以及面积生成工具对相关数据的计算处理过程:
【图 4.19 面积生成工具 d3.area() 与三个及更多访问器函数的组合用法示意图。绘制最高最低气温构成的温差区域,需要用到 x()、y0() 和 y1(),分别用于计算各数据点的水平坐标、面积区下边界(即当日最低气温)的垂直坐标、以及面积区上边界(即当日最高气温)的垂直坐标。】
与折线图类似,我们同样可以在面积生成工具函数上链式调用访问器函数 curve()
来对面积图的上下边缘作曲线插值处理,这里还是沿用上一节介绍过的 D3 内置插值函数 d3.curveCatmullRom
,如以下代码所示:
const areaGenerator = d3.area()
.x(d => xScale(d.date))
.y0(d => yScale(d.min_temp_F))
.y1(d => yScale(d.max_temp_F))
.curve(d3.curveCatmullRom);
面积生成器准备就绪后,就可以在内部图表选择集上添加一个 SVG 路径元素。该元素的 d
属性,可以通过刚才备好的面积生成器计算得到,传入参数为数据集 data
。剩下的都是一些与审美相关的属性设置:填充色通过 fill
属性实现,属性值就是前面声明过的紫红色常量 aubergine
;为了让面积区域与折线部分清晰可辨,可以给面积图加一点透明度,指定其 fill-opacity
属性为 20%,最终渲染效果如图 4.20 所示。注意,颜色常量 aubergine
必须先声明再使用:
innerChart
.append("path")
.attr("d", areaGenerator(data))
.attr("fill", aubergine) // 即 "#75485E"
.attr("fill-opacity", 0.2);
【图 4.20 由折线图表示的日均气温与面积图表示的当日气温波动范围的绘制效果图】
可以看出,D3 面积图的绘制过程与折线图的绘制十分类似。二者的主要区别在于,折线图只有一组数据点,用直线段进行绘制;而面积图需要处理上下两个边缘,每个边缘对应一组数据点。这就是直线生成器函数只需要两个访问器函数(即 x()
与 y()
)、而面积生成器需要至少三个访问器函数(本例即为 x()
、y0()
与 y1()
)的根本原因,如图 4.21 所示:
【图 4.21 D3 面积图的绘制步骤】
4.3.2 用标签提高图表的可读性 Enhancing readability with labels
至此,我们实现了 2021 年纽约市日均气温的折线图的绘制,并同步展示了当日最低气温与最高气温的变化区域,效果看上去已经很不错了;但我们还需要确保看到这张图表的人们能够轻松准确地理解折线与面积的含义。添加图表标签(label)就是个不错的想法。
前面学过,图表标签不过是放置在可视化作品里的 SVG 文本元素。在本例中,不妨创建三个标签,分别用于表示折线图末尾的平均气温、位置靠下的当日最低气温、以及靠上放置的当日最高气温。
先从折线图标签开始。先将 SVG 文本元素添加到内部图表选择集中,然后调用 text()
方法设置标签文本为 "Average temperature"
(即“平均气温”);接着通过 x
和 y
属性设置标签在绘图区内的坐标。
我们希望标签位于折线图末尾,也就是最后一个数据点的后面;其水平坐标可以通过先前声明的常量 lastDate
由水平比例尺函数 xScale()
算得;另外可以再给标签一个 10px
的间距。
而垂直坐标的计算还需要一个当天的平均气温值,可以先通过 data[data.length - 1]
获取到最后一个数据点;然后利用句点标识符拿到对应的平均气温;接着再调用垂直比例尺函数 yScale()
算得该数据点的垂直坐标。最后复用颜色常量 aubergine
,通过 fill
属性来指定标签文本的颜色,如以下代码所示:
innerChart
.append("text")
.text("Average temperature")
.attr("x", xScale(lastDate) + 10)
.attr("y", yScale(data[data.length - 1].avg_temp_F))
.attr("fill", aubergine);
如果保存项目并在浏览器中查看效果,会发现文本标签的底边与折线图最后一个数据点的中心垂直对齐了。默认情况下,SVG 文本元素的基准线(baseline)位于文本的底边位置,如图 4.22 (左图)所示。这时可以使用 dominant-baseline
属性来手动修改。如以下代码所示,将该属性值设为 middle
,基准线就改到了文本的中心位置:
innerChart
.append("text")
.text("Average temperature")
.attr("x", xScale(lastDate) + 10)
.attr("y", yScale(data[data.length - 1].avg_temp_F))
.attr("dominant-baseline", "middle")
.attr("fill", aubergine);
【图 4.22 SVG 文本元素的 y 属性可设置其基准线在垂直方向的位置,默认位置在文本底边。可通过 dominant-baseline 进行修改,值为 middle 时基准线与文本中心对齐;值为 hanging 时基准线则位于文本顶部。】
接着再给面积图的下边界添加一个文本标签,代表最低温度的变化趋势。具体做法与刚才一样,先添加一个 SVG 文本元素,并指定文本内容为 "Minimum temperature"
,即最低气温。
至于标签的坐标方位,可以参考最后一个下凹点(downward protuberance),即下边缘倒数第三个数据点。将该位置的数据值传入垂直坐标尺函数,算得其垂直坐标,然后再下移 20px
、右移 13px
。这些位移量都是通过在页面上不断尝试不同的位置得到的;借助浏览器的检查工具(inspector tool)是处理这类微调操作的绝佳工具。注意,此时文本标签的 dominant-baseline
属性值改为了 hanging
,如图 4.22 所示,表示 y
属性值是相对于文本的顶部而言的。
最后,根据以下代码片段,就能再给图表标签新增一条指示线,将面积图的下凹拐点与标签进行关联,进一步明确标签所代表的含义,如图 4.23 所示。同理,可以使用比例尺分别计算指示线的 x1
、y1
、x2
和 y2
属性值,最终确定出线段的起点和终点位置:
innerChart
.append("text")
.text("Minimum temperature")
.attr("x", xScale(data[data.length - 3].date) + 13)
.attr("y", yScale(data[data.length - 3].min_temp_F) + 20)
.attr("alignment-baseline", "hanging")
.attr("fill", aubergine);
innerChart
.append("line")
.attr("x1", xScale(data[data.length - 3].date))
.attr("y1", yScale(data[data.length - 3].min_temp_F) + 3)
.attr("x2", xScale(data[data.length - 3].date) + 10)
.attr("y2", yScale(data[data.length - 3].min_temp_F) + 20)
.attr("stroke", aubergine)
.attr("stroke-width", 2);
同理也可以给面积区的上边缘添加类似的文本标签,用以表示当日最高气温的变化情况。上边缘标签的定位可以参考其倒数第四个数据点的上凸拐点。然后用相同的手法在标签和上凸拐点间绘制一条指示线,如以下代码片段所示:
innerChart
.append("text")
.text("Maximum temperature")
.attr("x", xScale(data[data.length - 4].date) + 13)
.attr("y", yScale(data[data.length - 4].max_temp_F) - 20)
.attr("fill", aubergine);
innerChart
.append("line")
.attr("x1", xScale(data[data.length - 4].date))
.attr("y1", yScale(data[data.length - 4].max_temp_F) - 3)
.attr("x2", xScale(data[data.length - 4].date) + 10)
.attr("y2", yScale(data[data.length - 4].max_temp_F) - 20)
.attr("stroke", aubergine)
.attr("stroke-width", 2);
这样就完成了折线图部分的绘制:
【图 4.23 绘制完毕的 2021 年纽约市全年日均气温及其变化趋势效果图】
另附:专栏文章连载期间 完全免费,后续 不排除 调整为收费专栏。对 D3.js 感兴趣、或者想要从零开始彻底掌握 D3 的朋友们强烈建议及时关注本专栏,一起学习交流,共同进步!
目前译好的其他章节内容如下(可进入专栏查看详情):
- 第一部分 D3.js 基础知识
- 第一章 D3.js 简介(已完结)
- 1.1 何为 D3.js?
- 1.2 D3 生态系统——入门须知
- 1.3 数据可视化最佳实践(上)
- 1.3 数据可视化最佳实践(下)
- 1.4 本章小结
- 第二章 DOM 的操作方法(已完结)
- 2.1 第一个 D3 可视化图表
- 2.2 环境准备
- 2.3 用 D3 选中页面元素
- 2.4 向选择集添加元素
- 2.5 用 D3 设置与修改元素属性
- 2.6 用 D3 设置与修改元素样式
- 2.7 本章小结
- 第三章 数据的处理(已完结)
- 3.1 理解数据
- 3.2 准备数据
- 3.3 将数据绑定到 DOM 元素
- 3.3.1 利用数据给 DOM 属性动态赋值
- 3.4 让数据适应屏幕
- 3.4.1 比例尺简介(上篇)
- 3.4.2 线性比例尺(中篇)
- 3.4.2.1 基于 Mocha 测试 D3 线性比例尺(DIY 实战)
- 3.4.3 分段比例尺(下篇)
- 3.4.3.1 使用 Observable 在线绘制 D3 条形图(DIY 实战)
- 3.5 加注图表标签(上篇)
- 3.5.1 人物专访:Krisztina Szűcs(下篇)
- 3.6 本章小结