跳到主要内容

D3.js 数据可视化完全指南

D3.js(Data-Driven Documents)是一个用于创建数据驱动文档的 JavaScript 库。它不是普通的图表库,而是一个能够让你完全控制 SVG(可缩放矢量图形)元素的工具。通过 D3,你可以将任意数据映射为网页上的视觉元素,创造出从简单柱状图到复杂交互式可视化的一切。

为什么要学习 D3.js?

在深入具体用法之前,先理解 D3 能为你解决什么问题,这能帮助你在学习过程中保持清晰的方向。

D3 的核心优势

与 ECharts、Chart.js 等图表库不同,D3 采用的是「用数据描述图形」而不是「用配置描述图表」的设计理念。这种差异体现在:

完全控制力:D3 不会为你生成任何默认的图表样式,一切都需要你自己定义。这意味着你可以创造出完全符合设计需求的可视化效果,而不必受限于库本身的样式。

底层操作能力:D3 实际上是一个操作 DOM(文档对象模型)的工具,只是恰好被设计成特别适合处理 SVG 而已。这意味着即使图表库不能满足你的需求,你仍然可以直接操作 SVG 元素来实现。

数据驱动思维:D3 核心思想是将数据与 DOM 元素绑定,当数据发生变化时,DOM 自动更新。这种响应式的数据绑定机制让动态可视化变得自然而简洁。

D3 的适用场景

D3 特别适合以下情况:需要高度定制的可视化效果;需要处理复杂的动画交互动画;需要实现独特的数据展示方式;需要将数据可视化和业务逻辑深度结合。

如果你的需求是快速实现标准图表(如柱状图、折线图、饼图),且对样式没有特殊要求,那么 ECharts 或 Chart.js 可能会是更高效的选择。D3 的学习曲线较陡,但如果需要精细控制可视化效果,它的能力无可替代。

D3 的模块化架构

D3 并不是一个单一的库,而是一系列独立模块的集合。每个模块都有特定的职责,你可以按需引入。D3 v7 版本主要包含以下模块:

d3-selection:这是 D3 最核心的模块,用于选择 DOM 元素并对其进行操作。它仿照 jQuery 的语法,但功能更强大,支持更复杂的选择和链式调用。

d3-scale:用于将数据值映射到视觉输出。比例尺是 D3 最重要的概念之一,无论是将数值映射到坐标位置,还是将分类数据映射到颜色,都离不开它。

d3-axis:基于比例尺生成坐标轴。这个模块可以为你自动生成带有刻度线和刻度文本的坐标轴,省去手动绘制的麻烦。

d3-shape:用于生成各种图形形状,如线段、曲线、弧形。这是绘制折线图、饼图的基础模块。

d3-transition:为元素添加过渡动画。你可以定义属性变化的持续时间、缓动函数等,让可视化效果更加流畅。

d3-fetch:用于加载外部数据。支持 JSON、CSV、TSV 等常用数据格式的异步加载。

d3-array:提供数组操作功能。虽然这是工具模块,但它对数据处理至关重要,支持排序、筛选、分组等操作。

d3-force:实现力导向布局算法。用于创建网络图、关系图等需要模拟物理效果的可视化。

d3-hierarchy:提供层次数据结构的支持。用于创建树形图、旭日图、打包图等。

d3-geo:处理地理数据。可以将经纬度坐标映射为地图上的图形,用于创建地图可视化。

环境配置

使用 CDN 引入

最简单的方式是通过 CDN 直接引入 D3:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3.js 示例</title>
<!-- 引入 D3.js -->
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
// 在这里编写 D3 代码
</script>
</body>
</html>

CDN 引入的优点是零配置,直接打开 HTML 文件就能运行。缺点是无法使用 ES6 模块的导入导出语法。

使用 npm 安装

如果你使用现代前端构建工具(如 Vite、Webpack),推荐通过 npm 安装:

npm install d3

然后在 JavaScript 文件中导入所需的模块:

// 导入完整的 D3 库
import * as d3 from 'd3';

// 按需导入(可以减少打包体积)
import { select, selectAll } from 'd3-selection';
import { scaleLinear } from 'd3-scale';
import { axisBottom } from 'd3-axis';

