Dagre React Flow

Polar Sponsor
爱发电 赞助
.NET 9.0

使用 dagre 与 React Flow (@xyflow/react) 实现自动图布局。适用于自动布局、层级布局、树形结构或节点排列等场景。

Dagre 带有 React Flow.

功能概述

Dagre 带有 React Flow.是一项面向实际任务的技能,主要用于Dagre 是用于绘制定向图表的 JavaScript 库;它计算出用于等级/ 树形布局的最佳节点位置;

核心要点

  • React Flow hands 渲染;
  • . dagret hands。
  • 它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。

使用与执行

使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。

结果检查与注意事项

执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。该技能适合用于一次性任务,也可以接入自动化工作流,与其他技能或上层代理配合完成更完整的业务链路;在组合使用时,应明确每一步的输入输出关系,并避免不同步骤之间出现参数冲突。

Dagre 与 React Flow 集成

Dagre 是一个用于布局有向图的 JavaScript 库,可为层级结构/树状结构计算最优节点位置。React Flow 负责渲染,Dagre 负责节点定位。

快速开始

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 };
};

核心概念

坐标系差异

关键注意: Dagre 返回的是节点中心坐标;React Flow 使用的是节点左上角坐标。

// Dagre 输出:节点中心位置
const dagrePos = g.node(nodeId); // { x: 100, y: 50 } = 中心点

// React Flow 期望:左上角坐标
const rfPosition = {
  x: dagrePos.x - nodeWidth / 2,
  y: dagrePos.y - nodeHeight / 2,
};

节点尺寸

Dagre 需要显式指定节点宽高。三种常用方式:

1. 固定尺寸(最简单):

g.setNode(node.id, { width: 172, height: 36 });

2. 按节点数据动态指定尺寸:

g.setNode(node.id, {
  width: node.data.width ?? 172,
  height: node.data.height ?? 36,
});

3. 实际测量尺寸(最精确):

// React Flow 完成节点尺寸测量后
g.setNode(node.id, {
  width: node.measured?.width ?? 172,
  height: node.measured?.height ?? 36,
});

布局方向

值 方向 适用场景
TB 自上而下 组织架构图、决策树
BT 自下而上 依赖关系图(依赖项置于底部)
LR 从左到右 时间线、水平流程图
RL 从右到左 RTL(从右向左)布局
g.setGraph({ rankdir: 'LR' }); // 水平布局

硬性校验门(Hard gates)

在将布局视为正确前,必须按顺序执行以下检查(每一步均有明确的通过条件):

  1. 尺寸与坐标转换一致 —— 对每个节点 ID,传入 g.setNode 的 width 和 height 必须与后续从 g.node(id) 计算 position.x / position.y 所用的数值完全相同(即半宽/半高必须与 Dagre 内部节点框尺寸严格匹配)。
  2. 中心 → 左上角转换 —— position 必须是 { x: centerX - width/2, y: centerY - height/2 },而非直接使用 g.node(id).x / .y 原始值。
  3. React Flow 状态更新方式正确 —— 在程序化布局后,setNodes / setEdges 必须接收一个全新数组实例(例如 [...layouted] 或 layouted.map(...)),而非未修改的原引用。
  4. 可选合理性校验 —— 若在布局后调用 fitView,需确保其在节点状态提交之后执行(例如在下一个 requestAnimationFrame 或 setTimeout(0) 中),而非与 setNodes 同步执行(否则可能因测量数据过期而失效)。

完整实现

基础布局函数

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.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 集成示例

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' },
];

// 应用初始布局
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]);

    // 布局完成后以动画方式适配视图
    window.requestAnimationFrame(() => {
      fitView({ duration: 300 });
    });
  }, [nodes, edges, setNodes, setEdges, fitView]);

  return (
    
); } export default function App() { return ( ); }

useAutoLayout Hook

可复用的自动布局 Hook:

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]);

  // 初始化时自动执行布局
  useEffect(() => {
    if (nodesInitialized && !layoutApplied.current) {
      runLayout();
      layoutApplied.current = true;
    }
  }, [nodesInitialized, runLayout]);

  return { runLayout };
}

使用方式:

function Flow() {
  const { runLayout } = useAutoLayout({ direction: 'LR', ranksep: 100 });

  return (
    <>
      
      
    
  );
}

边(Edge)配置选项

通过 weight 和 minlen 控制边的路径走向:

edges.forEach((edge) => {
  g.setEdge(edge.source, edge.target, {
    weight: edge.data?.priority ?? 1,  // 值越大,路径越短、越直接
    minlen: edge.data?.minRanks ?? 1,  // 连接节点间的最小层级间隔
  });
});

