使用 WebCola 和 D3.js 创建分层布局

一个好的布局对于揭示连接数据的内在结构至关重要。分层布局通常用于显示图节点之间的父子关系。在现实世界的数据中,一个子节点可能有多个父节点,因此无法使用现成的 D3.js 分层布局实现。我们使用 [...]

Decorative image

一个好的布局对于揭示连接数据的内在结构至关重要。分层布局通常用于显示图节点之间的父子关系。在现实世界的数据中,一个子节点可能有多个父节点,因此无法使用现成的 D3.js 分层布局实现。我们使用 WebCola 和 D3.js,在几行代码中使用声明式约束规范绘制分层图布局。

现实世界有着丰富的相互联系,我认为我们的用户界面应该模仿这些联系。网络中的连接传递着信息。如果描绘得当,粗略地浏览一下网络,就能发现表面上看不到的内在结构。鉴于客户数据的高度关联性,Alyne 决定在可扩展可视化方面投入一些时间,以直观的方式表现数据及其之间的关联。

在 Alyne,利用它最多的用例之一是我们的对象库。我们让客户能够在我们应用程序的对象库中复制他们的数字和物理资产。通常情况下,这些资产是分层的,我们发现网络图是一个很好的工具,可以让客户鸟瞰他们的对象景观。

网络可视化

力导向网络是可视化网络的最简单、最强大的方法之一。力定向网络重新排列节点和边,使节点重叠和边交叉的数量降到最低。力导向布局能很好地表示网络,因为它们能揭示常见的模式,如节点集群、高度数节点、连接组件等,但不能直接描述层次信息。

有几个出色的库,如D3,可以计算网络布局的位置信息。

在这篇文章中,我们将特别关注数据的分层表示。D3 提供了开箱即用的分层布局,以分层方式计算节点的位置。但是,分层布局适用于树状格式(隐式父子结构)的数据,其限制条件是一个节点不能有一个以上的父节点。因此,当数据结构看起来像图时,我们无法使用d3-hierarchy创建树状布局来表示层次信息。

为了解决这个问题,我们可以利用D3 的强制定向布局来实现节点不重叠,并对几何节点位置施加额外的约束来实现分层布局。这完全可以在 D3 中实现,不过,用纯 JavaScript 编写约束有点繁琐。

还有一种更简单的方法--网络可乐

WebCola 为我们提供了一种通过几何约束来指定布局信息的方法。此外,它还能动态生成流布局、非重叠节点等约束,以简化网络图。我喜欢 WebCola,因为它使用起来既简单又灵活。

让我们从一个简单的 d3 力布局网络开始,数据如下。正如您所看到的,这些数据中没有固有的父子结构。因此,为了以分层方式排列节点,我们为每个节点引入了一个层级属性;它表示节点在层级中的位置。

我们将使用 D3 力布局库为以下数据显示 vanilla 力定向布局。

const nodes = [
  { id: 1, level: 1 }, { id: 2, level: 2 },
  { id: 3, level: 2 }, { id: 4, level: 2 },
  { id: 5, level: 3 }, { id: 6, level: 3 },
  { id: 7, level: 3 }, { id: 8, level: 3 }, 
  { id: 9, level: 3 }, { id: 10, level: 3 },
  { id: 10, level: 3 }, { id: 11, level: 4 },
  { id: 12, level: 4 }, { id: 13, level: 4 },
  { id: 14, level: 4 }, { id: 15, level: 4 },
  { id: 16, level: 4 }
];
const links = [
  { start: 1, end: 2 },
  { start: 1, end: 3 },
  { start: 1, end: 4 },
  { start: 2, end: 5 },
  { start: 2, end: 6 },
  { start: 3, end: 7 },
  { start: 3, end: 8 },
  { start: 3, end: 9 },
  { start: 4, end: 5 },
  { start: 4, end: 9 },
  { start: 4, end: 10 },
  { source: 5, target: 11 },
  { source: 5, target: 12 },
  { source: 7, target: 13 },
  { source: 8, target: 14 },
  { source: 10, target: 15 },
  { source: 10, target: 16 },
  { source: 9, target: 16 },
];

