阅读 2087

antv/X6 可视化连线流程图

用于实现流程图,可进行交互。本文记录的是vue的用法。

完整的demo

安装

建议使用yarn安装哈,一开始我也是贪cnpm速度快一点,结果安装不了x6-vue-shape

# yarn $ yarn add @antv/x6 # yarn yarn add @antv/x6-vue-shape # 在 vue2 下还需要安装 @vue/composition-api yarn add @vue/composition-api --dev 复制代码

1.创建画布

  • keyboard开启监听键盘

  • selecting设置选择节点,可通过filter去过滤。我只需要选择连接线(x6中叫边Edge)。graph.isEdge(node)判断传入的节点是否为边(Edge)

  • scroller使用scroller模式,超出container会出现滚动条可移动查看节点node,pannable是否启用画布平移能力

  • translating配置移动选项,我这里限制了子组件不可以拖动,限制在父组件里面,只能移动父组件

import '@antv/x6-vue-shape' import { Graph, Line, Path, Curve, Addon } from '@antv/x6' export default {   data () {     return {       graph: null     }   }, } // 创建画布 this.graph = new Graph({   container: document.getElementById('drag-container'),   keyboard: true,   scroller: {     enabled: true,     pannable: true   },   selecting: {     enabled: true,     rubberband: true,     filter (node) {       // 只选连接线(边)       return that.graph.isEdge(node)     }   },   translating: {     restrict (view) {       const cell = view.cell       if (cell.isNode()) {         const parent = cell.getParent()         if (parent) {           // 限制子节点不能移动           return {             x: cell.getBBox().x,             y: cell.getBBox().y,             width: 0,             height: 0           }         }       }       return null     }   } }) 复制代码

2.注册节点

我的节点都是用vue组件写的,并不是用原生svg的一些图形(circle、rect),实际开发的需求肯定不是这么简单,vue组件必须用registerVueComponent注册,否则后面做回显时,使用不了toJSONfromJSON。回显时使用这两个api,不需要任何的操作,渲染即可

import Drag from '@/components/drag/drag.vue' import Item from '@/components/drag/item.vue' const that = this // 注册父组件 Graph.registerVueComponent(   'Drag',   {     template: '<drag @parentnoderemove="parentnoderemove"></drag>',     methods: {       parentnoderemove ({ id }) {         // 删除对应的节点         that.saveNodes.splice(that.saveNodes.findIndex(item => item.id === id), 1)       }     },     components: {       Drag     }   },   true ) // 注册子组件 Graph.registerVueComponent(   'Item',   {     template: '<item @edge="edge"></item>',     methods: {       // 两个节点之间连线       edge: that.edge     },     components: {       Item     }   },   true ) 复制代码

3.生成节点

我这里用到了拖拽生成节点,对于拖拽,x6提供了Dnd。首先对Dnd进行创建配置

getDropNode配置拖拽结束,创建节点之前执行的函数,这个函数一定要return node节点

// 拖拽 this.dnd = new Addon.Dnd({   target: this.graph, // 画布对象   scaled: false,   animation: true,   getDropNode: that.handleEndDrag }) 复制代码

  • 对开始拖拽的目标节点<div>添加mousedown事件去执行Dnd.start事件即可,start函数要传入mousedown事件的event对象

<template>   <div>     <div>• 统计对象</div>     <div        v-for="(item, index) in leftSide"        :key="index"        @mousedown="handleDrag(item, $event)"      >       {{item.title}}    </div>   </div> </template> 复制代码

methods

创建节点需要提交x、y坐标值,所以在handleEndDrag事件中我先让返回的父组件渲染完成后,获取父组件的x、y的值,再创建子节点。

