React Flow has no built in layouting but it can be achieved with a third party library like elkjs or dagre.
Layouting Source Code
index.js
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useCallback } from 'react';
import ReactFlow, {
ReactFlowProvider,
addEdge,
removeElements,
isNode,
} from 'react-flow-renderer';
import dagre from 'dagre';
import initialElements from './initial-elements';
import './layouting.css';
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
// In order to keep this example simple the node width and height are hardcoded.
// In a real world app you would use the correct width and height values of
// const nodes = useStoreState(state => state.nodes) and then node.__rf.width, node.__rf.height
const nodeWidth = 172;
const nodeHeight = 36;
const getLayoutedElements = (elements, direction = 'TB') => {
const isHorizontal = direction === 'LR';
dagreGraph.setGraph({ rankdir: direction });
elements.forEach((el) => {
if (isNode(el)) {
dagreGraph.setNode(el.id, { width: nodeWidth, height: nodeHeight });
} else {
dagreGraph.setEdge(el.source, el.target);
}
});
dagre.layout(dagreGraph);
return elements.map((el) => {
if (isNode(el)) {
const nodeWithPosition = dagreGraph.node(el.id);
el.targetPosition = isHorizontal ? 'left' : 'top';
el.sourcePosition = isHorizontal ? 'right' : 'bottom';
// unfortunately we need this little hack to pass a slightly different position
// to notify react flow about the change. Moreover 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).
el.position = {
x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000,
y: nodeWithPosition.y - nodeHeight / 2,
};
}
return el;
});
};
const layoutedElements = getLayoutedElements(initialElements);
const LayoutFlow = () => {
const [elements, setElements] = useState(layoutedElements);
const onConnect = (params) =>
setElements((els) =>
addEdge({ ...params, type: 'smoothstep', animated: true }, els)
);
const onElementsRemove = (elementsToRemove) =>
setElements((els) => removeElements(elementsToRemove, els));
const onLayout = useCallback(
(direction) => {
const layoutedElements = getLayoutedElements(elements, direction);
setElements(layoutedElements);
},
[elements]
);
return (
<div className="layoutflow">
<ReactFlowProvider>
<ReactFlow
elements={elements}
onConnect={onConnect}
onElementsRemove={onElementsRemove}
connectionLineType="smoothstep"
/>
<div className="controls">
<button onClick={() => onLayout('TB')}>vertical layout</button>
<button onClick={() => onLayout('LR')}>horizontal layout</button>
</div>
</ReactFlowProvider>
</div>
);
};
export default LayoutFlow;
initial-elements.js
const position = { x: 0, y: 0 };
const edgeType = 'smoothstep';
export default [
{
id: '1',
type: 'input',
data: { label: 'input' },
position,
},
{
id: '2',
data: { label: 'node 2' },
position,
},
{
id: '2a',
data: { label: 'node 2a' },
position,
},
{
id: '2b',
data: { label: 'node 2b' },
position,
},
{
id: '2c',
data: { label: 'node 2c' },
position,
},
{
id: '2d',
data: { label: 'node 2d' },
position,
},
{
id: '3',
data: { label: 'node 3' },
position,
},
{
id: '4',
data: { label: 'node 4' },
position,
},
{
id: '5',
data: { label: 'node 5' },
position,
},
{
id: '6',
type: 'output',
data: { label: 'output' },
position,
},
{ id: '7', type: 'output', data: { label: 'output' }, position },
{ id: 'e12', source: '1', target: '2', type: edgeType, animated: true },
{ id: 'e13', source: '1', target: '3', type: edgeType, animated: true },
{ id: 'e22a', source: '2', target: '2a', type: edgeType, animated: true },
{ id: 'e22b', source: '2', target: '2b', type: edgeType, animated: true },
{ id: 'e22c', source: '2', target: '2c', type: edgeType, animated: true },
{ id: 'e2c2d', source: '2c', target: '2d', type: edgeType, animated: true },
{ id: 'e45', source: '4', target: '5', type: edgeType, animated: true },
{ id: 'e56', source: '5', target: '6', type: edgeType, animated: true },
{ id: 'e57', source: '5', target: '7', type: edgeType, animated: true },
];