按需导入是更推荐的做法,因为 D3 模块之间是互相独立的,按需导入可以让最终打包的 JavaScript 体积更小。

验证安装

安装完成后,可以用以下方式验证 D3 是否正常工作:

console.log(d3.version); // 输出 D3 版本号,如 "7.9.0"

// 测试基本功能
const selection = d3.select('body');
console.log(selection.empty()); // 如果 body 为空,返回 false

第一个 D3 示例

让我们从最简单的例子开始,逐步理解 D3 的工作方式。

目标

在网页上创建一个 SVG 画布,然后在画布中央绘制一个蓝色圆形。

代码实现

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>D3.js 第一个示例</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body {
font-family: sans-serif;
display: flex;
justify-content: center;
padding-top: 50px;
}
svg {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<script>
// 1. 设置画布尺寸
const width = 400;
const height = 300;

// 2. 创建 SVG 画布
const svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);

// 3. 绘制圆形
svg.append('circle')
.attr('cx', width / 2) // 圆心 x 坐标
.attr('cy', height / 2) // 圆心 y 坐标
.attr('r', 50) // 半径
.attr('fill', '#3498db'); // 填充颜色
</script>
</body>
</html>

代码解读

这个例子虽然简单,但包含了 D3 编程的基本模式:

选择元素:使用 d3.select('body') 选择 body 元素。这会返回一个「选择集」(Selection),可以在这上面进行后续操作。

链式调用:D3 的方法通常返回选择集本身,这样就可以用链式语法连续调用多个方法。代码中的 .append('svg').attr('width', width)... 就是链式调用的例子。

添加元素:使用 .append('circle') 添加一个 circle 元素到 SVG 中。这个方法返回一个指向新创建元素的选择集。

设置属性:使用 .attr('cx', ...) 设置元素的属性。D3 通过 attr 方法可以设置任意 SVG 属性。

D3 的核心概念

理解了第一个示例后,接下来需要理解几个关键概念,这些概念会贯穿整个 D3 开发过程。

选择集(Selections)

选择集是 D3 的基础。一个选择集代表零个或多个 DOM 元素,D3 的所有操作都是针对选择集进行的。

// 选择一个元素
const body = d3.select('body');

// 选择多个元素
const paragraphs = d3.selectAll('p');

// 使用 CSS 选择器
const myDiv = d3.select('#my-div');
const myClassElements = d3.selectAll('.my-class');
const listItems = d3.selectAll('ul li');

// 使用标签名选择
const allDivs = d3.selectAll('div');

选择集支持丰富的 CSS 选择器语法,包括标签选择器、类选择器、ID 选择器、属性选择器、伪类选择器等:

// 类选择器
d3.selectAll('.chart-bar');

// ID 选择器
d3.select('#main-container');

// 属性选择器
d3.selectAll('[data-category="electronics"]');

// 伪类选择器
d3.selectAll('li:first-child');
d3.selectAll('tr:nth-child(even)');

数据绑定(Data Binding)

数据绑定是 D3 区别于普通 DOM 操作库的核心特性。通过数据绑定,你可以将一个数据数组与一组 DOM 元素关联起来,数据的变化会自动反映到 DOM 上。

// 假设有一组数据
const fruits = [
{ name: '苹果', price: 5 },
{ name: '香蕉', price: 3 },
{ name: '橙子', price: 4 }
];

// 将数据绑定到所有段落
d3.selectAll('p')
.data(fruits)
.text(d => `${d.name}: ${d.price}`);

这段代码做了三件事:将 fruits 数组作为数据绑定到所有 p 元素;为每个 p 元素设置文本内容;使用箭头函数从数据中提取需要显示的文字。

数据绑定遵循「一对一」原则:data() 方法会按顺序将数组中的元素分配给选择集中的元素。如果选择集中元素的数量多于数据,多的元素会进入「退出选择集」;如果数据多于选择集元素,多的数据会进入「进入选择集」。

Enter、Update、Exit 模式

当数据发生变化时,你需要处理三种情况:D3 为此提供了 Enter-Update-Exit 模式。

Enter(进入):当数据多于现有 DOM 元素时,需要创建新元素来显示新增的数据:

d3.selectAll('p')
.data(newData)
.join('p') // 使用 join 自动处理
.text(d => d.name); // 设置文本

