D3.js 数据可视化 — 用数据驱动 DOM,画出任意交互图表¶
一句话说明¶
D3(Data-Driven Documents)是一个把数据绑定到 HTML/SVG 元素上的 JavaScript 库,所有图形都由你自己用代码"画"出来,灵活度极高,适合定制化科研图表。
安装与配置¶
# 通过 npm 安装 D3 v7(当前稳定版)
npm install d3
# 或者直接在 HTML 里用 CDN 引入(零配置)
# <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
# 查看已安装版本
node -e "console.log(require('d3/package.json').version)"
核心用法¶
import * as d3 from 'd3'; // 导入整个 D3 库
// ── 1. 选择元素(类似 jQuery 的 $ 选择器)──
const svg = d3.select('#chart') // 选中 id=chart 的 SVG 元素
.attr('width', 600) // 设置宽度
.attr('height', 400); // 设置高度
// ── 2. 绑定数据(D3 的核心思想)──
const data = [10, 30, 50, 20, 80]; // 原始数据数组
const bars = svg.selectAll('rect') // 选中所有 rect(可以不存在)
.data(data) // 把数据与元素一一绑定
.join('rect'); // 有数据就创建 rect,多余的删掉
// ── 3. 比例尺(把数据映射到像素)──
const xScale = d3.scaleBand() // 分组比例尺(适合柱状图)
.domain(d3.range(data.length)) // 输入域:0,1,2,3,4
.range([0, 600]) // 输出域:像素范围
.padding(0.2); // 柱子间距
const yScale = d3.scaleLinear() // 线性比例尺
.domain([0, d3.max(data)]) // 输入域:0 到最大值
.range([400, 0]); // 输出域:像素(注意 SVG 坐标系从上到下)
// ── 4. 设置属性(用函数接收每个数据点)──
bars
.attr('x', (d, i) => xScale(i)) // d=数据值, i=索引
.attr('y', d => yScale(d)) // 根据数据计算 y 坐标
.attr('width', xScale.bandwidth()) // 柱子宽度
.attr('height', d => 400 - yScale(d)) // 柱子高度
.attr('fill', '#4e79a7'); // 柱子颜色
// ── 5. 添加坐标轴 ──
const xAxis = d3.axisBottom(xScale); // 创建 X 轴
svg.append('g') // 新增一个分组元素
.attr('transform', 'translate(0,400)')// 移到底部
.call(xAxis); // 把坐标轴渲染进去
实战案例¶
// ── 折线图:绘制基因表达量随时间变化 ──
import * as d3 from 'd3';
const data = [
{ time: 0, expression: 1.2 }, // 时间点0,表达量1.2
{ time: 1, expression: 2.8 },
{ time: 2, expression: 4.1 },
{ time: 3, expression: 3.5 },
{ time: 4, expression: 5.0 },
];
const width = 500, height = 300; // 画布尺寸
const margin = { top: 20, right: 20, bottom: 40, left: 50 }; // 边距
const svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`); // 留出边距空间
// X 轴比例尺
const x = d3.scaleLinear()
.domain([0, d3.max(data, d => d.time)]) // 取时间的最大值
.range([0, width]);
// Y 轴比例尺
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.expression)]) // 取表达量最大值
.range([height, 0]);
// 折线生成器
const line = d3.line()
.x(d => x(d.time)) // 每个点的 X 坐标
.y(d => y(d.expression)) // 每个点的 Y 坐标
.curve(d3.curveMonotoneX); // 平滑曲线
// 画折线
svg.append('path')
.datum(data) // 整体数据(不是逐点绑定)
.attr('fill', 'none') // 只画线,不填充
.attr('stroke', '#e15759') // 线条颜色
.attr('stroke-width', 2) // 线条粗细
.attr('d', line); // 用 line 生成器计算路径
// 添加坐标轴
svg.append('g').attr('transform', `translate(0,${height})`).call(d3.axisBottom(x));
svg.append('g').call(d3.axisLeft(y)); // 左侧 Y 轴
常见报错与解决¶
| 报错 | 原因 | 解决方法 |
|---|---|---|
Cannot read properties of null | 选择器没找到元素 | 确认 HTML 中存在对应 id/class |
| 图表显示空白 | 数据绑定后没有 .join() | 在 .data() 后加 .join('rect') |
| Y 轴方向反了 | SVG 坐标系原点在左上角 | range([height, 0]) 而非 [0, height] |
| 柱子重叠 | scaleBand 没设 padding | 加 .padding(0.1) |
| 路径不显示 | path 元素有 fill | 加 .attr('fill', 'none') |
速查表¶
// 常用比例尺
d3.scaleLinear() // 线性:连续数值
d3.scaleBand() // 分组:离散类别
d3.scaleLog() // 对数:跨越多个数量级
d3.scaleTime() // 时间:Date 对象
// 常用形状生成器
d3.line() // 折线
d3.area() // 面积图
d3.arc() // 扇形(饼图)
d3.symbol() // 点形状
// 常用数组工具
d3.max(arr, fn) // 最大值
d3.min(arr, fn) // 最小值
d3.extent(arr, fn) // [最小, 最大]
d3.sum(arr, fn) // 求和
d3.group(arr, fn) // 按 key 分组
// 官方文档:https://d3js.org/
// 示例库:https://observablehq.com/@d3