AntV G6 【Dagre 流程图布局】在项目需求中的运用 | 🏆 技术专题第三期征文__Vue.js
发布于 4 年前 作者 banyungong 3596 次浏览 来自 分享
粉丝福利 : 关注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'          }        ]      }
  • 需求拆分
  1. 从节点视图上来看,‘与’规则本职是在该节点**(如节点A,对应规则A)**后添加一个节点;从规则组角度来看,实际是在规则A所在的规则链中插入一条规则,每条规则链互相,呈’与’关系,规则链互相之间成’或’关系。
  2. ‘或’规则,即在沿用规则A处在的规则链的前面所有的规则**(不包含规则A)**添加一条规则并连接至结束; 从视图层看,新增的规则节点与规则A呈’与’关系。
  3. ’删除’操作, 删除跟传统意义理解的删除不同, 可根据使用场景做出调整。这里的删除操作指的是删除规则链中的某个规则节点,但不影响整条规则,可以理解为 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>
回到顶部