数据可视化 D3.js 基础
FreeGuideOnline
最新
2026-07-09
3. 准备一个用于绘图的 SVG 或 Canvas 容器:
```html
<body>
<svg id="chart" width="600" height="400"></svg>
<script src="app.js"></script>
</body>
- 在
app.js中开始编写 D3 代码。本教程所有示例代码均基于 D3 v7。
初学者建议直接使用本地文件并用浏览器打开。无需 Node.js 或构建工具。
核心概念一:选择集与 DOM 操作
D3 使用类似 jQuery 的选择集来批量操作元素。
- 选中元素:
d3.select('svg')选中第一个匹配的 SVG 元素,d3.selectAll('rect')选中所有矩形。 - 设置属性:
.attr('x', 100)设置 SVG 属性,.style('fill', 'steelblue')设置样式。 - 动态创建:先用
selectAll选择尚不存在的元素,再用.enter()创建占位符。
// 选中 SVG,并追加一个圆形
d3.select('#chart')
.append('circle')
.attr('cx', 100)
.attr('cy', 100)
.attr('r', 50)
.attr('fill', 'orange');
这段代码会在 id="chart" 的 SVG 中绘制一个橙色圆形。
核心概念二:数据绑定——让数据与图形对话
D3 的精华在于将数组中的每个数据项映射到页面元素,并通过 enter、update、exit 模式管理元素的“活着”(存在)、“变化”和“离开”。
- data():将数据数组绑定到选择集中的元素。
- enter():为新增数据创建对应的元素。
- update(直接修改):已存在的元素会直接更新。
- exit():多余的元素会被移除。
经典模式(创建柱状图的基础):
const dataset = [30, 80, 45, 60, 20, 90, 55];
// 绑定数据到尚未存在的 rect 元素
const rects = d3.select('svg')
.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', (d, i) => i * 40)
.attr('y', d => 200 - d)
.attr('width', 30)
.attr('height', d => d);
这里 (d, i) 分别代表当前数据值 d 和索引 i,返回的值会动态设置属性。
核心概念三:比例尺——数据空间到屏幕空间的映射
真实数据往往不会刚好落在 0~600 的像素范围,比例尺(Scale) 负责将数据域(domain)映射到视觉范围(range)。
常用比例尺:
- 线性比例尺
d3.scaleLinear():连续数据的映射。 - 带状比例尺
d3.scaleBand():适用于分类数据(柱状图的 x 轴)。 - 序数比例尺
d3.scaleOrdinal():将离散值映射到颜色等。 - 时间比例尺
d3.scaleTime():处理日期对象。
示例:
const data = [80, 250, 30, 420, 180];
// 高度比例尺
const yScale = d3.scaleLinear()
.domain([0, d3.max(data)]) // 数据范围:0 到最大值
.range([200, 0]); // 视觉范围:柱状图从底部(200)到顶部(0)
// 使用比例尺设置条高
.attr('y', d => yScale(d))
.attr('height', d => 200 - yScale(d));
注意:SVG 坐标系 y 轴向下,所以将 range 设为 [height, 0] 可以让柱子向上生长。
核心概念四:坐标轴——让图表瞬间专业
D3 提供了坐标轴生成器 d3.axisBottom()、d3.axisLeft() 等,可自动生成刻度、标签和网格线。
绘制坐标轴步骤:
- 定义比例尺。
- 创建轴生成器并传入比例尺。
- 在 SVG 中调用轴生成器。
// 设置边距和宽高
const margin = {top: 20, right: 20, bottom: 30, left: 40};
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select('svg')
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// x 轴(带状比例尺)
const x = d3.scaleBand()
.domain(data.map((d, i) => i)) // 索引作为类别
.range([0, width])
.padding(0.1);
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x)); // 调用 x 轴生成器
// y 轴(线性比例尺)
const y = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([height, 0]);
svg.append('g')
.call(d3.axisLeft(y)); // 调用 y 轴生成器
动手实践:用 D3.js 绘制第一个柱状图
结合以上知识,绘制一个完整的柱状图,并添加交互效果(鼠标悬停高亮)。
完整代码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>D3 柱状图</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
rect { fill: steelblue; }
rect:hover { fill: orange; }
</style>
</head>
<body>
<svg id="chart" width="600" height="400"></svg>
<script>
const data = [30, 80, 45, 60, 20, 90, 55];
const svg = d3.select('#chart');
const margin = {top: 20, right: 20, bottom: 30, left: 40};
const width = +svg.attr('width') - margin.left - margin.right;
const height = +svg.attr('height') - margin.top - margin.bottom;
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// 比例尺
const x = d3.scaleBand()
.domain(data.map((d, i) => i))
.range([0, width])
.padding(0.2);
const y = d3.scaleLinear()
.domain([0, d3.max(data)])
.nice() // 让刻度更圆润
.range([height, 0]);
// 坐标轴
g.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
g.append('g')
.call(d3.axisLeft(y));
// 绘制柱体
g.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => x(i))
.attr('y', d => y(d))
.attr('width', x.bandwidth())
.attr('height', d => height - y(d));
</script>
</body>
</html>
用浏览器打开该文件,即可看到一个带有坐标轴和悬停变色的柱状图。
进阶起点:过渡与动画
D3 的过渡系统 .transition() 可以平滑地从一个状态变化到另一个状态,让数据更新栩栩如生。
基本用法:
rects.transition()
.duration(750) // 动画时长(毫秒)
.delay((d, i) => i * 50) // 交错延迟
.attr('y', newYValue)
.attr('height', newHeight);
当数据发生更新时,经典的 update 模式配合 transition:
// 更新绑定
const bars = g.selectAll('.bar')
.data(newData);
bars.exit() // 删除多余元素
.transition().duration(300)
.attr('y', height)
.attr('height', 0)
.remove();
bars // 更新现有元素
.transition().duration(500)
.attr('y', d => y(d))
.attr('height', d => height - y(d));
bars.enter() // 添加新元素
.append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => x(i))
.attr('y', height) // 从底部开始
.attr('width', x.bandwidth())
.attr('height', 0)
.transition().duration(500)
.attr('y', d => y(d))
.attr('height', d => height - y(d));