D3.js - 数据连接 (Data Join)
D3.js - 数据绑定
Section titled “D3.js - 数据绑定”数据绑定(Data join)是 D3.js 中的一个核心概念。它是将一个数据数组(array)与一个 DOM 元素选择集(selection)进行关联的过程。这使得你可以根据数据创建、更新和移除元素,从而构成了 D3 数据驱动转换(data-driven transformations)的核心。本章将解释数据绑定机制,包括现代的 selection.join() 方法。
什么是数据绑定?
Section titled “什么是数据绑定?”当你调用 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() 来访问 进入选择集 和 退出选择集。
传统的进入-更新-退出模式
Section titled “传统的进入-更新-退出模式”在 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(...)。
使用 selection.join() 的工作示例
Section titled “使用 selection.join() 的工作示例”让我们设置一个完整的 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 函数会重新渲染列表,通过简单的过渡效果演示进入、更新和退出阶段。
键函数为何重要
Section titled “键函数为何重要”当数据项是对象或者可能发生重新排序、从中间删除或插入时,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 文档 了解更多示例。