使用 AntV X6 + vue 实现单线流程图

使用 AntV X6 + vue 实现单线流程图

X6 是 AntV 旗下的图编辑引擎,提供了一系列开箱即用的交互组件和简单易用的节点定制能力,方便我们快速搭建 DAG 图、ER 图、流程图等应用。

在这里插入图片描述

官方文档

安装

yarn add  @antv/x6@1.34.6

Tips: 目前 X6 有 1.x 和 2.x 两个版本,因为官方文档的示例代码都是 1.x 版本的,所以本文档也是基于 1.x 版本的,如果你使用的是 2.x 版本,可以参考官方文档。

常用 API

API说明使用方法
Graph图实例const graph=new Graph()
graph.zoomTo缩放图形graph.zoomTo(0.8)
graph.centerContent图形居中graph.centerContent()
graph.getCell获取节点graph.getCell(node.id)
graph.addCell新增节点graph.addCell([node1,edge1,node2,node3])
graph.removeCells删除节点graph.removeCells(cell)
graph.createEdge创建连接线graph.createEdge(node1,node2)
graph.removeEdge删除连接线graph.removeEdge(edge.id)
graph.getNodes获取所有节点graph.getNodes()
graph.getEdges获取所有连接线graph.getEdges()
Graph.registerNod自定义元素样式可查看文档

demo 代码(以下为 vue 实现单线流程图示例)

实现效果

在这里插入图片描述

vue 代码

Tips: 示例代码需安装 dagre 和 insert-css 依赖