<script>   export default {     methods: {       // 开始拖拽       handleDrag (item, e, weidu) {         // 业务逻辑                  // item请求。。         this.createChildren = [1, 2, 3] // item请求回来的数据         this.handleCreateNode(item, e)       },       handleCreateNode (item, e) {         const that = this         // 创建父节点         const parent = this.graph.createNode({           shape: 'vue-shape',           x: 100,           y: 100,           height: that.createChildren.length * 60 + 58, // 根据实际的UI样式来           data: {             item,             height: that.createChildren.length * 60 + 58           },           component: 'Drag' // 这个名字对应registerVueComponent的名字         })         // 开始拖拽         this.dnd.start(parent, e)       },       // 拖拽结束,渲染节点之前,必须返回克隆的节点       handleEndDrag (node) {         const cloneNode = node.clone({ deep: true })         const that = this         // 父节点渲染之后再执行,因为需要父节点的位置         this.$nextTick(() => {           const { x, y } = cloneNode.position()           // 是否第一个节点           const cellCount = that.graph.getCellCount()           this.createChildren.forEach((item, index) => {             const child = this.graph.addNode({               shape: 'vue-shape',               x: x + 20,               y: index === 0 ? y + 58 : y + (index * 60 + 58),               width: 240,               height: 46,               data: {                 item               },               component: 'Item'             })             cloneNode.addChild(child) // 添加到父节点           })           this.saveNodes.push(cloneNode)           if (cellCount === 1) {             this.firstNode = cloneNode             that.$message.warning('第一个统计对象为主体')           }         })         return cloneNode       }     }   } </script> 复制代码

4.两个节点之间连线(创建边)

需求是点击两个node,就让它们连线

点击的事件我放在了item组件里,点击后触发父组件这边的edge方法

connector决定你的线是怎样的,我这边是圆弧

// 连线规则,圆弧 Graph.registerConnector( // Graph不是创建的画布实例(this.graph)!!!   'smooth',   (     sourcePoint,     targetPoint,     routePoints,     options   ) => {     const line = new Line(sourcePoint, targetPoint)     const diff = 5     const factor = 1     const vertice = line     .pointAtLength(line.length() / 2 + 12 * factor * Math.ceil(diff))     .rotate(90, line.getCenter())     const points = [sourcePoint, vertice, targetPoint]     const curves = Curve.throughPoints(points)     const path = new Path(curves)     return options.raw ? path : path.serialize()   },   true )      复制代码

// 两个node之间连线 edge (node) {   // console.log('node', node.id)   // console.log('AllEdges', this.graph.getEdges(node))   const allEdges = this.graph.getEdges(node)   this.waitEdgeNodes.push(node)   if (this.waitEdgeNodes.length === 2) {     // 改变active状态     this.$store.dispatch('callNodes', this.waitEdgeNodes.map(item => item.id))     // 判断不是同一父级     if (this.waitEdgeNodes[0]._parent.id !== this.waitEdgeNodes[1]._parent.id) {       // 要连线的目标id和来源id       const allTargetAndAllSource = allEdges.map((item) => [         item.getTargetCellId(),         item.getSourceCellId()       ])       const flag = allTargetAndAllSource.filter(item =>          item.includes(this.waitEdgeNodes[0].id) && item.includes(this.waitEdgeNodes[1].id       ))       // 如果两个点已经连过线,       if (flag.length) return (this.waitEdgeNodes.length = 0)       // 这里通过坐标决定连线的点       const sourceAnchor = this.waitEdgeNodes[0].getBBox().x < this.waitEdgeNodes[1].getBBox().x ? 'right' : 'left'       const targetAnchor = this.waitEdgeNodes[0].getBBox().x < this.waitEdgeNodes[1].getBBox().x ? 'left' : 'right'       // 设置箭头的大小       const args = {         size: 8       }       this.graph.addEdge({         source: { cell: this.waitEdgeNodes[0], anchor: sourceAnchor, connectionPoint: 'anchor' },         target: { cell: this.waitEdgeNodes[1], anchor: targetAnchor, connectionPoint: 'anchor' },         connector: { name: 'smooth' },         attrs: {           line: {             strokeDasharray: '5 5',             stroke: '#666',             strokeWidth: 1,             sourceMarker: {               args,               name: 'block' // 实心箭头             },             targetMarker: {               args,               name: 'block'             }           }         }       })     }     // 无论如何都清空     this.waitEdgeNodes.length = 0   } }, 复制代码

5.选择连接线,并监听键盘删除键进行删除

因为一开始的配置就限制了只能选择edge,所以这里不用判断其他的cell

这部分主要是更改样式

// 选择连接线(边)事件 this.graph.on('selection:changed', ({ added, removed }) => {   this.selectLine = added   added.forEach((cell) => {     const args = { size: 15 }     cell.setAttrs({       line: {         sourceMarker: {           args,           name: 'block'         },         targetMarker: {           args,           name: 'block'         },         stroke: '#2D8CF0',         strokeWidth: 4       }     })   })   removed.forEach((cell) => {     const args = { size: 8 }     cell.setAttrs({       line: {         sourceMarker: {           args,           name: 'block'         },         targetMarker: {           args,           name: 'block'         },         stroke: '#666',         strokeWidth: 1       }     })   }) }) 复制代码

监听删除键删除

通过cell.remove方法删除

// 删除连接线(边) this.graph.bindKey(['Backspace', 'Delete'], (e) => {   if (this.selectLine.length) {     this.$confirm('确认删除连线吗?', '提示', {       confirmButtonText: '确定',       cancelButtonText: '取消',       type: 'warning'     }).then(() => {       this.selectLine[0].remove()       this.$message({         type: 'success',         message: '删除成功!'       })     })   } })      复制代码

blog:gauharchan.github.io/,这是我博客地址,让我们一起在前端道路上持续发胖吧,


作者:GauharChan
链接:https://juejin.cn/post/7022833474667020302


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