{T}

D3.js v7+ 数据可视化指南

D3.js(Data-Driven Documents)是前端数据可视化领域最强大的底层库,通过数据驱动 DOM 操作,提供灵活的绑定、变换和过渡能力。D3 v7 基于 ES Module,全面拥抱现代 JavaScript。

核心理念

图表渲染中…
特性说明
数据驱动数据决定 DOM 结构,数据变化驱动视觉更新
底层控制不提供现成图表,提供构建图表的原子能力
模块化v7 拆分为 30+ 独立模块,按需引入
ES Module原生 ESM 支持,Tree Shaking 友好

安装与引入

完整引入

bash
npm install d3
javascript
import * as d3 from 'd3'

按需引入(推荐)

javascript
import { select, scaleLinear, axisBottom, line } from 'd3'

CDN 引入

html
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>

选择集与数据绑定

选择元素

javascript
const svg = d3.select('#chart')
  .append('svg')
  .attr('width', 600)
  .attr('height', 400)
 
d3.selectAll('.bar')
  .style('fill', 'steelblue')

数据绑定与 join

javascript
const data = [10, 20, 30, 40, 50]
 
d3.select('svg')
  .selectAll('circle')
  .data(data)
  .join('circle')
  .attr('cx', (d, i) => i * 80 + 40)
  .attr('cy', (d) => 200 - d * 3)
  .attr('r', (d) => d * 0.5)
  .attr('fill', 'steelblue')

Enter / Update / Exit 模式

javascript
function update(data) {
  const circles = svg.selectAll('circle').data(data)
 
  circles
    .join(
      (enter) =>
        enter
          .append('circle')
          .attr('cx', (d, i) => i * 60 + 30)
          .attr('cy', 200)
          .attr('r', 0)
          .call((e) => e.transition().duration(500).attr('r', (d) => d)),
      (update) =>
        update.call((u) =>
          u.transition().duration(500).attr('r', (d) => d)
        ),
      (exit) => exit.call((e) => e.transition().duration(500).attr('r', 0).remove())
    )
}

比例尺(Scales)

比例尺是 D3 的核心概念,将数据域映射到视觉域。

常用比例尺

比例尺用途输入 → 输出
scaleLinear连续数值连续数值
scaleBand分类数据离散带宽
scaleOrdinal分类数据离散颜色/值
scaleTime时间数据连续像素
scaleSequential连续数值颜色插值
javascript
const xScale = d3.scaleLinear()
  .domain([0, 100])
  .range([0, 500])
 
const yScale = d3.scaleBand()
  .domain(['A', 'B', 'C', 'D'])
  .range([0, 300])
  .padding(0.2)
 
const colorScale = d3.scaleOrdinal()
  .domain(['A', 'B', 'C'])
  .range(['#4e79a7', '#f28e2b', '#e15759'])

坐标轴(Axes)

javascript
const xAxis = d3.axisBottom(xScale)
  .ticks(5)
  .tickFormat(d3.format('.0f'))
 
const yAxis = d3.axisLeft(yScale)
 
svg.append('g')
  .attr('transform', 'translate(0, 300)')
  .call(xAxis)
 
svg.append('g')
  .call(yAxis)

常见图表实现

柱状图

javascript
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
const height = 400
const margin = { top: 20, right: 30, bottom: 40, left: 40 }
 
const x = d3.scaleBand()
  .domain(data.map((d) => d.name))
  .range([margin.left, width - margin.right])
  .padding(0.2)
 
const y = d3.scaleLinear()
  .domain([0, d3.max(data, (d) => d.value)])
  .nice()
  .range([height - margin.bottom, margin.top])
 
const svg = d3.select('#chart')
  .append('svg')
  .attr('viewBox', [0, 0, width, height])
 
svg.append('g')
  .attr('transform', `translate(0,${height - margin.bottom})`)
  .call(d3.axisBottom(x))
 
svg.append('g')
  .attr('transform', `translate(${margin.left},0)`)
  .call(d3.axisLeft(y))
 
svg.selectAll('.bar')
  .data(data)
  .join('rect')
  .attr('class', 'bar')
  .attr('x', (d) => x(d.name))
  .attr('y', (d) => y(d.value))
  .attr('width', x.bandwidth())
  .attr('height', (d) => y(0) - y(d.value))
  .attr('fill', 'steelblue')

折线图

javascript
const line = d3.line()
  .x((d) => x(d.date))
  .y((d) => y(d.value))
  .curve(d3.curveMonotoneX)
 
svg.append('path')
  .datum(data)
  .attr('fill', 'none')
  .attr('stroke', 'steelblue')
  .attr('stroke-width', 2)
  .attr('d', line)

饼图

