使用 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, 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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无涯教程-Lua - 环境安装

在Windows上安装 为Windows环境开发了一个单独的名为" SciTE"的IDE,可以从https://code.google.com/p/luaforwindows/下载部分。 运行下载的可执行文件以安装Lua IDE。 由于它是一个IDE&#xff0c;因此您可以使用它来创建和构建Lua代码。 如果您有兴趣在命令行模…

flutter minio

背景 前端 经常需要上传文件 图片 视频等等 到后端服务器&#xff0c; 如果到自己服务器 一般会有安全隐患。也不方便管理这些文件。如果要想使用一些骚操作 比如 按照前端请求生成不同分辨率的图片&#xff0c;那就有点不太方便了。 这里介绍以下 minio&#xff0c;&#xff0…

nginx入门 - 学习笔记(ing)

一、初识 1、相关概念 1&#xff09;正向代理 一个位于客户端和原始服务器之间的服务器&#xff0c;为了从原始服务器取得内容&#xff0c;客户端向代理发送一个请求并指定目标&#xff0c;然后代理向原始服务器转交请求并将获得内容返回给客户端。 2&#xff09;反向代理…

springboot整合mybatis分页(使用pagehelper 分页插件)-- 学习若依系统

学习文档&#xff08;参考若依系统&#xff09; 若依的文档&#xff1a;http://doc.ruoyi.vip/ruoyi-vue/document/htsc.html#%E5%88%86%E9%A1%B5%E5%AE%9E%E7%8E%B0 就不从零搭建springboot项目了&#xff0c;直接在自己的项目基础上引入。 1、引入的依赖 <!-- pagehel…

【ChatGPT辅助学Rust | 基础系列 | Cargo工具】Cargo介绍及使用

文章目录 前言一&#xff0c;Cargo介绍1&#xff0c;Cargo安装2&#xff0c;创建Rust项目2&#xff0c;编译项目&#xff1a;3&#xff0c;运行项目&#xff1a;4&#xff0c;测试项目&#xff1a;5&#xff0c;更新项目的依赖&#xff1a;6&#xff0c;生成项目的文档&#xf…

xml的学习笔记

学习视频&#xff1a;093-尚硅谷-xml-什么是XML以及它的作用_哔哩哔哩_bilibili 目录 XML简介 XML的作用 XML语法 1.文档声明 2.xml注释 3.元素标签 4.xml属性 5.语法规则 1.所有xml元素都须有关闭标签(也就是闭合) 2.xml 标签对大小写敏感 3.xml必须正确的嵌套 4…

8.泛型

目录 1 基本使用 2 多个泛型 3 泛型约束 3.1 数组 3.2 extends约束 3.3 用泛型约束泛型 4 泛型接口 5 ts中的数组用的就是泛型 6 泛型类 7 常用泛型工具类型 7.1 让所有属性变为可选属性 Partial 7.2 将所有属性都变为只读属性 Readonly 7.3 从指定类…

【LeetCode】不同路劲(动态规划)

不同路劲 题目描述算法流程编程代码 链接: 不同路劲 题目描述 算法流程 编程代码 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m 1,vector<int>(n 1));dp[1][0] 1;for(int i 1;i < m;i){for(int j 1;j < n…

用于视觉跟踪的在线特征选择研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

深入理解MVVM架构模式

MVVM原理 MVVM是一种用于构建用户界面的软件架构模式&#xff0c;它的名称代表着三个组成部分&#xff1a;Model&#xff08;模型&#xff09;、View&#xff08;视图&#xff09;和ViewModel&#xff08;视图模型&#xff09;。MVVM的主要目标是将应用程序的UI与其底层数据模…

认清现实重新理解游戏的本质

认清现实重新理解游戏的本质 OVERVIEW 认清现实重新理解游戏的本质现实两条小路的启发四个动机1.当前的学习任务或工作任务太艰巨2.完美主义3.对未来太过于自信/无知4.大脑小看未来的收益 四个方法1.让未来的收益足够巨大2.让未来的收益感觉就在眼前3.玩游戏有恶劣的结果4.玩游…

idea模块的pom.xml被划横线,不识别的解决办法

目录 问题&#xff1a; 解决办法&#xff1a; 1.打开设置 2. 取消勾选 3.点击确认 4.解决 问题提出&#xff1a; 写shi山的过程中&#xff0c;给模块取错名字了&#xff0c;改名的时候不知道点到了什么&#xff0c;一个模块的pom.xml变成灰色了&#xff0…

听说 Spring Bean 的创建还有一条捷径?

文章目录 1. resolveBeforeInstantiation1.1 applyBeanPostProcessorsBeforeInstantiation1.2 applyBeanPostProcessorsAfterInitialization1.3 案例 2. 源码实践2.1 切面 Bean2.2 普通 Bean 在 Spring Bean 的创建方法中&#xff0c;有如下一段代码&#xff1a; AbstractAutow…

【shell】获取ping的时延数据并分析网络情况及常用命令学习

文章目录 获取ping的时延数据并分析网络情况|、||、&、&&辨析teetailkillall 获取ping的时延数据并分析网络情况 网络情况经常让我们头疼&#xff0c;每次都需要手动在终端ping太麻烦了&#xff0c;不如写个脚本ping并将数据带上时间戳存入文件&#xff0c;然后也…

华为数通HCIA-华为VRP系统基础

什么是VRP? VRP是华为公司数据通信产品的通用操作系统平台&#xff0c;作为华为公司从低端到核心的全系列路由器、以太网交换机、业务网关等产品的软件核心引擎。 VRP提供以下功能&#xff1a; 实现统一的用户界面和管理界面 实现控制平面功能&#xff0c;并定义转发平面接口…

前端学习——Vue (Day6)

路由进阶 路由的封装抽离 //main.jsimport Vue from vue import App from ./App.vue import router from ./router/index// 路由的使用步骤 5 2 // 5个基础步骤 // 1. 下载 v3.6.5 // 2. 引入 // 3. 安装注册 Vue.use(Vue插件) // 4. 创建路由对象 // 5. 注入到new Vue中&…

无涯教程-jQuery - load( url, data, callback)方法函数

load(url&#xff0c;data&#xff0c;callback)方法从服务器加载数据&#xff0c;并将返回的HTML放入匹配的元素中。 load( url, [data], [callback] ) - 语法 [selector].load( url, [data], [callback] ) 这是此方法使用的所有参数的描述- url - 包含请求发送到…

【JMeter】JMeter添加插件

目录 一、前言 二、插件管理器 三、推荐插件 1.Custom Thread Groups &#xff08;1&#xff09;Ultmate Thread Group &#xff08;2&#xff09;Stepping Thread Group 2.3 Basic Graph 资料获取方法 一、前言 ​ 在我们的工作中&#xff0c;我们可以利用一些插件来帮…

数据结构:快速的Redis有哪些慢操作?

redis 为什么要这莫快&#xff1f;一个就是他是基于内存的&#xff0c;另外一个就是他是他的数据结构 说到这儿&#xff0c;你肯定会说&#xff1a;“这个我知道&#xff0c;不就是 String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、 Hash&#xff08…

(文章复现)梯级水光互补系统最大化可消纳电量期望短期优化调度模型matlab代码

参考文献&#xff1a; [1]罗彬,陈永灿,刘昭伟等.梯级水光互补系统最大化可消纳电量期望短期优化调度模型[J].电力系统自动化,2023,47(10):66-75. 1.基本原理 1.1 目标函数 考虑光伏出力的不确定性&#xff0c;以梯级水光互补系统的可消纳电量期望最大为目标&#xff0c;函数…