weight:权重越高的边,在布局中越优先采用更短、更直接的路径。

minlen:强制规定相连节点之间至少相隔的层级数量。

// 强制节点 a 与 b 之间相隔 2 个层级
g.setEdge('a', 'b', { minlen: 2 });

常见模式

根据布局方向调整 Handle 位置

针对水平与垂直布局分别设置 Handle 位置:

function CustomNode({ data }: NodeProps) {
  const isHorizontal = data.direction === 'LR' || data.direction === 'RL';

  return (
    
{data.label}
); }

带动画效果的布局过渡

使用 CSS transition 实现平滑的位置变化:

.react-flow__node {
  transition: transform 300ms ease-out;
}

如需程序化控制动画,请参考 reference.md。

支持节点分组(Node Groups)的布局

在 Dagre 布局中排除分组节点(group nodes):

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 };
};

故障排查

节点重叠

增大节点间距:

g.setGraph({
  rankdir: 'TB',
  nodesep: 100,  // 增大水平间距
  ranksep: 100,  // 增大垂直间距
});

布局未更新

确保传入新数组引用:

// 错误 —— 使用同一引用
setNodes(layoutedNodes);

// 正确 —— 创建新引用
setNodes([...layoutedNodes]);

节点位置错误

检查坐标转换逻辑:

// Dagre 返回中心点,React Flow 需要左上角
position: {
  x: pos.x - width / 2,   // 不能仅用 pos.x
  y: pos.y - height / 2,  // 不能仅用 pos.y
}

大规模图性能问题

  • 在 Web Worker 中执行布局
  • 对布局调用进行防抖(debounce)
  • 对布局函数使用 useMemo 缓存
  • 仅对变更部分重新布局

配置项参考

完整 Dagre 配置选项请参阅 reference.md。

相关专题

更多
JavaScript React组件优化与性能提升实战
JavaScript React组件优化与性能提升实战

本专题围绕 React 框架在大型前端项目中的性能优化展开,讲解组件生命周期优化、虚拟 DOM 原理、状态管理策略及懒加载技术。通过实战案例,帮助开发者构建性能优良、响应迅速的前端应用。

2026.04.13

165

18

React 18状态管理与Hooks高级实践
React 18状态管理与Hooks高级实践

本专题专注于 React 18 的高级开发技术,详细讲解 useState、useEffect、useReducer、useContext 等 Hooks 的使用技巧,以及 Redux、Zustand 等状态管理工具的集成与优化方法。通过真实案例,帮助前端开发者构建可维护、性能优良的现代 React 应用。

2026.02.10

183

17

LLVM自定义Pass怎么写
LLVM自定义Pass怎么写

本专题聚焦LLVM自定义Pass开发,整理Pass类结构、run()方法、PreservedAnalyses、CMake构建、插件注册、-load-pass-plugin加载和测试用例编写流程。

2026.09.30

0

10

LLVM RISC-V参数配置教程
LLVM RISC-V参数配置教程

本专题介绍LLVM对RISC-V基础ISA和扩展的支持方式,涵盖RV32、RV64、标准扩展、实验性扩展、厂商扩展、-menable-experimental-extensions和版本差异。

2026.09.30

0

14

LLVM IR中间表示入门指南
LLVM IR中间表示入门指南

本专题整理LLVM IR的核心概念,包括中间表示作用、模块结构、函数、基本块、SSA形式、类型系统和常见语法,帮助新手理解LLVM编译流程中的关键层。

2026.09.30

0

12

PDF转图片方法
PDF转图片方法

需要把 PDF 页面用于上传、预览、分享或图片归档时,PDF 转图片方法专题整理 JPG/PNG 格式选择、逐页导出、清晰度设置、批量下载和结果检查等流程,帮助用户稳定完成 PDF 图片化处理。

2026.09.30

0

26

PixTV AI视频生成与无限画布创作
PixTV AI视频生成与无限画布创作

PixTV专题整理AI视频与视觉内容创作相关功能使用教程,涵盖AI生图、视频生成、无限画布、多模型创作、素材管理、声音音乐及视频剪辑等功能,帮助用户快速掌握PixTV从创意到成片的完整制作方法。

2026.09.29

0

15

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

2026.09.23

0

15

Buffalo框架路由与请求处理实操指南
Buffalo框架路由与请求处理实操指南

本专题讲解Buffalo框架路由与请求处理机制,涵盖路由注册与分组、资源路由、Handler编写规范、Context上下文方法、参数绑定、中间件编写挂载、Session与Cookie读写、Flash消息及错误页面定制方法。

2026.09.23

0

15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 12万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.4万人学习