Skip to content

D3.js - 集合 API

有效地处理数据是 D3.js 的核心。虽然 D3 之前有自己的集合类型,如 d3.map 和 d3.set,但现代 JavaScript 提供了原生的 Map 和 Set 对象。现在 D3.js 鼓励使用这些原生结构,以及标准的 JavaScript 对象和数组方法。d3-array 模块(主 D3 bundle 的一部分)提供了用于数组操作、分组和聚合的强大实用工具。

大多数与集合相关的实用工具,特别是来自 d3-array 的,都包含在主 D3.js 库中。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3.js Collections Example</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<div id="output"></div>
<script>
// Your D3.js and data manipulation code will go here
</script>
</body>
</html>

D3.js 可以与 JavaScript 的内置数据结构无缝集成。

标准的 JavaScript 对象常用于表示单个数据项。您可以使用点符号或方括号符号访问它们的属性(properties)。

现代 JavaScript 提供了内置方法来处理对象属性:

  • Object.keys(object): 返回给定对象自身可枚举属性名(keys)的数组。
  • Object.values(object): 返回给定对象自身可枚举属性值(values)的数组。
  • Object.entries(object): 返回给定对象自身可枚举字符串键控属性 [key, value] 对的数组。

示例:

const monthData = { "jan": 1, "feb": 2, "mar": 3, "apr": 4 };
console.log(Object.keys(monthData)); // Output: ["jan", "feb", "mar", "apr"] (输出:["一月", "二月", "三月", "四月"])
console.log(Object.values(monthData)); // Output: [1, 2, 3, 4] (输出:[1, 2, 3, 4])
console.log(Object.entries(monthData));
// Output: [["jan", 1], ["feb", 2], ["mar", 3], ["apr", 4]] (输出:[["一月", 1], ["二月", 2], ["三月", 3], ["四月", 4]])
// To display in HTML (example) (在 HTML 中显示(示例))
document.getElementById('output').innerHTML += `<p>Keys: ${Object.keys(monthData).join(', ')}</p>`;

JavaScript 的原生 Map 对象存储键值对(key-value pairs),并记住键的原始插入顺序。键可以是任何类型(不限于字符串)。这通常比 D3 历史上的 d3.map 更受欢迎。

  • new Map(): 创建一个新的 Map。
  • map.set(key, value): 添加或更新一个条目(entry)。
  • map.get(key): 获取键对应的值。
  • map.has(key): 检查是否存在某个键。
  • map.delete(key): 删除一个条目。
  • map.clear(): 删除所有条目。
  • map.size: 返回条目数量。
  • map.keys()、map.values()、map.entries(): 返回键、值或条目的迭代器(iterators)。

示例:

const cityPopulations = new Map();
cityPopulations.set("New York", 8399000);
cityPopulations.set("London", 8982000);
console.log(cityPopulations.get("London")); // Output: 8982000 (输出:8982000)
console.log(cityPopulations.has("Paris")); // Output: false (输出:false)
console.log(cityPopulations.size); // Output: 2 (输出:2)
// Iterating over a Map (often converted to an array for D3 data joins) (迭代 Map(通常转换为数组用于 D3 数据连接))
// const mapEntriesArray = Array.from(cityPopulations.entries());
// Or for D3 data binding, you might use Array.from(cityPopulations) which gives [[key, value], ...] (或者对于 D3 数据绑定,您可以使用 Array.from(cityPopulations),它会生成 [[key, value], ...])
let mapOutput = "<p>City Populations (Map):</p><ul>";
for (const [city, population] of cityPopulations) {
mapOutput += `<li>${city}: ${population.toLocaleString()}</li>`;
}
mapOutput += "</ul>";
document.getElementById('output').innerHTML += mapOutput;

JavaScript 的原生 Set 对象允许您存储任何类型的唯一值。这比 D3 历史上的 d3.set 更受欢迎。

  • new Set(): 创建一个新的 Set。
  • set.add(value): 添加一个值。
  • set.has(value): 检查是否存在某个值。
  • set.delete(value): 删除一个值。
  • set.clear(): 删除所有值。
  • set.size: 返回值数量。
  • set.values() (或直接迭代 set): 返回值的迭代器。

