当前位置:   article > 正文

react-flow基础使用及dagre库的使用_react flow

react flow

前言

最近项目中需要用到拓扑图的展示,最开始选用的是antv的拓扑图组件。antv组件虽然此很方便,但是在布局的时候总是会有莫名其妙的bug,然后自己也想法去解决(看前辈经验、官方issue),最后还是不能解决。于是更换了组件库,也就是我们今天的主角:react-flow。

react-flow简介

React Flow 是一个基于 React 的用于构建可视化流程图和图形编辑器的库。它提供了一个灵活的、可扩展的组件集合,使开发者可以轻松地创建交互式的流程图和图形编辑器应用。

react-flow特点

  1. 可视化流程图: React Flow 提供了一个易于使用的组件集合,用于创建流程图和图形编辑器。您可以使用这些组件来构建节点、边缘和连接线,以及定义它们之间的关系。
  2. 拖放支持: React Flow 支持节点和边缘的拖放操作,使用户可以方便地调整它们的位置和连接关系。您可以自定义节点和边缘的外观和行为,以适应您的应用需求。
  3. 布局算法: React Flow 内置了多种布局算法(部分收费),例如树形布局、网格布局和自由布局,可以帮助您自动排列和布局图形元素。这些算法可以根据图形的结构和属性自动调整节点的位置,使图形看起来更整齐和美观。
  4. 交互性: React Flow 提供了丰富的交互功能,包括缩放、平移、选择、多选、编辑和删除等。您可以根据需要启用或禁用这些交互功能,以创建符合用户需求的可交互图形编辑器。
  5. 事件处理: React Flow 提供了事件处理机制,允许您监听和响应图形元素的各种事件,例如节点的拖动、边缘的创建和删除等。您可以根据这些事件来实现自定义的业务逻辑和交互行为。
  6. 可定制性: React Flow 具有高度可定制性,您可以通过自定义组件、样式和配置选项来调整其外观和行为。这使得您可以根据自己的设计和需求来创建独特的图形编辑器应用。

react-flow例子

安装

tyarn add react-flow-renderer

