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

前言

最近项目中需要用到拓扑图的展示,最开始选用的是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

 

使用

import React, { useCallback } from "react";
import ReactFlow, { useNodesState, useEdgesState, updateEdge } from "reactflow";

import { TrialCmdWrapper } from "@/pages/trial/style";
import { FlowContent } from "./style";

import "reactflow/dist/style.css";
import { useMemo } from "react";
import CuFlowNode from "@/components/Home/resTopo/topo/node";

const initialNodes = [
	{
		id: "root",
		type: "input",
		data: { label: "全局节点" },
		position: { x: 0, y: 0 }
	},
	{
		id: "horizontal-2",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "A Node" },
		position: { x: 250, y: 0 }
	},
	{
		id: "horizontal-3",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "Node 3" },
		position: { x: 250, y: 160 }
	},
	{
		id: "horizontal-4",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "Node 4" },
		position: { x: 500, y: 0 }
	},
	{
		id: "horizontal-5",
		sourcePosition: "top",
		targetPosition: "bottom",
		data: { label: "Node 5" },
		position: { x: 500, y: 100 }
	},
	{
		id: "horizontal-6",
		sourcePosition: "bottom",
		targetPosition: "top",
		data: { label: "Node 6" },
		position: { x: 500, y: 230 }
	},
	{
		id: "horizontal-7",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "Node 7" },
		position: { x: 750, y: 50 }
	},
	{
		id: "horizontal-8",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "Node 8" },
		position: { x: 750, y: 300 }
	}
];

const initialEdges = [
	{
		id: "horizontal-e1-2",
		source: "root",
		type: "smoothstep",
		target: "horizontal-2",
		animated: true
	},
	{
		id: "horizontal-e1-3",
		source: "root",
		type: "smoothstep",
		target: "horizontal-3",
		animated: true
	},
	{
		id: "horizontal-e1-4",
		source: "horizontal-2",
		type: "smoothstep",
		target: "horizontal-4",
		label: "edge label"
	},
	{
		id: "horizontal-e3-5",
		source: "horizontal-3",
		type: "smoothstep",
		target: "horizontal-5",
		animated: true
	},
	{
		id: "horizontal-e3-6",
		source: "horizontal-3",
		type: "smoothstep",
		target: "horizontal-6",
		animated: true
	},
	{
		id: "horizontal-e5-7",
		source: "horizontal-5",
		type: "smoothstep",
		target: "horizontal-7",
		animated: true
	},
	{
		id: "horizontal-e6-8",
		source: "horizontal-6",
		type: "smoothstep",
		target: "horizontal-8",
		animated: true
	}
];

export default function TrialFlowContent({ width, height }) {
	// 图操作
	const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
	const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
	const onEdgeUpdate = useCallback((oldEdge, newConnection) => setEdges(els => updateEdge(oldEdge, newConnection, els)), []);
	// const onConnect = useCallback(params => setEdges(els => addEdge(params, els)), []);
	const nodeTypes = useMemo(() => ({ textUpdater: CuFlowNode }), []);
	const onConnect = () => {
		// 禁止手动连线
		return;
	};

	return (
		<TrialCmdWrapper
			height={height}
			width={width}
			style={{
				padding: 0,
				float: "left",
				marginLeft: "15px"
			}}>
			<FlowContent>
				<ReactFlow
					nodeTypes={nodeTypes}
					nodes={nodes}
					edges={edges}
					onNodesChange={onNodesChange}
					onEdgesChange={onEdgesChange}
					onConnect={onConnect}
					onEdgeUpdate={onEdgeUpdate}></ReactFlow>
			</FlowContent>
		</TrialCmdWrapper>
	);
}

 效果

 

dagre布局库使用

安装

tyarn add dagre

使用

组件外新增代码

// dagre 数据
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));

const nodeWidth = 172;
const nodeHeight = 36;

const getLayoutedElements = (nodes, edges, direction = "TB") => {
	const isHorizontal = direction === "LR";
	dagreGraph.setGraph({ rankdir: direction });

	nodes.forEach(node => {
		dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
	});

	edges.forEach(edge => {
		dagreGraph.setEdge(edge.source, edge.target);
	});

	dagre.layout(dagreGraph);

	nodes.forEach(node => {
		const nodeWithPosition = dagreGraph.node(node.id);
		node.targetPosition = isHorizontal ? "left" : "top";
		node.sourcePosition = isHorizontal ? "right" : "bottom";

		// We are shifting the dagre node position (anchor=center center) to the top left
		// so it matches the React Flow node anchor point (top left).
		node.position = {
			x: nodeWithPosition.x - nodeWidth / 2,
			y: nodeWithPosition.y - nodeHeight / 2
		};

		return node;
	});

	return { nodes, edges };
};

 组件内部新增方法

 

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

 

使用方法

useEffect(() => {
		setTreeTopoData(nodes, edges);
	}, []);

 最终代码

 

import React, { useCallback } from "react";
import ReactFlow, { useNodesState, useEdgesState, updateEdge } from "reactflow";

