D3.js入门教程
注:本文不对 D3.js 的整体背景与功能做过多赘述,相关介绍请读者自行查阅 D3.js 官方网站:d3js.org/。本文仅聚焦于帮助读者快速上手 D3.js 项目搭建与基础使用,文中内容如有疏漏,欢迎各位读者批评指正。
1. D3 简介
D3.js(全称 Data-Driven Documents,数据驱动文档)是一款免费、开源的 JavaScript 数据可视化库,基于 HTML、CSS、SVG 等 Web 标准构建,专注于为数据提供极致灵活的动态可视化能力。
与 ECharts、Chart.js 等开箱即用的封装型图表库不同,D3.js 没有预设好的 “柱状图”“折线图”“饼图” 等标准化图表组件,它的核心定位是可视化底层工具集。它能将数据与 DOM 元素绑定,通过强大的数据处理、比例尺、布局、动画交互等核心能力,让开发者自由组合 SVG、Canvas 等基础图形元素,从零构建任意形式的可视化作品 —— 无论是常规统计图表、动态交互大屏,还是网络图、地图、桑基图、力导向图等复杂定制化可视化,都能实现。
D3.js 的核心优势是数据驱动与高度自由:它支持数据实时更新、平滑过渡动画、丰富的鼠标交互与事件响应,能完美适配浏览器环境,兼顾可视化效果与性能。凭借无上限的定制化能力,它成为数据可视化领域专业开发者、数据分析师、科研可视化从业者的首选工具,也是构建高端、个性化数据可视化项目的核心技术。
2. 前置条件
在正式学习 D3.js 之前,我们需要了解一些必要的 Web 基础知识,包括 HTML、CSS、JavaScript 以及 SVG。本文不对 HTML、CSS、JavaScript 做详细介绍,读者可通过 W3C 官网(www.w3schools.com/)进行查阅学习。下面我们将对 SVG 做简单介绍 —— 它是 D3.js 实现数据可视化的核心底层载体。
2.1 .SVG 简介
SVG 全称可缩放矢量图形,是基于XML的矢量图形格式,依靠代码绘制图形,放大缩小永不失真。
SVG 拥有专属坐标系,以画布左上角为坐标原点 (0,0),水平向右为 x 轴正方向,垂直向下为 y 轴正方向。可通过标签绘制矩形、圆形、直线、文字、路径等基础图形,支持填充、描边、透明度、平移变换等样式与布局设置,是 D3.js 实现数据可视化的核心底层载体。
2.1.1. 通用属性
-
fill:填充颜色 -
stroke:描边 / 边框颜色 -
stroke-width:边框粗细 -
opacity:透明度 0~1
<rect fill="skyblue" stroke="black" stroke-width="2" opacity="0.8"/>
2.1.2. 矩形 rect
-
x:左上角横坐标 -
y:左上角纵坐标 -
width:宽度 -
height:高度
<rect x="50" y="50" width="100" height="60" fill="red"/>
2.1.3. 圆形 circle
-
cx:圆心 x -
cy:圆心 y -
r:半径
<circle cx="100" cy="100" r="40" fill="green"/>
2.1.4. 椭圆 ellipse
-
rx:水平半径 -
ry:垂直半径
<ellipse cx="150" cy="100" rx="50" ry="30" fill="orange"/>
2.1.5. 直线 line
-
x1 y1:起点 -
x2 y2:终点
<line x1="20" y1="20" x2="200" y2="150" stroke="#333" stroke-width="3"/>
2.1.6. 文字 text
-
x y:文字左下角坐标 -
font-size:字号
<text x="80" y="80" font-size="14" fill="#000">我是文字</text>
2.1.7. path 路径
D3 折线、面积、地图 GeoJSON 最后都生成 path。语法靠指令 + 坐标:
常用指令:
-
M x y:移动到起点(Move) -
L x y:画直线到下一点(Line) -
Z:闭合路径
示例:三角形
<path d="M 50 50 L 150 50 L 100 150 Z" stroke="black" fill="none"/>
D3 里 d3.line() 自动帮你生成这个 d 属性字符串,不用自己手写。
2.1.8. 分组 g 标签
<g> 相当于容器,统一管理一组图形,可整体平移。
<g transform="translate(50,30)">
<rect x="0" y="0" width="80" height="50"/>
<text x="10" y="30">组内文字</text>
</g>
transform="translate(x,y)":整体向右移 x,向下移 y
D3 画坐标轴、图例、图表分区全靠 <g>
3. D3 安装
3.1. D3 在纯 HTML 中引入
在不使用构建工具的情况下,我们可以直接通过 CDN 引入 D3.js,快速搭建开发环境。
方式 1:使用 CDN 引入(推荐新手使用)直接在 HTML 文件的 <head> 或 <body> 中添加如下代码:
<!-- 引入 D3.js v7 稳定版 -->
<script src="https://d3js.org/d3.v7.min.js"></script>
引入后,全局会暴露 d3 对象,即可在后续 <script> 标签中直接使用 D3 的所有 API。
方式 2:下载本地文件引入
- 前往 D3 官网下载最新版:d3js.org/
- 将下载的
d3.min.js文件放入项目目录中 - 在 HTML 中通过相对路径引入:
<script src="./js/d3.v7.min.js"></script>
3.2. D3 在 Vue / React 项目中引入
如果是在工程化项目中使用,推荐通过 npm 安装:
步骤 1:安装依赖
npm install d3
# 或 yarn
yarn add d3
步骤 2:在组件中引入并使用
// 引入整个 D3 库
import * as d3 from 'd3';
// 也可以按需引入模块(推荐,减少打包体积)
import { scaleLinear, select } from 'd3';
4. 选择元素与绑定数据
D3 的核心逻辑是数据驱动 DOM,而实现这一切的第一步,就是学会如何选择元素、如何把数据绑定到元素上。
4.1. 如何选择元素
D3 提供了两个最基础的选择方法:
-
d3.select(selector):选择文档中第一个匹配的元素 -
d3.selectAll(selector):选择文档中所有匹配的元素
它们返回的结果被称为选择集(Selection),支持链式调用,是 D3 所有操作的起点。
// 示例:选择元素
const body = d3.select("body"); // 选择 <body> 元素
const firstP = body.select("p"); // 选择 body 里第一个 <p>
const allP = body.selectAll("p"); // 选择 body 里所有 <p>
const svg = body.select("svg"); // 选择 <svg> 元素
const allRect = svg.selectAll("rect"); // 选择 svg 里所有 <rect>
4.2. 如何绑定数据
D3 最独特的功能之一,就是能将数据直接绑定到 DOM 元素上,后续所有的渲染、更新操作,都可以围绕这些数据展开。
D3 提供了两种绑定数据的方法:
-
.datum(value):将单个数据绑定到整个选择集上 -
.data(array):将数组绑定到选择集,数组的每一项会依次与选择集的元素一一对应(最常用)
4.2.1. 用 .datum() 绑定单个数据
<p>Apple</p>
<p>Pear</p>
<p>Banana</p>
<script>
let text = "Polaris";
d3.selectAll("p")
.datum(text)
.text(function (d, i) {
return "Hello " + i + " " + d;
});
</script>
Hello 1 Polaris
Hello 2 Polaris
Hello 3 Polaris
运行后三个段落都会显示:Hello 1/2/3 Polaris。这里用到的 (d, i) 回调是 D3 的标准写法:
-
d:当前元素绑定的数据 -
i:当前元素在选择集中的索引(从 0 开始)
4.2.2. 用 .data() 绑定数组(重点)
<p>Apple</p>
<p>Pear</p>
<p>Banana</p>
<script>
const dataset = ["I like dogs", "I like cats", "I like snakes"];
d3.selectAll("p")
.data(dataset)
.text((d) => d);
</script>
I like cats
I like snakes
Banana
运行后三个段落会依次显示数组里的三个字符串,实现了数据与 DOM 的一一对应。
5. 元素的增删改
在选择集和数据绑定的基础上,我们可以对 DOM 元素进行插入、删除和修改操作。
5.1. 选择元素进阶
D3 的选择器完全兼容 CSS 选择器语法,支持通过标签、ID、类名来选择元素:
<p>Apple</p>
<p id="my-id">Pear</p>
<p class="my-class">Banana</p>
<p class="my-class">Orange</p>
// 1. 选择第一个 <p>
d3.select("p").style("color", "red");
// 2. 选择所有 <p>
d3.selectAll("p").style("color", "blue");
// 3. 选择 id="my-id" 的元素(用 #)
d3.select("#my-id").style("color", "green");
// 4. 选择 class="my-class" 的所有元素(用 .)
d3.selectAll(".my-class").style("color", "purple");
5.2. 插入元素
-
.append(tag):在选择集的末尾追加新元素 -
.insert(tag, beforeSelector):在指定元素的前面插入新元素
// 在 body 末尾追加一个 <p>
d3.select("body").append("p").text("我是 append 添加的段落");
// 在 #my-id 元素前面插入一个 <p>
d3.select("body").insert("p", "#my-id").text("我是 insert 添加的段落");
Apple
我是 insert 添加的段落
Pear
Banana
Orange
我是 append 添加的段落
5.3. 删除元素
-
.remove():删除选择集中的所有元素
// 删除 id="my-id" 的元素
d3.select("#my-id").remove();
6. 第一个简单图表
了解了基础操作后,我们来画第一个图表:一个横向柱状图,只包含矩形部分,用来理解 D3 绘图的核心流程。
6.1. 创建 SVG 画布
D3 推荐在 SVG 中绘图,我们先创建一个 SVG 画布:
// 画布尺寸
const width = 300;
const height = 300;
// 在 body 中添加 SVG 元素
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
6.2. 绘制矩形(数据驱动)
// 数据(代表每个矩形的宽度)
const dataset = [250, 210, 170, 130, 90];
const rectHeight = 25; // 每个矩形的高度
// 核心流程:选择集 + 数据绑定 + enter() + append()
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 20) // 矩形左上角 x 坐标
.attr("y", (d, i) => i * rectHeight) // 矩形左上角 y 坐标
.attr("width", d => d) // 矩形宽度 = 数据值
.attr("height", rectHeight - 2) // 矩形高度
.attr("fill", "red"); // 填充颜色
这里的 .selectAll("rect").data(dataset).enter().append("rect") 是 D3 绘图的核心公式:
-
.selectAll("rect"):先选择所有已有的 rect(初始为空) -
.data(dataset):绑定数据 -
.enter():处理 “数据比元素多” 的情况 -
.append("rect"):为多余的数据创建新元素
运行后会在 SVG 中生成 5 个红色横向矩形:
- 每个矩形的宽度直接对应数据值(250、210、170、130、90)
- 矩形按索引
i依次向下排列,y坐标为i * rectHeight - 矩形之间会有
2px的间距,整体看起来整齐不重
这种写法虽然简单,但数据值会直接影响画布尺寸,比如数据过大就会超出 SVG 边界,数据过小则几乎看不见。为了解决这个问题,我们需要引入比例尺的概念,这也是下一章的重点。
7. 比例尺与坐标轴
上一节直接用数据值作为像素值有明显的局限性,比如数据太大超出画布、太小看不见,这时候就需要比例尺来解决问题。
7.1. 什么是比例尺
比例尺是 D3 提供的一种映射函数,它可以把数据(定义域 domain)映射到画布像素(值域 range)上,保持数据的大小关系不变。
7.2. 常用比例尺(D3 v7 API)
7.2.1 线性比例尺 d3.scaleLinear()
用于连续数值的映射,比如柱状图的 Y 轴、折线图的坐标轴。
const dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
const min = d3.min(dataset);
const max = d3.max(dataset);
// 创建线性比例尺
const linearScale = d3.scaleLinear()
.domain([min, max]) // 数据范围:0.9 ~ 3.3
.range([0, 300]); // 像素范围:0 ~ 300px
console.log(linearScale(0.9)); // 输出 0
console.log(linearScale(3.3)); // 输出 300
console.log(linearScale(2.3)); // 输出 175
7.2.2 条带比例尺 d3.scaleBand()
用于离散分类数据的映射,比如柱状图的 X 轴,用来给每个柱子分配位置和宽度。
const labels = ["A", "B", "C", "D", "E"];
const bandScale = d3.scaleBand()
.domain(labels) // 分类数据
.range([0, 300]) // 像素范围
.padding(0.1); // 柱子之间的间距
console.log(bandScale("A")); // 第一个柱子的 x 坐标
console.log(bandScale.bandwidth()); // 每个柱子的宽度
7.2.3 坐标轴(D3 v7 API)
D3 提供了现成的坐标轴组件,基于比例尺生成刻度和轴线。
-
d3.axisBottom(scale):底部 X 轴 -
d3.axisLeft(scale):左侧 Y 轴
// 基于线性比例尺创建坐标轴
const xAxis = d3.axisBottom(linearScale);
// 添加坐标轴到 SVG
svg.append("g")
.attr("transform", "translate(20, 280)") // 移动到画布底部
.call(xAxis); // 渲染坐标轴
8. 完整柱状图实战
综合前面的知识,我们来实现一个包含矩形、文字标签、坐标轴的完整柱状图,以下是可直接运行的完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>D3 v7 完整柱状图</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.axis path, .axis line {
fill: none;
stroke: #333;
shape-rendering: crispEdges;
}
.axis text {
font-size: 12px;
}
</style>
</head>
<body>
<script>
// 1. 画布配置
const width = 400;
const height = 400;
const padding = { top: 20, right: 20, bottom: 30, left: 40 };
// 2. 创建 SVG
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${padding.left},${padding.top})`);
// 3. 数据
const dataset = [10, 20, 30, 40, 33, 24, 12, 5];
// 4. 比例尺
const xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, width - padding.left - padding.right])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([height - padding.top - padding.bottom, 0]);
// 5. 坐标轴
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.attr("class", "axis")
.attr("transform", `translate(0,${height - padding.top - padding.bottom})`)
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.call(yAxis);
// 6. 绘制矩形
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => xScale(i))
.attr("y", d => yScale(d))
.attr("width", xScale.bandwidth())
.attr("height", d => height - padding.top - padding.bottom - yScale(d))
.attr("fill", "red");
// 7. 文字标签
svg.selectAll("text.label")
.data(dataset)
.enter()
.append("text")
.attr("class", "label")
.attr("x", (d, i) => xScale(i) + xScale.bandwidth() / 2)
.attr("y", d => yScale(d) - 5)
.attr("text-anchor", "middle")
.text(d => d);
</script>
</body>
</html>