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.
For layouting nodes, there are a few third-party libraries that we think are worth checking out:
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.
- Repo: https://github.com/dagrejs/dagre
- Docs: https://github.com/dagrejs/dagre/wiki#configuring-the-layout
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
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, including properties to set for spacing and alignment.
In this example we use
Element.getBoundingClientRect to get the dimensions of
each node. You should think twice about doing this in your own apps if you allow
zooming: the dimensions of the nodes will change as the user zooms in and out!
Dagre also expects every node in the graph to have the same dimensions. If you have lots of different-sized nodes you may want to consider one of the other options below, or configuring dagre with the largest dimensions you expect to see.
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.
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
instead. Additonally, instead of passing in the nodes and edges explicitly, we'll
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.
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.
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.
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 handy.
We've also included a few examples of some of the other layouting algorithms available, including a non-interactive force layout.
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 and entitree-flex look promising.
- Cola.js 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.
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:
- elkjs (again!) has some algorithms just for edge routing, such as libavoid.
If you do explore some custom edge routing options, consider contributing back to the community by writing a blog post or creating a library!