javascript
const pie = d3.pie()
  .value((d) => d.value)
  .sort(null)
 
const arc = d3.arc()
  .innerRadius(0)
  .outerRadius(150)
 
const arcs = svg.selectAll('path')
  .data(pie(data))
  .join('path')
  .attr('d', arc)
  .attr('fill', (d) => colorScale(d.data.name))

过渡动画(Transitions)

javascript
svg.selectAll('.bar')
  .data(newData)
  .join('rect')
  .transition()
  .duration(750)
  .ease(d3.easeCubicInOut)
  .attr('y', (d) => y(d.value))
  .attr('height', (d) => y(0) - y(d.value))

缓动函数

函数效果
easeLinear线性
easeCubicInOut平滑起止
easeBounceOut弹跳
easeElasticOut弹性
easeBackInOut回弹

交互

鼠标事件

javascript
svg.selectAll('.bar')
  .on('mouseover', function (event, d) {
    d3.select(this).attr('fill', 'orange')
    tooltip.style('opacity', 1).html(`${d.name}: ${d.value}`)
  })
  .on('mousemove', function (event) {
    tooltip
      .style('left', `${event.pageX + 10}px`)
      .style('top', `${event.pageY - 20}px`)
  })
  .on('mouseout', function () {
    d3.select(this).attr('fill', 'steelblue')
    tooltip.style('opacity', 0)
  })

缩放与平移

javascript
const zoom = d3.zoom()
  .scaleExtent([0.5, 5])
  .on('zoom', (event) => {
    chartGroup.attr('transform', event.transform)
  })
 
svg.call(zoom)

拖拽

javascript
const drag = d3.drag()
  .on('start', function (event) {
    d3.select(this).raise().attr('stroke', 'black')
  })
  .on('drag', function (event) {
    d3.select(this)
      .attr('cx', (d) => (d.x = event.x))
      .attr('cy', (d) => (d.y = event.y))
  })
  .on('end', function () {
    d3.select(this).attr('stroke', null)
  })
 
svg.selectAll('circle').call(drag)

数据处理

D3-array 工具

javascript
d3.mean([1, 2, 3, 4, 5])
d3.median([1, 2, 3, 4, 5])
d3.deviation([1, 2, 3, 4, 5])
d3.extent([1, 2, 3, 4, 5])
d3.bin().thresholds(10)(data)
d3.group(data, (d) => d.category)
d3.rollup(data, (v) => v.length, (d) => d.category)

数据加载

javascript
const csv = await d3.csv('data.csv')
const json = await d3.json('data.json')
const tsv = await d3.tsv('data.tsv')

地理可视化

javascript
const projection = d3.geoMercator()
  .center([104, 35])
  .scale(600)
  .translate([width / 2, height / 2])
 
const path = d3.geoPath().projection(projection)
 
svg.selectAll('path')
  .data(geoFeatures)
  .join('path')
  .attr('d', path)
  .attr('fill', (d) => colorScale(d.properties.value))
  .attr('stroke', '#fff')

与框架集成

Vue 3 + D3

Vue SFC
<template>
  <div ref="chartRef"></div>
</template>
 
<script setup>
import { ref, onMounted, watch } from 'vue'
import * as d3 from 'd3'
 
const props = defineProps({ data: Array })
const chartRef = ref(null)
 
function render(data) {
  d3.select(chartRef.value).selectAll('*').remove()
  const svg = d3.select(chartRef.value).append('svg')
  // ... 绑定数据绘制
}
 
onMounted(() => render(props.data))
watch(() => props.data, render)
</script>

React + D3

jsx
function BarChart({ data }) {
  const ref = useRef()
 
  useEffect(() => {
    d3.select(ref.current).selectAll('*').remove()
    const svg = d3.select(ref.current).append('svg')
    // ... 绑定数据绘制
  }, [data])
 
  return <div ref={ref} />
}

D3 v7 新特性

特性说明
ES Module全面 ESM,支持 Tree Shaking
d3.group / d3.rollup替代 d3.nest,更简洁的分组 API
selection.join简化 Enter/Update/Exit 模式
async 数据加载d3.csv/json 等返回 Promise
d3.scaleRadial径向比例尺
d3.bisect改进的二分查找

最佳实践

  1. SVG viewBox:使用 viewBox 实现响应式图表
  2. margin convention:统一使用 margin 对象管理边距
  3. 数据驱动:始终通过数据绑定操作 DOM,避免手动操作
  4. 过渡动画:使用 transition 而非即时更新,提升用户体验
  5. 按需引入:只引入需要的 D3 子模块,减小打包体积
  6. 组件化:将图表封装为可复用函数或类
  7. 无障碍:为 SVG 元素添加 aria-labeltitle 子元素