| name | dagre-react-flow |
| description | Automatic graph layout using dagre with React Flow (@xyflow/react). Use when implementing auto-layout, hierarchical layouts, tree structures, or arranging nodes programmatically. Triggers on dagre, auto-layout, automatic layout, getLayoutedElements, rankdir, hierarchical graph. |
Dagre with React Flow
Dagre is a JavaScript library for laying out directed graphs. It computes optimal node positions for hierarchical/tree layouts. React Flow handles rendering; dagre handles positioning.
Quick Start
pnpm add @dagrejs/dagre
import dagre from '@dagrejs/dagre';
import { Node, Edge } from '@xyflow/react';
const getLayoutedElements = (
nodes: Node[],
edges: Edge[],
direction: 'TB' | 'LR' = 'TB'
) => {
const g = new dagre.graphlib.Graph();
g.setGraph({ rankdir: direction });
g.setDefaultEdgeLabel(() => ({}));
nodes.forEach((node) => {
g.setNode(node.id, { width: 172, height: 36 });
});
edges.forEach((edge) => {
g.setEdge(edge.source, edge.target);
});
dagre.layout(g);
const layoutedNodes = nodes.map((node) => {
const pos = g.node(node.id);
return {
...node,
position: { x: pos.x - 86, y: pos.y - 18 },
};
});
return { nodes: layoutedNodes, edges };
};
Core Concepts
Coordinate System Difference
Critical: Dagre returns center coordinates; React Flow uses top-left.
const dagrePos = g.node(nodeId);
const rfPosition = {
x: dagrePos.x - nodeWidth / 2,
y: dagrePos.y - nodeHeight / 2,
};
Node Dimensions
Dagre requires explicit dimensions. Three approaches:
1. Fixed dimensions (simplest):
g.setNode(node.id, { width: 172, height: 36 });
2. Per-node dimensions from data:
g.setNode(node.id, {
width: node.data.width ?? 172,
height: node.data.height ?? 36,
});
3. Measured dimensions (most accurate):
g.setNode(node.id, {
width: node.measured?.width ?? 172,
height: node.measured?.height ?? 36,
});
Layout Directions
| Value | Direction | Use Case |
|---|
TB | Top to Bottom | Org charts, decision trees |
BT | Bottom to Top | Dependency graphs (deps at bottom) |
LR | Left to Right | Timelines, horizontal flows |
RL | Right to Left | RTL layouts |
g.setGraph({ rankdir: 'LR' });
Hard gates
Run these in order before treating layout as correct (each step has an objective pass condition):
- Dimensions match conversion — For every node id, the
width and height given to g.setNode for that id are the same numbers used to compute position.x / position.y from g.node(id) (half-width / half-height must match the dagre node box).
- Center → top-left —
position is { x: centerX - width/2, y: centerY - height/2 }, not raw g.node(id).x / .y alone.
- React Flow state update — After programmatic layout,
setNodes / setEdges receive a new array instance (e.g. [...layouted] or layouted.map(...)), not the previous reference unchanged.
- Optional sanity — If you use
fitView after layout, it runs after nodes are committed (e.g. next requestAnimationFrame or setTimeout(0)), not in the same synchronous tick as setNodes with stale measurements.
Complete Implementation
Basic Layout Function
import dagre from '@dagrejs/dagre';
import type { Node, Edge } from '@xyflow/react';
interface LayoutOptions {
direction?: 'TB' | 'BT' | 'LR' | 'RL';
nodeWidth?: number;
nodeHeight?: number;
nodesep?: number;
ranksep?: number;
}
export function getLayoutedElements(
nodes: Node[],
edges: Edge[],
options: LayoutOptions = {}
): { nodes: Node[]; edges: Edge[] } {
const {
direction = 'TB',
nodeWidth = 172,
nodeHeight = 36,
nodesep = 50,
ranksep = 50,
} = options;
const g = new dagre.graphlib.Graph();
g.setGraph({ rankdir: direction, nodesep, ranksep });
g.( ({}));
nodes.( {
width = node.?. ?? nodeWidth;
height = node.?. ?? nodeHeight;
g.(node., { width, height });
});
edges.( {
g.(edge., edge.);
});
dagre.(g);
layoutedNodes = nodes.( {
pos = g.(node.);
width = node.?. ?? nodeWidth;
height = node.?. ?? nodeHeight;
{
...node,
: {
: pos. - width / ,
: pos. - height / ,
},
};
});
{ : layoutedNodes, edges };
}
React Flow Integration
import { useCallback } from 'react';
import {
ReactFlow,
useNodesState,
useEdgesState,
useReactFlow,
ReactFlowProvider,
} from '@xyflow/react';
import { getLayoutedElements } from './layout';
const initialNodes = [
{ id: '1', data: { label: 'Start' }, position: { x: 0, y: 0 } },
{ id: '2', data: { label: 'Process' }, position: { x: 0, y: 0 } },
{ id: '3', data: { label: 'End' }, position: { x: 0, y: 0 } },
];
const initialEdges = [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e2-3', source: '2', target: '3' },
];
{ : layoutedNodes, : layoutedEdges } = (
initialNodes,
initialEdges,
{ : }
);
() {
[nodes, setNodes, onNodesChange] = (layoutedNodes);
[edges, setEdges, onEdgesChange] = (layoutedEdges);
{ fitView } = ();
onLayout = ( {
{ : newNodes, : newEdges } = (
nodes,
edges,
{ direction }
);
([...newNodes]);
([...newEdges]);
.( {
({ : });
});
}, [nodes, edges, setNodes, setEdges, fitView]);
(
);
}
() {
(
);
}
useAutoLayout Hook
Reusable hook for automatic layout:
import { useCallback, useEffect, useRef } from 'react';
import {
useReactFlow,
useNodesInitialized,
type Node,
type Edge,
} from '@xyflow/react';
import dagre from '@dagrejs/dagre';
interface UseAutoLayoutOptions {
direction?: 'TB' | 'BT' | 'LR' | 'RL';
nodesep?: number;
ranksep?: number;
}
export function useAutoLayout(options: UseAutoLayoutOptions = {}) {
const { direction = 'TB', nodesep = 50, ranksep = 50 } = options;
const { getNodes, getEdges, setNodes, fitView } = useReactFlow();
const nodesInitialized = useNodesInitialized();
const layoutApplied = useRef(false);
const runLayout = useCallback(() => {
const nodes = getNodes();
const edges = getEdges();
const g = new dagre..();
g.({ : direction, nodesep, ranksep });
g.( ({}));
nodes.( {
g.(node., {
: node.?. ?? ,
: node.?. ?? ,
});
});
edges.( {
g.(edge., edge.);
});
dagre.(g);
layouted = nodes.( {
pos = g.(node.);
width = node.?. ?? ;
height = node.?. ?? ;
{
...node,
: { : pos. - width / , : pos. - height / },
};
});
(layouted);
.( ({ : }));
}, [direction, nodesep, ranksep, getNodes, getEdges, setNodes, fitView]);
( {
(nodesInitialized && !layoutApplied.) {
();
layoutApplied. = ;
}
}, [nodesInitialized, runLayout]);
{ runLayout };
}
Usage:
function Flow() {
const { runLayout } = useAutoLayout({ direction: 'LR', ranksep: 100 });
return (
<>
<button onClick={runLayout}>Re-layout</button>
<ReactFlow ... />
</>
);
}
Edge Options
Control edge routing with weight and minlen:
edges.forEach((edge) => {
g.setEdge(edge.source, edge.target, {
weight: edge.data?.priority ?? 1,
minlen: edge.data?.minRanks ?? 1,
});
});
weight: Higher weight edges are prioritized for shorter, more direct paths.
minlen: Forces minimum rank separation between connected nodes.
g.setEdge('a', 'b', { minlen: 2 });
Common Patterns
Handle Position Based on Direction
Adjust handles for horizontal vs vertical layouts:
function CustomNode({ data }: NodeProps) {
const isHorizontal = data.direction === 'LR' || data.direction === 'RL';
return (
<div>
<Handle
type="target"
position={isHorizontal ? Position.Left : Position.Top}
/>
<div>{data.label}</div>
<Handle
type="source"
position={isHorizontal ? Position.Right : Position.Bottom}
/>
</div>
);
}
Animated Layout Transitions
Smooth position changes using CSS transitions:
.react-flow__node {
transition: transform 300ms ease-out;
}
For programmatic animation, see reference.md.
Layout with Node Groups
Exclude group nodes from dagre layout:
const layoutWithGroups = (nodes: Node[], edges: Edge[]) => {
const regularNodes = nodes.filter((n) => n.type !== 'group');
const groupNodes = nodes.filter((n) => n.type === 'group');
const { nodes: layouted } = getLayoutedElements(regularNodes, edges);
return { nodes: [...groupNodes, ...layouted], edges };
};
Troubleshooting
Nodes Overlapping
Increase spacing:
g.setGraph({
rankdir: 'TB',
nodesep: 100,
ranksep: 100,
});
Layout Not Updating
Ensure new array references:
setNodes(layoutedNodes);
setNodes([...layoutedNodes]);
Nodes at Wrong Position
Check coordinate conversion:
position: {
x: pos.x - width / 2,
y: pos.y - height / 2,
}
Performance with Large Graphs
- Layout in a Web Worker
- Debounce layout calls
- Use
useMemo for layout function
- Only re-layout changed portions
Configuration Reference
See reference.md for complete dagre configuration options.