当前内容所在位置:
- 第五章 饼图布局与堆叠布局 ✔️
- 5.1 饼图和环形图的创建 ✔️
- 5.1.1 准备阶段(上篇) ✔️
- 5.1.2 饼图布局生成器(中篇)
- 5.1.3 圆弧的绘制
- 5.1.4 数据标签的添加
文章目录
- 第五章 饼图布局与堆叠布局 Pie and stack layouts
- 5.1 饼图和环形图的创建 Creating pie and donut charts
- 5.1.1 准备阶段 Preparatory steps
《D3.js in Action》全新第三版封面
译者按
上一章重点介绍了 D3 折线图、曲线图以及面积图的绘制方法,但对饼图环形图的介绍却相对较少。一方面是因为后者的很多基础实现都可以参照前者的思路;另一方面则是饼图或环形图还可以通过自定义布局函数来让图形渲染更上一层楼。为了避免学习过程中在以往章节内容间“反复横跳”,本篇及后续章节的内容都不推荐完全零基础的小伙伴直接上手。万丈高楼平地起,没有坚实的 D3 基础,即便是有这本全新的《D3.js in Action》护驾,也严重违背我一向倡导的长期主义路线。尤其像 D3 这样设计精妙组织严密的可视化工具库,更不可囫囵吞枣自欺欺人。前行路上,始终需要一点笨拙与耐心。望共勉之!
第五章 饼图布局与堆叠布局 Pie and stack layouts
本章概要
- 深入理解 D3 布局函数
- 利用饼图布局绘制环形图表
- 通过图形堆叠来生成堆积柱状图和流图(streamgraph)
- 简单图例的创建方法
上一章我们学习了怎样利用 D3 的图形生成器函数计算复杂图形(如曲线图、面积图、弧形图等)的 d
属性。本章将进一步通过布局(layouts)的学习将这些基本图元的绘制提升到新的高度。在 D3 中,布局是这样一类函数:它以原数据集为参数,并返回一个带注解的(annotated)新的数据集。这个新的数据集含有绘制具体可视化图形所需的各类属性(attributes)。例如,饼图布局(pie layout)将算得构成饼图各个分段的角度值,并用这些值来给数据集添加注解;同理,堆叠布局(stack layout)则会算出堆积柱状图或者流图(streamgraph)中各个堆叠图形的位置信息。
布局不参与可视化图形的具体绘制,也不像图形生成器那样会在绘制代码中被调用;相反,它们构成了一个聚焦数据格式化的预处理步骤,让便让它们以既定方案进行展示,整个工作流程如图 5.1 所示。
【图 5.1 布局函数是数据预处理的重要环节,用于计算绘制特定图表所需的相关信息】
本章我们将利用 D3 的饼图布局和堆叠布局,并结合第四章中介绍的圆弧生成器与面积生成器,详细演示如图 5.2 所示的可视化项目。您也可以在 http://mng.bz/6nVo 查看最终效果的线上版本。该项目是针对 1973 年至 2019 年音乐产业不同格式类型的销量占比情况所做的可视化展示;其创作灵感源自 2020 年由 MakeoverMonday 主办的挑战活动(详见 www.makeovermonday.co.uk)。
虽然本章只着重介绍了饼图布局和堆叠布局,但它们遵循的基本原则对于其他布局而言,例如和弦图布局甚至更为新奇的布局也将同样适用。学完本章内容后,后续遇到相关内容的处理也将更加得心应手。
在演示开始之前,先找到第五章的源码文件。如果还没有下载到本地,可以从随书附带的 GitHub 仓库进行下载(详见 http://mng.bz/Xqjv);然后进入 chapter_05
文件夹。本章代码已按各章节内容进行了分组。想要开始本章代码的练习,请在您熟悉的代码编辑器中打开 5.1-Pie_layout/start
文件夹,并启动本地 Web 服务器。有关本地开发环境的具体搭建,请参考 附录 A(待整理)。您也可以在本章源码根目录下的 README
文件中找到有关项目文件夹结构的更多详情介绍。
本章将要构建的三个可视化图表分别是环形图(donut charts)、堆积柱状图(stacked bar chart)以及流图(streamgraph)。它们共享相同的数据、维度和比例尺。为了避免代码重复,整个项目将被拆分为多个 JavaScript
文件,其中包括一个用于可视化图表间共享的常量文件,以及一个专门处理比例尺的通用文件。这样代码的可读性会更强,修改起来也会更方便。在生产环境下,我们可能还会使用 JavaScript
的导入导出功能来访问不同的函数,并且会用到 Node
环境及相应的打包工具。相关内容还会在第 8 章介绍前端框架时再次讲到;而本章我们仍将沿用类似历史遗留代码的项目结构,以便专注于 D3 知识点的讲解。注意,D3 工具库以及所需的 JavaScript
文件均已加载到 index.html
中。
【图 5.2 本章将要构建的 1973 年至 2019 年音乐产业销售情况可视化效果图】
警告
在使用代码编辑器练习本章源码时,请务必只打开一个
start
文件夹,或者只打开一个end
文件夹。如若将本章所有示例文件视为一个项目并使用Live Server
扩展插件来运行项目,则数据文件的关联路径将无法正常工作。
5.1 饼图和环形图的创建 Creating pie and donut charts
本节将利用 D3 中的饼图布局来实现前面给出的图 5.2 所示的环形图。您也可以在 http://mng.bz/6nVo 查看线上版的最终效果。具体说来,我们将分别对 1975 年、1995 年和 2013 年的音乐销售数据按媒介格式进行分类;每个环形图的中心点将对应于下方的流图以及堆积柱状图对应年份在 x
轴上的坐标位置。
5.1.1 准备阶段 Preparatory steps
为了确保每个图表在 x
轴上的年份坐标相互对齐,不妨先花些心思明确一下构建思路。其中一个简单的解决方案是利用第四章学过的外边距约定。本章我们将利用三个 SVG 容器来分别绘制环形图、流图以及堆积柱状图。这些容器将设置相同的尺寸大小并共享同一套边距设置。这样一来其内部绘图区(即不含坐标轴与轴标签的可视化区域)也将具有相同的尺寸,从而实现水平对齐,如图 5.3 所示。各图表共享的外边距对象和维度尺寸等常量已经包含在了 js/shared-constants.js
文件中。
此外,CSV 数据文件的加载逻辑也放到了 js/load-data.js
文件中。关于 D3 数据加载的具体操作及相关介绍,详见第 3 章和第 4 章内容。数据加载完成后,会调用本节将要实现的两个函数:defineScales()
和 drawDonutCharts()
。
先来给环形图添加一个 SVG 容器,然后在里面添加一个 SVG 分组元素表示其内部绘图区域。为此,需要进入 js/donut-charts.js
,并在函数 drawDonutCharts()
内创建 SVG 容器以及对应的分组元素 g
。如以下代码片段所示,我们在一个 ID 值为 "donut"
的 div
元素中添加了一个 SVG 容器。注意:边距约定是在分组元素的平移过程中,通过左外边距和上边距的设置来集中体现的:
const svg = d3.select("#donut").append("svg") // 添加 SVG 容器并设置其 viewBox 属性.attr("viewBox", `0 0 ${width} ${height}`); const donutContainers = svg.append("g") // 添加 SVG 分组元素,并基于图表的左外边距及上边距完成平移操作.attr("transform", `translate(${margin.left},${margin.top})`);
您可能会好奇:既然环形图的坐标轴和标签无需占用额外的绘图空间,为什么还要设置平移来履行边距约定呢?这是因为每个环形图都将与 x
轴对应的年份对齐。为了让这些年份的水平坐标能与后续的流图及堆积柱状图保持一致,需要一并考虑绘制这些图表时将会执行的边距约定设置。
【图 5.3 分别通过三个 SVG 容器来创建本章要实现的图表:一个用于环形图,一个用于流图,最后一个用于堆积柱状图。该策略将为内部绘图区预留出统一的子区域,以便后续各图表间的对齐设置】
在第 4 章中,我们学习了极坐标的相关知识,也知道了怎样将弧形图添加到 SVG 分组、并将其平移至图表中心的方法,从而简化饼图或环形图的构建。这样一来,弧线就会自动围绕极坐标原点进行绘制。
本例将采用同样的绘制策略,唯一不同的是本例需要绘制三个环形图,并且其中心的水平坐标要与它们代表的年份一一对应,如图 5.4 所示。
【图 5.4 构成环形图的每组圆弧都位于某个 SVG 分组内。这些分组元素将根据其对应的年份平移到水平方向的指定位置。其平移量则是通过 D3 比例尺计算到的】
而计算每个环形图的中点水平坐标值,需要运用比例尺的相关知识。根据前面的介绍,需要使用 D3 比例尺将数据(即年份)转换为屏幕上的视觉属性(即水平坐标)。虽然线性比例尺和时间比例尺都可以很好地满足本例的需求,但这里我们选用分段比例尺,因为后续还会基于相同的比例尺来完成堆积柱状图的绘制。关于分段比例尺的工作原理及具体用法,详见第 3 章相关内容。
于是来到 js/scale.js
文件。首先通过函数 d3.scaleBand()
初始化一个分段比例尺,然后将其赋给一个常量 xScale
。注意观察示例代码中函数 defineScales()
在声明比例尺时作用域和值域的写法。这样写可以在数据加载完成后再来确定定义域。待数据准备就绪后,defineScales()
方法将从 load-data.js
文件调用执行。将 xScale
常量放到函数外声明主要是为了方便其他 JavaScript
文件引用该常量。
代码清单 5.1 分段比例尺的声明(scales.js)
const xScale = d3.scaleBand(); // 声明分段比例尺const defineScales = (data) => {xScale.domain(data.map(d => d.year)) // 指定比例尺的定义域和值域.range([0, innerWidth]);
};
分段比例尺以离散型输入作为其定义域,并从给定值域中返回一个连续型输出。在代码清单 5.1 中,我们利用 JavaScript
的原生 map()
方法得到了数据集中每项数据的年份所构成的年份数组,然后将其作为水平比例尺的定义域;而比例尺值域的声明,则需要传入一个由绘图区水平可用空间的两个边界值所构成的坐标数组:其中一个为最小值(即 0
),而另一个则为最大值(即绘图区的内部宽度 innerWidth
)。
再来看看函数 drawDonutCharts()
的实现。如代码清单 5.2 所示,首先声明一个年份数组 years
,包含三个目标年份:1975、1995 和 2013。然后利用 forEach()
循环,给每一年添加一个 SVG 分组元素,在将其赋给一个常量 donutContainer
。最后通过指定其 transform
属性完成平移操作。分组元素的水平位移量是通过比例尺函数 xScale
算得的,参数为当前年份;而垂直位移量则对应内部绘图区高度的一半。
代码清单 5.2 给每个环形图添加一个 SVG 分组元素(donut-charts.js)
const years = [1975, 1995, 2013];
years.forEach(year => {const donutContainer = donutContainers.append("g").attr("transform", `translate(${xScale(year)}, ${innerHeight/2})`);});
(上篇完)
另附:专栏文章连载期间 完全免费,后续 不排除 调整为收费专栏。对 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 本章小结
- 第四章 直线、曲线与弧线的绘制
- 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.4.1 D3 中的极坐标系
- 4.4.2 圆弧生成器的使用
- 4.4.3 圆弧形心的计算
- 4.4.4 人物专访:Francis Gagnon、Patricia Angkiriwang 和 Olivia Gélinas
- 4.5 本章小结