antv/g6 实现区县架构组织图

记录一下

点击查看代码
<template>
  <div ref="container" style="width: 100%; height: 100vh; border: 1px solid #ddd;"></div>
</template>

<script>
import G6 from '@antv/g6'
import * as Hierarchy from '@antv/hierarchy'

// 显式注册布局,防止 tree-shaking
G6.registerLayout('dendrogram', Hierarchy.dendrogram)

export default {
  name: 'ReservoirTopology',
  data() {
    return {
      graph: null
    }
  },
  mounted() {
    this.initGraph()
  },
  methods: {
    initGraph() {
      const container = this.$refs.container
      let graphRef = null

      const data = {
        id: 'JiNan',
        label: '济南市',
        isCity: true,
        children: [
          {
            id: 'ChangQingQu',
            label: '长清区',
            children: [
              {
                id: 'ZhangXiaZhen',
                label: '张夏镇',
                children: [
                  { id: 'PuTaoWan', label: '葡萄湾水库' },
                  { id: 'TengJiaGou', label: '滕家沟水库' },
                  { id: 'DuZhuang', label: '杜庄水库' }
                ]
              }
            ]
          },
          {
            id: 'PingYinXian',
            label: '平阴县',
            children: [
              {
                id: 'DongEzhen',
                label: '东阿镇',
                children: [
                  { id: 'DongE', label: '东阿水库' }
                ]
              }
            ]
          },
          {
            id: 'ZhangQiuShi',
            label: '章丘市',
            children: [
              {
                id: 'GuanZhuangZhen',
                label: '官庄镇',
                children: [
                  { id: 'DongFeng', label: '东风水库' }
                ]
              },
              {
                id: 'PuJiZhen',
                label: '普集镇',
                children: [
                  { id: 'LongHua', label: '龙华水库' }
                ]
              }
            ]
          }
        ]
      }

      // 【新增】注册组织架构图风格的边
      G6.registerEdge('org-chart-edge', {
        draw(cfg, group) {
          const { startPoint, endPoint } = cfg
          const x1 = startPoint.x
          const y1 = startPoint.y
          const x2 = endPoint.x
          const y2 = endPoint.y
          const midY = y1 + (y2 - y1) * 0.5 // 中间水平线高度

          const path = [
            ['M', x1, y1],
            ['L', x1, midY],   // 向下
            ['L', x2, midY],   // 横向
            ['L', x2, y2]     // 向下
          ]

          return group.addShape('path', {
            attrs: {
              path,
              stroke: '#AAB7C4',
              lineWidth: 1.5,
              lineCap: 'round',
              lineJoin: 'round'
            },
            name: 'org-edge-path'
          })
        }
      })

      //节点注册
      G6.registerNode('reservoir-node', {
        draw(cfg, group) {
          const isLeaf = !cfg.children || cfg.children.length === 0
          const displayLabel = isLeaf
            ? cfg.label.split('').join('\n')
            : cfg.label

          const lines = displayLabel.split('\n')
          const lineHeight = 5
          const width = isLeaf ? 35 : Math.max(60, (cfg.label.length + 1) * 12)
          const height = isLeaf ? 70 :  Math.max(lines.length * lineHeight, 32)
          const backImage = isLeaf
            ? require('@/assets/images/screen/sizhi/hengBlueLabel.png')
            : require('@/assets/images/screen/sizhi/shuBlueLabel.png')

          // 1. 添加背景图片(必须用 image shape)
          const bg = group.addShape('image', {
            attrs: {
              x: -width / 2,
              y: -height / 2,
              width: width,
              height: height,
              img: backImage, // 注意:属性名是 img,不是 backgroundImage
            },
            name: 'bg-image',
            draggable: false,
          });

          group.addShape('rect', {
            attrs: {
              x: -width / 2,
              y: -height / 2,
              width,
              height:height,
              radius: 4,
              fill: 'transparent', // 使用透明填充,因为我们已经有了背景图片
              // stroke: cfg.isCity ? '#1890ff' : '#597ef7',
              // lineWidth: cfg.isCity ? 2 : 1.5,
            },
            name: 'rect'
          })

          group.addShape('text', {
            attrs: {
              text: displayLabel,
              x: 0,
              y: 0,
              textAlign: 'center',
              textBaseline: 'middle',
              fontSize: 12,
              fill: isLeaf ? '#333' : '#000',
              fontWeight: cfg.isCity ? 'bold' : 'normal'
            },
            name: 'label'
          })

          if (!isLeaf) {
            const buttonX = 0
            const buttonY = height / 2

            const badge = group.addShape('circle', {
              attrs: {
                x: buttonX,
                y: buttonY,
                r: 8,
                fill: '#fff',
                stroke: '#999',
                lineWidth: 1
              },
              name: 'collapse-btn-bg'
            })

            const icon = group.addShape('text', {
              attrs: {
                text: cfg.collapsed ? '+' : '−',
                x: buttonX,
                y: buttonY,
                textAlign: 'center',
                textBaseline: 'middle',
                fontSize: 10,
                fill: '#666',
                fontWeight: 'bold'
              },
              name: 'collapse-icon'
            })

            const handleClick = (e) => {
              e.preventDefault()
              e.stopPropagation()
              const nodeItem = graphRef.findById(cfg.id)
              if (!nodeItem) return

              const model = nodeItem.getModel()
              model.collapsed = !model.collapsed

              const getAllDescendants = (nodeModel) => {
                let result = []
                if (nodeModel.children) {
                  nodeModel.children.forEach(child => {
                    result.push(child)
                    result = result.concat(getAllDescendants(child))
                  })
                }
                return result
              }

              const descendants = getAllDescendants(model)

              if (model.collapsed) {
                descendants.forEach(desc => {
                  const item = graphRef.findById(desc.id)
                  if (item && item.isVisible()) {
                    graphRef.hideItem(item)
                  }
                })
              } else {
                descendants.forEach(desc => {
                  const item = graphRef.findById(desc.id)
                  if (item && !item.isVisible()) {
                    graphRef.showItem(item)
                  }
                })
              }

              const group = nodeItem.getContainer()
              const iconShape = group.find(child => child.get('name') === 'collapse-icon')
              if (iconShape) {
                iconShape.attr('text', model.collapsed ? '+' : '−')
              }
            }

            badge.on('click', handleClick)
            icon.on('click', handleClick)
          }

          return bg
        }
      })

      // 创建 TreeGraph
      this.graph = new G6.TreeGraph({
        container,
        width: container.clientWidth,
        height: container.clientHeight,
        modes: {
          default: ['drag-canvas', 'zoom-canvas']
        },
        layout: {
          type: 'dendrogram',
          direction: 'TB',
          nodeSep: 80,
          rankSep: 80
        },
        defaultNode: {
          type: 'reservoir-node',
          anchorPoints: [
            [0.5, 0],
            [0.5, 1]
          ]
        },
        defaultEdge: {
          type: 'org-chart-edge', // 使用新边类型
          style: {
            stroke: '#AAB7C4',
            lineWidth: 1.5
          }
        },
        animate: true,
        animateCfg: {
          duration: 300,
          easing: 'easeCubic'
        },
        fitView: true,
        fitViewPadding: [10, 10, 10, 10],
        autoLayout: false
      })

      graphRef = this.graph

      function mapData(node) {
        const newNode = {
          ...node,
          visible: true,
          collapsed: false
        }
        if (node.children && Array.isArray(node.children)) {
          newNode.children = node.children.map(child => mapData(child))
        }
        return newNode
      }

      this.graph.data(mapData(data))
      this.graph.render()

      this.graph.on('node:mouseenter', (evt) => {
        const node = evt.item.getModel()
        if (!node.children) {
          console.log(`当前水库: ${node.label}`)
        }
      })

      window.addEventListener('resize', () => {
        if (this.graph) {
          this.graph.changeSize(container.clientWidth, container.clientHeight)
          this.graph.fitView()
        }
      })
    }
  },
  beforeDestroy() {
    if (this.graph) {
      this.graph.destroy()
      this.graph = null
    }
  }
}
</script>

PixPin_2025-11-10_15-13-38

posted @ 2025-11-10 15:14  _seven7  阅读(3)  评论(0)    收藏  举报