D3.js 数据绑定 #
数据绑定是 D3.js 的核心概念,它将数据与 DOM 元素关联起来,使数据驱动文档的更新。理解数据绑定是掌握 D3.js 的关键。
数据绑定基础 #
核心概念 #
text
┌─────────────────────────────────────────────────────────────┐
│ 数据绑定三态模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 数据 ──► 元素 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Enter │ │ Update │ │ Exit │ │
│ │ 新数据 │ │ 已绑定 │ │ 多余元素 │ │
│ │ 需创建 │ │ 需更新 │ │ 需删除 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
绑定数据 #
selection.data() #
将数据绑定到选择:
javascript
const data = [10, 20, 30, 40, 50];
const circles = svg.selectAll('circle')
.data(data);
绑定对象数组 #
javascript
const data = [
{ name: 'A', value: 10 },
{ name: 'B', value: 20 },
{ name: 'C', value: 30 }
];
const bars = svg.selectAll('rect')
.data(data);
使用键函数 #
为数据指定唯一标识:
javascript
const data = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 3, name: 'C' }
];
const circles = svg.selectAll('circle')
.data(data, d => d.id);
selection.datum() #
绑定单个数据:
javascript
const data = { x: 100, y: 200 };
svg.select('circle')
.datum(data)
.attr('cx', d => d.x)
.attr('cy', d => d.y);
const text = d3.select('text').datum('Hello');
text.text(d => d);
Enter-Update-Exit 模式 #
完整模式 #
javascript
const data = [10, 20, 30, 40, 50];
const update = svg.selectAll('circle')
.data(data);
const enter = update.enter()
.append('circle')
.attr('cx', (d, i) => i * 50 + 25)
.attr('cy', 150)
.attr('r', 0);
update
.attr('fill', 'steelblue');
enter.merge(update)
.attr('r', d => d);
update.exit()
.remove();
Enter 选择 #
处理新数据对应的元素:
javascript
const circles = svg.selectAll('circle')
.data(data);
circles.enter()
.append('circle')
.attr('cx', (d, i) => i * 50)
.attr('cy', 150)
.attr('r', d => d)
.attr('fill', 'steelblue');
Exit 选择 #
处理多余元素:
javascript
const circles = svg.selectAll('circle')
.data(data);
circles.exit()
.transition()
.duration(500)
.attr('r', 0)
.remove();
Update 选择 #
更新已有元素:
javascript
const circles = svg.selectAll('circle')
.data(data);
circles
.transition()
.duration(500)
.attr('r', d => d)
.attr('fill', 'orange');
join 方法(推荐) #
D3 v5+ 提供了更简洁的 join 方法:
基本用法 #
javascript
svg.selectAll('circle')
.data(data)
.join('circle')
.attr('cx', (d, i) => i * 50)
.attr('cy', 150)
.attr('r', d => d)
.attr('fill', 'steelblue');
自定义 enter/update/exit #
javascript
svg.selectAll('circle')
.data(data)
.join(
enter => enter.append('circle')
.attr('cx', (d, i) => i * 50)
.attr('cy', 150)
.attr('r', 0)
.call(enter => enter.transition()
.duration(500)
.attr('r', d => d)),
update => update
.attr('fill', 'orange'),
exit => exit.call(exit => exit.transition()
.duration(500)
.attr('r', 0)
.remove())
);
简化的 join #
javascript
svg.selectAll('circle')
.data(data)
.join(
enter => enter.append('circle'),
update => update,
exit => exit.remove()
)
.attr('cx', (d, i) => i * 50)
.attr('cy', 150)
.attr('r', d => d);
数据绑定示例 #
柱状图更新 #
javascript
const data1 = [10, 20, 30, 40, 50];
const data2 = [15, 25, 35, 20, 45, 60];
function updateChart(data) {
const bars = svg.selectAll('rect')
.data(data, (d, i) => i);
bars.enter()
.append('rect')
.attr('x', (d, i) => i * 60)
.attr('y', height)
.attr('width', 50)
.attr('height', 0)
.merge(bars)
.transition()
.duration(500)
.attr('y', d => height - d * 5)
.attr('height', d => d * 5);
bars.exit()
.transition()
.duration(500)
.attr('y', height)
.attr('height', 0)
.remove();
}
updateChart(data1);
setTimeout(() => updateChart(data2), 1000);
使用 join 重写 #
javascript
function updateChart(data) {
svg.selectAll('rect')
.data(data, (d, i) => i)
.join(
enter => enter.append('rect')
.attr('x', (d, i) => i * 60)
.attr('y', height)
.attr('width', 50),
update => update,
exit => exit.transition()
.duration(500)
.attr('y', height)
.attr('height', 0)
.remove()
)
.transition()
.duration(500)
.attr('y', d => height - d * 5)
.attr('height', d => d * 5);
}
散点图更新 #
javascript
const data = [
{ x: 10, y: 20, id: 1 },
{ x: 30, y: 40, id: 2 },
{ x: 50, y: 60, id: 3 }
];
function updateScatter(data) {
const circles = svg.selectAll('circle')
.data(data, d => d.id);
circles.enter()
.append('circle')
.attr('cx', d => xScale(d.x))
.attr('cy', d => yScale(d.y))
.attr('r', 0)
.merge(circles)
.transition()
.duration(500)
.attr('r', 5)
.attr('fill', 'steelblue');
circles.exit()
.transition()
.duration(500)
.attr('r', 0)
.remove();
}
键函数详解 #
为什么需要键函数 #
javascript
const data1 = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 3, name: 'C' }
];
const data2 = [
{ id: 3, name: 'C' },
{ id: 2, name: 'B' },
{ id: 1, name: 'A' }
];
svg.selectAll('circle')
.data(data1, d => d.id);
使用索引作为键 #
javascript
svg.selectAll('circle')
.data(data, (d, i) => i);
使用属性作为键 #
javascript
svg.selectAll('circle')
.data(data, d => d.id);
svg.selectAll('circle')
.data(data, d => d.name);
数据访问 #
在回调中访问数据 #
javascript
d3.selectAll('circle')
.data(data)
.attr('cx', function(d, i, nodes) {
console.log(d);
console.log(i);
console.log(nodes);
console.log(this);
return d.x;
});
获取绑定数据 #
javascript
const data = d3.select('circle').datum();
console.log(data);
d3.selectAll('circle').each(function(d) {
console.log(d);
});
数据绑定模式 #
模式一:完整更新 #
javascript
function update(data) {
const circles = svg.selectAll('circle')
.data(data, d => d.id);
circles.enter()
.append('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 0)
.attr('fill', 'steelblue')
.merge(circles)
.transition()
.attr('r', 5);
circles.exit()
.transition()
.attr('r', 0)
.remove();
}
模式二:分离更新 #
javascript
function update(data) {
const circles = svg.selectAll('circle')
.data(data, d => d.id);
circles.enter()
.append('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 5);
circles
.attr('fill', 'orange');
circles.exit()
.remove();
}
模式三:使用 join #
javascript
function update(data) {
svg.selectAll('circle')
.data(data, d => d.id)
.join('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 5);
}
嵌套数据绑定 #
分组元素 #
javascript
const data = [
{ name: 'Group A', values: [1, 2, 3] },
{ name: 'Group B', values: [4, 5, 6] }
];
const groups = svg.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform', (d, i) => `translate(0, ${i * 100})`);
groups.selectAll('circle')
.data(d => d.values)
.enter()
.append('circle')
.attr('cx', (d, i) => i * 50)
.attr('cy', 50)
.attr('r', d => d * 3);
层次结构 #
javascript
const data = [
{
category: 'A',
items: [
{ name: 'A1', value: 10 },
{ name: 'A2', value: 20 }
]
},
{
category: 'B',
items: [
{ name: 'B1', value: 15 },
{ name: 'B2', value: 25 }
]
}
];
const groups = svg.selectAll('g.category')
.data(data)
.enter()
.append('g')
.attr('class', 'category');
groups.selectAll('rect')
.data(d => d.items)
.enter()
.append('rect')
.attr('x', (d, i) => i * 50)
.attr('height', d => d.value);
数据绑定与事件 #
事件中访问数据 #
javascript
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 5)
.on('click', function(event, d) {
console.log('Clicked:', d);
d3.select(this).attr('fill', 'red');
});
事件中修改数据 #
javascript
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.on('click', function(event, d) {
d.selected = !d.selected;
d3.select(this).classed('selected', d.selected);
});
数据绑定最佳实践 #
1. 始终使用键函数 #
javascript
svg.selectAll('circle')
.data(data, d => d.id);
2. 使用 join 简化代码 #
javascript
svg.selectAll('circle')
.data(data)
.join('circle');
3. 分离数据和视图 #
javascript
function render(data) {
svg.selectAll('circle')
.data(data, d => d.id)
.join('circle')
.attr('cx', d => xScale(d.x))
.attr('cy', d => yScale(d.y))
.attr('r', 5);
}
4. 使用过渡动画 #
javascript
svg.selectAll('circle')
.data(data)
.join(
enter => enter.append('circle')
.attr('r', 0)
.call(enter => enter.transition()
.attr('r', 5)),
update => update,
exit => exit.transition()
.attr('r', 0)
.remove()
);
常见问题 #
1. 数据绑定顺序 #
javascript
const data = [10, 20, 30];
svg.selectAll('circle')
.data(data)
.enter()
.append('circle');
2. 重复绑定 #
javascript
const circles = svg.selectAll('circle')
.data(data);
circles.enter().append('circle');
circles.attr('fill', 'steelblue');
3. 键函数不正确 #
javascript
svg.selectAll('circle')
.data(data, d => d.id);
svg.selectAll('circle')
.data(data);
数据绑定方法速查表 #
| 方法 | 描述 | 示例 |
|---|---|---|
| data | 绑定数据数组 | .data([1, 2, 3]) |
| data(key) | 使用键函数绑定 | .data(data, d => d.id) |
| datum | 绑定单个数据 | .datum({ x: 10 }) |
| enter | 获取 enter 选择 | .enter() |
| exit | 获取 exit 选择 | .exit() |
| join | 简化的 enter/exit | .join('circle') |
| merge | 合并选择 | .merge(selection) |
| order | 重新排序元素 | .order() |
| sort | 排序元素 | .sort((a, b) => a - b) |
下一步 #
现在你已经掌握了 D3.js 数据绑定的核心概念,接下来学习 SVG 基础,了解如何在 D3.js 中使用 SVG 创建图形!
最后更新:2026-03-28