<template><div id="container"></div>
</template>
<script setup lang="ts">import { Graph, Cell, Node, Color, Dom } from '@antv/x6'import dagre from 'dagre'import insertCss from 'insert-css'import { ref, reactive, computed, watch, onMounted, onUnmounted } from 'vue'const male ='https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*kUy8SrEDp6YAAAAAAAAAAAAAARQnAQ'const female ='https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*f6hhT75YjkIAAAAAAAAAAAAAARQnAQ'let graph, nodes, edges// 自定义节点,使用的是 svg格式Graph.registerNode('org-node',{width: 260,height: 88,markup: [{tagName: 'rect',attrs: {class: 'card',},},{tagName: 'image',attrs: {class: 'image',},},{tagName: 'text',attrs: {class: 'rank',},},{tagName: 'text',attrs: {class: 'name',},},{tagName: 'g',attrs: {class: 'btn add',},children: [{tagName: 'circle',attrs: {class: 'add',},},{tagName: 'text',attrs: {class: 'add',},},],},{tagName: 'g',attrs: {class: 'btn del',},children: [{tagName: 'circle',attrs: {class: 'del',},},{tagName: 'text',attrs: {class: 'del',},},],},],attrs: {'.card': {rx: 10,ry: 10,refWidth: '100%',refHeight: '100%',fill: '#5F95FF',stroke: '#5F95FF',strokeWidth: 1,pointerEvents: 'visiblePainted',},'.image': {x: 16,y: 16,width: 56,height: 56,opacity: 0.7,},'.rank': {refX: 0.95,refY: 0.5,fill: 'blue',fontFamily: 'Courier New',fontSize: 13,textAnchor: 'end',textVerticalAnchor: 'middle',},'.name': {refX: 0.95,refY: 0.7,fill: '#fff',fontFamily: 'Arial',fontSize: 14,fontWeight: '600',textAnchor: 'end',},'.btn.add': {refDx: -16,refY: 16,event: 'node:add',},'.btn.del': {refDx: -44,refY: 16,event: 'node:delete',},'.btn > circle': {r: 10,fill: 'transparent',stroke: '#fff',strokeWidth: 1,},'.btn.add > text': {fontSize: 20,fontWeight: 800,fill: '#fff',x: -5.5,y: 7,fontFamily: 'Times New Roman',text: '+',},'.btn.del > text': {fontSize: 28,fontWeight: 500,fill: '#fff',x: -4.5,y: 6,fontFamily: 'Times New Roman',text: '-',},},},true,)// 自定义边Graph.registerEdge('org-edge',{zIndex: -1,attrs: {line: {strokeWidth: 2,stroke: '#A2B1C3',sourceMarker: null,targetMarker: null,},},},true,)let i = 1// 监听自定义事件function setup() {graph.on('node:add', ({ e, node }) => {e.stopPropagation()const member = createNode('新建字段' + i, '新建字段' + i, Math.random() < 0.5 ? male : female)i++graph.freeze()const { preEdge, nextEdge, preNode, nextNode } = getPreAndNextNodeEdge(node.id)if (nextEdge) {graph.removeEdge(nextEdge.id)graph.addCell([createEdge(member, nextNode)])}graph.addCell([member, createEdge(node, member)])layout()})graph.on('node:delete', ({ e, node }) => {e.stopPropagation()graph.freeze()const { preEdge, nextEdge, preNode, nextNode } = getPreAndNextNodeEdge(node.id)if (preEdge) {graph.removeEdge(preEdge.id)}if (nextEdge) {graph.removeEdge(nextEdge.id)}if (preEdge && nextEdge) {graph.addCell([createEdge(preNode, nextNode)])}graph.removeNode(node.id)layout()})}function updateEdges() {edges = nodes.reduce((arr, node, index) => {if (index === 0) {return []}arr.push(createEdge(nodes[index - 1], node))return arr}, [])console.log('edges', edges)}function getPreAndNextNodeEdge(id: string) {let preEdge, nextEdge, preNode, nextNodeconst edges = graph.getEdges()edges.forEach(edge => {const _preId = edge.store.previous.source.cellconst _nextId = edge.store.previous.target.cellif (_preId === id) {nextEdge = edgenextNode = graph.getCell(_nextId)}if (_nextId === id) {preEdge = edgepreNode = graph.getCell(_preId)}})return { preEdge, nextEdge, preNode, nextNode }}// 自动布局function layout() {const nodes = graph.getNodes()const edges = graph.getEdges()const g = new dagre.graphlib.Graph()g.setGraph({ nodesep: 16, ranksep: 16 })g.setDefaultEdgeLabel(() => ({}))const width = 260const height = 90nodes.forEach(node => {g.setNode(node.id, { width, height })})edges.forEach(edge => {const source = edge.getSource()const target = edge.getTarget()g.setEdge(source.cell, target.cell)})dagre.layout(g)graph.freeze()g.nodes().forEach(id => {const node = graph.getCell(id) as Nodeif (node) {const pos = g.node(id)node.position(pos.x, pos.y)}})edges.forEach(edge => {const source = edge.getSourceNode()!const target = edge.getTargetNode()!const sourceBBox = source.getBBox()const targetBBox = target.getBBox()console.log(sourceBBox, targetBBox)if (sourceBBox.x !== targetBBox.x) {const gap = targetBBox.y - sourceBBox.y - sourceBBox.heightconst fix = sourceBBox.heightconst y = sourceBBox.y + fix + gap / 2edge.setVertices([{ x: sourceBBox.center.x, y },{ x: targetBBox.center.x, y },])} else {edge.setVertices([])}})graph.unfreeze()}function createNode(rank: string, name: string, image: string) {return graph.createNode({shape: 'org-node',attrs: {'.image': { xlinkHref: image },'.rank': {text: Dom.breakText(rank, { width: 160, height: 45 }),},'.name': {text: Dom.breakText(name, { width: 160, height: 45 }),},},})}function createEdge(source: Cell, target: Cell) {return graph.createEdge({shape: 'org-edge',source: { cell: source.id },target: { cell: target.id },})}onMounted(() => {// 定义样式// 我们用 insert-css 演示引入自定义样式// 推荐将样式添加到自己的样式文件中// 若拷贝官方代码,别忘了 npm install insert-cssinsertCss(`    .x6-cell {cursor: default;}.x6-node .btn {cursor: pointer;}`)// 创建画布graph = new Graph({container: document.getElementById('container')!,scroller: true,interacting: false,width: 800,height: 600,})nodes = [createNode('董事长', '审批', male),createNode(' CEO', '呵呵', female),createNode('小李', '描述', male),]updateEdges()graph.resetCells([...nodes, ...edges])layout()graph.zoomTo(0.8)graph.centerContent()setup()})onUnmounted(() => {graph.dispose()})
</script><style scoped></style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/54542.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用IDEA操作Mysql数据库

idea中自带了关于数据库的连接 首先要确保你的MySQL正在运行中 打开idea找到database&#xff08; view —> Tool Windows —> database&#xff09;&#xff0c;大家也可以定个快捷键&#xff0c;方便以后日常操作 就是这个样子&#xff0c;然后点加号 然后就可以编写执…

