D3.js 数据可视化
D3.js 是 JavaScript 数据可视化的"瑞士军刀",用 SVG/Canvas/HTML 把数据变成任何你能想象的图表,从简单柱状图到复杂的力导向图、地图、桑基图都能做,是数据可视化领域的标杆库。
核心知识点
| 知识点 | 说明 |
|---|
| 工具定位 | JavaScript 数据驱动文档可视化库 |
| 最新版本 | v7.9.0(2024 年发布,稳定维护中) |
| 开发者 | Mike Bostock / Observable 团队 |
| 核心优势 | 完全自由度、数据绑定、过渡动画、丰富的子模块 |
| 适用场景 | 自定义图表、交互式数据仪表盘、地理可视化 |
| 生态工具 | Observable Plot(高层封装)、D3 子模块 30+ 个 |
安装配置
# npm 安装
npm install d3 # 安装完整 D3 库
# 或只安装需要的子模块(推荐,减小体积)
npm install d3-selection d3-scale d3-axis d3-shape # 按需安装
# CDN 引入(快速原型)
# <script src="https://d3js.org/d3.v7.min.js"></script>
HTML 模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 可视化</title>
<script src="https://d3js.org/d3.v7.min.js"></script> <!-- 引入 D3 -->
<style>
.bar { fill: steelblue; } /* 柱状图颜色 */
.bar:hover { fill: orange; } /* 悬停变色 */
</style>
</head>
<body>
<div id="chart"></div> <!-- 图表容器 -->
<script src="app.js"></script> <!-- 你的可视化脚本 -->
</body>
</html>
基本使用
1. 选择元素与数据绑定(D3 的核心)
// D3 的核心思想:数据 → 元素 → 属性
// 选择元素(类似 jQuery)
d3.select("#chart") // 选择单个元素
d3.selectAll(".bar") // 选择所有匹配元素
// 数据绑定(D3 最核心的概念)
const data = [10, 20, 30, 40, 50]; // 准备数据
d3.select("#chart")
.selectAll("div") // 选择所有 div(此时还没有)
.data(data) // 绑定数据
.join("div") // 为每个数据点创建 div
.style("width", d => d * 10 + "px") // 宽度 = 数据值 × 10
.style("height", "20px") // 固定高度
.style("background", "steelblue") // 蓝色背景
.style("margin", "2px") // 间距
.text(d => d); // 显示数据值
2. SVG 柱状图
// 数据
const data = [
{ name: "样本A", value: 30 }, // 样本数据
{ name: "样本B", value: 80 },
{ name: "样本C", value: 45 },
{ name: "样本D", value: 60 },
{ name: "样本E", value: 20 }
];
// 画布尺寸
const width = 600, height = 400; // SVG 宽高
const margin = { top: 20, right: 20, bottom: 40, left: 40 }; // 边距
const innerWidth = width - margin.left - margin.right; // 内部宽度
const innerHeight = height - margin.top - margin.bottom; // 内部高度
// 创建 SVG
const svg = d3.select("#chart")
.append("svg") // 添加 SVG 元素
.attr("width", width) // 设置宽度
.attr("height", height) // 设置高度
.append("g") // 添加分组元素
.attr("transform", `translate(${margin.left},${margin.top})`); // 偏移
// X 轴比例尺(分类轴)
const x = d3.scaleBand() // 分段比例尺(离散值)
.domain(data.map(d => d.name)) // 输入域:样本名称
.range([0, innerWidth]) // 输出范围:像素宽度
.padding(0.2); // 柱间距 20%
// Y 轴比例尺(数值轴)
const y = d3.scaleLinear() // 线性比例尺(连续值)
.domain([0, d3.max(data, d => d.value)]) // 输入域:0 到最大值
.range([innerHeight, 0]); // 输出范围:注意方向反转
// 画柱子
svg.selectAll(".bar")
.data(data) // 绑定数据
.join("rect") // 为每个数据创建矩形
.attr("class", "bar") // 添加类名
.attr("x", d => x(d.name)) // X 位置
.attr("y", d => y(d.value)) // Y 位置
.attr("width", x.bandwidth()) // 柱宽
.attr("height", d => innerHeight - y(d.value)); // 柱高
// 添加坐标轴
svg.append("g")
.attr("transform", `translate(0,${innerHeight})`) // 移到底部
.call(d3.axisBottom(x)); // 画 X 轴
svg.append("g")
.call(d3.axisLeft(y)); // 画 Y 轴
3. 折线图
// 时间序列数据
const timeData = [
{ date: new Date("2025-01"), value: 100 }, // 月度数据
{ date: new Date("2025-02"), value: 150 },
{ date: new Date("2025-03"), value: 120 },
{ date: new Date("2025-04"), value: 200 },
{ date: new Date("2025-05"), value: 180 }
];
// 时间比例尺
const xTime = d3.scaleTime() // 时间比例尺
.domain(d3.extent(timeData, d => d.date)) // 时间范围
.range([0, innerWidth]); // 像素范围
// 折线生成器
const line = d3.line() // 创建折线生成器
.x(d => xTime(d.date)) // X 坐标映射
.y(d => y(d.value)) // Y 坐标映射
.curve(d3.curveMonotoneX); // 平滑曲线
// 画折线
svg.append("path")
.datum(timeData) // 绑定整组数据
.attr("fill", "none") // 不填充
.attr("stroke", "steelblue") // 线条颜色
.attr("stroke-width", 2) // 线条宽度
.attr("d", line); // 应用折线生成器
高级用法
1. 过渡动画
// D3 的过渡动画非常强大
svg.selectAll(".bar")
.data(newData) // 绑定新数据
.join("rect")
.transition() // 开启过渡动画
.duration(750) // 动画时长 750ms
.ease(d3.easeElasticOut) // 弹性缓动效果
.attr("y", d => y(d.value)) // 动画目标 Y
.attr("height", d => innerHeight - y(d.value)); // 动画目标高度
2. 交互事件
// 鼠标交互
svg.selectAll(".bar")
.on("mouseover", function(event, d) { // 鼠标悬停
d3.select(this)
.attr("fill", "orange"); // 变色
tooltip.text(`${d.name}: ${d.value}`) // 显示提示
.style("visibility", "visible");
})
.on("mouseout", function() { // 鼠标移出
d3.select(this)
.attr("fill", "steelblue"); // 恢复颜色
tooltip.style("visibility", "hidden"); // 隐藏提示
});
3. 力导向图(网络可视化)
// 适合基因互作网络、蛋白质相互作用等
const nodes = [
{ id: "GeneA" }, { id: "GeneB" }, // 节点数据
{ id: "GeneC" }, { id: "GeneD" }
];
const links = [
{ source: "GeneA", target: "GeneB" }, // 连接关系
{ source: "GeneB", target: "GeneC" },
{ source: "GeneC", target: "GeneD" }
];
// 创建力模拟
const simulation = d3.forceSimulation(nodes) // 力模拟器
.force("link", d3.forceLink(links).id(d => d.id)) // 连接力
.force("charge", d3.forceManyBody().strength(-200)) // 排斥力
.force("center", d3.forceCenter(width / 2, height / 2)) // 居中力
.on("tick", () => { // 每帧更新位置
linkElements
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
nodeElements
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
4. 读取 CSV 数据
// D3 内置数据加载器
d3.csv("abundance.csv").then(data => { // 读取 CSV 文件
data.forEach(d => {
d.value = +d.value; // 字符串转数字(+ 号)
d.date = new Date(d.date); // 字符串转日期
});
drawChart(data); // 传给绑图函数
});
// 也支持 JSON、TSV
d3.json("data.json").then(data => { /* ... */ }); // 读取 JSON
d3.tsv("data.tsv").then(data => { /* ... */ }); // 读取 TSV
常见报错与解决
| 报错信息 | 原因 | 解决方法 |
|---|
Cannot read property of undefined | 数据绑定失败 | 检查 .data() 传入的数据格式 |
| 图表不显示 | SVG 尺寸为 0 | 检查 width/height 和 margin 设置 |
| 坐标轴文字重叠 | 标签太多 | 旋转标签或用 tickValues 减少刻度 |
| 比例尺输出 NaN | domain 包含非数字 | 用 +d.value 确保数据是数字类型 |
速查表
// ===== D3.js v7 速查表 =====
// 选择
d3.select("#id") // 选择单个元素
d3.selectAll(".class") // 选择所有
// 数据绑定
.data(array).join("tag") // 绑定数据并创建元素
// 比例尺
d3.scaleLinear() // 线性(连续数值)
d3.scaleBand() // 分段(分类数据)
d3.scaleTime() // 时间轴
d3.scaleOrdinal() // 离散颜色映射
d3.scaleLog() // 对数比例尺
// 坐标轴
d3.axisBottom(scale) // 底部坐标轴
d3.axisLeft(scale) // 左侧坐标轴
// 形状生成器
d3.line() // 折线
d3.area() // 面积图
d3.arc() // 弧形(饼图)
d3.pie() // 饼图布局
// 数据工具
d3.max(data, d => d.value) // 最大值
d3.min(data, d => d.value) // 最小值
d3.extent(data, d => d.value) // [最小, 最大]
d3.mean(data, d => d.value) // 平均值
// 颜色
d3.schemeCategory10 // 10 色分类配色
d3.interpolateBlues // 蓝色渐变
// 动画
.transition().duration(500) // 500ms 过渡动画
// 数据加载
d3.csv("file.csv") // 加载 CSV
d3.json("file.json") // 加载 JSON
// D3 vs Observable Plot
// D3: 完全自由度,适合复杂定制图表
// Observable Plot: 高层 API,几行代码出图,适合快速探索