antv G6 脑图设计

首先是要安装antv G6插件

安装命令:

npm install --save @antv/g6

 

Vue页面开发:

<template>
    <div id="container"></div>
</template>
<script lang="tsx" setup>
import { onMounted, ref, reactive} from 'vue'
import G6 from '@antv/g6'
onMounted(() => {
  const mapData: any = mockData||[]
  const temp = {
    ...mapData,
    label: mapData.name,
    children: mapData?.children.length && formatData(mapData?.children)
  }
  formItems[1].selectOptions = temp
  datas.TreeSelectorDatas = [...temp?.children, { id: '0', nodeName: '根节点' }]
  const minWidth = 60
  const BaseConfig = {
    nameFontSize: 12,
    childCountWidth: 22,
    countMarginLeft: 0,
    itemPadding: 16,
    nameMarginLeft: 24,
    // nameMarginLeft: 4,
    rootPadding: 18
  }

  G6.registerNode('treeNode', {
    draw: function drawShape(cfg: any, group) {
      const { image, label, selected, children, depth } = cfg
      const rootNode = depth === 0
      const hasChildren = children && children.length !== 0

      const {
        childCountWidth,
        countMarginLeft,
        itemPadding,
        nameMarginLeft,
        rootPadding
      } = BaseConfig

      let width = 0
      const height = 28
      const x = 0
      const y = -height / 2

      // 名称文本
      const text = group.addShape('text', {
        attrs: {
          text: label,
          x: x * 2 + 10,
          y,
          textAlign: 'left',
          textBaseline: 'top',
          fontFamily: 'PingFangSC-Regular'
        },
        cursor: 'pointer',
        name: 'name-text-shape'
      })
      // 字体图标
      const img = group.addShape('image', {
        attrs: {
          img: image,
          x: x * 2 + 12,
          y: y - 9,
          width: 16,
          height: 16,
          opacity: 0.85
        },
        cursor: 'pointer',
        name: 'name-text-shape'
      })
      const textWidth = text.getBBox().width
      width = textWidth + itemPadding + nameMarginLeft

      width = width < minWidth ? minWidth : width

      if (!rootNode && hasChildren) {
        width += countMarginLeft
        width += childCountWidth
      }

      const keyShapeAttrs: any = {
        x,
        y,
        width,
        height,
        radius: 4
      }

      // keyShape根节点选中样式
      if (rootNode && selected) {
        keyShapeAttrs.fill = '#e8f7ff'
        keyShapeAttrs.stroke = '#e8f7ff'
      }
      const keyShape = group.addShape('rect', {
        attrs: keyShapeAttrs,
        name: 'root-key-shape-rect-shape'
      })

      if (!rootNode) {
        // 底部横线
        group.addShape('path', {
          attrs: {
            path: [
              ['M', x - 1, 0],
              ['L', width, 0]
            ],
            stroke: '#AAB7C4',
            lineWidth: 1
          },
          name: 'node-path-shape'
        })
      }

      const mainX = x
      const mainY = -height + 15

      if (rootNode) {
        // 根节点
        group.addShape('rect', {
          attrs: {
            x: mainX,
            y: mainY,
            width: width,
            height,
            radius: 14,
            fill: '#e8f7ff',
            cursor: 'pointer'
          },
          name: 'main-shape'
        })
      }

      let nameColor = 'rgba(0, 0, 0, .65)'
      if (selected) {
        nameColor = '#40A8FF'
      }

      // 名称
      if (rootNode) {
        group.addShape('text', {
          attrs: {
            text: label,
            x: mainX + rootPadding,
            y: 1,
            textAlign: 'left',
            textBaseline: 'middle',
            fill: nameColor,
            fontSize: 12,
            fontFamily: 'PingFangSC-Regular',
            cursor: 'pointer'
          },
          name: 'root-text-shape'
        })
      } else {
        group.addShape('text', {
          attrs: {
            text: label,
            x: selected ? mainX + 36 + nameMarginLeft : mainX + 36,
            y: y - 5,
            textAlign: 'start',
            textBaseline: 'top',
            fill: nameColor,
            fontSize: 12,
            fontFamily: 'PingFangSC-Regular',
            cursor: 'pointer'
          },
          name: 'not-root-text-shape'
        })
      }
      cfg.children?.length &&
        group.addShape('marker', {
          attrs: {
            x: width,
            y: 0,
            r: 6,
            cursor: 'pointer',
            symbol: G6.Marker.collapse,
            stroke: '#666',
            lineWidth: 1,
            fill: '#fff'
          },
          name: 'collapse-icon'
        })

      return keyShape
    },
    setState(name, value, item) {
      if (name === 'collapsed') {
        const marker = item
          .get('group')
          .find(ele => ele.get('name') === 'collapse-icon')
        const icon = value ? G6.Marker.expand : G6.Marker.collapse
        marker.attr('symbol', icon)
      }
    }
  })

  /**
   * 工具栏
   */
  const toolbar = new G6.ToolBar({
    getContent: () => {
      return `<ul class="g6-component-toolbar" style="top: 10px; left: 10px;">           
            <li code="zoomOut">
              <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
                <path d="M658.432 428.736a33.216 33.216 0 0 1-33.152 33.152H525.824v99.456a33.216 33.216 0 0 1-66.304 0V461.888H360.064a33.152 33.152 0 0 1 0-66.304H459.52V296.128a33.152 33.152 0 0 1 66.304 0V395.52H625.28c18.24 0 33.152 14.848 33.152 33.152z m299.776 521.792a43.328 43.328 0 0 1-60.864-6.912l-189.248-220.992a362.368 362.368 0 0 1-215.36 70.848 364.8 364.8 0 1 1 364.8-364.736 363.072 363.072 0 0 1-86.912 235.968l192.384 224.64a43.392 43.392 0 0 1-4.8 61.184z m-465.536-223.36a298.816 298.816 0 0 0 298.432-298.432 298.816 298.816 0 0 0-298.432-298.432A298.816 298.816 0 0 0 194.24 428.8a298.816 298.816 0 0 0 298.432 298.432z"></path>
              </svg>
            </li>
            <li code="zoomIn">
              <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
                <path d="M639.936 416a32 32 0 0 1-32 32h-256a32 32 0 0 1 0-64h256a32 32 0 0 1 32 32z m289.28 503.552a41.792 41.792 0 0 1-58.752-6.656l-182.656-213.248A349.76 349.76 0 0 1 480 768 352 352 0 1 1 832 416a350.4 350.4 0 0 1-83.84 227.712l185.664 216.768a41.856 41.856 0 0 1-4.608 59.072zM479.936 704c158.784 0 288-129.216 288-288S638.72 128 479.936 128a288.32 288.32 0 0 0-288 288c0 158.784 129.216 288 288 288z" p-id="3853"></path>
              </svg>
            </li>
            <li code="realZoom">
              <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="24">
                <path d="M384 320v384H320V320h64z m256 0v384H576V320h64zM512 576v64H448V576h64z m0-192v64H448V384h64z m355.968 576H92.032A28.16 28.16 0 0 1 64 931.968V28.032C64 12.608 76.608 0 95.168 0h610.368L896 192v739.968a28.16 28.16 0 0 1-28.032 28.032zM704 64v128h128l-128-128z m128 192h-190.464V64H128v832h704V256z"></path>
              </svg>
            </li>
            <li code="autoZoom">
              <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="24">
                <path d="M684.288 305.28l0.128-0.64-0.128-0.64V99.712c0-19.84 15.552-35.904 34.496-35.712a35.072 35.072 0 0 1 34.56 35.776v171.008h170.944c19.648 0 35.84 15.488 35.712 34.432a35.072 35.072 0 0 1-35.84 34.496h-204.16l-0.64-0.128a32.768 32.768 0 0 1-20.864-7.552c-1.344-1.024-2.816-1.664-3.968-2.816-0.384-0.32-0.512-0.768-0.832-1.088a33.472 33.472 0 0 1-9.408-22.848zM305.28 64a35.072 35.072 0 0 0-34.56 35.776v171.008H99.776A35.072 35.072 0 0 0 64 305.216c0 18.944 15.872 34.496 35.84 34.496h204.16l0.64-0.128a32.896 32.896 0 0 0 20.864-7.552c1.344-1.024 2.816-1.664 3.904-2.816 0.384-0.32 0.512-0.768 0.768-1.088a33.024 33.024 0 0 0 9.536-22.848l-0.128-0.64 0.128-0.704V99.712A35.008 35.008 0 0 0 305.216 64z m618.944 620.288h-204.16l-0.64 0.128-0.512-0.128c-7.808 0-14.72 3.2-20.48 7.68-1.28 1.024-2.752 1.664-3.84 2.752-0.384 0.32-0.512 0.768-0.832 1.088a33.664 33.664 0 0 0-9.408 22.912l0.128 0.64-0.128 0.704v204.288c0 19.712 15.552 35.904 34.496 35.712a35.072 35.072 0 0 0 34.56-35.776V753.28h170.944c19.648 0 35.84-15.488 35.712-34.432a35.072 35.072 0 0 0-35.84-34.496z m-593.92 11.52c-0.256-0.32-0.384-0.768-0.768-1.088-1.088-1.088-2.56-1.728-3.84-2.688a33.088 33.088 0 0 0-20.48-7.68l-0.512 0.064-0.64-0.128H99.84a35.072 35.072 0 0 0-35.84 34.496 35.072 35.072 0 0 0 35.712 34.432H270.72v171.008c0 19.84 15.552 35.84 34.56 35.776a35.008 35.008 0 0 0 34.432-35.712V720l-0.128-0.64 0.128-0.704a33.344 33.344 0 0 0-9.472-22.848zM512 374.144a137.92 137.92 0 1 0 0.128 275.84A137.92 137.92 0 0 0 512 374.08z"></path>
              </svg>
            </li>
          </ul>`
    }
  })

  // 实例化 minimap 插件
  const minimap = new G6.Minimap({
    size: [200, 100],
    className: 'minimap',
    type: 'keyShape'
  })

  //   菜单插件
  const contextMenu = new G6.Menu({
    getContent(val) {
      const record = val.item._cfg.model
      if (!record.depth) {
        return ''
      }
      let indexItem: any = ''
      if (record.category === 1) {
        // 新增文件夹过滤指标画像和模型编辑菜单
      } else {
        indexItem = '<li>指标画像</li>'
      }
      return `
          <ul class="meun-list">
            ${indexItem}
          </ul>`
    },
    handleMenuClick: (target, item: any) => {
      switch (target.innerHTML) {
        case '新增文件夹':
          onAddItem(item._cfg.model)
          break
        case '关联指标':
          onSelectedItem(item._cfg.model)
          break
        case '指标画像':
          toPortrait(item._cfg.model)
          break
        case '编辑':
          handleEdit(item._cfg.model)
          break
        case '删除':
          onDeleteItem(item._cfg.model)
          break
      }
    },
    offsetX: 10,
    offsetY: 10,
    // 在哪些类型的元素上响应
    itemTypes: ['node']
  })

  G6.registerEdge('hvh', {
    draw(cfg, group) {
      const startPoint = cfg.startPoint
      const endPoint = cfg.endPoint
      const shape = group.addShape('path', {
        attrs: {
          stroke: '#AAB7C4',
          path: [
            ['M', startPoint.x, startPoint.y],
            ['A', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], // 三分之一处
            ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], // 三分之二处
            ['L', endPoint.x, endPoint.y]
          ],
          endArrow: {
            path: G6.Arrow.triangle(5, 5, 0), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应)
            d: 0,
            fill: '#409EFF',
            opacity: 0.5,
            lineWidth: 1
          }
        },
        // must be assigned in G6 3.3 and later versions. it can be any value you want
        name: 'path-shape'
      })
      return shape
    }
  })

  const container = document.getElementById('container')
  const width = parentContent.value.clientWidth // 画布宽度
  const height = parentContent.value.clientHeight - 65 // 画布高度
  const graph = new G6.TreeGraph({
    container: 'container',
    width,
    height,
    plugins: [toolbar, minimap, contextMenu], // 将 minimap 实例配置到图上
    modes: {
      default: [
        {
          type: 'drag-canvas',
          onChange: function onChange(item, collapsed) {
            const data = item.get('model')
            graph.updateItem(item, {
              collapsed
            })
            data.collapsed = collapsed
            return true
          }
        },
        'drag-canvas',
        'zoom-canvas'
      ]
    },
    defaultNode: {
      type: 'treeNode',
      anchorPoints: [
        [0, 0.5],
        [1, 0.5]
      ]
    },
    defaultEdge: {
      type: 'hvh',
      style: {
        stroke: '#A3B1BF'
      }
    },
    layout: {
      type: 'compactBox',
      direction: 'LR',
      getId: function getId(d) {
        return d.id
      },
      getHeight: function getHeight() {
        return 16
      },
      getWidth: function getWidth(d) {
        const labelWidth = G6.Util.getTextSize(
          d.label,
          BaseConfig.nameFontSize
        )[0]
        const width =
          BaseConfig.itemPadding +
          BaseConfig.nameMarginLeft +
          labelWidth +
          BaseConfig.rootPadding +
          BaseConfig.childCountWidth
        return width
      },
      getVGap: function getVGap() {
        return 15
      },
      getHGap: function getHGap() {
        return 30
      }
    }
  })
  
  treeGraph.value = graph
  graph.data(temp)
  graph.render()
  graph.fitCenter()

  graph.on('node:click', (e: any) => {
    // 单击图标展开收起子节点
    if (e.target.get('name') === 'collapse-icon') {
      e.item.getModel().collapsed = !e.item.getModel().collapsed
      graph.setItemState(e.item, 'collapsed', e.item.getModel().collapsed)
      graph.layout()
    }
  })
  if (typeof window !== 'undefined')
    window.onresize = () => {
      if (!graph || graph.get('destroyed')) return
      if (!container || !container.scrollWidth || !container.scrollHeight)
        return
      graph.changeSize(container.scrollWidth, container.scrollHeight)
    }
})
</script>

 

posted @ 2023-01-04 17:18  土豆儿哥  阅读(468)  评论(0编辑  收藏  举报