Update(更新):当数据与现有 DOM 元素数量刚好匹配时,直接更新元素属性:

const p = d3.selectAll('p').data(data);
p.text(d => d.name); // 更新现有元素的文本

Exit(退出):当数据少于现有 DOM 元素时,需要删除多余的 DOM 元素:

const p = d3.selectAll('p').data(data);
p.exit().remove(); // 移除不再需要的元素

D3 v5 及以上版本推荐使用 join() 方法,它自动处理了 Enter、Update、Exit 的逻辑,让代码更加简洁:

d3.selectAll('p')
.data(data)
.join('p')
.text(d => d.name);

D3 的坐标系统

理解 SVG 坐标系是创建可视化图表的前提。SVG 和常见的屏幕坐标系有细微但重要的差异。

坐标原点

在 SVG 中,原点(0, 0)位于画布的左上角,x 轴向右延伸,y 轴向下延伸。这与数学中常见的坐标系统不同,数学中 y 轴通常向上延伸。

(0,0)─────────────────────────────→ x 轴





y 轴

这种坐标系设计源于屏幕渲染的历史原因——早期的显示器的电子束从左上角开始扫描。

坐标变换

由于 y 轴向下延伸,直接用原始数据绘制图表会导致图形上下颠倒。为此需要使用比例尺来进行坐标转换:

// 创建线性比例尺,将数据域 [0, 100] 映射到范围 [300, 0]
// 注意范围是 [300, 0] 而不是 [0, 300],这样 y 轴方向就倒过来了
const yScale = d3.scaleLinear()
.domain([0, 100])
.range([300, 0]);

// 现在调用 yScale(50) 会返回 150,实现了 y 轴方向的倒转
console.log(yScale(50)); // 输出 150

教程学习路径

为了帮助你有系统地掌握 D3,我们设计了以下学习路径。每个章节都会在前一个章节的基础上递进,强烈建议按顺序学习。

基础篇

第一阶段需要掌握 D3 的核心概念和基本操作。这些内容是后续所有可视化的基础,必须完全理解。

第一步:选择集与数据绑定 — 这是 D3 的核心中的核心。理解选择元素的两种方法(select 和 selectAll)、理解如何用 data() 方法将数据绑定到元素上、理解 Enter-Update-Exit 模式的工作原理。

第二步:比例尺与坐标轴 — 可视化本质上就是数据值到视觉位置的映射。比例尺是实现这种映射的工具,坐标轴则是帮助用户理解数据的参考线。

第三步:过渡与动画 — 让可视化从静态走向动态的必备技能。掌握 transition() 的用法、缓动函数的选择、链式动画的组合。

进阶篇

完成基础篇的学习后,你已经具备了创建基本可视化的能力。进阶篇将扩展你的技能范围,让你能够创建更丰富的图表类型。

第四步:数据加载 — 真实项目中的数据通常来自外部文件或其他数据源。学习如何使用 d3-fetch 模块加载 JSON、CSV 等格式的数据。

第五步:折线图与面积图 — 折线图是展示时间序列数据的标准方式。理解如何使用 d3.line() 生成折线路径,如何处理日期轴。

第六步:饼图与环形图 — 饼图适合展示比例关系。理解如何使用 d3.pie() 计算角度、d3.arc() 生成扇形路径。

第七步:散点图 — 散点图用于展示两个变量之间的相关关系。理解如何处理坐标轴标签、添加数据点悬停交互。

高级篇

高级篇将带你进入更复杂的可视化领域,涉及关系数据、层次结构数据、地理数据的处理。

第八步:力导向图 — 网络关系数据的标准展示方式。理解物理模拟如何应用于可视化、如何动态计算节点位置。

第九步:树形图与旭日图 — 层次结构数据的展示方式。理解如何将嵌套数据转换为可绘制的图形。

第十步:地图可视化 — 结合地理数据的可视化。理解 GeoJSON 数据的结构和如何使用 d3-geo 进行地图投影。

准备好了吗?

D3 是一个学以致用的技能,看再多的教程不动手实践也很难掌握。建议在学习过程中始终保持一个「演示页面」运行,随时尝试新的代码片段。

现在让我们进入下一章,开始学习选择集与数据绑定的详细内容。