import { TrialCmdWrapper } from "@/pages/trial/style";
import { FlowContent } from "./style";

import "reactflow/dist/style.css";
import { useRef } from "react";
import { useEffect } from "react";
import { useMemo } from "react";
import CuFlowNode from "@/components/Home/resTopo/topo/node";
import dagre from "dagre";

const initialNodes = [
	{
		id: "root",
		type: "input",
		data: { label: "全局节点" },
		position: { x: 0, y: 0 }
	},
	{
		id: "horizontal-2",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "A Node" },
		position: { x: 250, y: 0 }
	},
	{
		id: "horizontal-3",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "Node 3" },
		position: { x: 250, y: 160 }
	},
	{
		id: "horizontal-4",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "Node 4" },
		position: { x: 500, y: 0 }
	},
	{
		id: "horizontal-5",
		sourcePosition: "top",
		targetPosition: "bottom",
		data: { label: "Node 5" },
		position: { x: 500, y: 100 }
	},
	{
		id: "horizontal-6",
		sourcePosition: "bottom",
		targetPosition: "top",
		data: { label: "Node 6" },
		position: { x: 500, y: 230 }
	},
	{
		id: "horizontal-7",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "Node 7" },
		position: { x: 750, y: 50 }
	},
	{
		id: "horizontal-8",
		sourcePosition: "right",
		targetPosition: "left",
		data: { label: "Node 8" },
		position: { x: 750, y: 300 }
	}
];

const initialEdges = [
	{
		id: "horizontal-e1-2",
		source: "root",
		type: "smoothstep",
		target: "horizontal-2",
		animated: true
	},
	{
		id: "horizontal-e1-3",
		source: "root",
		type: "smoothstep",
		target: "horizontal-3",
		animated: true
	},
	{
		id: "horizontal-e1-4",
		source: "horizontal-2",
		type: "smoothstep",
		target: "horizontal-4",
		label: "edge label"
	},
	{
		id: "horizontal-e3-5",
		source: "horizontal-3",
		type: "smoothstep",
		target: "horizontal-5",
		animated: true
	},
	{
		id: "horizontal-e3-6",
		source: "horizontal-3",
		type: "smoothstep",
		target: "horizontal-6",
		animated: true
	},
	{
		id: "horizontal-e5-7",
		source: "horizontal-5",
		type: "smoothstep",
		target: "horizontal-7",
		animated: true
	},
	{
		id: "horizontal-e6-8",
		source: "horizontal-6",
		type: "smoothstep",
		target: "horizontal-8",
		animated: true
	}
];

// dagre 数据
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));

const nodeWidth = 172;
const nodeHeight = 36;

const getLayoutedElements = (nodes, edges, direction = "TB") => {
	const isHorizontal = direction === "LR";
	dagreGraph.setGraph({ rankdir: direction });

	nodes.forEach(node => {
		dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
	});

	edges.forEach(edge => {
		dagreGraph.setEdge(edge.source, edge.target);
	});

	dagre.layout(dagreGraph);

	nodes.forEach(node => {
		const nodeWithPosition = dagreGraph.node(node.id);
		node.targetPosition = isHorizontal ? "left" : "top";
		node.sourcePosition = isHorizontal ? "right" : "bottom";

		// We are shifting the dagre node position (anchor=center center) to the top left
		// so it matches the React Flow node anchor point (top left).
		node.position = {
			x: nodeWithPosition.x - nodeWidth / 2,
			y: nodeWithPosition.y - nodeHeight / 2
		};

		return node;
	});

	return { nodes, edges };
};

export default function TrialFlowContent({ width, height }) {
	// 拖拽相关
	const dropDomRef = useRef(null);

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

	// 图操作
	const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
	const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
	const onEdgeUpdate = useCallback((oldEdge, newConnection) => setEdges(els => updateEdge(oldEdge, newConnection, els)), []);
	// const onConnect = useCallback(params => setEdges(els => addEdge(params, els)), []);
	const nodeTypes = useMemo(() => ({ textUpdater: CuFlowNode }), []);
	const onConnect = () => {
		// 禁止手动连线
		return;
	};

	useEffect(() => {
		setTreeTopoData(nodes, edges);
	}, []);

	return (
		<TrialCmdWrapper
			height={height}
			width={width}
			style={{
				padding: 0,
				float: "left",
				marginLeft: "15px"
			}}>
			<FlowContent ref={dropDomRef}>
				<ReactFlow
					nodeTypes={nodeTypes}
					nodes={nodes}
					edges={edges}
					onNodesChange={onNodesChange}
					onEdgesChange={onEdgesChange}
					onConnect={onConnect}
					onEdgeUpdate={onEdgeUpdate}></ReactFlow>
			</FlowContent>
		</TrialCmdWrapper>
	);
}

展示效果

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

 

 

版权声明:
作者:小何
链接:https://ligo100.cn/qianduanjishu/531.html
来源:小何博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
< <上一篇
下一篇>>
文章目录
关闭
目 录