当前内容所在位置(可进入专栏查看其他译好的章节内容)
- 第一部分 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 本章小结
- 第四章 直线、曲线与弧线的绘制 ✔️
- 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.2 折线图的绘制 Drawing a line chart
- 4.2.1 直线生成工具的使用 Using the line generator
- 4.2.2 对数据点作曲线插值处理 Interpolating data points into a curve
《D3.js in Action》全新第三版封面
译者按
上一节我们学习了 D3 时间坐标轴与线性轴的绘制方法,这一节就可以正式开始折线图的绘制了,根据最终的示例效果,该折线图包含折线部分和面积图部分,本篇只涉及纯折线部分;面积图的实现则放到下一节介绍。
4.2 折线图的绘制 Drawing a line chart
下面实现数据可视化最常见的一类图表:折线图。折线图由连接各数据点的线段、或对这些数据点作插值计算而得到的曲线组成。它们通常用于展示某个现象随时间的演变过程。在 D3 中,这些线条和曲线由 SVG 路径元素(path elements)构建,其形状由 SVG 路径元素的 d
属性(attribute)决定。通过第 1 章的学习,我们知道了 d
属性是由一系列命令组成的;这些命令决定了绘制的形状。此外还提到过,d
属性很容易变得很复杂。所幸 d3-shape
模块提供了生成直线和曲线的工具函数,专门负责 d
属性的计算,从而简化了折线图的创建。
本节将绘制一条反映 2021 年纽约市平均气温变化趋势的直线/曲线,具体效果参考线上项目(http://mng.bz/5orB)或前面章节中的图 4.1。在此之前,先来绘制每个数据点。尽管折线图未必非要画出数据点,但它们有助于理解 D3 折线生成工具的工作原理。
由于专栏文章是分章节发表,这里直接给出图 4.1 的效果:
【图 4.1 本章实现项目:2021 年纽约市温度变化及全年降水天数占比情况可视化】
在函数 drawLineChart()
中,通过 D3 的数据绑定为数据集 weekly_temperature.csv
中的每一行创建一个 circle
元素,然后添加到选择集 innerChart
中,半径设为 4px
。然后用 x
和 y
方向的比例尺分别计算每个圆心的横纵坐标。
回忆一下第 3 章介绍的数据绑定相关知识点,这里需要用到访问器函数(accessor function)来访问每个圆上的绑定数据。在以下代码片段中,d
即为每个 circle
元素绑定的数据项。由于 data
是一个 JavaScript
对象,这里可以通过句点表示法拿到对应的日期和平均气温。相关知识点详见第 3 章 3.3.1 小节。
注意,这里特地声明了一个名为 aubergine
(译注:读作 /ˈəʊbəʒiːn/
,紫红色)的颜色常量,用于指定圆的 fill
属性值。因为示例项目还会多次用到该颜色,因此单独声明为一个常量。您也可以根据自己的喜好启用任意颜色:
const aubergine = "#75485E";
innerChart
.selectAll("circle") // 利用数据绑定模式为数据集中的每一行添加一个圆
.data(data)
.join("circle")
.attr("r", 4)
.attr("cx", d => xScale(d.date)) // 根据绑定数据使用比例尺来定位数据点
.attr("cy", d => yScale(d.avg_temp_F))
.attr("fill", aubergine);
保存项目并在浏览器中查看图形,就能看到这些 circle
元素呈穹顶状分布于 29°F
到 80°F
之间,如图 4.13 所示:
【图 4.13 平均气温随时间变化的数据点绘制效果图】
此处也可以绘制散点图(scatterplots)
走到这一步必须要强调的一个惊喜是,您已经在不知不觉间学会了 D3 散点图(scatterplots)的绘制!散点图 是一种简单的图表,用于展示
x
轴与y
轴的数据点集合,可以直观地揭示两个或多个变量间的相关关系。只要知道了坐标轴的绘制方法,再结合绑定数据定位屏幕上的每个数据点,您就完全可以绘制出一个散点图效果——这正是 D3 的魅力所在——不必特地去学怎样绘制特定的图表类型,而是通过创建并组合一些基本要素来构建可视化效果。对于散点图而言,这些基本要素甚至可以简单到仅仅包含两个坐标轴和一组
circle
元素。第 7 章我们还将实现一个散点图,让圆的面积根据变量的值而同步变化。【图 4.13.1 D3 散点图示例效果】
4.2.1 直线生成工具的使用 Using the line generator
至此,每个数据点的位置就画好了,接下来介绍 D3 的直线生成工具(line generator)。直线生成工具 d3.line()
是一个函数,它以各数据点的横纵坐标为输入,并将穿过这些数据点的 SVG 路径元素或折线(polyline)的 d
属性值作为输出。通常需要在直线生成器上链式调用 x()
和 y()
两个访问器函数,并分别传入水平和垂直位置的坐标值,如图 4.14 所示:
【图 4.14 直线生成工具 d3.line() 函数与访问器函数 x() 和 y() 的组合式写法。后者需分别将各数据点的横纵坐标作为参数传入。】
下面来给折线图创建一个直线生成工具函数。首先调用 d3.line()
方法,然后分别链式调用访问器函数 x()
和 y()
。x()
需要传入各数据点的水平坐标,这里通过参数 d
来访问每个绑定数据项,类似遍历数组时用到的循环变量。数据点的水平坐标可以通过对应的日期和水平比例尺函数 xScale()
计算得出;同理,垂直坐标则可以通过当天的平均气温结合纵向比例尺 yScale()
得到。最后将生成的工具函数赋给常量 lineGenerator
备用:
const lineGenerator = d3.line()
.x(d => xScale(d.date)) // 每个数据点的水平位置
.y(d => yScale(d.avg_temp_F)); // 每个数据点的垂直位置
接着,调用该工具函数,并将数据集 data
作为参数传入,其结果作为 path
元素 d
属性的属性值。
SVG 路径元素默认按黑色渲染图形,如果只想看到一条折线,则需要令 fill
属性为 none
或 transparent
,并将 stroke
属性(attribute)指定为想要的描边色;本例中即为 aubergine
紫红色。绘制效果如图 4.15 所示:
innerChart
.append("path")
.attr("d", lineGenerator(data)) // 利用行生成工具将数据集作为参数传入
.attr("fill", "none")
.attr("stroke", aubergine);
【图 4.15 利用 D3 直线生成工具创建的 SVG 路径元素穿过每个数据点,形成了一条折线】
4.2.2 对数据点作曲线插值处理 Interpolating data points into a curve
在这个示例折线图中,离散数据点分布在整个数据范围内,用普通的折线段来连接数据点就能实现既定目标;但偶尔也会在数据点之间对数据作插值处理 1。为此,D3 提供了多种插值函数(interpolation functions)来生成曲线。
曲线生成工具是以 d3.line()
的访问器函数的形式出现的。要将刚才的直线工具函数变为曲线工具函数,只需要再链式调用一个访问器函数 curve()
即可,参数为 D3 的某个内置插值函数。如以下代码片段所示,传入参数为 d3.curveCatmullRom
2,它可以生成一个 立方样条曲线(cubic spline) (根据各数据点并结合三阶多项式函数计算得到的平滑而灵活的图形)。其渲染效果如图 4.16 所示。
const curveGenerator = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.avg_temp_F)
.curve(d3.curveCatmullRom);
【图 4.16 利用 Catmull-Rom 插值算法绘制的折线图效果】
关于最佳插值算法 What’s the best interpolation?
插值处理会修改数据呈现方式,不同的插值函数会产生不同的可视化效果。数据的可视化方式多种多样,从编程角度理解,这些方式方法都是合理的;但关键是让可视化效果传递客观实际(actual phenomena)。
由于数据可视化涉及统计原理的视觉呈现,因此也面临着误用统计数据带来的风险(dangers of misusing statistics)。其中线性插值是数据误用的重灾区,因为它能将看似粗糙的直线段处理成平滑自然的曲线段。
如图 4.17 所示,同一组折线数据在不同的曲线插值处理下将呈现不同的视觉效果。选择适当的插值函数很大程度上取决于目标数据集。在本节演示的折线图中,d3.curveBasis
会拉直曲线段的同时减少其变化,这显然不适合我们的示例数据。如果不在图表上绘制出数据点作对比,就无从知晓曲线段与这些数据点的误差。因此,甄选和测试曲线插值函数就显得尤为重要了。
【图 4.17 不同的曲线插值处理对数据都有不同程度地修改】
与此同时,函数 d3.curveMonotoneX
和 d3.curveCatmullRom
创建的插值曲线则紧挨数据点,与原始折线图相似;此外,d3.curveStep
函数还可以在适当的情况下对数据作另类处理。图 4.17 只给出了部分插值情况对比,还有一些插值工具还可以设置一些影响最终曲线形状的参数,具体配置情况,详见 d3-shape
模块相关文档。
这样 D3 折线图的绘制就完成了!再复盘梳理一下:首先需要初始化一个直线生成工具函数,并设置其访问函数 x()
和 y()
,如图 4.18 所示。这两个函数分别用于计算每个数据点的水平与垂直坐标;接着可以链式调用 curve()
访问器函数并指定插值算法,将直线段改为曲线段;最后,在绘图区添加 SVG 路径元素 path
,并通过调用直线生成工具函数、传入数据集 data
来设置路径元素的 d
属性。第 7 章还将利用工具提示(tooltip
)组件提升折线图的可交互性。如果想立即学习,也可以直接跳到该章节(译注:待翻译)。
【图 4.18 D3 折线图的实现步骤】
插值 是一个数学和统计学领域的专用术语,指的是在已知数据点之间估算出新的数据点,常用于图表或曲线的平滑处理。 ↩︎
d3.curveCatmullRom
即 Catmull-Rom 样条,也叫 卡特穆尔-罗姆插值,它在计算机图形学和数据可视化领域应用广泛,是一种能够有效生成平滑曲线的数学方法描述。所谓样条(Spline),则是一种由多段多项式函数组成的分段函数,用于平滑地连接一组给定的点(即控制点)。样条通过一组控制点来定义,它们通常是数据的离散采样值。 ↩︎