← 返回 Skills 市场
anderskev

Dagre React Flow

作者 Kevin Anderson · GitHub ↗ · v1.1.1 · MIT-0
cross-platform ✓ 安全检测通过
139
总下载
0
收藏
1
当前安装
2
版本数
在 OpenClaw 中安装
/install dagre-react-flow
功能描述
Automatic graph layout using dagre with React Flow (@xyflow/react). Use when implementing auto-layout, hierarchical layouts, tree structures, or arranging no...
使用说明 (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

Hard gates

Run these in order before treating layout as correct (each step has an objective pass condition):

  1. 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).
  2. Center → top-leftposition is { x: centerX - width/2, y: centerY - height/2 }, not raw g.node(id).x / .y alone.
  3. 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.
  4. 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;  // 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 (
    \x3Cdiv style={{ width: '100%', height: '100vh' }}>
      \x3Cdiv style={{ position: 'absolute', zIndex: 10, padding: 10 }}>
        \x3Cbutton onClick={() => onLayout('TB')}>Vertical\x3C/button>
        \x3Cbutton onClick={() => onLayout('LR')}>Horizontal\x3C/button>
      \x3C/div>
      \x3CReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        fitView
      />
    \x3C/div>
  );
}

export default function App() {
  return (
    \x3CReactFlowProvider>
      \x3CFlow />
    \x3C/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 (
    \x3C>
      \x3Cbutton onClick={runLayout}>Re-layout\x3C/button>
      \x3CReactFlow ... />
    \x3C/>
  );
}

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 (
    \x3Cdiv>
      \x3CHandle
        type="target"
        position={isHorizontal ? Position.Left : Position.Top}
      />
      \x3Cdiv>{data.label}\x3C/div>
      \x3CHandle
        type="source"
        position={isHorizontal ? Position.Right : Position.Bottom}
      />
    \x3C/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 useMemo for layout function
  • Only re-layout changed portions

Configuration Reference

See reference.md for complete dagre configuration options.

安全使用建议
This appears to be a documentation-only helper for layout using @dagrejs/dagre and @xyflow/react. It's coherent and doesn't request secrets or install arbitrary code, but before using in a project: (1) install dagre from a trusted registry (pnpm/yarn/npm) and verify package versions, (2) ensure @xyflow/react is the intended React Flow package in your stack, and (3) follow the performance guidance (web worker, debouncing) for large graphs to avoid UI freezes.
能力评估
Purpose & Capability
Name and description describe auto-layout with dagre and React Flow; the SKILL.md and reference.md only include code snippets and guidance for that exact task. No unrelated credentials, binaries, or config paths are requested.
Instruction Scope
Runtime instructions and examples are limited to computing node positions, converting center-to-top-left coordinates, integrating with React Flow state, and performance tips (workers, debouncing). They do not instruct reading unrelated files, environment variables, or sending data to external endpoints.
Install Mechanism
Instruction-only skill with no install spec or downloads. The only install hint is a standard package install (pnpm add @dagrejs/dagre) which is appropriate and expected for this functionality.
Credentials
No environment variables, credentials, or config paths are requested. The skill does not ask for secrets or unrelated service access.
Persistence & Privilege
always is false and the skill is user-invocable. It does not request persistent/system-wide privileges or modify other skills' configs. Autonomous invocation is allowed (platform default) but is not combined with other red flags.
如何使用
  1. 确保已安装 OpenClaw(本地或 Docker 部署)
  2. 在对话框中输入安装命令:/install dagre-react-flow
  3. 安装完成后,直接呼叫该 Skill 的名称或使用 /dagre-react-flow 触发
  4. 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
版本历史
v1.1.1
- Added a "Hard gates" section listing four explicit, ordered criteria for validating layouts, including dimension consistency, coordinate conversion, state updates, and `fitView` timing. - Documentation now emphasizes pass/fail conditions for common layout mistakes to improve reliability when integrating Dagre with React Flow. - No changes to code; update is limited to clarifying and reinforcing best practices in the SKILL.md documentation.
v1.1.0
- Improved and expanded documentation in SKILL.md, including detailed usage instructions, advanced configuration, and clear integration examples with React Flow. - Added complete code examples for layout functions, React Flow integration, and a reusable useAutoLayout hook. - Clarified core concepts such as coordinate system differences, node dimensions, and layout directions. - Enhanced guidance on auto-layout, hierarchical layouts, and working with measured node sizes. - Documentation now provides both quick start and deep-dive implementation details for all major use cases.
元数据
Slug dagre-react-flow
版本 1.1.1
许可证 MIT-0
累计安装 1
当前安装数 1
历史版本数 2
常见问题

Dagre React Flow 是什么?

Automatic graph layout using dagre with React Flow (@xyflow/react). Use when implementing auto-layout, hierarchical layouts, tree structures, or arranging no... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 139 次。

如何安装 Dagre React Flow?

在 OpenClaw 或 Claude Code 对话框中运行命令「/install dagre-react-flow」即可一键安装,无需额外配置。

Dagre React Flow 是免费的吗?

是的,Dagre React Flow 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。

Dagre React Flow 支持哪些平台?

Dagre React Flow 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。

谁开发了 Dagre React Flow?

由 Kevin Anderson(@anderskev)开发并维护,当前版本 v1.1.1。

💬 留言讨论