使用

  1. import React, { useCallback } from "react";
  2. import ReactFlow, { useNodesState, useEdgesState, updateEdge } from "reactflow";
  3. import { TrialCmdWrapper } from "@/pages/trial/style";
  4. import { FlowContent } from "./style";
  5. import "reactflow/dist/style.css";
  6. import { useMemo } from "react";
  7. import CuFlowNode from "@/components/Home/resTopo/topo/node";
  8. const initialNodes = [
  9. {
  10. id: "root",
  11. type: "input",
  12. data: { label: "全局节点" },
  13. position: { x: 0, y: 0 }
  14. },
  15. {
  16. id: "horizontal-2",
  17. sourcePosition: "right",
  18. targetPosition: "left",
  19. data: { label: "A Node" },
  20. position: { x: 250, y: 0 }
  21. },
  22. {
  23. id: "horizontal-3",
  24. sourcePosition: "right",
  25. targetPosition: "left",
  26. data: { label: "Node 3" },
  27. position: { x: 250, y: 160 }
  28. },
  29. {
  30. id: "horizontal-4",
  31. sourcePosition: "right",
  32. targetPosition: "left",
  33. data: { label: "Node 4" },
  34. position: { x: 500, y: 0 }
  35. },
  36. {
  37. id: "horizontal-5",
  38. sourcePosition: "top",
  39. targetPosition: "bottom",
  40. data: { label: "Node 5" },
  41. position: { x: 500, y: 100 }
  42. },
  43. {
  44. id: "horizontal-6",
  45. sourcePosition: "bottom",
  46. targetPosition: "top",
  47. data: { label: "Node 6" },
  48. position: { x: 500, y: 230 }
  49. },
  50. {
  51. id: "horizontal-7",
  52. sourcePosition: "right",
  53. targetPosition: "left",
  54. data: { label: "Node 7" },
  55. position: { x: 750, y: 50 }
  56. },
  57. {
  58. id: "horizontal-8",
  59. sourcePosition: "right",
  60. targetPosition: "left",
  61. data: { label: "Node 8" },
  62. position: { x: 750, y: 300 }
  63. }
  64. ];
  65. const initialEdges = [
  66. {
  67. id: "horizontal-e1-2",
  68. source: "root",
  69. type: "smoothstep",
  70. target: "horizontal-2",
  71. animated: true
  72. },
  73. {
  74. id: "horizontal-e1-3",
  75. source: "root",
  76. type: "smoothstep",
  77. target: "horizontal-3",
  78. animated: true
  79. },
  80. {
  81. id: "horizontal-e1-4",
  82. source: "horizontal-2",
  83. type: "smoothstep",
  84. target: "horizontal-4",
  85. label: "edge label"
  86. },
  87. {
  88. id: "horizontal-e3-5",
  89. source: "horizontal-3",
  90. type: "smoothstep",
  91. target: "horizontal-5",
  92. animated: true
  93. },
  94. {
  95. id: "horizontal-e3-6",
  96. source: "horizontal-3",
  97. type: "smoothstep",
  98. target: "horizontal-6",
  99. animated: true
  100. },
  101. {
  102. id: "horizontal-e5-7",
  103. source: "horizontal-5",
  104. type: "smoothstep",
  105. target: "horizontal-7",
  106. animated: true
  107. },
  108. {
  109. id: "horizontal-e6-8",
  110. source: "horizontal-6",
  111. type: "smoothstep",
  112. target: "horizontal-8",
  113. animated: true
  114. }
  115. ];
  116. export default function TrialFlowContent({ width, height }) {
  117. // 图操作
  118. const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  119. const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  120. const onEdgeUpdate = useCallback((oldEdge, newConnection) => setEdges(els => updateEdge(oldEdge, newConnection, els)), []);
  121. // const onConnect = useCallback(params => setEdges(els => addEdge(params, els)), []);
  122. const nodeTypes = useMemo(() => ({ textUpdater: CuFlowNode }), []);
  123. const onConnect = () => {
  124. // 禁止手动连线
  125. return;
  126. };
  127. return (
  128. <TrialCmdWrapper
  129. height={height}
  130. width={width}
  131. style={{
  132. padding: 0,
  133. float: "left",
  134. marginLeft: "15px"
  135. }}>
  136. <FlowContent>
  137. <ReactFlow
  138. nodeTypes={nodeTypes}
  139. nodes={nodes}
  140. edges={edges}
  141. onNodesChange={onNodesChange}
  142. onEdgesChange={onEdgesChange}
  143. onConnect={onConnect}
  144. onEdgeUpdate={onEdgeUpdate}></ReactFlow>
  145. </FlowContent>
  146. </TrialCmdWrapper>
  147. );
  148. }

 效果

dagre布局库使用

安装

tyarn add dagre

使用

组件外新增代码
  1. // dagre 数据
  2. const dagreGraph = new dagre.graphlib.Graph();
  3. dagreGraph.setDefaultEdgeLabel(() => ({}));
  4. const nodeWidth = 172;
  5. const nodeHeight = 36;
  6. const getLayoutedElements = (nodes, edges, direction = "TB") => {
  7. const isHorizontal = direction === "LR";
  8. dagreGraph.setGraph({ rankdir: direction });
  9. nodes.forEach(node => {
  10. dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
  11. });
  12. edges.forEach(edge => {
  13. dagreGraph.setEdge(edge.source, edge.target);
  14. });
  15. dagre.layout(dagreGraph);
  16. nodes.forEach(node => {
  17. const nodeWithPosition = dagreGraph.node(node.id);
  18. node.targetPosition = isHorizontal ? "left" : "top";
  19. node.sourcePosition = isHorizontal ? "right" : "bottom";
  20. // We are shifting the dagre node position (anchor=center center) to the top left
  21. // so it matches the React Flow node anchor point (top left).
  22. node.position = {
  23. x: nodeWithPosition.x - nodeWidth / 2,
  24. y: nodeWithPosition.y - nodeHeight / 2
  25. };
  26. return node;
  27. });
  28. return { nodes, edges };
  29. };
 组件内部新增方法

  1. const setTreeTopoData = (nodes, edges, direction = "TB") => {
  2. const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(nodes, edges, direction);
  3. setNodes([...layoutedNodes]);
  4. setEdges([...layoutedEdges]);
  5. };

