Agent skill
dagre-react-flow
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.
Install this agent skill to your Project
npx add-skill https://github.com/existential-birds/beagle/tree/main/plugins/beagle-react/skills/dagre-react-flow
SKILL.md
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 }, // Center to top-left
};
});
return { nodes: layoutedNodes, edges };
};
Core Concepts
Coordinate System Difference
Critical: Dagre returns center coordinates; React Flow uses top-left.
// Dagre output: center of node
const dagrePos = g.node(nodeId); // { x: 100, y: 50 } = center
// React Flow expects: top-left corner
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):
// After React Flow measures nodes
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' }); // Horizontal layout
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; // Horizontal spacing
ranksep?: number; // Vertical spacing (between ranks)
}
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.setDefaultEdgeLabel(() => ({}));
nodes.forEach((node) => {
const width = node.measured?.width ?? nodeWidth;
const height = node.measured?.height ?? nodeHeight;
g.setNode(node.id, { width, height });
});
edges.forEach((edge) => {
g.setEdge(edge.source, edge.target);
});
dagre.layout(g);
const layoutedNodes = nodes.map((node) => {
const pos = g.node(node.id);
const width = node.measured?.width ?? nodeWidth;
const height = node.measured?.height ?? nodeHeight;
return {
...node,
position: {
x: pos.x - width / 2,
y: pos.y - height / 2,
},
};
});
return { nodes: 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' },
];
// Apply initial layout
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
initialNodes,
initialEdges,
{ direction: 'TB' }
);
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(layoutedEdges);
const { fitView } = useReactFlow();
const onLayout = useCallback((direction: 'TB' | 'LR') => {
const { nodes: newNodes, edges: newEdges } = getLayoutedElements(
nodes,
edges,
{ direction }
);
setNodes([...newNodes]);
setEdges([...newEdges]);
// Fit view after layout with animation
window.requestAnimationFrame(() => {
fitView({ duration: 300 });
});
}, [nodes, edges, setNodes, setEdges, fitView]);
return (
<div style={{ width: '100%', height: '100vh' }}>
<div style={{ position: 'absolute', zIndex: 10, padding: 10 }}>
<button onClick={() => onLayout('TB')}>Vertical</button>
<button onClick={() => onLayout('LR')}>Horizontal</button>
</div>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
/>
</div>
);
}
export default function App() {
return (
<ReactFlowProvider>
<Flow />
</ReactFlowProvider>
);
}
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.graphlib.Graph();
g.setGraph({ rankdir: direction, nodesep, ranksep });
g.setDefaultEdgeLabel(() => ({}));
nodes.forEach((node) => {
g.setNode(node.id, {
width: node.measured?.width ?? 172,
height: node.measured?.height ?? 36,
});
});
edges.forEach((edge) => {
g.setEdge(edge.source, edge.target);
});
dagre.layout(g);
const layouted = nodes.map((node) => {
const pos = g.node(node.id);
const width = node.measured?.width ?? 172;
const height = node.measured?.height ?? 36;
return {
...node,
position: { x: pos.x - width / 2, y: pos.y - height / 2 },
};
});
setNodes(layouted);
window.requestAnimationFrame(() => fitView({ duration: 200 }));
}, [direction, nodesep, ranksep, getNodes, getEdges, setNodes, fitView]);
// Auto-layout on initialization
useEffect(() => {
if (nodesInitialized && !layoutApplied.current) {
runLayout();
layoutApplied.current = true;
}
}, [nodesInitialized, runLayout]);
return { 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, // Higher = more direct path
minlen: edge.data?.minRanks ?? 1, // Minimum ranks between nodes
});
});
weight: Higher weight edges are prioritized for shorter, more direct paths.
minlen: Forces minimum rank separation between connected nodes.
// Force 2 ranks between 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[]) => {
// Separate regular nodes from groups
const regularNodes = nodes.filter((n) => n.type !== 'group');
const groupNodes = nodes.filter((n) => n.type === 'group');
// Layout only regular nodes
const { nodes: layouted } = getLayoutedElements(regularNodes, edges);
// Combine back
return { nodes: [...groupNodes, ...layouted], edges };
};
Troubleshooting
Nodes Overlapping
Increase spacing:
g.setGraph({
rankdir: 'TB',
nodesep: 100, // Increase horizontal spacing
ranksep: 100, // Increase vertical spacing
});
Layout Not Updating
Ensure new array references:
// Wrong - same reference
setNodes(layoutedNodes);
// Correct - new reference
setNodes([...layoutedNodes]);
Nodes at Wrong Position
Check coordinate conversion:
// Dagre returns center, React Flow needs top-left
position: {
x: pos.x - width / 2, // Not just pos.x
y: pos.y - height / 2, // Not just pos.y
}
Performance with Large Graphs
- Layout in a Web Worker
- Debounce layout calls
- Use
useMemofor layout function - Only re-layout changed portions
Configuration Reference
See reference.md for complete dagre configuration options.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
review-python
Comprehensive Python/FastAPI backend code review with optional parallel agents
review-verification-protocol
Mandatory verification steps for all code reviews to reduce false positives. Load this skill before reporting ANY code review findings.
sqlalchemy-code-review
Reviews SQLAlchemy code for session management, relationships, N+1 queries, and migration patterns. Use when reviewing SQLAlchemy 2.0 code, checking session lifecycle, relationship() usage, or Alembic migrations.
fastapi-code-review
Reviews FastAPI code for routing patterns, dependency injection, validation, and async handlers. Use when reviewing FastAPI apps, checking APIRouter setup, Depends() usage, or response models.
pytest-code-review
Reviews pytest test code for async patterns, fixtures, parametrize, and mocking. Use when reviewing test_*.py files, checking async test functions, fixture usage, or mock patterns.
postgres-code-review
Reviews PostgreSQL code for indexing strategies, JSONB operations, connection pooling, and transaction safety. Use when reviewing SQL queries, database schemas, JSONB usage, or connection management.
Didn't find tool you were looking for?