AntV G6 【Dagre 流程图布局】在项目需求中的运用 | 🏆 技术专题第三期征文__Vue.js
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利
介绍
antV G6作为非开箱即用的可视化引擎,其高定制能力让我在对应定制化的需求的条件下选择了它。接下来,讲讲我的踩坑之旅,本文可能需要一定门槛,建议学习具备G6对应的Graph及自定义节点相关文档后进行阅读。话不多说,让我们一起来看看Dagre流程图相关知识吧。
使用语言为Vue2.0+element+G6
环境搭建
-
安装
npm install --save @antv/g6
-
引入
import G6 from ‘@antv/g6’;
需求
g6自定义节点
// 实例化 grid 插件 const grid = new G6.Grid() G6.registerNode('card-node', { draw: function drawShape (cfg, group) { const r = 2 const strokeColor = '#DCDEE2' const color = '#0090FF' const w = cfg.size[0] const h = cfg.size[1] let shape = null // 开始结束 if (cfg.label === '开始' || cfg.label === '结束') { shape = group.addShape('rect', { attrs: { x: -w / 2, y: -h / 2, width: w / 2, height: h, stroke: strokeColor, radius: r, fill: '#fff', cursor: 'pointer' }, name: 'main-box', draggable: true }) group.addShape('text', { attrs: { textBaseline: 'top', x: -w / 2 + 25, y: -h / 2 + 28, fontSize: 20, lineHeight: h, text: cfg.label, fill: '#0090FF', cursor: 'pointer' }, name: 'title' }) } else { shape = group.addShape('rect', { attrs: { x: -w / 2, y: -h / 2, width: w, // 200, height: h, // 60 stroke: strokeColor, radius: r, fill: '#fff' }, name: 'main-box', draggable: true }) group.addShape('rect', { attrs: { x: -w / 2, y: -h / 2, width: w, // 200, height: h / 2 - 6, // 60 fill: color, radius: [r, r, 0, 0] }, name: 'title-box', draggable: true }) // title text group.addShape('text', { attrs: { textBaseline: 'top', x: -w / 2 + 20, y: -h / 2 + 8, fontSize: 18, text: cfg.label, fill: '#fff' }, name: 'title' }) group.addShape('text', { attrs: { textBaseline: 'top', x: -w / 2 + 20, y: -h / 2 + 40, fontSize: 18, lineHeight: 20, text: cfg.description, fill: 'rgba(0,0,0, 1)' }, name: `description` }) } return shape }, getAnchorPoints () { return [ [0, 0.5], [1, 0.5] ] }, setState (name, value, item) { if (name === 'active') { console.log('setState -> value', value) const marker = item.get('group').find(ele => ele.get('name') === 'main-box') if (value) { that.chooseNode = item.get('model') marker.attr('stroke', 'red') } else { marker.attr('stroke', '#DCDEE2') that.chooseNode = {} } } } })
自定义行为
// 自定义行为 G6.registerBehavior('activate-node', { getDefaultCfg () { return { multiple: true } }, getEvents () { return { 'node:click': 'onNodeClick', 'canvas:click': 'onCanvasClick' } }, removeNodesState () { graph.findAllByState('node', 'active').forEach(node => { graph.setItemState(node, 'active', false) // const marker = node.get('group').find(ele => ele.get('name') === 'main-box') // marker.attr('stroke', '#DCDEE2') }) }, onNodeClick (e) { console.log('onNodeClick -> e', e) const graph = this.graph const item = e.item console.log('onNodeClick -> item', item) // 点击取消选中 本业务无须此功能 // if (item.hasState('active')) { // graph.setItemState(item, 'active', false) // return // } // this 上即可取到配置,如果不允许多个 'active',先取消其他节点的 'active' 状态 this.removeNodesState() // 置点击的节点状态 'active' 为 true graph.setItemState(item, 'active', true) const tapList = ['main-box', 'title-box', 'title', 'description'] const name = e.target.get('name') if (tapList.indexOf(name) > -1) { // 点击 card-node console.log('点击 card-node') // const marker = item.get('group').find(ele => ele.get('name') === 'main-box') // marker.attr('stroke', 'red') // 选中node // const id = item.get('id') // that.chooseNode = item.get('model') } }, onCanvasClick (e) { // shouldUpdate 可以由用户复写,返回 true 时取消所有节点的 'active' 状态,即将 'active' 状态置为 false if (this.shouldUpdate(e)) { this.removeNodesState() } } })
配置节点之间的连接线
G6.registerEdge( 'line-arrow', { getPath (points) { const startPoint = points[0] const endPoint = points[1] const leftPointX = startPoint.x + 25 return [ ['M', startPoint.x, startPoint.y], ['L', leftPointX, startPoint.y], ['L', leftPointX, endPoint.y], ['L', endPoint.x, endPoint.y] ] }, getShapeStyle (cfg) { const startPoint = cfg.startPoint const endPoint = cfg.endPoint const controlPoints = this.getControlPoints(cfg) let points = [startPoint] // the start point // the control points if (controlPoints) { points = points.concat(controlPoints) } // the end point points.push(endPoint) const path = this.getPath(points) const style = Object.assign( {}, G6.Global.defaultEdge.style, { stroke: '#B0B6B8', lineWidth: 4, path }, cfg.style ) return style } }, 'line' )
先看图
所有的流程共用开始至结束,每添加一个节点则生成一条“规则”,节点之间存在与或以及删除的操作逻辑。(实际业务场景存在‘非’逻辑,只需加上标识即可)
模拟数据
// 模拟数据 lineData: { nodes: [ { id: '0', label: '开始', description: 'description' }, { id: '9', label: '结束', description: 'description' } ], edges: [ { source: '0', target: '9' } ] }
- 需求拆分
- 从节点视图上来看,‘与’规则本职是在该节点**(如节点A,对应规则A)**后添加一个节点;从规则组角度来看,实际是在规则A所在的规则链中插入一条规则,每条规则链互相,呈’与’关系,规则链互相之间成’或’关系。
- ‘或’规则,即在沿用规则A处在的规则链的前面所有的规则**(不包含规则A)**添加一条规则并连接至结束; 从视图层看,新增的规则节点与规则A呈’与’关系。
- ’删除’操作, 删除跟传统意义理解的删除不同, 可根据使用场景做出调整。这里的删除操作指的是删除规则链中的某个规则节点,但不影响整条规则,可以理解为
slice(index,1)
,除非删除此规则后,规则链中只剩下‘开始’与‘结束’,则删除此规则链。
业务代码,放在git仓中, 需要自取。(业务代码写的有些粗糙,各位大佬手下留情)
要点说明
-
关于选中节点高亮问题,使用了
graph.setItemState(item,name,值)
,自定义了removeNodesState()
方法来点击规则节点时,去除其他选中节点的高亮状态。 -
自定义节点中使用
getAnchorPoints()
方法来控制连接线的拐点,可配置参数进行调试。getAnchorPoints () { return [ [0, 0.5], [1, 0.5] ] },
-
自定义节点中
setState
方法,来控制选中节点后的处理逻辑。
有任何问题,可以通过私信,留言的方式告诉我,欢迎各位大佬指出问题,谢谢!
git仓地址
https://github.com/lsCloud109/vue-antv-g6-lineArrow<p style="line-height: 20px; color: #ccc">
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者: 凉生物语丶
原文链接:<a href='https://juejin.im/post/6864838468715937799'>https://juejin.im/post/6864838468715937799</a>
</p>