From c53acfc62dc082ead8a01fdf187da0e6c4404851 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Wed, 18 Feb 2026 21:00:35 -0500 Subject: [PATCH] feat: add dagre layout utility for React Flow node positioning Co-Authored-By: Claude Opus 4.6 --- frontend/src/lib/dagreLayout.ts | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 frontend/src/lib/dagreLayout.ts diff --git a/frontend/src/lib/dagreLayout.ts b/frontend/src/lib/dagreLayout.ts new file mode 100644 index 00000000..228882c7 --- /dev/null +++ b/frontend/src/lib/dagreLayout.ts @@ -0,0 +1,44 @@ +import dagre from '@dagrejs/dagre' +import type { Node, Edge } from '@xyflow/react' + +const NODE_WIDTH = 280 +const DEFAULT_NODE_HEIGHT = 100 + +interface LayoutOptions { + direction?: 'TB' | 'LR' + rankSep?: number + nodeSep?: number +} + +export function getLayoutedElements( + nodes: Node[], + edges: Edge[], + options: LayoutOptions = {} +): Node[] { + const { direction = 'TB', rankSep = 100, nodeSep = 40 } = options + + const g = new dagre.graphlib.Graph() + g.setDefaultEdgeLabel(() => ({})) + g.setGraph({ rankdir: direction, ranksep: rankSep, nodesep: nodeSep }) + + nodes.forEach((node) => { + const height = node.measured?.height ?? node.data?.estimatedHeight ?? DEFAULT_NODE_HEIGHT + g.setNode(node.id, { width: NODE_WIDTH, height }) + }) + + edges.forEach((edge) => { + g.setEdge(edge.source, edge.target) + }) + + dagre.layout(g) + + return nodes.map((node) => { + const dagreNode = g.node(node.id) + // dagre gives center positions — convert to top-left for React Flow + const x = dagreNode.x - NODE_WIDTH / 2 + const y = dagreNode.y - (dagreNode.height ?? DEFAULT_NODE_HEIGHT) / 2 + return { ...node, position: { x, y } } + }) +} + +export { NODE_WIDTH, DEFAULT_NODE_HEIGHT }