跳至内容

层次结构

在你可以计算 层次布局 之前,你需要一个根节点。如果你的数据已经处于层次结构格式,例如 JSON,你可以直接将其传递给 hierarchy;否则,你可以使用 stratify 将表格数据(例如逗号分隔值 (CSV))重新排列成层次结构。

hierarchy(data, children)

示例 · 源代码 · 从指定的层次结构 data 中构建根节点。指定的 data 必须是一个表示根节点的对象。例如

js
const data = {
  name: "Eve",
  children: [
    {name: "Cain"},
    {name: "Seth", children: [{name: "Enos"}, {name: "Noam"}]},
    {name: "Abel"},
    {name: "Awan", children: [{name: "Enoch"}]},
    {name: "Azura"}
  ]
};

要构建层次结构

js
const root = d3.hierarchy(data);

指定的 children 访问器函数会为每个数据调用,从根 data 开始,并且必须返回一个表示子节点(如果有)的可迭代对象。如果未指定 children 访问器,则默认值为

js
function children(d) {
  return d.children;
}

如果 data 是一个 Map,则它会隐式转换为条目 [undefined, data],并且 children 访问器会改为默认值为

js
function children(d) {
  return Array.isArray(d) ? d[1] : null;
}

这允许你将 grouprollup 的结果传递给 hierarchy。

返回的根节点和每个后代都具有以下属性

  • node.data - 与传递给 hierarchy 的数据相关联的数据
  • node.depth - 根节点为零,每个后代生成都会增加一
  • node.height - 与任何后代叶节点之间的最大距离,或叶节点为零
  • node.parent - 父节点,或根节点为 null
  • node.children - 子节点数组(如果有),或叶节点为 undefined
  • node.value - 节点及其 后代 的可选总和值

此方法还可用于测试节点是否为 instanceof d3.hierarchy 以及扩展节点原型。

node.ancestors()

源代码 · 返回祖先节点数组,从该节点开始,然后依次是每个父节点,直到根节点。

node.descendants()

源代码 · 返回后代节点数组,从该节点开始,然后依次是每个子节点,按照拓扑顺序。

node.leaves()

源代码 · 返回叶节点数组,按照遍历顺序。叶节点是指没有子节点的节点。

node.find(filter)

源代码 · 返回从该 node 开始的层次结构中的第一个节点,其指定的 filter 返回一个真值。如果未找到此类节点,则返回 undefined。

node.path(target)

源代码 · 返回从该 node 到指定的 target 节点的层次结构中的最短路径。该路径从该 node 开始,向上移动到该 nodetarget 节点的最近公共祖先,然后向下移动到 target 节点。这对于 层次边缘捆绑 非常有用。

源代码 · 返回该 node 及其后代的链接数组,其中每个 link 都是一个定义源和目标属性的对象。每个链接的源是父节点,目标是子节点。

node.sum(value)

示例 · 源代码 · 对该 node 及其每个后代(按照 后序遍历)评估指定的 value 函数,然后返回该 node。每个节点的 node.value 属性都设置为指定的函数返回的数值加上所有子节点的组合值。该函数会传递节点的数据,并且必须返回一个非负数。value 访问器会针对 node 和每个后代(包括内部节点)进行评估;如果你只希望叶节点具有内部值,则对于任何具有子节点的节点都返回零。 例如,作为 node.count 的替代方法

js
root.sum((d) => d.value ? 1 : 0);

你必须在调用需要 node.value 的层次布局(例如 treemap)之前调用 node.sum 或 node.count。例如

js
// Construct the treemap layout.
const treemap = d3.treemap();
treemap.size([width, height]);
treemap.padding(2);

// Sum and sort the data.
root.sum((d) => d.value);
root.sort((a, b) => b.height - a.height || b.value - a.value);

// Compute the treemap layout.
treemap(root);

// Retrieve all descendant nodes.
const nodes = root.descendants();

由于 API 支持 方法链,你也可以说

js
d3.treemap()
    .size([width, height])
    .padding(2)
  (root
      .sum((d) => d.value)
      .sort((a, b) => b.height - a.height || b.value - a.value))
  .descendants()

此示例假设节点数据具有一个 value 字段。

node.count()

示例 · 源代码 · 计算该 node 下面的叶节点数量并将其分配给 node.value,对 node 的每个后代也执行此操作。如果该 node 是一个叶节点,则其计数为 1。返回该 node。另请参见 node.sum

node.sort(compare)

示例 · 源代码 · 使用指定的 compare 函数,按照 前序遍历 对该 node(如果有)的子节点以及该 node 的每个后代的子节点进行排序,然后返回该 node

指定的函数会传递两个节点 ab 进行比较。如果 a 应该在 b 之前,该函数必须返回一个小于零的值;如果 b 应该在 a 之前,该函数必须返回一个大于零的值;否则,ab 的相对顺序未指定。有关更多信息,请参见 array.sort

node.sum 不同,compare 函数会传递两个 节点 而不是两个节点的数据。例如,如果数据具有一个 value 属性,这会按节点及其所有后代的总和值的降序对节点进行排序,这是 圆形打包 的推荐做法

js
root
    .sum((d) => d.value)
    .sort((a, b) => b.value - a.value);

类似地,按降序高度(从任何后代叶节点的距离最大)然后按降序值对节点进行排序,这是 树状图冰柱图 的推荐做法

js
root
    .sum((d) => d.value)
    .sort((a, b) => b.height - a.height || b.value - a.value);

按降序高度然后按升序 ID 对节点进行排序,这是 树状图 的推荐做法

js
root
    .sum((d) => d.value)
    .sort((a, b) => b.height - a.height || d3.ascending(a.id, b.id));

如果你希望新的排序顺序影响布局,则必须在调用层次布局之前调用 node.sort;有关示例,请参见 node.sum

node[Symbol.iterator]()

源代码 · 返回 node 的后代的广度优先遍历迭代器。例如

js
for (const descendant of node) {
  console.log(descendant);
}

node.each(function, that)

示例 · 源代码 · 对 node 及其每个后代(按照 广度优先遍历)调用指定的 function,因此,只有在所有深度较小的节点以及同一深度的所有先前节点都已访问后,才会访问给定的 node。指定的函数会传递当前 descendant、基于零的遍历 index 以及该 node。如果指定了 that,则它是回调的 this 上下文。

node.eachAfter(function, that)

示例 · 源代码 · 对节点及其每个后代执行指定函数,按照后序遍历顺序,确保在访问一个节点之前,其所有后代都已被访问。指定的函数会传递当前后代、基于零的遍历索引以及此节点。如果指定了that,它将成为回调的 this 上下文。

node.eachBefore(function, that)

示例 · 源代码 · 对节点及其每个后代执行指定函数,按照前序遍历顺序,确保在访问一个节点之前,其所有祖先都已被访问。指定的函数会传递当前后代、基于零的遍历索引以及此节点。如果指定了that,它将成为回调的 this 上下文。

node.copy()

源代码 · 返回从此节点开始的子树的深层副本。(返回的深层副本共享相同的数据。)返回的节点是新树的根;返回的节点的父节点始终为 null,其深度始终为零。