Layouting
We regularly get asked how to handle layouting in React Flow. While we could build some basic layouting into React Flow, we believe that you know your app's requirements best and with so many options out there we think it's better you choose the best right tool for the job (not to mention it'd be a whole bunch of work for us).
That doesn't help very much if you don't know what the options are, so this guide is here to help! We'll split things up into resources for layouting nodes and resources for routing edges.
To start let's put together a simple example flow that we can use as a base for testing out the different layouting options.
Each of the examples that follow will be built on this empty flow. Where possible
we've tried to keep the examples confined to just one index.js
file so it's easy
for you to compare how they're set up.
Layouting Nodes
For layouting nodes, there are a few third-party libraries that we think are worth checking out:
Library | Dynamic node sizes | Sub-flow layouting | Edge routing | Bundle size |
---|---|---|---|---|
Dagre (opens in a new tab) | Yes | Yes¹ | No | |
D3-Hierarchy (opens in a new tab) | No | No | No | |
D3-Force (opens in a new tab) | Yes | No | No | |
ELK (opens in a new tab) | Yes | Yes | Yes |
¹ Dagre currently has an open issue (opens in a new tab) that prevents it from laying out sub-flows correctly if any nodes in the sub-flow are connected to nodes outside the sub-flow.
We've loosely ordered these options from simplest to most complex, where dagre is largely a drop-in solution and elkjs is a full-blown highly configurable layouting engine. Below, we'll take a look at a brief example of how each of these libraries can be used with React Flow. For dagre and elkjs specifically, we have some separate examples you can refer back to here and here.
Dagre
- Repo: https://github.com/dagrejs/dagre (opens in a new tab)
- Docs: https://github.com/dagrejs/dagre/wiki#configuring-the-layout (opens in a new tab)
Dagre is a simple library for layouting directed graphs. It has minimal configuration options and a focus on speed over choosing the most optimal layout. If you need to organise your flows into a tree, we highly recommend dagre.
With no effort at all we get a well-organised tree layout! Whenever
getLayoutedElements
is called, we'll reset the dagre graph and set the graph's
direction (either left-to-right or top-to-bottom) based on the direction
prop.
Dagre needs to know the dimensions of each node in order to lay them out, so we
iterate over our list of nodes and add them to dagre's internal graph.
After laying out the graph, we'll return an object with the layouted nodes and edges. We do this by mapping over the original list of nodes and updating each node's position according to node stored in the dagre graph.
Documentation for dagre's configuration options can be found here (opens in a new tab), including properties to set for spacing and alignment.
D3-Hierarchy
- Repo: https://github.com/d3/d3-hierarchy (opens in a new tab)
- Docs: https://d3js.org/d3-hierarchy (opens in a new tab)
When you know your graph is a tree with a single root node, d3-hierarchy can provide a handful of interesting layouting options. While the library can layout a simple tree just fine, it also has layouting algorithms for tree maps, partition layouts, and enclosure diagrams.
D3-hierarchy expects your graphs to have a single root node, so it won't work in all cases. It's also important to note that d3-hierarchy assigns the same width and height to all nodes when calculating the layout, so it's not the best choice if you're displaying lots of different node types.
D3-Force
- Repo: https://github.com/d3/d3-force (opens in a new tab)
- Docs: https://d3js.org/d3-force (opens in a new tab)
Force something more interesting than a tree, a force-directed layout might be the way to go. D3-Force is a physics-based layouting library that can be used position nodes by applying different forces to them.
As a consequence, it's a little more complicated to configure and use compared to dagre and d3-hierarchy. Importantly, d3-force's layouting algorithm is iterative, so we need a way to keep computing the layout across multiple renders.
First, let's see what it does:
We've changed our getLayoutedElements
to a hook called useLayoutedElements
instead. Additonally, instead of passing in the nodes and edges explicitly, we'll
use get getNodes
and getEdges
functions from the useReactFlow
hook. This
is important when combined with the store selector in initialised
because it
will prevent us from reconfiguring the simulation any time the nodes update.
The simulation is configured with a number of different forces applied so you can see how they interact: play around in your own code to see how you want to configure those forces. You can find the documentation and some different examples of d3-force here (opens in a new tab).
Rectangular collisions
D3-Force has a built-in collision force, but it assumes nodes are circles. We've
thrown together a custom force in collision.js
that uses a similar algorithm
but accounts for our rectangular nodes instead. Feel free to steal it or let us
know if you have any suggestions for improvements!
The tick function progresses the simulation by one step and then updates React Flow with the new node positions. We've also included a demonstration on how to handle node dragging while the simulation is running: if your flow isn't interactive you can ignore that part!
For larger graphs, computing the force layout every render forever is going to incur a big performance hit. In this example we have a simple toggle to turn the layouting on and off, but you might want to come up with some other approach to only compute the layout when necessary.
Elkjs
- Repo: https://github.com/kieler/elkjs (opens in a new tab)
- Docs: https://eclipse.dev/elk/reference.html (opens in a new tab) (good luck!)
Elkjs is certainly the most configurable option available, but it's also the most complicated. Elkjs is a Java library that's been ported to JavaScript, and it provides a huge number of options for configuring the layout of your graph.
At it's most basic we can compute layouts similar to dagre, but because the layouting
algorithm runs asynchronously we need to create a useLayoutedElements
hook similar
to the one we created for d3-force.
The ELK reference is your new best friend We don't often recommend elkjs because it's complexity makes it difficult for us to support folks when they need it. If you do decide to use it, you'll want to keep the original Java API reference (opens in a new tab) handy.
We've also included a few examples of some of the other layouting algorithms available, including a non-interactive force layout.
Honourable Mentions
Of course, we can't go through every layouting library out there: we'd never work on anything else! Here are some other libraries we've come across that might be worth taking a look at:
-
If you want to use dagre or d3-hierarchy but need to support nodes with different dimensions, both d3-flextree (opens in a new tab) and entitree-flex (opens in a new tab) look promising.
You can find an example of how to use entitree-flex with React Flow here.
-
Cola.js (opens in a new tab) looks like a promising option for so-called "constraint-based" layouts. We haven't had time to properly investigate it yet, but it looks like you can achieve results similar to d3-force but with a lot more control.
Routing Edges
If you don't have any requirements for edge routing, you can use one of the layouting libraries above to position nodes and let the edges fall wherever they may. Otherwise, you'll want to look into some libraries and techniques for edge routing.
Your options here are more limited than for node layouting, but here are some resources we thought looked promising:
- react-flow-smart-edge (opens in a new tab)
- Routing Orthogonal Diagram Connectors in JavaScript (opens in a new tab)
If you do explore some custom edge routing options, consider contributing back to the community by writing a blog post or creating a library!
Our editable edge Pro Example could also be used as a starting point for implementing a custom edge that can be routed along a specific path.