【C语言进阶(2)】数据存储练习题

文章目录 练习1练习2练习3练习4练习5练习6练习7 练习1 下面代码的结果为什么会是这样&#xff1f; 代码分析 char a -1; 11111111 11111111 11111111 11111111 //-1 的补码 - 因为 a 是 char 类型的变量&#xff0c;只能存储 8 个比特位&#xff0c;将 -1 补码的后 8 位截断…

Mac系统下使用远程桌面连接Windows系统

一、远程桌面工具 Microsoft Remote Desktop 二、下载地址 https://go.microsoft.com/fwlink/?linkid868963 三、下载并安装 四、添加远程PC PC name:云服务器IP。 User account: 添加系统用户 PC name&#xff1a;远程桌面 IP 地址User account&#xff1a;可以选择是…

SpringBoot实现数据库读写分离

SpringBoot实现数据库读写分离 参考博客https://blog.csdn.net/qq_31708899/article/details/121577253 实现原理&#xff1a;翻看AbstractRoutingDataSource源码我们可以看到其中的targetDataSource可以维护一组目标数据源(采用map数据结构)&#xff0c;并且做了路由key与目标…

基于Go编写一个可视化Navicat本地密码解析器

前提 开发小组在测试环境基于docker构建和迁移一个MySQL8.x实例&#xff0c;过程中大意没有记录对应的用户密码&#xff0c;然后发现某开发同事本地Navicat记录了根用户&#xff0c;于是搜索是否能够反解析Navicat中的密码掩码&#xff08;这里可以基本断定Navicat对密码是采用…

kafka-保证数据不重复-生产者开启幂等性和事务的作用?

1. 生产者开启幂等性为什么能去重&#xff1f; 1.1 场景 适用于消息在写入到服务器日志后&#xff0c;由于网络故障&#xff0c;生产者没有及时收到服务端的ACK消息&#xff0c;生产者误以为消息没有持久化到服务端&#xff0c;导致生产者重复发送该消息&#xff0c;造成了消…

LeetCode 0024. 两两交换链表中的节点:粗暴易懂的方法(几个临时变量)

【LetMeFly】24.两两交换链表中的节点&#xff1a;粗暴易懂的方法&#xff08;几个临时变量&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/swap-nodes-in-pairs/ 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点…

Docker学习(二十四)报错速查手册

目录 一、This error may indicate that the docker daemon is not running 报错docker login 报错截图&#xff1a;原因分析&#xff1a;解决方案&#xff1a; 二、Get "https://harbor.xxx.cn/v2/": EOF 报错docker login 报错截图&#xff1a;原因分析&#xff1a…

Java根据坐标经纬度计算两点距离(5种方法)、校验经纬度是否在圆/多边形区域内的算法推荐

目录 前言 一、根据坐标经纬度计算两点距离&#xff08;5种方法&#xff09; 1.方法一 2.方法二 3.方法三 4.方法四 5.方法五 5.1 POM引入第三方依赖 5.2 代码 6.测试结果对比 二、校验经纬度是否在制定区域内 1.判断一个坐标是否在圆形区域内 2.判断一个坐标是否…

WMS仓库管理系统研发规划说明

01 产品背景 1.1 背景概述 aboss WMS东南亚仓库管理系统是一个基于BigSeller系统的使用基础上&#xff0c;加上多仓库的解决思路&#xff0c;解决入库业务、出库业务、仓库调拨、库存调拨和虚仓管理等功能&#xff0c;对批次管理、物料对应、库存盘点、质检管理、虚仓管理和即…

Django实现音乐网站 ⑷

使用Python Django框架制作一个音乐网站&#xff0c;在系列文章3的基础上继续开发&#xff0c; 本篇主要是后台歌曲类型表、歌单表模块功能开发。 目录 表结构设计 歌曲类型表结构 歌单表结构 创建表模型 创建表 后台注册表模型 引入表模型 后台自定义 总结 表结构设计…

Mr. Cappuccino的第56杯咖啡——Mybatis拦截器

Mybatis拦截器 概述应用场景项目结构实现分页查询其它拦截器的使用 概述 Mybatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用&#xff0c;通过织入拦截器&#xff0c;在不同节点修改一些执行过程中的关键属性&#xff0c;从而影响SQL的生成、执行和返回结果…