Overview
We regularly get asked how to handle layouting in React Flow. We have not implemented our own layouting solution yet, but will present some viable external libraries on this page. We’ll split things up into resources for layouting nodes and resources for routing edges.
You can test out some of the layouting options in our playground or have a look at the examples we’ve put together.
To start let’s put together a simple example flow that we can use as a base for testing out the different layouting options.
Example: learn/layouting-flow-1-empty
App.jsx
import { useCallback } from 'react';
import {
ReactFlow,
ReactFlowProvider,
useNodesState,
useEdgesState,
useReactFlow,
} from '@xyflow/react';
import { initialNodes, initialEdges } from './nodes-edges.js';
import '@xyflow/react/dist/style.css';
const getLayoutedElements = (nodes, edges) => {
return { nodes, edges };
};
const LayoutFlow = () => {
const { fitView } = useReactFlow();
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onLayout = useCallback(() => {
const layouted = getLayoutedElements(nodes, edges);
setNodes([...layouted.nodes]);
setEdges([...layouted.edges]);
fitView();
}, [nodes, edges]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
colorMode="system"
/>
);
};
export default function () {
return (
<ReactFlowProvider>
<LayoutFlow />
</ReactFlowProvider>
);
}index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);nodes-edges.js
export const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input' },
position: { x: 0, y: 0 },
},
{
id: '2',
data: { label: 'node 2' },
position: { x: 0, y: 100 },
},
{
id: '2a',
data: { label: 'node 2a' },
position: { x: 0, y: 200 },
},
{
id: '2b',
data: { label: 'node 2b' },
position: { x: 0, y: 300 },
},
{
id: '2c',
data: { label: 'node 2c' },
position: { x: 0, y: 400 },
},
{
id: '2d',
data: { label: 'node 2d' },
position: { x: 0, y: 500 },
},
{
id: '3',
data: { label: 'node 3' },
position: { x: 200, y: 100 },
},
];
export const initialEdges = [
{ id: 'e12', source: '1', target: '2', animated: true },
{ id: 'e13', source: '1', target: '3', animated: true },
{ id: 'e22a', source: '2', target: '2a', animated: true },
{ id: 'e22b', source: '2', target: '2b', animated: true },
{ id: 'e22c', source: '2', target: '2c', animated: true },
{ id: 'e2c2d', source: '2c', target: '2d', animated: true },
];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:
¹ Dagre currently has an open issue 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
- 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 organize your flows into a tree, we highly recommend dagre.
Example: learn/layouting-flow-2-dagre
App.jsx
import Dagre from '@dagrejs/dagre';
import { useCallback } from 'react';
import {
ReactFlow,
ReactFlowProvider,
Panel,
useNodesState,
useEdgesState,
useReactFlow,
} from '@xyflow/react';
import { initialNodes, initialEdges } from './nodes-edges.js';
import '@xyflow/react/dist/style.css';
const getLayoutedElements = (nodes, edges, options) => {
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
g.setGraph({ rankdir: options.direction });
edges.forEach((edge) => g.setEdge(edge.source, edge.target));
nodes.forEach((node) =>
g.setNode(node.id, {
...node,
width: node.measured?.width ?? 0,
height: node.measured?.height ?? 0,
}),
);
Dagre.layout(g);
return {
nodes: nodes.map((node) => {
const position = g.node(node.id);
// We are shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
const x = position.x - (node.measured?.width ?? 0) / 2;
const y = position.y - (node.measured?.height ?? 0) / 2;
return { ...node, position: { x, y } };
}),
edges,
};
};
const LayoutFlow = () => {
const { fitView } = useReactFlow();
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onLayout = useCallback(
(direction) => {
console.log(nodes);
const layouted = getLayoutedElements(nodes, edges, { direction });
setNodes([...layouted.nodes]);
setEdges([...layouted.edges]);
fitView();
},
[nodes, edges],
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
colorMode="system"
>
<Panel position="top-right">
<button onClick={() => onLayout('TB')}>vertical layout</button>
<button onClick={() => onLayout('LR')}>horizontal layout</button>
</Panel>
</ReactFlow>
);
};
export default function () {
return (
<ReactFlowProvider>
<LayoutFlow />
</ReactFlowProvider>
);
}index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);nodes-edges.js
export const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input' },
position: { x: 0, y: 0 },
},
{
id: '2',
data: { label: 'node 2' },
position: { x: 0, y: 100 },
},
{
id: '2a',
data: { label: 'node 2a' },
position: { x: 0, y: 200 },
},
{
id: '2b',
data: { label: 'node 2b' },
position: { x: 0, y: 300 },
},
{
id: '2c',
data: { label: 'node 2c' },
position: { x: 0, y: 400 },
},
{
id: '2d',
data: { label: 'node 2d' },
position: { x: 0, y: 500 },
},
{
id: '3',
data: { label: 'node 3' },
position: { x: 200, y: 100 },
},
];
export const initialEdges = [
{ id: 'e12', source: '1', target: '2', animated: true },
{ id: 'e13', source: '1', target: '3', animated: true },
{ id: 'e22a', source: '2', target: '2a', animated: true },
{ id: 'e22b', source: '2', target: '2b', animated: true },
{ id: 'e22c', source: '2', target: '2c', animated: true },
{ id: 'e2c2d', source: '2c', target: '2d', animated: true },
];With no effort at all we get a well-organized 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, including properties to set for spacing and alignment.
D3-Hierarchy
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.
Example: learn/layouting-flow-3-d3-hierarchy
App.jsx
import { useCallback } from 'react';
import { stratify, tree } from 'd3-hierarchy';
import {
ReactFlow,
ReactFlowProvider,
Panel,
useNodesState,
useEdgesState,
useReactFlow,
} from '@xyflow/react';
import { initialNodes, initialEdges } from './nodes-edges';
import '@xyflow/react/dist/style.css';
const g = tree();
const getLayoutedElements = (nodes, edges, options) => {
if (nodes.length === 0) return { nodes, edges };
const { width, height } = document
.querySelector(`[data-id="${nodes[0].id}"]`)
.getBoundingClientRect();
const hierarchy = stratify()
.id((node) => node.id)
.parentId((node) => edges.find((edge) => edge.target === node.id)?.source);
const root = hierarchy(nodes);
const layout = g.nodeSize([width * 2, height * 2])(root);
return {
nodes: layout
.descendants()
.map((node) => ({ ...node.data, position: { x: node.x, y: node.y } })),
edges,
};
};
const LayoutFlow = () => {
const { fitView } = useReactFlow();
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onLayout = useCallback(
(direction) => {
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
nodes,
edges,
{
direction,
},
);
setNodes([...layoutedNodes]);
setEdges([...layoutedEdges]);
fitView();
},
[nodes, edges],
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
colorMode="system"
>
<Panel position="top-right">
<button onClick={onLayout}>layout</button>
</Panel>
</ReactFlow>
);
};
export default function () {
return (
<ReactFlowProvider>
<LayoutFlow />
</ReactFlowProvider>
);
}index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);nodes-edges.js
export const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input' },
position: { x: 0, y: 0 },
},
{
id: '2',
data: { label: 'node 2' },
position: { x: 0, y: 100 },
},
{
id: '2a',
data: { label: 'node 2a' },
position: { x: 0, y: 200 },
},
{
id: '2b',
data: { label: 'node 2b' },
position: { x: 0, y: 300 },
},
{
id: '2c',
data: { label: 'node 2c' },
position: { x: 0, y: 400 },
},
{
id: '2d',
data: { label: 'node 2d' },
position: { x: 0, y: 500 },
},
{
id: '3',
data: { label: 'node 3' },
position: { x: 200, y: 100 },
},
];
export const initialEdges = [
{ id: 'e12', source: '1', target: '2', animated: true },
{ id: 'e13', source: '1', target: '3', animated: true },
{ id: 'e22a', source: '2', target: '2a', animated: true },
{ id: 'e22b', source: '2', target: '2b', animated: true },
{ id: 'e22c', source: '2', target: '2c', animated: true },
{ id: 'e2c2d', source: '2c', target: '2d', animated: true },
];D3-Force
For 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 to 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:
Example: learn/layouting-flow-4-d3-force
App.jsx
import { forceSimulation, forceLink, forceManyBody, forceX, forceY } from 'd3-force';
import { useCallback, useMemo, useRef } from 'react';
import {
ReactFlow,
ReactFlowProvider,
Panel,
useNodesState,
useEdgesState,
useReactFlow,
useNodesInitialized,
} from '@xyflow/react';
import { initialNodes, initialEdges } from './nodes-edges.js';
import { collide } from './collide.js';
import '@xyflow/react/dist/style.css';
const simulation = forceSimulation()
.force('charge', forceManyBody().strength(-1000))
.force('x', forceX().x(0).strength(0.05))
.force('y', forceY().y(0).strength(0.05))
.force('collide', collide())
.alphaTarget(0.05)
.stop();
const useLayoutedElements = () => {
const { getNodes, setNodes, getEdges, fitView } = useReactFlow();
const initialized = useNodesInitialized();
// You can use these events if you want the flow to remain interactive while
// the simulation is running. The simulation is typically responsible for setting
// the position of nodes, but if we have a reference to the node being dragged,
// we use that position instead.
const draggingNodeRef = useRef(null);
const dragEvents = useMemo(
() => ({
start: (_event, node) => (draggingNodeRef.current = node),
drag: (_event, node) => (draggingNodeRef.current = node),
stop: () => (draggingNodeRef.current = null),
}),
[],
);
return useMemo(() => {
let nodes = getNodes().map((node) => ({
...node,
x: node.position.x,
y: node.position.y,
}));
let edges = getEdges().map((edge) => edge);
let running = false;
// If React Flow hasn't initialized our nodes with a width and height yet, or
// if there are no nodes in the flow, then we can't run the simulation!
if (!initialized || nodes.length === 0) return [false, {}, dragEvents];
simulation.nodes(nodes).force(
'link',
forceLink(edges)
.id((d) => d.id)
.strength(0.05)
.distance(100),
);
// The tick function is called every animation frame while the simulation is
// running and progresses the simulation one step forward each time.
const tick = () => {
getNodes().forEach((node, i) => {
const dragging = draggingNodeRef.current?.id === node.id;
// Setting the fx/fy properties of a node tells the simulation to "fix"
// the node at that position and ignore any forces that would normally
// cause it to move.
if (dragging) {
nodes[i].fx = draggingNodeRef.current.position.x;
nodes[i].fy = draggingNodeRef.current.position.y;
} else {
delete nodes[i].fx;
delete nodes[i].fy;
}
});
simulation.tick();
setNodes(
nodes.map((node) => ({
...node,
position: { x: node.fx ?? node.x, y: node.fy ?? node.y },
})),
);
window.requestAnimationFrame(() => {
// Give React and React Flow a chance to update and render the new node
// positions before we fit the viewport to the new layout.
fitView();
// If the simulation hasn't been stopped, schedule another tick.
if (running) tick();
});
};
const toggle = () => {
if (!running) {
getNodes().forEach((node, index) => {
let simNode = nodes[index];
Object.assign(simNode, node);
simNode.x = node.position.x;
simNode.y = node.position.y;
});
}
running = !running;
running && window.requestAnimationFrame(tick);
};
const isRunning = () => running;
return [true, { toggle, isRunning }, dragEvents];
}, [initialized, dragEvents, getNodes, getEdges, setNodes, fitView]);
};
const LayoutFlow = () => {
const [nodes, , onNodesChange] = useNodesState(initialNodes);
const [edges, , onEdgesChange] = useEdgesState(initialEdges);
const [initialized, { toggle, isRunning }, dragEvents] = useLayoutedElements();
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodeDragStart={dragEvents.start}
onNodeDrag={dragEvents.drag}
onNodeDragStop={dragEvents.stop}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
colorMode="system"
>
<Panel>
{initialized && (
<button onClick={toggle}>
{isRunning() ? 'Stop' : 'Start'} force simulation
</button>
)}
</Panel>
</ReactFlow>
);
};
export default function () {
return (
<ReactFlowProvider>
<LayoutFlow />
</ReactFlowProvider>
);
}collide.js
import { quadtree } from 'd3-quadtree';
export function collide() {
let nodes = [];
let force = (alpha) => {
const tree = quadtree(
nodes,
(d) => d.x,
(d) => d.y,
);
for (const node of nodes) {
const r = node.measured.width / 2;
const nx1 = node.x - r;
const nx2 = node.x + r;
const ny1 = node.y - r;
const ny2 = node.y + r;
tree.visit((quad, x1, y1, x2, y2) => {
if (!quad.length) {
do {
if (quad.data !== node) {
const r = node.measured.width / 2 + quad.data.width / 2;
let x = node.x - quad.data.x;
let y = node.y - quad.data.y;
let l = Math.hypot(x, y);
if (l < r) {
l = ((l - r) / l) * alpha;
node.x -= x *= l;
node.y -= y *= l;
quad.data.x += x;
quad.data.y += y;
}
}
} while ((quad = quad.next));
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
}
};
force.initialize = (newNodes) => (nodes = newNodes);
return force;
}
export default collide;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);nodes-edges.js
export const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input' },
position: { x: 0, y: 0 },
},
{
id: '2',
data: { label: 'node 2' },
position: { x: 0, y: 100 },
},
{
id: '2a',
data: { label: 'node 2a' },
position: { x: 0, y: 200 },
},
{
id: '2b',
data: { label: 'node 2b' },
position: { x: 0, y: 300 },
},
{
id: '2c',
data: { label: 'node 2c' },
position: { x: 0, y: 400 },
},
{
id: '2d',
data: { label: 'node 2d' },
position: { x: 0, y: 500 },
},
{
id: '3',
data: { label: 'node 3' },
position: { x: 200, y: 100 },
},
];
export const initialEdges = [
{ id: 'e12', source: '1', target: '2', animated: true },
{ id: 'e13', source: '1', target: '3', animated: true },
{ id: 'e22a', source: '2', target: '2a', animated: true },
{ id: 'e22b', source: '2', target: '2b', animated: true },
{ id: 'e22c', source: '2', target: '2c', animated: true },
{ id: 'e2c2d', source: '2c', target: '2d', animated: true },
];We’ve changed our getLayoutedElements to a hook called useLayoutedElements instead.
Additionally, 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 initialized 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.
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!
Elkjs
- Repo: https://github.com/kieler/elkjs
- Docs: https://eclipse.dev/elk/reference.html (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.
Example: learn/layouting-flow-6-elkjs
App.jsx
import ELK from 'elkjs/lib/elk.bundled.js';
import { useCallback } from 'react';
import {
ReactFlow,
ReactFlowProvider,
Panel,
useNodesState,
useEdgesState,
useReactFlow,
} from '@xyflow/react';
import { initialNodes, initialEdges } from './nodes-edges.js';
import '@xyflow/react/dist/style.css';
const elk = new ELK();
const useLayoutedElements = () => {
const { getNodes, setNodes, getEdges, fitView } = useReactFlow();
const defaultOptions = {
'elk.algorithm': 'layered',
'elk.layered.spacing.nodeNodeBetweenLayers': 100,
'elk.spacing.nodeNode': 80,
};
const getLayoutedElements = useCallback((options) => {
const layoutOptions = { ...defaultOptions, ...options };
const graph = {
id: 'root',
layoutOptions: layoutOptions,
children: getNodes().map((node) => ({
...node,
width: node.measured.width,
height: node.measured.height,
})),
edges: getEdges(),
};
elk.layout(graph).then(({ children }) => {
// By mutating the children in-place we saves ourselves from creating a
// needless copy of the nodes array.
children.forEach((node) => {
node.position = { x: node.x, y: node.y };
});
setNodes(children);
fitView();
});
}, []);
return { getLayoutedElements };
};
const LayoutFlow = () => {
const [nodes, , onNodesChange] = useNodesState(initialNodes);
const [edges, , onEdgesChange] = useEdgesState(initialEdges);
const { getLayoutedElements } = useLayoutedElements();
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
colorMode="system"
>
<Panel position="top-right">
<button
onClick={() =>
getLayoutedElements({
'elk.algorithm': 'layered',
'elk.direction': 'DOWN',
})
}
>
vertical layout
</button>
<button
onClick={() =>
getLayoutedElements({
'elk.algorithm': 'layered',
'elk.direction': 'RIGHT',
})
}
>
horizontal layout
</button>
<button
onClick={() =>
getLayoutedElements({
'elk.algorithm': 'org.eclipse.elk.radial',
})
}
>
radial layout
</button>
<button
onClick={() =>
getLayoutedElements({
'elk.algorithm': 'org.eclipse.elk.force',
})
}
>
force layout
</button>
</Panel>
</ReactFlow>
);
};
export default function () {
return (
<ReactFlowProvider>
<LayoutFlow />
</ReactFlowProvider>
);
}index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);nodes-edges.js
export const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input' },
position: { x: 0, y: 0 },
},
{
id: '2',
data: { label: 'node 2' },
position: { x: 0, y: 100 },
},
{
id: '2a',
data: { label: 'node 2a' },
position: { x: 0, y: 200 },
},
{
id: '2b',
data: { label: 'node 2b' },
position: { x: 0, y: 300 },
},
{
id: '2c',
data: { label: 'node 2c' },
position: { x: 0, y: 400 },
},
{
id: '2d',
data: { label: 'node 2d' },
position: { x: 0, y: 500 },
},
{
id: '3',
data: { label: 'node 3' },
position: { x: 200, y: 100 },
},
];
export const initialEdges = [
{ id: 'e12', source: '1', target: '2', animated: true },
{ id: 'e13', source: '1', target: '3', animated: true },
{ id: 'e22a', source: '2', target: '2a', animated: true },
{ id: 'e22b', source: '2', target: '2b', animated: true },
{ id: 'e22c', source: '2', target: '2c', animated: true },
{ id: 'e2c2d', source: '2c', target: '2d', animated: true },
];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’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 and entitree-flex look promising.
You can find an example of how to use entitree-flex with React Flow here.
-
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.
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:
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.