为了便于观察,在上面的可视化图示中,我们显示的节点都具有相同的 level 属性使用相同的颜色,我们根据 levellevel 值越大,半径越小。

如图所示,力导向布局只是在各种力的帮助下为非重叠节点生成约束。但在 D3 中,没有一种简单的方法可以根据节点的外生属性分层排列数据。

我们可以利用 WebCola 添加几何约束,根据节点的级别属性对不同级别的节点进行对齐。

网络可乐制约因素

WebCola 中有不同类别的约束,如对齐、分组、相等约束等。我们将使用对齐约束和不等式约束使节点沿 Y 轴对齐到同一水平。这些约束可以用 JSON 格式指定如下:

{
  "type": "alignment",
  "axis": "y",
  "offsets": [
    { "node": 1, "offset": 0 },
    { "node": 2, "offset": 0 },
    { "node": 3, "offset": 0 }
  ]
}

此指定约束将使索引 1、2 和 3 的节点沿 Y 轴(即水平方向)对齐。 offset 表示节点中心的位移,通常用于大小可变的节点。

现在,让我们尝试将网络排列成一棵树。我们需要以下两种约束条件:

  1. 对齐限制 - 我们希望具有相同级别属性的节点具有相同的 y 统筹
  2. 平等约束- 为提高可读性,同一层级的节点应在左右两侧留出边距。

因此,在我们的示例中,我们按级别属性对节点进行分组,并为同一组中的每个节点指定沿 Y 轴的对齐约束。此外,在每个组中,我们沿 x 轴指定位置相等约束,间隙为 50 像素;这有助于我们清除同一级别上的节点。间隙值可以根据节点的大小动态生成,但这需要根据具体的渲染情况来决定。

const constraints = [];
const { nodes, links } = loadData();
const groups = _.groupBy(nodes, "level");

for (const level of Object.keys(groups)) {
  const nodeGroup = groups[level];
  const constraint = {
    type: "alignment",
    axis: "y",
    offsets: [],
  };
  let prevNodeId = -1;
  for (const node of nodeGroup) {
    constraint.offsets.push({
      node: _.findIndex(nodes, (d) => d.id === node.id),
      offset: 0,
    });

    if (prevNodeId !== -1) {
      constraints.push({
        axis: "x",
        left: _.findIndex(nodes, (d) => d.id === prevNodeId),
        right: _.findIndex(nodes, (d) => d.id === node.id),
        gap: 50,
      });
    }

    prevNodeId = node.id;
  }

  constraints.push(constraint);
}

d3Cola
  .nodes(nodes)
  .links(links)
  .constraints(constraints)
  .flowLayout("y", 80)
  .linkDistance(50)
  .symmetricDiffLinkLengths(40)
  .avoidOverlaps(true)
  .on("tick", ticked)
  .start(10, 40, 50);

基于节点级别属性的约束是用户自定义的约束。除了这些用户定义的约束外,WebCola 还会动态生成约束,以确定链接长度(linkDistance、flowLayout)、非重叠约束(avoidOverlaps)等。

我们在模拟中应用动态生成的约束条件进行 10 次迭代,应用用户定义的约束条件进行 40 次迭代,同时应用两组约束条件进行 50 次迭代。这些数字可根据模拟中节点和约束条件的数量进行启发式调整。

有了这些约束条件,尽管没有树状数据结构,我们也能通过力定向模拟得到类似树状的布局。我们可以看到,具有相同级别值的节点具有相同的颜色,并且位于相同的高度。此外,我们的可视化效果对于具有多个父节点的节点也很稳定。

这只是 WebCola 如何通过几行代码在网络图中应用不同布局的一个缩影。我建议您查看SetCola,这是一种用于创建 WebCola 约束的高级语言,只需几行代码,您就可以创建复杂的图形布局。

[content_block id=53166]