antv x6在vue中应用小结 - 随时补充

官网:AntV X6

开发背景:业务想要开发一组类似下图的流程图组件
在这里插入图片描述

要求:

  • 可创建多个根节点
  • 节点可以衍生子节点 可展开收起 配置最大展开层级
  • 节点内容可以编辑字体颜色(可选中部分文字修改)、节点编号
  • 每个节点可创建多个标注 内容编辑效果同节点 标注可自定义宽高
  • 节点拖拽可实现排序和切换父级

此处省略挣扎部分 后续有心情再补充哈 — 比较痛苦 边敲边pua

记录是关注 x6自定义节点


import {Graph, Path, Node, Markup, Cell, CellView, DataUri} from '@ant/x6'
import 'antv/x6-vue-shape'
import Hierarchy from '@antv/hierarchy'
/*注册节点*/
registerNode(){
  let _this = this
  // 标注
  Graph.registerNode(
    'mark',
    {
      inherit: 'rect',
      markup: [
        {
          tagName: 'rect',
          selector: 'body'
        }, {
          tagName: 'text',
          selector: 'label'
        }
      ],
      attrs: {
        body: {
          rx: 16,
          ry: 16,
          stroke: '#5f95ff',
          fill: '#f0f0f0',
          strokeWidth: 1
        },
        label: {
          fontSize: 14,
          fill: '#262626'
        }
      }
    },
    true
  )
  
  // 连接器
  Graph.registerConnector(
    'mindmap',
    (sourcePoint, targetPoint, routerPoints, options)=>{
      const midX = sourcePoint.x + 10
      const midY = sourcePoint.y
      const ctrX = (targetPoint.x - midX) / 5 + midX
      const ctrY = targetPoint.y
      const pathData = `
      M ${sourcePoint.x} ${sourcePoint.y}
      L ${midX} ${midY}
      Q ${ctrX} ${ctrY} ${targetPoint.x} ${targetPoint.y}
      `
      return options.raw ? Path
    },
    true
  )
  
  // 边
  Graph.registereEdge(
    'mindmao-edge',
    {
      inherit: 'edge',
      connector: {
        name: 'mindmap'
      },
      attrs: {
        line: {
          targetMarker: '',
          stroke: '#a2b1c3',
          strokeWidth: 2
        }
      },
      zIndex: 0,
      data: {
        onlyId: null
      }
    },
    true
  )
  
  // 标注组件
  Graph.registerVueComponent('annotation',{
    template: '<annotation @update="update"></annotation>',
    components: { Annotation },
    methods: {
      update(label, data){
        let Id = data.targetId || ''
        let index = _this.marksData[data.sourceId].findIndex(it => it.id == Id)
        if(index != -1)
        _this.marksData[data.sourceId][index].data.label = label
        const cell = _this.graph.getCellById(Id)
        cell && cell.updateData({label: label})
      }
    }
  })
  
  // 自定义节点
  Graph.registereVueComponent('nodediy', {
    template: '<nodediy @collapse="collapse" @expand="expand"></nodediy>',
    components: { Nodediy },
    methods: {
      // 收起
      collapse(nodeId){
        const res = _this.findItem(_this.data, nodeId)
        const dataItem = res
        if(dataItem){
          dataItem.node.isExpand = false
        }
        _this.renderTree()  // 加载页面树
      }// 展开
      expand(nodeId){
        const res = _this.findItem(_this.data, nodeId)
        const dataItem = res
        if(dataItem){
          dataItem.node.isExpand = true
        }
        _this.renderTree()
      }
    }
  })
}
/*节点挂载*/
initData(){
  this.graph && this.graph.dispose()
  let _this = this
  this.graph = new Graph({
    container: document.getElementById('mindmap-container'),
    width: document.getElementById('content').offsetWidth || 1350,
    height: document.getElementById('content').offsetHeight || 777,
    connecting: {
      connectionPoint: 'anchor',
    },
    scroller: true,
    panning: {
      enabled: true
    },
    selection: {
      enabled: true
    },
    keyboard: {
      enabled: true
    },
    embedding: {
      enabled: true,
      findParent({node}){
        const bbox = node.getBBox()
        return this.getNodes().filter(sunNode => {
          const data = sunNode.getData()
          const sunBbox = sunNode.getBBox()
          return bbox.isIntersectWidthRect(sunBbox)
        })
      }
    },
    resizing: {
      minWidth: 120,
      height: 40,
      enabled: (node) => {
        if(node.component === 'annotation')
        return true
        else
        return false
      }
    }
  })
  
  // 标注缩放
  this.graph.on('node:resized', ({e, x, y, node, view}) => {
    let Id = node.id || ''
    let {width, height} = node.size()
    let index = _this.marksData[node.data.sourceId].findIndex(it => it.id == Id)
    if(index != -1){
      _this.marksData[node.data.sourceId][index].x = node.position().x
      _this.marksData[node.data.sourceId][index].y = node.position().y
      _this.marksData[node.data.sourceId][index].data.width = width
      _this.marksData[node.data.sourceId][index].data.height = height
    }
  })
  
  // 节点单击
  this.graph.on('node:click', ({cell  e}) => {
    this.graph.resetSelection(cell)
    if(e.target.className.indexOf('close-icon') != -1 || e.target.className.indexOf('right-icon') != -1){
      this.$refs.btnGroupRef.close()
      return
    }
    let pageX = e.pageX
    let pageY = e.pageY
    let width = cell.getData().width || 160
    let height = cell.getData().height || 40
    pageX -= e.offsetX - width
    pageY -= e.offsetY -+ 28
    const offsetHeight = document.body.offsetHeight
    const offsetWidth = document.body.offsetWidth
    if(offsetWidth - pageX <= 200){
      pageX -= 200
    }
    if(e.target.className === 'innode-div-text'){
      if(cell.getData().rank === 0 || !cell.getData().rank || cell.getData().parentId == 'hide-root'){
        pageX += 12
      }
      pageX -= 20
      pageY -= (height - 16)*0.5
    }
    this.tooltipStyle = {
      left: pageX + 'px',
      top: pageY + 'px'
    }
    this.editInfo = cell.getData()
    this.$refs.btnGroupRef.open(this.editInfo, this.tooltipStyle)
  })
  
  this.graph.on('node:moved', data => {
    let node = data.node
    if(node && node.component && node.componet ==='annotation' && _this.marksData[node.data.sourceId]){
      let Id = node.data.targetId || ''
      let {x,y} = _this.graph.getCellByID(Id).position()
      let index = _this.marksData[node.data.sourceId].findIndex(it => it.id == Id)
      if(index != -1){
        _this.marksData[node.data.sourceId][index].x = x || data.x
        _this.marksData[node.data.sourceId][index].y = y || data.y
      }
    }
  })
  
  this.graph.on('node:embedded', data => {
    let {currentParent, node} = node
    // 嵌入
    if(currentParent && node){
      let data_a = currentParent.getData()
      let data_b = node.getData()
      // 标注拖拽 要删除当前标注 并新增标注 marksData同步修改
      if(data_a && data_b && node.component === 'annotation' && currentParent.component === 'nodediy'){
        let nodeId = currentParent.id
        let markId = node.id
        _this.graph.renoveCells(markId)
        _this.delTheMarks(data_b)
        let x = data.x - 40
        let y = data.y - 60
        let _obj = {
          id: markId,
          x,
          y,
          data: {
            ...data_b,
            targetId: markId,
            sourceId: nodeId
          }
        }
        let nowMarks = _this.marksData[nodeId] || []
        nowMarks.push(_obj)
        _this.marksData[nodeId] = nowMarks
        _this.graph.addCell(_this.renderMarksByObj([_obj]))
      }else if(data_a && data_b){ // 节点拖拽
        const dataItem = _this.findItem(_this.data, data_b.a)
        if(!dataItem) return
        const {children} = dataItem.parent
        const index = children.findIndex((item) => item.id == data_b.id)
        children.splice(index, 1)
        
        const res = _this.findIndex(_this.data, data_a.id)
        if(res){
          let len = res.node.children.length || 0
          let newLeaf = _this.resetDataLevel([data_b], res.node.level)
          res.node.children.push({...newLeaf[0], parentId: data_a.id})
          _this.graph.clearCells()
          _this.$nextTick(()=>{
            _this.renderTree()
          })
        }
      }
    }else if(node.compoent == 'nodediy'){  // 排序
      let {parentId, id} = data.cell.getData()
      const res = this.findItem(_this.data, parentId)
      const dataItem = res
      if(!dataItem) return
      const index = dataItem.node.children.findIndex(item => item.id === id)
      
      let y0 = data.cell.position().y
      let child = res.node.children
      let j = null
      if (child.length > 1) {
        for(let i = 0; i < child.length; i++){
          let y1 = 0
          if(_this.graph.getCellById(child[i].id)){
            y1 = _this.graph.getCellById(child[i].id).position().y
          }
            
          let y2 = null
          if(i < child.length -1){
            y2 = _this.graph.getCellById(child[i+1].id).position().y
          }
          
          if(y0>y1 && (y0<y2 || i == child.length - 1)){
            j = i < index ? i + 1 : i
            break;
          } else if(y0<y1 && y0 === y2){
            j = i
            break;
          }
        }
        
        if(j !== null){
          const swapLine = dataItem.node.children.splice(index, 1)[0]
          dataItem.node.children.splice(j, 0, swapLine)
          _this.graph.clearCells()
          _this.$nextTick(()=>{
            _this.rendreTree()
          })
        }
      }
    }
  })
}

