数据可视化 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>
  1. 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() 等,可自动生成刻度、标签和网格线。

绘制坐标轴步骤:

  1. 定义比例尺。
  2. 创建轴生成器并传入比例尺。
  3. 在 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));