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