示例:

const fruitSet = new Set();
fruitSet.add("apple").add("banana").add("apple"); // "apple" is added only once ("apple" 只添加一次)
console.log(fruitSet.has("orange")); // Output: false (输出:false)
console.log(fruitSet.size); // Output: 2 (apple, banana) (输出:2 (苹果, 香蕉))
// const fruitArray = Array.from(fruitSet); // Useful for D3 data joins (对于 D3 数据连接很有用)
let setOutput = "<p>Unique Fruits (Set):</p><ul>";
for (const fruit of fruitSet) {
setOutput += `<li>${fruit}</li>`;
}
setOutput += "</ul>";
document.getElementById('output').innerHTML += setOutput;

d3-array 模块提供了强大的数据转换函数,特别是 d3.group() 和 d3.rollup(),它们取代了旧的 d3.nest() 功能。

d3.group() 通过一个或多个键函数对可迭代对象(iterable,例如对象数组)中的项目进行分组。它返回一个 JavaScript Map。如果提供了多个键函数,它会创建嵌套的 Map。

示例:按属性对数据进行分组。

const data = [
{ category: "A", value: 10 },
{ category: "B", value: 20 },
{ category: "A", value: 30 },
{ category: "C", value: 40 },
{ category: "B", value: 50 }
];
// Group by 'category' (按 'category' 分组)
const groupedByCategory = d3.group(data, d => d.category);
// groupedByCategory is a Map: (groupedByCategory 是一个 Map:)
// Map {
// "A" => [ { category: "A", value: 10 }, { category: "A", value: 30 } ],
// "B" => [ { category: "B", value: 20 }, { category: "B", value: 50 } ],
// "C" => [ { category: "C", value: 40 } ]
// }
console.log(groupedByCategory.get("A"));
// Output: [ { category: "A", value: 10 }, { category: "A", value: 30 } ] (输出:[ { category: "A", value: 10 }, { category: "A", value: 30 } ])
// To convert to an array of {key, values} objects (similar to d3.nest().entries()): (转换为 {key, values} 对象的数组(类似于 d3.nest().entries()):)
const groupedArray = Array.from(groupedByCategory, ([key, values]) => ({ key, values }));
console.log(groupedArray);
/* Output: (输出:)
[
{ key: "A", values: [ { category: "A", value: 10 }, { category: "A", value: 30 } ] },
{ key: "B", values: [ { category: "B", value: 20 }, { category: "B", value: 50 } ] },
{ key: "C", values: [ { category: "C", value: 40 } ] }
]
*/

d3.rollup() 类似于 d3.group(),但它还会对每个组中的值应用一个 reduce 函数,返回约简后的值而不是值的数组。这对于聚合操作(如求和、计数、平均值等)非常有用。

示例:按类别对值求和。

const dataForRollup = [
{ category: "A", value: 10 },
{ category: "B", value: 20 },
{ category: "A", value: 30 },
{ category: "C", value: 40 },
{ category: "B", value: 50 }
];
// Sum values for each category (计算每个类别的总和)
const sumByCategory = d3.rollup(
dataForRollup,
v => d3.sum(v, d => d.value), // Reduce function: sum the 'value' property of items in group 'v' (约简函数:对组 'v' 中项目的 'value' 属性求和)
d => d.category // Key function: group by 'category' (键函数:按 'category' 分组)
);
// sumByCategory is a Map: (sumByCategory 是一个 Map:)
// Map {
// "A" => 40, (10 + 30)
// "B" => 70, (20 + 50)
// "C" => 40
// }
console.log(sumByCategory.get("A")); // Output: 40 (输出:40)
// To convert to an array of {key, value} (where value is the rolled-up result): (转换为 {key, value} 的数组(其中 value 是约简结果):)
const rolledUpArray = Array.from(sumByCategory, ([key, value]) => ({ key, value }));
console.log(rolledUpArray);
/* Output: (输出:)
[
{ key: "A", value: 40 },
{ key: "B", value: 70 },
{ key: "C", value: 40 }
]
*/

通过利用原生 JavaScript 数据结构和 d3-array 提供的强大实用工具,您可以有效地管理和准备数据,以便使用 D3.js 进行可视化。