Skip to content

D3.js - 数据连接 (Data Join)

数据绑定(Data join)是 D3.js 中的一个核心概念。它是将一个数据数组(array)与一个 DOM 元素选择集(selection)进行关联的过程。这使得你可以根据数据创建、更新和移除元素,从而构成了 D3 数据驱动转换(data-driven transformations)的核心。本章将解释数据绑定机制,包括现代的 selection.join() 方法。

当你调用 selection.data(dataArray) 时,D3 会尝试将 dataArray 中的每个数据项与 selection 中的一个 DOM 元素进行匹配。这个比较过程会产生三个组(子选择集):

  • 进入选择集 (Enter selection): 包含那些数据项还没有对应的 DOM 元素,需要创建新元素。你使用这个选择集来 append 新元素。
  • 更新选择集 (Update selection): 包含那些成功匹配到数据项的现有 DOM 元素。你使用这个选择集来修改现有元素的属性(attributes)或样式(styles)。
  • 退出选择集 (Exit selection): 包含那些不再有对应数据项的现有 DOM 元素(例如,如果数据数组缩小了)。你使用这个选择集来 remove 元素。

这通常被称为“进入-更新-退出”(Enter-Update-Exit)模式。

数据绑定工作原理:selection.data() 方法

Section titled “数据绑定工作原理:selection.data() 方法”

selection.data(data[, keyFunction]) 方法是数据绑定的核心。

  • data: 数据值的数组(或返回数组的函数)。
  • keyFunction (可选): 一个函数,对于选择集中的每个元素和数据数组中的每个数据项,它会返回一个字符串键(string key)。这个键用于匹配数据和元素,对于数据变化或重新排序时保持对象恒定性(object constancy)至关重要。

调用 selection.data() 后,原始选择集代表 更新选择集。你可以分别使用 selection.enter() 和 selection.exit() 来访问 进入选择集 和 退出选择集。

在 selection.join() 出现之前,通常需要更显式地处理这种模式:

// 示例数据
let myData = [10, 20, 30];
function render(data) {
const listItems = d3.select("#myList")
.selectAll("li")
.data(data, d => d); // 使用键函数(数据值本身)
// 进入:为新数据添加新的 <li> 元素
listItems.enter()
.append("li")
.text(d => `New item: ${d}`);
// 更新:修改现有的 <li> 元素(已被 listItems 选中)
listItems.text(d => `Updated item: ${d}`);
// 退出:移除不再有数据对应的 <li> 元素
listItems.exit()
.remove();
}
// 初次渲染
render(myData);
// 之后,更新数据并重新渲染
// myData = [20, 40];
// render(myData);

使用 selection.join() 进行现代数据绑定

Section titled “使用 selection.join() 进行现代数据绑定”

D3 v5 引入了 selection.join(enter[, update][, exit]),它提供了一种更简洁的方式来处理进入、更新和退出选择集。每个参数都是一个函数(或用于进入选择集的选择器字符串),D3 会用相应的选择集调用它们。

使用 selection.join() 实现前面示例的代码如下:

// 示例数据
let myData = [10, 20, 30];
function renderWithJoin(data) {
d3.select("#myList")
.selectAll("li")
.data(data, d => d) // 键函数对于对象恒定性至关重要
.join(
enter => enter.append("li") // 进入
.style("color", "green") // 应用样式到新元素
.text(d => `Joined (enter): ${d}`),
update => update.style("color", "blue") // 更新
.text(d => `Joined (update): ${d}`),
exit => exit.style("color", "red") // 退出(可以在移除前设置样式)
.remove() // 然后移除
);
}
// 设置示例的 HTML
// document.body.innerHTML = '<ul id="myList"></ul>';
// d3.select("body").append("button").text("Update Data").on("click", () => {
// myData = [20, Math.random() * 50, 10]; // 改变数据以查看更新/退出效果
// renderWithJoin(myData);
// });
// renderWithJoin(myData);

如果你只需要处理 enter(例如,从初始数据创建静态元素),你可以给 join 传递一个字符串作为要添加的元素类型:selection.data(data).join("circle").attr(...)。

让我们设置一个完整的 HTML 示例来动态演示 selection.join()。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3.js 数据绑定与 join()</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body { font-family: Arial, sans-serif; }
li { margin-bottom: 5px; padding: 5px; border: 1px solid #eee; }
</style>
</head>
<body>
<button id="updateButton">更新数据</button>
<ul id="dynamicList"></ul>
<script>
let currentData = [1, 2, 3];
function updateList(data) {
d3.select("#dynamicList")
.selectAll("li")
.data(data, d => d) // 使用数据值作为键
.join(
enter => enter.append("li")
.style("background-color", "lightgreen")
.text(d => `进入: ${d}`)
.style("opacity", 0)
.transition().duration(500)
.style("opacity", 1),
update => update.style("background-color", "lightyellow")
.text(d => `更新: ${d}`),
exit => exit.style("background-color", "pink")
.transition().duration(500)
.style("opacity", 0)
.remove()
);
}
// 初次渲染
updateList(currentData);
// 改变数据的按钮
d3.select("#updateButton").on("click", () => {
// 生成一些新数据,打乱、添加和移除元素
currentData = d3.shuffle(currentData.slice()); // 打乱
if (Math.random() > 0.5 && currentData.length > 1) {
currentData.pop(); // 有时移除一个
}
if (Math.random() > 0.3) {
currentData.push(Math.floor(Math.random() * 10) + 1); // 有时添加一个
}
currentData = [...new Set(currentData)]; // 确保键的唯一性
console.log("New data:", currentData);
updateList(currentData);
});
</script>
</body>
</html>

在此示例中,点击“更新数据”按钮将改变 currentData 数组,并且 updateList 函数会重新渲染列表,通过简单的过渡效果演示进入、更新和退出阶段。

当数据项是对象或者可能发生重新排序、从中间删除或插入时,selection.data(data, keyFunction) 中的 keyFunction 就至关重要。它告诉 D3 如何唯一地识别每个数据点及其对应的 DOM 元素。如果没有键函数,D3 会按索引进行匹配,这在数据重新排序时可能导致低效的更新或丢失元素状态(如过渡效果或用户输入)。

示例:selection.data(myObjects, d => d.id) 会使用 myObjects 中每个对象的 id 属性作为其键。

数据绑定是以下场景的基础:

  • 创建条形图(bar charts),其中每个条形对应一个数据点。
  • 生成散点图(scatter plots),其中每个数据项对应一个圆点。
  • 根据变化的数据动态更新列表、表格或任何一组重复元素。
  • 实现数据进入、更新或退出时的动画和过渡效果。

掌握数据绑定和 selection.join() 是有效使用 D3.js 进行动态、数据驱动可视化的关键。请参阅 官方 selection.join 文档 了解更多示例。