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

news2024/9/23 19:28:54

使用 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, nextNode
    const edges = graph.getEdges()
    edges.forEach(edge => {
      const _preId = edge.store.previous.source.cell
      const _nextId = edge.store.previous.target.cell

      if (_preId === id) {
        nextEdge = edge
        nextNode = graph.getCell(_nextId)
      }
      if (_nextId === id) {
        preEdge = edge
        preNode = 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 = 260
    const height = 90
    nodes.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 Node
      if (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.height
        const fix = sourceBBox.height
        const y = sourceBBox.y + fix + gap / 2
        edge.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-css
    insertCss(`    .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.coloradmin.cn/o/823397.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

css滚动条样式指南

css滚动条样式指南 滚动条是网页设计中经常被忽视的元素。虽然它看起来像是一个小细节&#xff0c;但它在网站导航中起着至关重要的作用。默认的滚动条可能看起来不合适&#xff0c;有损整体美观。本文将介绍如何使用 CSS 自定义滚动条。 在 Chrome、Edge 和 Safari 中设置滚…

微信小程序接入腾讯云天御验证码

腾讯云新一代行为验证码&#xff08;Captcha&#xff09;&#xff0c;基于十道安全防护策略&#xff0c;为网页、APP、小程序开发者打造立体、全面的人机验证。在保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时&#xff0c;提供更精细化的用户体验。 …

Uniapp_app端使用重力感应实现横屏竖屏自动切换

1、进入页面默认是竖屏当手机横着的时候页面也跟着横着 进入页面开启定时器调用相关api去触发横屏竖屏&#xff0c;主要核心代码都在onShow()里面和onHide()里 <template> <view class"monitor"><u-no-network></u-no-network><web-view …

6.使用typeof注释,只读修饰符

目录 1 使用typeof注释 2 只读修饰符 readonly 2.1 在类中使用 2.2 在接口中使用 2.3 在很多的地方都能使用 1 使用typeof注释 比如我就像传入像 p 这个变量的样子的参数 如果你给的不是这个样子的就会报错 也可以这样用 ts中的typeof后面不能跟未定义返回值类型的…

金融学复习博迪(第1-5章)

第一部分 金融和金融体系 第1章 金融学 金融&#xff1a;资金的流通&#xff0c;即储蓄&#xff0c;信贷、汇兑、股票和证券交易等经济活动的总称。 金融学&#xff1a;研究货币流通的学问。 传统的金融学研究领域大致有两个方向&#xff1a; >宏观层面的金融市场运行理论…

k8s概念-深入pod

回到目录 工作负载&#xff08;workloads&#xff09; 工作负载&#xff08;workload&#xff09;是在kubernetes集群中运行的应用程序。无论你的工作负载是单一服务还是多个一同工作的服务构成&#xff0c;在kubernetes中都可以使用pod来运行它 workloads分为pod与control…

Android 13(T) - Media框架(2)- MediaPlayer与native的串接 libmedia

这一节学习有两个目标&#xff1a; 1 熟悉Android Media API的源码路径与调用层次 2 从MediaPlayer的创建与销毁了解与native的串接 1、源码路径 Media相关的API位于&#xff1a;frameworks/base/media/java/android/media&#xff0c;里面提供有MediaPlayer MediaCodecList M…

数据结构一轮复习 之 第二章

一、线性表&#xff08;逻辑结构&#xff09;-顺序表&#xff08;物理结构&#xff09; 操作&#xff1a; 静态分配&#xff1a;ElemType data[Lenght] 动态分配&#xff08;空间可扩充&#xff1a;新开辟一个更大的空间&#xff0c;并移动原数据&#xff09;&#xff1a;Ele…

【机器学习】西瓜书学习心得及课后习题参考答案—第5章神经网络

5.1神经元模型——这是神经网络中最基本的成分。 5.2感知机与多层网络——由简单的感知机循序渐进引出多层前馈神经网络。 5.3误差逆传播算法——BP算法&#xff0c;迄今最成功的神经网络学习算法。算法如下&#xff08;公式参考西瓜书&#xff09; 停止条件与缓解BP过拟合的…

与“云”共舞,联想凌拓的新科技与新突破

伴随着数字经济的高速发展&#xff0c;IT信息技术在数字中国建设中起到的驱动和支撑作用也愈发凸显。特别是2023年人工智能和ChatGPT在全球的持续火爆&#xff0c;更是为整个IT产业注入了澎湃动力。那么面对日新月异的IT信息技术&#xff0c;再结合疫情之后截然不同的经济环境和…

【Redis】内存数据库Redis进阶(Redis持久化)

目录 分布式缓存 Redis 四大问题Redis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 A…

2023年的深度学习入门指南(23) - ChatGLM2

2023年的深度学习入门指南(23) - ChatGLM2 在《在你的电脑上运行大模型》这一节&#xff0c;我们曾经介绍过ChatGLM模型&#xff0c;它是当时最好的中文大模型之一。现在&#xff0c;它又更新到了第二代&#xff0c;即ChatGLM2。 当时&#xff0c;我们的技术储备还不足&#…

selenium 遇到更新chorme驱动

打开浏览器,在地址栏输入chrome://version/便可以查看到谷歌当前的版本号 谷歌浏览器驱动的下载网址 http://chromedriver.storage.googleapis.com/index.htmlhttp://chromedriver.storage.googleapis.com/index.html 解压后把chromedriver.exe 放到python安装的目录下&am…

3.netty和protobuf

1.ChannelGroup可以免遍历由netty提供,覆盖remove方法即可触发删除channel\ 2.群聊私聊 13.群聊私聊简单原理图 3.netty心跳检测机制,客户端对服务器有没有读写(读,写空闲) //IdleStateHandler(3,5,7,TimeUnite.SECONDS)是netty提供的检测状态的处理器,也加到pipeline,读,写,…

Windows下FreeImage库的配置

首先下载FreeImage库&#xff0c;http://freeimage.sourceforge.net/download.html&#xff0c;官网下载如下&#xff1a; 内部下载地址&#xff1a;https://download.csdn.net/download/qq_36314864/88140305 解压后&#xff0c;打开FreeImage.2017.sln&#xff0c;如果是vs…

【 Redis】的乱码问题

问题描述&#xff1a; 使用RedisTemplate存储的数据&#xff0c;在 redis-cli 客户端查看时&#xff0c;key 和 value 都会携带类似\xac\xad\这样的字符串。 原因&#xff1a; 由于默认使用了 jdk 的序列化方式。以下是支持的序列化方式 项目一般都会有缓存&#xff0c;常常…

Python人工智能在气象中怎样应用?

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;在数据处理、科学计算、数学建模、数据挖掘和数据可视化方面具备优异的性能&#xff0c;这些优势使得Python在气象、海洋、地理、气候、水文和生态等地学领域的科研和工程项目中得到广泛应用。可以…

软件安全测试和渗透测试的区别在哪?安全测试报告有什么作用?

软件安全测试和渗透测试在软件开发过程中扮演着不同的角色&#xff0c;同时也有不同的特点和目标。了解这些区别对于软件开发和测试人员来说非常重要。本文将介绍软件安全测试和渗透测试的区别&#xff0c;以及安全测试报告在软件开发和测试过程中的作用。 一、 软件安全测试和…

【《概率图模型原理与应用:第2版》——概率机器学习与人工智能可解释性领域不可多得的著作】

《概率图模型原理与应用&#xff1a;第2版》反映了PGM的理论基础与进展。取材精炼&#xff0c;层次分明&#xff0c;是一-本很好的关于PGM的专业书籍。同时结合了大量的案例分析与代码算例,使得初学者能快速掌握前沿的PGM理论。本书的翻译与出版能进一步推进国内 人工智能算法领…

Vue3 element-plus表单嵌套表格实现动态表单验证

Vue3结合element-plus表单项可以动态添加/删除 部分效果图如下&#xff1a; 另表格有添加和删除按钮&#xff0c;点击提交进行表单验证。 首先data格式必须是对象包裹数组 import { ref, reactive } from vue; import { FormInstance } from element-plus const froms re…