/*渲染数据*/
rendereTree(translate){
  let data = this.data
  if(!data.children || data.children.length == 0){
    this.graph.clearCells()
    return
  }
  let _this = this
  const result = Hierarchy.mindmap(data, {
    direction: 'H',
    getHeight(d){
      return d.height
    },
    getWidth(d){
      return d.width
    },
    getChildren(d){
      if(d.id !== 'hide-root' && d.children && d.children.length > 0 && !d.isExpand){
        return []
      }else{
        return d.children
      }
    },
    getHGap(){
      return 60
    },
    getVGap(){
      return 25
    },
    getSide: ()=>{
      return 'right'
    }
  })
  let cells = []
  const traverse = (hierarchyItem, index) => {
    if(hierarchyItem){
      const {data, children} = hierarchyItem
      if(data.id !== 'hide-root'){
        cells.push(
          this.graph.createNOde({
            id: data.id,
            shape: 'vue-shape',
            x: data.x || hierarchyItem.x,
            y: data.y || hierarchyItem.y,
            width: data.level == 1? 140: 120,
            height: data.level == 1? 50: data.level == 2? 40: 30,
            lable: data.label,
            type: data.type,
            data: {...data, rank: index},
            component: 'nodediy'
          })  
        )
        let markObj = _this.marksData[data.id] || null
        if(markObj){
          cells = cells.concat(this.renderMarksByObj(markobj))
        }
      }
      let _isExpand = data.isExpand? true: false
      if(children && (_isExpand || data.id ==='hide-root')){
        children.forEach((item, index) => {
          const {id, data} = item
          let text = this.edgesData[hieratchyItem.id + '_' + id] || ''
          if(hierarchyItem.id !== 'hide-root'){
            cells.push(
              this.graph.createEdge({
                shape: 'mindmap-edge',
                id: hierarchyItem.id + '_' + id] || '',
                labels: [{
                  attrs: {label: {text: text}},
                  position: {distance: 0.5}
                }],
                source: {
                  cell: hierarchyItem.id,
                  anchor:{nameL 'right'}
                },
                target: {
                  cell: id,
                  anchor: {name: 'right'}
                }
              })
            )
          }
          let _rank = children.length ===1? 0: index+1  // 无同级节点不显示编号
          traverse(item, _rank)
        })
      }
    }
  }
  travekse(result)
  
  this.graph.resetCells(cells)
  if(translate){
    this.graph.positionContent('left',{padding: {left: 50}})
  }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值