概述
dagre-react-flow 是一种在 React Flow(@xyflow/react)中实现有向图自动布局的实践方案。其分工明确:dagre 是一个用于有向图布局的 JavaScript 库,负责计算层级/树形结构中节点的最优位置;React Flow 负责图形的渲染与交互。二者结合可实现自动布局、层级布局、树形结构与节点排列。
安装
pnpm add @dagrejs/dagre
主要能力
- 使用 dagre 计算节点坐标,再由 React Flow 渲染。
- 提供 getLayoutedElements 布局函数,支持可选参数:direction('TB' | 'BT' | 'LR' | 'RL')、nodeWidth、nodeHeight、nodesep(水平间距)、ranksep(垂直/层间距)。
- 提供 useAutoLayout Hook,可在节点测量初始化后自动执行一次布局,并返回 runLayout 供手动重新布局。
- 支持通过边的 weight(越高路径越直接)与 minlen(最小层级间隔)控制边路由。
使用方式
核心:布局函数
import dagre from '@dagrejs/dagre';
import type { Node, Edge } from '@xyflow/react';
export function getLayoutedElements(
nodes: Node[],
edges: Edge[],
options = {}
) {
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 };
}
关键:坐标系差异
dagre 返回的是节点中心坐标,React Flow 期望左上角坐标,因此需要换算:
const rfPosition = {
x: dagrePos.x - nodeWidth / 2,
y: dagrePos.y - nodeHeight / 2,
};
节点尺寸的三种来源
- 固定尺寸:g.setNode(node.id, { width: 172, height: 36 })。
- 来自节点数据的尺寸:g.setNode(node.id, { width: node.data.width ?? 172, height: node.data.height ?? 36 })。
- 实测尺寸(最准确):使用 node.measured?.width / node.measured?.height,缺省回退到默认值。
布局方向
| 值 | 方向 | 典型用途 |
|---|---|---|
| TB | 从上到下 | 组织图、决策树 |
| BT | 从下到上 | 依赖图(依赖在底部) |
| LR | 从左到右 | 时间线、水平流程 |
| RL | 从右到左 | RTL 布局 |
设置方式:g.setGraph({ rankdir: 'LR' })。
React Flow 集成
使用 useNodesState / useEdgesState 管理状态,通过 useReactFlow 的 fitView 在布局后适配视图:
setNodes([...newNodes]);
setEdges([...newEdges]);
window.requestAnimationFrame(() => {
fitView({ duration: 300 });
});
需要在外层包裹 ReactFlowProvider。
useAutoLayout Hook
在 useNodesInitialized() 为真时执行一次布局,使用 useRef 防止重复应用;内部通过 getNodes / getEdges / setNodes / fitView 操作,并在下一帧调用 fitView。返回 { runLayout } 供手动触发。
边选项
edges.forEach((edge) => {
g.setEdge(edge.source, edge.target, {
weight: edge.data?.priority ?? 1,
minlen: edge.data?.minRanks ?? 1,
});
});
weight 越高,该边越倾向于更短、更直接的路径;minlen 强制两个相连节点之间至少间隔若干层级,例如 g.setEdge('a', 'b', { minlen: 2 })。
常用模式
- 根据布局方向调整 Handle 位置:水平布局使用 Left/Right,垂直布局使用 Top/Bottom。
- 动画过渡:为 .react-flow__node 添加 CSS transition(如 transform 300ms ease-out)。
- 节点分组:将 type 为 'group' 的节点从 dagre 布局中排除,布局完常规节点后再合并回去。
验证要点
文档给出若干硬性校验步骤:
- 尺寸一致——传给 g.setNode 的宽高与用于计算 position 的宽高必须相同(半宽/半高需匹配 dagre 的节点框)。
- 中心转左上角——position 必须为 { x: centerX - width/2, y: centerY - height/2 },不能直接用原始中心坐标。
- 状态更新——布局后 setNodes / setEdges 必须收到新数组实例(如 [...layouted]),而非原引用。
- 可选的合理性检查——若布局后使用 fitView,应在节点提交后(如下一帧 requestAnimationFrame 或 setTimeout(0))执行,而不是与 setNodes 处于同一同步周期且使用过期测量值。
适用场景
- 需要自动布局的有向图、层级图、树形结构。
- 组织图、决策树、依赖图、时间线、水平/垂直流程图,以及 RTL 布局。
- 需要按方向切换、按优先级控制路径走向的复杂节点图。
限制与注意事项
- dagre 需要显式提供节点尺寸;不提供则使用默认值(172×36)。
- 坐标换算不可省略,否则节点位置会偏移半个节点尺寸。
- React Flow 状态更新必须使用新的数组引用,否则视图不刷新。
- 关于大图性能,文档提到可在 Web Worker 中布局、对布局调用防抖、使用 useMemo、仅重新布局变化的部分,但未提供完整实现代码。
- 文档未提及该方案的收费、价格、许可与支持的平台信息,暂未提供。
- 更完整的 dagre 配置项参见其 reference.md,资料中未展开。