antv/x6 vue

6,560 阅读1分钟

前言

研究了两周的时间基本上搞清楚了,x6 的来龙去脉,最起码能完成我项目中复杂的功能了,在写这篇博客之前,费了老鼻子劲了, 网上的资料呢又少之甚少,为了让博友们少走点弯路我把我这几天总结的心得在这里赘述一下。 我只是写了部分功能,如果还有一位可以私信我, 如果我会 我会直言不讳的耐心解答

创建画布

    <template>
      <div class="surface">
        <div id="canvasPanel" /> // 画布
        <div id="minimapContainer" /> // minimap
      </div>
    </template>
    <script>
        import { Graph, Shape } from '@antv/x6'
        mounted () {
            this.createGraphic()
        },
        methods: {
            createGraphic () {
              const container = document.getElementById('canvasPanel')
              const minimapContainer = document.getElementById('minimapContainer')
              const width = container.scrollWidth
              const height = (container.scrollHeight || 500) - 20
              this.graph = new Graph({
                container: container,
                width,
                height,
                // 这个非常非常重要,我在这个上边至少耽误了两天, 甚至因为这个差点放弃使用 x6
                // 因为我在项目中 是要滚动条的,当点击之后 滚动条会回到顶部,加上这个的话 svg 不会重新排序
                sorting: 'approx',
                grid: true,
                keyboard: true,
                scroller: {
                  enabled: true,
                  pageVisible: false,
                  pageBreak: true,
                  pannable: true
                },
                minimap: {
                  enabled: true,
                  width: 200,
                  height: 160,
                  padding: 10,
                  container: minimapContainer
                },
                snapline: true, // 对齐线
                connecting: {
                  // connectionPoint 和 anchor 也很重要 但是这个在文档中说的很明白了, 但是有一个坑一定注意x6 的版本,我就是因为版本不是最新的导致 port 不起作用, 这个配置是 node 和 port 同时起作用的
                  connectionPoint: {
                    name: 'anchor',
                    args: {
                      sticky: true
                    }
                  },
                  anchor: {
                    name: 'midSide',
                    args: {
                      direction: 'H'
                    }
                  },
                  snap: {
                    radius: 100
                  }, // snap 设置为 true 时连线的过程中距离节点或者连接桩 10px 时会触发自动吸附
                  allowBlank: false, // 是否允许连接到画布空白位置的点
                  allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点
                  // 这个是拖拽的时候线的样式,可以在拖拽成功之后再修改线的样式
                  createEdge () {
                    return new Shape.Edge({
                      attrs: {
                        line: {
                          strokeDasharray: '5 5',
                          stroke: 'red',
                          strokeWidth: 2,
                          sourceMarker: {
                            tagName: 'circle',
                            r: 5,
                            cx: 5,
                            fill: 'red'
                          },
                          targetMarker: {
                            tagName: 'circle',
                            r: 5,
                            cx: 5,
                            fill: 'red'
                          }
                        }
                      }
                    })
                  }
                  // 可以写连线的验证
                  validateConnection ({ sourceView, targetView, sourceMagnet, targetMagnet}{}
              })
              // 这里我只做了连线成功的线样式的处理
              this.graph.on('edge:connected', (args) => {
                const edge = args.edge
                const node = args.currentCell
                const elem = args.currentMagnet
                const portId = elem.getAttribute('port')
                // 触发 port 重新渲染
                node.setPortProp(portId, 'connected', true)
                // 更新连线样式
                edge.attr({
                  line: {
                    strokeDasharray: ''
                  }
                })
              })
              this.graph.on('edge:removed', ({ cell, edge }) => {
              })
              // 双击的时候编辑视图
              this.graph.on('node:dblclick', ({ cell, node }) => {
                // 获取 node 中的 vue 模板数据
                let currentNode = node.store.data.component.data().nodeData
                // 不改变引用地址
                Object.assign(this.currentNode, currentNode)
                this.currentNode.drawer = true
              })
              // 添加刪除按鈕, 暂时没有找到删除按钮的回调和样式
              this.graph.on('edge:mouseenter', ({ edge }) => {
                edge.addTools([
                  {
                    name: 'button-remove',
                    args: {
                      distance: -30
                    }
                  }
                ])
              })
              // 刪除按鈕
              this.graph.on('edge:mouseleave', ({ edge }) => {
                // 可以只定删除某个 tool
                edge.removeTools()
              })
            },
        }
    </script>

注册 node 节点

import baseNode from './node'
export default (Graph, node) => {
  let [startIndex, itemHeight, titleHeight, viewCount] = [0, 25, 30, 3]
  let customNode = Graph.addNode({
    shape: 'vue-shape',
    view: 'ball',
    x: node.x,
    y: node.y,
    width: 300,
    height: 105,
    id: node.id,
    // 这里 注意 一定要引入   import { VueShapeView } from '@antv/x6-vue-shape' 文档中没有,资料也搜不到
    // 如果使用不到 VueShapeView 也可以 import '@antv/x6-vue-shape' 这样引入
    component: {
      template: `<base-node @loadport="loadPort" :node="nodeData"></base-node>`,
      data () {
        return {
          nodeData: node
        }
      },
      methods: {
        loadPort (event) {
          startIndex = Math.floor(event.srcElement.scrollTop / itemHeight)
          let afterList = node.columns.slice(
            startIndex,
            startIndex + viewCount
          )
          customNode.removePorts({ silent: true })
          const ports = getRenderPorts(afterList)
          customNode.addPorts(ports)
        }
      },
      components: {
        baseNode
      }
    },
    tools: [
      {
        name: 'button-remove',
        args: {
          attrs: {
            fill: '#7c68fc',
            stroke: '#333'
          },
          x: '100%',
          y: 0,
          offset: { x: -15, y: 14 }
        }
      }
    ],
    ports: {
      groups: {
        in: {
          markup: [
            {
              tagName: 'rect',
              selector: 'rect'
            }
          ],
          attrs: {
            rect: {
              magnet: true,
              stroke: '#31d0c6',
              fill: 'rgba(255,255,255,0.1)',
              strokeWidth: 0,
              width: 294,
              height: 25,
              x: 0,
              y: -12
            }
          },
          position: {
            name: 'absolute'
          }
        },
        root: {
          markup: [
            {
              tagName: 'rect',
              selector: 'rect'
            }
          ],
          attrs: {
            rect: {
              stroke: '#31d0c6',
              fill: 'rgba(255,255,255,0.1)',
              strokeWidth: 0,
              width: 300,
              height: 30,
              x: 0,
              y: 0
            }
          },
          position: {
            name: 'absolute'
          }
        }
      },
      items: [
      ]
    }
  })
  function getRenderPorts (visibleView) {
    const offsetY = titleHeight + itemHeight / 2
    let visiblePorts = []
    for (let [index, column] of visibleView.entries()) {
      visiblePorts.push({
        id: column.nodeId,
        group: 'in',
        zIndex: 100,
        args: {
          x: 0,
          y: index * itemHeight + offsetY
        }
      })
    }
    visiblePorts.unshift({
      id: 'port-in-root',
      group: 'root',
      index: -1,
      args: {
        offset: Math.round(titleHeight / 2)
      }
    })
    return visiblePorts
  }
  let defaultList = node.columns.slice(0, 3)
  const ports = getRenderPorts(defaultList)
  customNode.addPorts(ports)
}