React中antv X6 2.0的使用体验(含demo)

2023-11-02 05:30

本文主要是介绍React中antv X6 2.0的使用体验(含demo),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

BB一下🫧:写这antv X6系列文章的初衷是发现antv X6 2.0版本的相关文章不多,大多都是老版本的一些操作,2.0版本有一些升级,当我遇到问题时能够参考的资料非常有限,同时也是为了记录自己的学习到的一些知识点。

本次X6使用体验的功能包括:

1、节点右键菜单;

2、节点自动布局;

3、根据列表动态渲染节点;

4、节点之间的连线;

5、模版引用时的预览功能;

6、使用拖拽组件添加节点(包含2种样式 及群组的添加);

仓库地址:https://github.com/Tipchak5/antvX6_2.0_React.git


一、节点右键菜单

参考了官方文档写法后自己的的右键菜单功能( 官方案例:https://x6.antv.antgroup.com/zh/examples/edge/tool/#context-menu),个人感觉就是注册一个带有右键菜单功能的自定义节点。在初始化画布时注册一个菜单组件,并将你定义的节点返回出来(返回出来的节点要被Dropdown组件包裹),最后 再把你注册的这个右键菜单作为component放进注册的自定义节点中。 具体如下:

首先在init函数中去把你需要的组件定义好然后注册,我这里是CustomComponent,label是这个节点中要显示的内容,color是这个节点要动态显示的颜色(你可以在生成节点时去定义你要的字段),考虑到可能会有使用动态图片的情况,也在节点中添加了img,是否需要图标可以自行判断。

	const CustomComponent = ({ node }) => {const label = node.prop('label');const color = node.prop('color');const boder = node.store?.data?.attrs?.body?.stroke;return (<Dropdownmenu={{items: [{key: 'add',label: 'addNode',onClick: () => {console.log('addNode!!!');},},],}}trigger={['contextMenu']}><divclassName='custom-react-node'style={{background: label === '开始' ? '#7AA874' : color,border: `3px solid ${boder}`,}}><img className='img' src={male} alt='Icon' />{label}</div></Dropdown>);};register({shape: 'custom-react-node', // 后续生成的节点shap只要是这个 就会有右键菜单width: 100,height: 40,attrs: {label: {textAnchor: 'left',refX: 8,textWrap: {ellipsis: true,},},},component: CustomComponent,});

当组件注册好了之后,再引入注册自定义节点用的插件 import { register } from '@antv/x6-react-shape'; 
注册的时候有一个shape字段 ,当你后续生成的节点shap只要和注册的节点shap一致就会有右键菜单


当对某一节点右键操作后,想要获取改节点信息可以使用:

   graph.on('node:contextmenu', ({ node }) => {setNodeInfo(node); // 获取点击了右键的节点信息console.log(node, '我是被右键点击的节点!');});

完整代码 

import { useEffect, useRef, useState } from 'react';
import { register } from '@antv/x6-react-shape';
import { Graph } from '@antv/x6';
import { Export } from '@antv/x6-plugin-export';
import { Selection } from '@antv/x6-plugin-selection';
import { Snapline } from '@antv/x6-plugin-snapline';
import { Keyboard } from '@antv/x6-plugin-keyboard';
import { Clipboard } from '@antv/x6-plugin-clipboard';
import { History } from '@antv/x6-plugin-history';
import { Transform } from '@antv/x6-plugin-transform';
import { reset, showPorts } from '../../utils/method';
import './nodeFlow.less';const male ='https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*kUy8SrEDp6YAAAAAAAAAAAAAARQnAQ'; // iconconst ports = {}; // 连接桩 此处省略 可参考demo 或官网/** 函数组件 */
function Flow(props) {let graph;const nodeInfoRef = useRef(null);const [newGraph, setNewGraph] = useState(null); // 画布const [nodeInfo, setNodeInfo] = useState(null); // 节点信息useEffect(() => {nodeInfoRef.current = nodeInfo;// console.log(nodeInfoRef.current);}, [nodeInfo]);useEffect(() => {init(); // 初始graph.centerContent();graph.use(new Snapline({enabled: true,})).use(new Selection({enabled: true,})).use(new Keyboard({enabled: true,})).use(new Clipboard({enabled: true,})).use(new History({enabled: true,})).use(new Transform({resizing: true,rotating: true,enabled: true,})).use(new Export());graph.on('node:click', ({ node }) => {console.log(node);});graph.on('edge:click', ({ edge }) => {reset(graph);edge.attr('line/stroke', 'orange');});/** 右键操作 */graph.on('node:contextmenu', ({ node }) => {setNodeInfo(node); // 获取点击了右键的节点信息console.log(node, '我是被右键点击的节点!');});graph.bindKey(['ctrl+1', 'meta+1'], () => {const zoom = graph.zoom();if (zoom < 1.5) {graph.zoom(0.1);}});graph.bindKey(['ctrl+2', 'meta+2'], () => {const zoom = graph.zoom();if (zoom > 0.5) {graph.zoom(-0.1);}});// 删除处理graph.bindKey('backspace', () => {const cells = graph.getSelectedCells();const cellsId = cells[0].id;if (cellsId) {graph.removeCells(cells);// 删除节点信息 接口}});graph.zoomTo(0.8);return () => {graph.dispose(); // 销毁画布};}, []);return (<div className='FlowManage'><div className='content'><div className='graphBox'><div className='react-shape-app graph'><div id='graph-container' className='app-content' style={{ flex: 1 }}></div></div></div></div></div>);/** 初始化画布 */function init() {// 右键菜单const CustomComponent = ({ node }) => {const label = node.prop('label');const color = node.prop('color');const boder = node.store?.data?.attrs?.body?.stroke;return (<Dropdownmenu={{items: [{key: 'add',label: 'addNode',onClick: () => {console.log('addNode!!!');},},],}}trigger={['contextMenu']}><divclassName='custom-react-node'style={{background: label === '开始' ? '#7AA874' : color,border: `3px solid ${boder}`,}}>{label === '开始' ? null : <img className='img' src={male} alt='Icon' />}{label}</div></Dropdown>);};register({shape: 'custom-react-node',width: 100,height: 40,attrs: {label: {textAnchor: 'left',refX: 8,textWrap: {ellipsis: true,},},},component: CustomComponent,});graph = new Graph({container: document.getElementById('graph-container'),grid: true,panning: true,mousewheel: {enabled: true,zoomAtMousePosition: true,modifiers: 'ctrl',minScale: 0.4,maxScale: 3,},connecting: {snap: true,router: 'manhattan', // 路由模式highlight: true,},scroller: true,});graph.addNode({shape: 'custom-react-node',id: -1,label: '开始',ports: { ...ports },});graph.centerContent();graph.on('node:mouseenter', () => {const container = document.getElementById('graph-container');const ports = container.querySelectorAll('.x6-port-body');showPorts(ports, true);});graph.on('node:mouseleave', () => {const container = document.getElementById('graph-container');const ports = container.querySelectorAll('.x6-port-body');showPorts(ports, false);});setNewGraph(graph);}
}export default Flow;

二、节点自动布局

未自动布局:

自动布局后:

自动布局官方也有推荐(使用的是antv/layout插件,但是我这边和官方有点差别,我这里是需要安装dagre,使用的"dagre": "^0.8.5"版本,在使用的地方引入:import dagre from 'dagre';

 具体代码如下(你只用在需要的时候直接调用这个函数即可,如:新增节点后调用):

import dagre from 'dagre';function layout() {const g = graph ? graph : newGraph;const layout = new dagre.graphlib.Graph();layout.setGraph({ rankdir: 'LR', ranksep: 50, nodesep: 50, controlPoints: true });layout.setDefaultEdgeLabel(() => ({}));g?.getNodes()?.forEach((node) => {layout.setNode(node.id, { width: node.size().width, height: node.size().height });});g?.getEdges()?.forEach((edge) => {layout.setEdge(edge.getSourceCell()?.id, edge.getTargetCell()?.id);});dagre.layout(layout);g?.getNodes()?.forEach((node) => {const pos = layout.node(node.id);node.position(pos.x, pos.y);});g?.centerContent();}

当然,你可以根据官方文档来实现(网址:x6.antv.antgroup.com/temp/layout#布局流程)


三、根据列表动态渲染节点

最开始我想的是画布上渲染的内容由前端导出成json给后端,当进入页面又让后端返回给前端,这样来渲染,加上当时新增的节点ID和后端是两套ID,而且每次有节点修改都需要后端去json里面改了又返给我,这样对后端不是很友好,在我这个项目中也不算很合理,尤其是一些操作上的处理会很麻烦,考虑到这些问题,最后领导推荐根据列表数据去动态渲染节点,只需要在列表里,把每个节点的信息定义好,比如:节点的名称、颜色、源节点的id,然后再通过遍历去将节点添加到画布中。

具体代码如下:

	/** 根据列表渲染节点 */function refreshGraph() {const g = graph ? graph : newGraph;g?.clearCells(); // 清除先前的数据graph.addNode({shape: 'custom-react-node',id: -1,label: '开始',ports: { ...ports },}); // 原节点treeList[0].children?.forEach((i) => { // treeList节点列表let newNodeOptions = null;newNodeOptions = {shape: 'custom-react-node',id: i.key,label: i.title,color: i.color,ports: { ...ports },};// 如果存在父节点 连接两个节点let newNode = null;if (i.parents && g) {// const node = g.getCellById(i.parent);newNode = g?.addNode(newNodeOptions);i.parents.forEach((id) => {// 根据父id 连接子g?.addEdge({source: id,target: i.key,router: {name: 'manhattan',},});});} else {// 如果没有父节点g?.addEdge({source: -1,target: i.key,router: {name: 'manhattan',},});g?.addNode(newNodeOptions);}autoLayout(g); // 自动布局 抽成的公共方法 记得的引入});}

四、节点连线时的操作

在useEffect中加入这段代码,当你连接两个节点时,就能获取到源节点 和 目标节点的信息,比如:ID、Label等。

具体代码如下:

	graph.on('edge:connected', ({ isNew, edge }) => {if (isNew) {// 如果连接节点成功const source = edge.getSourceNode(); // 源节点const target = edge?.getTargetNode(); // 目标节点console.log(`源节点`, source, `目标节点`, target);}});

五、模版插入时的预览功能

antv X6 的模版预览比较简单,这边是抽成一个组件,只需要从父组件把要预览的数据导出之后,传递到模版预览组件就好了。

具体代码如下:

import { Graph } from '@antv/x6';
import { useState, useEffect, useRef } from 'react';const templateGraph = (props) => {const graphRef = useRef(null);const [graph, setGraph] = useState(null);useEffect(() => {// 画布初始化if (graphRef.current) {const newGraph = new Graph({container: graphRef.current,width: '100%',height: 200,grid: true,panning: true,mousewheel: {enabled: true,zoomAtMousePosition: true,modifiers: 'ctrl',minScale: 0.4,maxScale: 3,},scroller: true,node: {draggable: false,},});newGraph.centerContent();newGraph.zoomTo(0.5); // 画布缩放setGraph(newGraph);return () => {newGraph.dispose(); // 销毁画布};}}, [graphRef]);useEffect(() => {// 渲染父组件传来的画布数据if (props.data && graph) {tempate();}}, [props.data, graph]);return (<div className='graphBox' style={{ width: '100%', height: '200px' }}><div className='react-shape-app' style={{ width: '100%' }}><div ref={graphRef} style={{ width: '100%', height: '100%' }}></div></div></div>);/** 模版渲染 */async function tempate() {graph.removeCells(graph.getCells()); // 清除先前的数据await new Promise((resolve) => setTimeout(resolve, 100));graph.fromJSON(props.data).centerContent(); // 节点内容渲染与剧中}
};export default templateGraph;

六、使用拖拽组件添加节点 (两种样式)

我这里的写法是根据自己的需要改的,可以直接使用官方的(使用场景 | X6 (antgroup.com))

记得安装dnd插件 然后引入 import { Dnd } from '@antv/x6-plugin-dnd';

        样式一:(代码详情看demo,内含添加节点群组的方法 写的比较简单)

        样式二 :

 这里比较重要的一点是在list中想要使用拖拽的这个插件功能只能在原生的标签中使用(这里用的是ul和li标签),如果想要在antd的组件中使用需要自行去修改源码。

注意:在如果每个节点的img不一样那么就需要在init方法中把每个样式都作为一个自定义的节点去注册一遍!

代码太多,这边就放一小部分,具体的可以看demo

		nodeArr[0]?.children?.forEach((node) => {const { key, title, img } = node;const shape = `custom-node${key}`;register({shape,width: 100,height: 40,attrs: {image: {'xlink:href': img,},},component: (props) => <CustomComponent {...props} image={img} label={title} />,});}); // 注册每一个节点
const nodeArr = [{title: '其他节点',key: 'myTool',children: [{key: 1,title: 'Node1',img:'',},{key: 2,title: 'Node2',img:'',},],},{title: '通用节点',key: 'publicTool',children: [],},
];<ul><li>其他节点<ul>{nodeArr[0].children.map((i) => {return (<div style={{ marginTop: '10px' }} key={i.key} onMouseDown={(e)=> {startDrag(e, i);}}>{i.title}</div>);})}</ul></li><li style={{ marginTop: '10px' }}>通用节点</li>
</ul>/** 拖拽节点到画布 */
function startDrag(e, i) {const g = graph ? graph : newGraph;// console.log(id);const nodeTypes = {Node1: `custom-node${i.key}`,Node2: `custom-node${i.key}`,//其他节点类型};const node = g?.createNode({label: i.title,ports: { ...ports },color: '',shape: nodeTypes[i.title],});dndRef.current?.start(node, e?.nativeEvent);
}

项目差不多也要进入测试阶段了,抽空把自己在antv X6 2.0版本中遇到的一些难点和值得记录的地方写了出来,欢迎大家共同交流,一起进步!👏👏👏~

这篇关于React中antv X6 2.0的使用体验(含demo)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/328437

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma