D3.js 知识速查表
本速查表汇总了 D3.js 开发中最常用的 API、技巧和模式,方便快速查阅。
模块导入
// 完整导入
import * as d3 from 'd3';
// 按需导入(推荐,减少打包体积)
import { select, selectAll } from 'd3-selection';
import { scaleLinear, scaleBand, scaleOrdinal } from 'd3-scale';
import { axisBottom, axisLeft } from 'd3-axis';
import { line, area, arc, pie } from 'd3-shape';
import { transition } from 'd3-transition';
import { json, csv, tsv } from 'd3-fetch';
import { max, min, extent, sum, group, rollup } from 'd3-array';
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceCollide } from 'd3-force';
选择与查询
选择元素
// 选择单个元素
d3.select('body')
d3.select('#my-div')
d3.select('.my-class')
// 选择多个元素
d3.selectAll('p')
d3.selectAll('.items')
d3.selectAll('li.item')
选择器语法
// 基本选择器
d3.selectAll('div')
d3.selectAll('.class')
d3.selectAll('#id')
// 组合选择器
d3.selectAll('div.chart')
d3.selectAll('ul li')
d3.selectAll('div.container > .item')
// 属性选择器
d3.selectAll('[data-type="primary"]')
d3.selectAll('[disabled]')
// 伪类选择器
d3.selectAll('li:first-child')
d3.selectAll('tr:nth-child(even)')
选择集操作
// 修改属性
selection.attr('class', 'new-class')
selection.attr({'id': 'myId', 'title': 'tooltip'})
// 修改样式
selection.style('color', 'red')
selection.style({'font-size': '14px', 'font-weight': 'bold'})
// 获取属性值
selection.attr('class') // 获取值
// 添加/删除/检查类名
selection.classed('active', true) // 添加
selection.classed('active', false) // 删除
selection.classed('active') // 检查
// 修改 HTML/文本内容
selection.html('<span>content</span>')
selection.text('new text')
// 获取元素
selection.node() // 单个元素
selection.nodes() // 所有元素数组
DOM 操作
// 添加元素
selection.append('svg')
selection.append('g')
selection.append('circle')
selection.insert('div', ':first-child') // 在指定位置插入
// 删除元素
selection.remove()
// 复制元素
selection.clone(true) // 带事件处理器
// 清空内容
selection.selectAll('*').remove()
selection.html('')
数据绑定
基本用法
// 绑定数组数据
selection.data(dataArray)
// 键函数(用于精确匹配)
selection.data(dataArray, d => d.id)
// 更新数据
selection.data(newData)
// datum:绑定单个数据对象
selection.datum(singleObject)
Enter-Update-Exit 模式
// 传统写法
selection.data(data)
.join('div') // 等价于 enter + update + exit
.text(d => d.name)
// 或者分开写
const selection = svg.selectAll('circle').data(data)
const enter = selection.enter().append('circle')
const update = selection
const exit = selection.exit().remove()
// 合并处理
enter.merge(update)
.attr('r', 10)
// 分别处理
selection.join(
enter => enter.append('circle').attr('r', 0),
update => update,
exit => exit.remove()
)
比例尺
常用比例尺
// 线性比例尺
d3.scaleLinear()
.domain([0, 100])
.range([0, 500])
.nice() // 取整
// 带原点对称的比例尺
d3.scaleSymmetriclog()
d3.scaleSymlog()
// 条形比例尺(离散)
d3.scaleBand()
.domain(['A', 'B', 'C'])
.range([0, width])
.padding(0.1)
// 时间比例尺
d3.scaleTime()
.domain([new Date('2024-01-01'), new Date('2024-12-31')])
.range([0, width])
// 序数比例尺
d3.scaleOrdinal()
.domain(['A', 'B', 'C'])
.range(['red', 'green', 'blue'])
// 颜色比例尺
d3.scaleSequential(d3.interpolateRainbow)
d3.scaleCategory10() // D3 内置配色
// 大小比例尺(sqrt 保证面积成比例)
d3.scaleSqrt()
.domain([1, 100])
.range([3, 20])
比例尺操作
// 坐标映射
scale(value) // 值→输出
scale.invert(output) // 输出→值(仅 linear/time)
// 区间取整
scale.nice()
scale.ticks(5) // 获取参考刻度
// 克隆比例尺(用于复用)
scale.copy()
坐标轴
创建坐标轴
const xAxis = d3.axisBottom(scale)
const yAxis = d3.axisLeft(scale)
// 变体
d3.axisTop(scale) // 顶部
d3.axisBottom(scale) // 底部
d3.axisLeft(scale) // 左侧
d3.axisRight(scale) // 右侧
配置选项
d3.axisBottom(scale)
.ticks(10) // 刻度数量
.tickValues([0, 50, 100]) // 自定义刻度值
.tickFormat(d => `${d}%`) // 刻度文本格式
.tickSize(10) // 刻度线长度
.tickSizeInner(6) // 内刻度线
.tickSizeOuter(6) // 外刻度线
.tickPadding(10) // 文字与刻度间距
绘制与样式
// 添加坐标轴
svg.append('g')
.call(d3.axisBottom(scale))
// 移除轴线
svg.selectAll('.domain').remove()
// 自定义样式
svg.selectAll('.tick line')
.attr('stroke', '#ccc')
svg.selectAll('.tick text')
.attr('font-size', '12px')
图形生成器
折线生成器
const line = d3.line()
.x(d => xScale(d.x))
.y(d => yScale(d.y))
.curve(d3.curveCatmullRom) // 曲线类型
// 可选曲线
d3.curveLinear // 直线
d3.curveCatmullRom // 平滑曲线(经过所有点)
d3.curveBasis // B 样条曲线
d3.curveStep // 阶梯
面积生成器
const area = d3.area()
.x(d => xScale(d.x))
.y0(height) // 基线
.y1(d => yScale(d.y)) // 顶部
.curve(d3.curveCatmullRom)
弧形生成器
const arc = d3.arc()
.innerRadius(50) // 内半径(环形图)
.outerRadius(100) // 外半径
.cornerRadius(4) // 圆角
.startAngle(0)
.endAngle(Math.PI / 2)
// 使用 pie 数据
d3.pie().value(d => d.value)(data)
arc(d) // d 是 pie() 返回的元素,包含 startAngle/endAngle
饼图生成器
const pie = d3.pie()
.value(d => d.value) // 数值访问器
.sort((a, b) => b.value - a.value) // 排序
.sort(null) // 不排序
.padAngle(0.02) // 扇形间隙
.startAngle(0) // 起始角度
const pieData = pie(dataArray)
// pieData: [{data, value, startAngle, endAngle}, ...]
过渡与动画
创建过渡
selection.transition()
.duration(1000) // 持续时间(毫秒)
.delay(500) // 延迟开始
.ease(d3.easeLinear) // 缓动函数
.ease(d3.easeQuadIn) // 缓入
.ease(d3.easeQuadOut) // 缓出
.ease(d3.easeQuadInOut) // 缓入缓出
.ease(d3.easeElastic) // 弹性效果
.ease(d3.easeBounce) // 弹跳效果
缓动函数
d3.easeLinear // 线性
d3.easeQuad // 二次
d3.easeCubic // 三次(默认)
d3.easeSin // 正弦
d3.easeExp // 指数
d3.easeCircle // 圆
d3.easeElastic // 弹性
d3.easeBack // 回退
d3.easeBounce // 弹跳
链式动画
element.transition()
.duration(500)
.attr('r', 50)
.transition() // 新的过渡
.duration(500)
.attr('r', 100)
// 补间动画(中间状态)
element.transition()
.attrTween('r', function() {
const interpolator = d3.interpolate(this.getAttribute('r'), 100);
return t => interpolator(t);
})
数据加载
JSON/CSV/TSV
// JSON
d3.json('data.json').then(data => console.log(data))
// CSV(自动类型转换)
d3.csv('data.csv', d3.autoType).then(data => console.log(data))
// TSV
d3.tsv('data.tsv').then(data => console.log(data))
// 并行加载
Promise.all([
d3.json('users.json'),
d3.json('posts.json')
]).then(([users, posts]) => { /* 处理 */ })
错误处理
d3.json('data.json')
.then(data => console.log(data))
.catch(error => console.error('加载失败:', error))
数组工具
统计函数
d3.max(array, accessor) // 最大值
d3.min(array, accessor) // 最小值
d3.extent(array, accessor) // [min, max]
d3.sum(array, accessor) // 求和
d3.mean(array, accessor) // 平均值
d3.median(array, accessor) // 中位数
d3.variance(array, accessor) // 方差
d3.deviation(array, accessor) // 标准差
分组聚合
// 分组
const grouped = d3.group(array, d => d.category)
// Map { "A" => [...], "B" => [...] }
// 聚合
const rolled = d3.rollup(array, v => v.length, d => d.category)
// Map { "A" => 10, "B" => 15 }
Array.from(grouped, ([key, value]) => ({key, value}))
// 转换为数组
查找与排序
// 二分查找(用于定位)
const bisect = d3.bisector(d => d.date).left
const index = bisect(data, searchValue)
// 排序
const sorted = [...data].sort((a, b) => a.value - b.value)
// 唯一值
d3.set(array).values()
力导向图
创建模拟器
const simulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink(links).id(d => d.id).distance(80))
.force('charge', d3.forceManyBody().strength(-300))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide().radius(30))
.force('radial', d3.forceRadial(radius, x, y))
simulation.on('tick', () => {
// 更新图形位置
link.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
.attr('x2', d => d.target.x).attr('y2', d => d.target.y)
node.attr('cx', d => d.x).attr('cy', d => d.y)
})
// 手动停止
simulation.stop()
simulation.tick()
// 重新加热
simulation.alpha(1).restart()
拖拽交互
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart()
event.subject.fx = event.subject.x
event.subject.fy = event.subject.y
}
function dragged(event) {
event.subject.fx = event.x
event.subject.fy = event.y
}
function dragended(event) {
if (!event.active) simulation.alphaTarget(0)
event.subject.fx = null
event.subject.fy = null
}
selection.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended))
事件处理
基本用法
selection.on('click', function(event, d) {
// this: 当前 DOM 元素
// event: 原生事件对象
// d: 绑定的数据
console.log(d)
})
// 鼠标事件
selection.on('click', handler)
selection.on('mouseover', handler)
selection.on('mouseout', handler)
selection.on('mousemove', handler)
// 键盘事件
selection.on('keydown', handler)
// 多个事件
selection.on('click.mouseover', handler)
工具函数
颜色处理
d3.color('#ff0000') // 解析颜色
d3.rgb('red') // RGB
d3.hsl('red') // HSL
// 颜色操作
color.darker(0.5) // 变暗
color.brighter(0.5) // 变亮
color.opacity // 透明度
格式化
d3.format('.2f')(3.14159) // "3.14"(小数位数)
d3.format('$,.0f')(1000) // "$1,000"(货币)
d3.format('.0%')(0.567) // "57%"(百分比)
// 时间格式化
d3.timeFormat('%Y-%m-%d')(new Date()) // "2024-01-01"
d3.timeParse('%Y-%m-%d')('2024-01-01') // Date 对象
插值器
d3.interpolate(0, 100)(0.5) // 50
d3.interpolate('red', 'blue')(0.5) // 紫色中间色
// 颜色插值
d3.interpolateRgb('red', 'blue')
d3.interpolateHsl('red', 'blue')
// 数值插值
d3.interpolateNumber(0, 100)
生成唯一ID
d3.uniqueId() // 每次调用生成递增唯一 ID
d3.randomNormal() // 正态分布随机数
d3.randomUniform(min, max) // 均匀分布随机数
常用配色方案
// D3 内置配色
d3.schemeCategory10 // 10 种颜色
d3.schemeAccent // 8 种颜色
d3.schemeDark2 // 8 种颜色
d3.schemePastel1 // 9 种颜色
d3.schemeSet1 // 9 种颜色
// 连续配色(适合数值映射)
d3.interpolateWarm // 暖色调
d3.interpolateCool // 冷色调
d3.interpolateViridis // 黄蓝绿
d3.interpolateMagma // 火成岩色
d3.interpolatePlasma // 等离子色
常见问题处理
SVG 居中
// 方式一:transform
g.attr('transform', `translate(${width/2},${height/2})`)
// 方式二:分组居中
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
Y 轴反转(坐标原点在上)
const yScale = d3.scaleLinear()
.domain([0, max])
.range([height, 0]) // 反向:最大值在上
提示框定位
tooltip
.style('left', (event.pageX + 15) + 'px')
.style('top', (event.pageY - 10) + 'px')
// pageX/Y 相对于文档左上角,避免使用 offsetX/Y(相对于目标元素)
处理大数据量
// 分批渲染
const BATCH_SIZE = 1000;
function renderBatch(startIndex) {
const endIndex = Math.min(startIndex + BATCH_SIZE, data.length);
svg.selectAll('.dot')
.data(data.slice(startIndex, endIndex))
.join('circle')
// ... 绑定数据
if (endIndex < data.length) {
requestAnimationFrame(() => renderBatch(endIndex));
}
}
Canvas vs SVG
// 数据量 < 1000:使用 SVG(交互方便)
// 数据量 1000-10000:考虑 Canvas
// 数据量 > 10000:必须使用 Canvas 或 WebGL
// Canvas 绘制示例
const canvas = d3.select('canvas')
.attr('width', width)
.attr('height', height)
const context = canvas.node().getContext('2d')
// 创建 Canvas 上下文的选择器
d3.select(canvas.node().canvas)
性能优化技巧
| 场景 | 优化方法 |
|---|---|
| 大量元素渲染 | 使用 Canvas 而非 SVG |
| 频繁更新 | 使用 join 而非 enter().append() 每帧重新创建 |
| 复杂过渡 | 使用 attrTween 进行补间 |
| 缩放交互 | 结合 d3-zoom 使用 |
| 大数据计算 | 使用 Web Workers 后台处理 |
| 颜色映射 | 预先计算,避免每帧重新映射 |