当前内容所在位置:
- 第五章 饼图布局与堆叠布局 ✔️
- 5.1 饼图和环形图的创建
- 5.1.1 准备阶段(一)
- 5.1.2 饼图布局生成器(二)
- 5.1.3 圆弧的绘制(三)
- 5.1.4 数据标签的添加(四)
- 5.1.5 DIY-在 Observable 平台实现多个 D3 环形图的绘制
- 5.2 图形的堆叠 ✔️
- 5.2.1 堆叠布局生成器(一) ✔️
- 5.2.2 堆积柱状图的绘制(二)
文章目录
- 5.2 图形的堆叠 Stacking shapes
- 5.2.1 堆叠布局生成器 The stack layout generator
《D3.js in Action》全新第三版封面
译者按
就让这篇全新介绍 D3 堆积图的文章来结束不平凡的 2024 年吧。由于前期一直忙于另一个专栏《CSS in Depth 2》的收尾工作,更新这篇内容前我还系统回顾了一遍第 1 到 4 章的所有知识点,并且把 5.1 节的 D3 环形图从头到尾动手实现了一遍。不得不说,一个多月没练,相关的知识点多少有些生疏了。遗忘并不可怕,可怕的是以为自己什么都记得;忘了就去复习,一遍不够就多练它几遍,直到练出 D3 的专属 “码感”。
5.2 图形的堆叠 Stacking shapes
至此,我们已经学习了一些简单的信息可视化案例,其中的图形也可以在其他电子表格软件中轻松实现。但您跨入这一行并非只为构建出类似 Excel
这样的图表。您可能想用漂亮的数据惊艳到您的用户,或者凭借您独具品味的审美视角赢得殊荣,又或者通过数据随时间变化规律的完美呈现唤起强烈的情感共鸣。
流图(streamgraph) 是信息可视化领域一种用于描述变化与变更情况的精妙图表类型。流图的构建乍一看貌似颇具挑战,但当您将它的各个部分逐个击破并组合到一块后,也许就不会这么想了。最后您会发现,流图不过是堆积面积图的一种变体形式罢了:每一个图层相互叠加,然后根据靠近中轴线的各组件所占的空间比例自动调整元素上下的面积大小。这种可视化图形的层次结构像生物体的生长一样,具有一种自然的(organic)流动性,呈现出柔和而渐进的变化。我们稍后会重点考察流图的外形特征;在此之前,先来看看它的构建方法。
我们之所以在本书的第一部分就来考察流图,不过是因为它其实也没那么神秘。流图是一种堆积型图表,换句话说它与堆积柱状图在本质上是共通的,如图 5.11 所示。与此同时,流图也与我们在第 4 章中构建的折线图下方的面积图类似,只不过流图中的这些面积区域是堆积在一起的。本节我们将利用 D3 的堆叠与面积生成器(stack and area generators)来实现一个堆积柱状图,然后再实现一个流图。
【图 5.11 流图的绘制方法本质上与堆积柱状图类似,它们在 D3 中都是利用堆叠布局生成器实现的】
在 D3 中,堆积柱状图或流图的绘制步骤大同小异,如图 5.12 所示。首先,我们需要初始化一个堆叠布局生成器(stack layout generator)并配置相关参数;然后,将原始数据集传入该堆叠生成器,并由此获得一个含有每个数据点上下边界信息的、带注解的新数据集。如果绘制的是一个流图,还需要初始化一个面积生成器(area generator),方法与上一章介绍的折线生成器、曲线生成器类似。最后,再将新数据集绑定到绘制流图所需的 SVG 图元,即堆积柱状图的矩形条、或者流图中的 path
元素中。如果绘制的是流图,则调用面积生成器函数来计算各路径元素的 d
属性(attribute)值。后续内容将详细考察这些绘制步骤。
【图 5.12 D3 堆积型图表的绘制步骤】
5.2.1 堆叠布局生成器 The stack layout generator
堆叠布局生成器(stack layout generator) 是一个 D3 工具函数,其参数为一个包含多个类别的数据集。本章示例用到的数据集为 1973 年至 2019 年各种音乐格式的总销售额。每一种音乐格式对应堆积图中的某个数据系列。
与之前介绍过的饼图布局生成器类似,堆叠布局函数也返回一个带注解信息的新数据集,只不过注解包含的是不同数据系列在 “堆叠” 时的位置信息。堆叠生成器分属 d3-shape
模块。
接下来我们就上手 D3 的堆叠布局,一起来实现 stacked-bars.js
中的 drawStackedBars()
函数逻辑。注意,该函数已经实现了在 ID 为 "bars"
的 div
元素中新增 SVG 容器、进而新增分组元素来作为内部绘图区的相关逻辑。具体方法与第 4 章相同,都遵循 D3 边距约定。
在下列代码片段中,我们先来声明一个堆叠布局生成器,即利用 d3.stack()
方法将其赋给常量 stackGenerator
;然后,需要让生成器知晓我们想要堆积的数值(即后续数据系列的具体内容)在数据集的哪个 key 键上。这可以通过配置访问器函数 keys()
来实现,该函数的参数为一个类别 ID 的数组,即每种音乐格式的唯一标识符。该数组可以通过常量数组 formatsInfo
对应的 ID 来构建。此外也可以通过数据集附带的列数组 data.columns
过滤掉年份列 year
得到。具体方法详见 5.1.2
节内容。
最后,调用堆叠生成器函数,并将数据集作为参数传入,就得到了带注解的新数据集,赋给常量 annotatedData
:
const stackGenerator = d3.stack() // 初始化堆叠布局生成器
.keys(formatsInfo.map(f => f.id)); // 告知布局函数从数据集的哪个 key 键获取数据来构建数据系列
const annotatedData = stackGenerator(data); // 调用布局生成器,传入 data,得到带注解的数据集,并赋给一个常量
如果将新数据集输出到控制台,会看到一个多维数组结构。如图 5.13 所示,我们先给每个数据系列创建一个数组;数据系列的 ID 值可通过 key
属性获取。该系列数组又包含另一个数组,每个数组对应数据集中的一年。最里层的数组包含相关类别的上下边界值,以及对应年份的原始数据。如果用 d
来表示该数组,则该类别的下边界和上边界可以分别用 d[0]
和 d[1]
来表示。
【图 5.13 由堆叠布局生成器返回的带注解的新数据集示意图】
可以看到,音乐格式 "vinyl"
为堆叠布局要处理的第一个 key
值。注意它的下边界始终为 0
,而上边界对应该格式在这一年的销售额;接下来的类别为 "8-track"
,其下边界即为 "vinyl"
黑胶唱片的上边界,而加上自身的销售额就得到了 "8-track"
八轨磁带的上边界,这样就形成了一个堆叠效果。
如果对 “堆叠” 的概念还不太清楚,可以参考图 5.14 给出的说明。如果再以 1986 年的数据为例,会看到这一年的音乐销量主要有三种格式类型:黑胶唱片为 28.25 亿美元,磁带为 58.30 亿美元,CS 唱片则为 21.70 亿美元。如图 5.14 所示,左侧为这些独立绘制的数据点,右侧为对应的堆叠情况。
【图 5.14 堆叠布局生成器将数据点转换为堆积的数据系列,并返回一个新数据集,其中包含每个数据系列的上下边界。图为 1986 年的示例数据。】
在使用堆叠布局时,我们创建的是一组 “数据列(data columns)” 而不是 “数据点(data points)”。每一列都对应一对上下边界值。如果堆积图形是由黑胶唱片开始的,那么下边界就为 0
,上边界即黑胶唱片当年的销售额 $2,825M
;然后再将磁带的销售额叠上去:下边界对应黑胶唱片的上边界(即 $2,825M
),而上边界为黑胶唱片与磁带销售额的总和(即 $8,655M
)。同理,该上边界值又成为 CD 唱片的下边界,而 CD 的上边界即为三种格式的销售总额(即 $10,825M
)。这些边界值都能通过新数据集的索引下标进行访问(即 d[0]
和 d[1]
)。