学习布局

布局

我们经常被问到如何在 React Flow 中处理布局。虽然我们可以在 React Flow 中构建一些基本的布局,但我们相信 **您最了解应用程序的要求**,并且有这么多选择,我们认为最好让您选择最适合工作的工具(更不用说对我们来说这将是一大堆工作)。

如果您不知道有哪些选择,这并不能提供太多帮助,因此本指南将为您提供帮助!我们将把内容分成节点布局资源和边路由资源。

首先,让我们创建一个简单的示例流程,作为测试不同布局选项的基础。

export default function App() {
  const data: string = "world"

  return <h1>Hello {data}</h1>
}

只读

以下每个示例都将基于此空流程构建。如果可能,我们尝试将示例限制在一个 index.js 文件中,以便您可以轻松比较它们的设置方式。

节点布局

对于节点布局,我们认为有一些值得一试的第三方库

动态节点大小子流程布局边路由捆绑包大小
Dagre是¹
D3-Hierarchy
D3-Force
ELK

¹ Dagre 目前有一个 未解决的问题,如果子流程中的任何节点连接到子流程外部的节点,它将无法正确地布局子流程。

我们大致按照从最简单到最复杂的顺序对这些选项进行了排序,其中 dagre 基本上是一个直接替换的解决方案,而 elkjs 则是一个功能齐全、高度可配置的布局引擎。在下面,我们将简要介绍如何将这些库中的每一个与 React Flow 一起使用。特别是对于 dagre 和 elkjs,我们有一些单独的示例供您参考 此处此处

Dagre

Dagre 是一个用于布局有向图的简单库。它只有最少的配置选项,并且注重速度而不是选择最优布局。如果您需要将流程整理成树状结构,我们强烈推荐 dagre

export default function App() {
  const data: string = "world"

  return <h1>Hello {data}</h1>
}

只读

无需任何操作,我们就能得到一个井井有条的树状布局!每当调用 getLayoutedElements 时,我们将重置 dagre 图并根据 direction 属性设置图的方向(从左到右或从上到下)。Dagre 需要知道每个节点的尺寸才能对其进行布局,因此我们遍历节点列表并将它们添加到 dagre 的内部图中。

布局完图后,我们将返回一个包含布局后的节点和边的对象。我们通过遍历原始节点列表并根据 dagre 图中存储的节点更新每个节点的位置来实现这一点。

Dagre 配置选项的文档可以在 此处 找到,包括用于设置间距和对齐的属性。

D3-Hierarchy

当您知道您的图是一个具有单个根节点的树时,d3-hierarchy 可以提供一些有趣的布局选项。虽然该库可以很好地布局简单的树,但它也具有用于树状图、分区布局和外接图布局的算法。

export default function App() {
  const data: string = "world"

  return <h1>Hello {data}</h1>
}

只读
💡

D3-hierarchy 要求您的图具有单个根节点,因此它并不适用于所有情况。同样重要的是要注意,d3-hierarchy 在计算布局时为所有节点分配相同的宽度和高度,因此如果您要显示大量不同类型的节点,它不是最佳选择。

D3-Force

如果要比树更有趣的东西,力导向布局可能是您的选择。D3-Force 是一个基于物理的布局库,它可以通过对节点施加不同的力来定位节点。

因此,与 dagre 和 d3-hierarchy 相比,它在配置和使用上要复杂一些。重要的是,d3-force 的布局算法是迭代的,因此我们需要一种方法来跨多个渲染持续计算布局。

首先,让我们看看它能做什么

export default function App() {
  const data: string = "world"

  return <h1>Hello {data}</h1>
}

只读

我们已经将 getLayoutedElements 更改为一个名为 useLayoutedElements 的钩子。此外,我们不会显式地传入节点和边,而是使用 useReactFlow 钩子中的 getNodesgetEdges 函数。这与 initialised 中的存储选择器结合使用时非常重要,因为它可以防止我们在节点更新时重新配置模拟。

模拟配置了多种不同的作用力,以便您可以观察它们之间的相互作用:在您自己的代码中随意调整这些作用力以观察您想要的配置效果。您可以 在这里 找到 d3-force 的文档和一些不同的示例。

💡

矩形碰撞 D3-Force 内置了碰撞力,但它假设节点是圆形。我们在 collision.js 中创建了一个自定义力,它使用类似的算法,但考虑了我们的矩形节点。您可以随意使用它,或者如果您有任何改进建议,请告诉我们!

tick 函数通过一步推进模拟,然后使用新的节点位置更新 React Flow。我们还包含了一个关于如何在模拟运行时处理节点拖动的演示:如果您的流程没有交互性,您可以忽略这部分!

💡

对于较大的图,每次渲染都计算力布局将导致严重的性能损耗。在这个示例中,我们有一个简单的切换来打开和关闭布局,但您可能需要想出其他方法来仅在必要时计算布局。

Elkjs

Elkjs 绝对是最可配置的选项,但它也是最复杂的。Elkjs 是一个已移植到 JavaScript 的 Java 库,它提供了大量选项来配置图的布局。

export default function App() {
  const data: string = "world"

  return <h1>Hello {data}</h1>
}

只读

在最基本的情况下,我们可以计算类似于 dagre 的布局,但由于布局算法是异步运行的,因此我们需要创建一个类似于为 d3-force 创建的 useLayoutedElements 钩子。

💡

ELK 参考是您新的最佳朋友 我们通常不推荐 elkjs,因为它的复杂性使得我们在用户需要时难以提供支持。如果您确实决定使用它,您需要随时备好原始的 Java API 参考

我们还包含了一些其他可用布局算法的示例,包括非交互式力布局。

值得一提的

当然,我们无法遍历所有可用的布局库:我们永远不会做其他事情!以下是一些我们遇到的可能值得一看的其他库。

  • 如果您想使用 dagre 或 d3-hierarchy,但需要支持具有不同维度的节点,则 d3-flextreeentitree-flex 看起来很有希望。

    您可以 在这里 找到如何将 entitree-flex 与 React Flow 结合使用的示例。

  • Cola.js 看起来是所谓的“基于约束”布局的有希望的选择。我们还没有时间进行适当的调查,但看起来您可以实现类似于 d3-force 的结果,但可以更好地控制。

路由边缘

如果您对边缘路由没有任何要求,您可以使用上述布局库之一来定位节点,并让边缘落在它们可能的位置。否则,您需要研究一些用于边缘路由的库和技术。

您在这里的选择比节点布局更有限,但以下是一些我们认为很有希望的资源。

如果您确实探索了一些自定义边缘路由选项,请考虑通过撰写博客文章或创建库来回馈社区!

我们的 可编辑边缘 Pro 示例 也可以用作实现可沿特定路径路由的自定义边缘的起点。