跳转至

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 减少刻度
比例尺输出 NaNdomain 包含非数字+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,几行代码出图,适合快速探索