使用方法
  1. useEffect(() => {
  2. setTreeTopoData(nodes, edges);
  3. }, []);
 最终代码

  1. import React, { useCallback } from "react";
  2. import ReactFlow, { useNodesState, useEdgesState, updateEdge } from "reactflow";
  3. import { TrialCmdWrapper } from "@/pages/trial/style";
  4. import { FlowContent } from "./style";
  5. import "reactflow/dist/style.css";
  6. import { useRef } from "react";
  7. import { useEffect } from "react";
  8. import { useMemo } from "react";
  9. import CuFlowNode from "@/components/Home/resTopo/topo/node";
  10. import dagre from "dagre";
  11. const initialNodes = [
  12. {
  13. id: "root",
  14. type: "input",
  15. data: { label: "全局节点" },
  16. position: { x: 0, y: 0 }
  17. },
  18. {
  19. id: "horizontal-2",
  20. sourcePosition: "right",
  21. targetPosition: "left",
  22. data: { label: "A Node" },
  23. position: { x: 250, y: 0 }
  24. },
  25. {
  26. id: "horizontal-3",
  27. sourcePosition: "right",
  28. targetPosition: "left",
  29. data: { label: "Node 3" },
  30. position: { x: 250, y: 160 }
  31. },
  32. {
  33. id: "horizontal-4",
  34. sourcePosition: "right",
  35. targetPosition: "left",
  36. data: { label: "Node 4" },
  37. position: { x: 500, y: 0 }
  38. },
  39. {
  40. id: "horizontal-5",
  41. sourcePosition: "top",
  42. targetPosition: "bottom",
  43. data: { label: "Node 5" },
  44. position: { x: 500, y: 100 }
  45. },
  46. {
  47. id: "horizontal-6",
  48. sourcePosition: "bottom",
  49. targetPosition: "top",
  50. data: { label: "Node 6" },
  51. position: { x: 500, y: 230 }
  52. },
  53. {
  54. id: "horizontal-7",
  55. sourcePosition: "right",
  56. targetPosition: "left",
  57. data: { label: "Node 7" },
  58. position: { x: 750, y: 50 }
  59. },
  60. {
  61. id: "horizontal-8",
  62. sourcePosition: "right",
  63. targetPosition: "left",
  64. data: { label: "Node 8" },
  65. position: { x: 750, y: 300 }
  66. }
  67. ];
  68. const initialEdges = [
  69. {
  70. id: "horizontal-e1-2",
  71. source: "root",
  72. type: "smoothstep",
  73. target: "horizontal-2",
  74. animated: true
  75. },
  76. {
  77. id: "horizontal-e1-3",
  78. source: "root",
  79. type: "smoothstep",
  80. target: "horizontal-3",
  81. animated: true
  82. },
  83. {
  84. id: "horizontal-e1-4",
  85. source: "horizontal-2",
  86. type: "smoothstep",
  87. target: "horizontal-4",
  88. label: "edge label"
  89. },
  90. {
  91. id: "horizontal-e3-5",
  92. source: "horizontal-3",
  93. type: "smoothstep",
  94. target: "horizontal-5",
  95. animated: true
  96. },
  97. {
  98. id: "horizontal-e3-6",
  99. source: "horizontal-3",
  100. type: "smoothstep",
  101. target: "horizontal-6",
  102. animated: true
  103. },
  104. {
  105. id: "horizontal-e5-7",
  106. source: "horizontal-5",
  107. type: "smoothstep",
  108. target: "horizontal-7",
  109. animated: true
  110. },
  111. {
  112. id: "horizontal-e6-8",
  113. source: "horizontal-6",
  114. type: "smoothstep",
  115. target: "horizontal-8",
  116. animated: true
  117. }
  118. ];
  119. // dagre 数据
  120. const dagreGraph = new dagre.graphlib.Graph();
  121. dagreGraph.setDefaultEdgeLabel(() => ({}));
  122. const nodeWidth = 172;
  123. const nodeHeight = 36;
  124. const getLayoutedElements = (nodes, edges, direction = "TB") => {
  125. const isHorizontal = direction === "LR";
  126. dagreGraph.setGraph({ rankdir: direction });
  127. nodes.forEach(node => {
  128. dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
  129. });
  130. edges.forEach(edge => {
  131. dagreGraph.setEdge(edge.source, edge.target);
  132. });
  133. dagre.layout(dagreGraph);
  134. nodes.forEach(node => {
  135. const nodeWithPosition = dagreGraph.node(node.id);
  136. node.targetPosition = isHorizontal ? "left" : "top";
  137. node.sourcePosition = isHorizontal ? "right" : "bottom";
  138. // We are shifting the dagre node position (anchor=center center) to the top left
  139. // so it matches the React Flow node anchor point (top left).
  140. node.position = {
  141. x: nodeWithPosition.x - nodeWidth / 2,
  142. y: nodeWithPosition.y - nodeHeight / 2
  143. };
  144. return node;
  145. });
  146. return { nodes, edges };
  147. };
  148. export default function TrialFlowContent({ width, height }) {
  149. // 拖拽相关
  150. const dropDomRef = useRef(null);
  151. const setTreeTopoData = (nodes, edges, direction = "TB") => {
  152. const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(nodes, edges, direction);
  153. setNodes([...layoutedNodes]);
  154. setEdges([...layoutedEdges]);
  155. };
  156. // 图操作
  157. const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  158. const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  159. const onEdgeUpdate = useCallback((oldEdge, newConnection) => setEdges(els => updateEdge(oldEdge, newConnection, els)), []);
  160. // const onConnect = useCallback(params => setEdges(els => addEdge(params, els)), []);
  161. const nodeTypes = useMemo(() => ({ textUpdater: CuFlowNode }), []);
  162. const onConnect = () => {
  163. // 禁止手动连线
  164. return;
  165. };
  166. useEffect(() => {
  167. setTreeTopoData(nodes, edges);
  168. }, []);
  169. return (
  170. <TrialCmdWrapper
  171. height={height}
  172. width={width}
  173. style={{
  174. padding: 0,
  175. float: "left",
  176. marginLeft: "15px"
  177. }}>
  178. <FlowContent ref={dropDomRef}>
  179. <ReactFlow
  180. nodeTypes={nodeTypes}
  181. nodes={nodes}
  182. edges={edges}
  183. onNodesChange={onNodesChange}
  184. onEdgesChange={onEdgesChange}
  185. onConnect={onConnect}
  186. onEdgeUpdate={onEdgeUpdate}></ReactFlow>
  187. </FlowContent>
  188. </TrialCmdWrapper>
  189. );
  190. }
展示效果

通过dagre,实现了自动树形布局的功能

原文地址

react-flow基础使用及dagre库的使用-小何博客前言最近项目中需要用到拓扑图的展示,最开始选用的是antv的拓扑图组件。antv组件虽然此很方便,但是在布局的时候总是会有莫名其妙的bug,然后自己也想法去解决(看前辈经验、官方issue),最后还是不能解决。于…https://ligo100.cn/qianduanjishu/531.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/864498
推荐阅读
相关标签
  

闽ICP备14008679号