Vue3项目使用G6可视化组件实现一个树形机构图

前言

之前一直使用Echarts或Highcharts实现图表,也有听说G2/G6这些玩意,于是在项目中使用看看效果,还不错的样子。

一、示例代码

javascript 复制代码
<template>
  <div class="index">
    <div id="org-tree"></div>
  </div>
</template>
<script>

/**
  一、SVG中,path的指令有以下几种:
  1. M/m:移动到指定点
  2. L/l:从当前点画一条直线到指定点
  3. H/h:从当前点画一条水平线到指定x坐标
  4. V/v:从当前点画一条垂直线到指定y坐标
  5. C/c:从当前点画一条三次贝塞尔曲线到指定点
  6. S/s:从当前点画一条光滑的三次贝塞尔曲线到指定点
  7. Q/q:从当前点画一条二次贝塞尔曲线到指定点
  8. T/t:从当前点画一条光滑的二次贝塞尔曲线到指定点
  9. A/a:从当前点画一条椭圆弧到指定点
  10. Z/z:闭合路径

  二、在SVG中,path是用来绘制各种形状的元素,其中M、L、H、Z和V是path中的指令,具体含义如下:
  - M:移动到指定的坐标点,不画线。例如,M10 10表示将当前点移动到坐标(10,10)。
  - L:从当前点画一条直线到指定的坐标点。例如,L50 50表示从当前点画一条直线到坐标(50,50)。
  - H:从当前点画一条水平线到指定的x坐标点。例如,H100表示从当前点画一条水平线到x坐标为100的点。
  - Z:闭合路径,即从当前点画一条直线到路径起点,形成一个封闭的图形。例如,Z表示从当前点画一条直线到路径起点,形成一个封闭的图形。
  - V:从当前点画一条垂直线到指定的y坐标点。例如,V100表示从当前点画一条垂直线到y坐标为100的点。
  这些指令可以组合使用,用来绘制各种复杂的形状。
 */
import G6 from '@antv/g6'
export default {
  data() {
    return {
      treeData: {
        id: "root",
        label: "新机场建设项目",
        children: [
          {
            id: "c1",
            label: "航站区",
            children: [
              {
                id: "c1-1",
                label: "旅客航站楼",
                children: [
                  {
                    id: "c1-1-1",
                    label: "主航站楼",
                  },
                  {
                    id: "c1-1-2",
                    label: "连接楼",
                  },
                  {
                    id: "c1-1-3",
                    label: "登机楼",
                  },
                ],
              },
              {
                id: "c1-2",
                label: "塔台",
              },
              {
                id: "c1-3",
                label: "机场酒店、停车楼",
              },
              {
                id: "c1-4",
                label: "大关小学",
              },
            ],
          },
          {
            id: "c2",
            label: "飞行区",
            children: [
              {
                id: "c2-1",
                label: "跑道",
                children: [
                  {
                    id: "c2-1-1",
                    label: "一号跑道",
                  },
                  {
                    id: "c2-1-2",
                    label: "二号跑道",
                  },
                ],
              },
              {
                id: "c2-2",
                label: "滑行道",
                children: [
                  {
                    id: "c2-2-1",
                    label: "滑行道入口",
                  },
                  {
                    id: "c2-2-2",
                    label: "滑行道出口",
                  },
                ],
              },
              {
                id: "c2-3",
                label: "停机坪",
                children: [
                  {
                    id: "c2-3-1",
                    label: "客机坪",
                  },
                  {
                    id: "c2-3-2",
                    label: "货机坪",
                  },
                  {
                    id: "c2-3-3",
                    label: "商务机坪",
                  },
                ],
              },
            ],
          },
          {
            id: "c3",
            label: "工作区",
            children: [
              {
                id: "c3-1",
                label: "飞机维修区",
              },
              {
                id: "c3-2",
                label: "航空食品区",
              },
              {
                id: "c3-3",
                label: "油库区",
              },
              {
                id: "c3-4",
                label: "环保设施区",
              },
              {
                id: "c3-5",
                label: "机场当局、航空公司办公楼",
              },
            ],
          },
        ],
      },
    }
  },
  mounted() {
    this.init_G6_OrgTree()
  },
  methods: {
    /**
     * 初始化一个 G6 树图
     */
    init_G6_OrgTree() {
      // 自定义 icon-node 节点
      G6.registerNode(
        'icon-node',
        {
          draw(cfg, group) {
            const styles = this.getShapeStyle(cfg)
            const { labelCfg = {} } = cfg
            const w = styles.width
            const h = styles.height
            const keyShape = group.addShape('rect', {
              attrs: {
                ...styles,
              },
            })
            if (cfg.label) {
              group.addShape('text', {
                attrs: {
                  ...labelCfg.style,
                  text: cfg.label.split('').join('\n'), // 换行
                  y: 108- h / 2+ cfg.label.length * 8,
                },
              })
            }
            return keyShape
          },
          update: undefined,
        },
        'rect'
      )
      
      // 自定义 flow-line 线
      G6.registerEdge('flow-line', {
        draw(cfg, group) {
          const startPoint = cfg.startPoint
          const endPoint = cfg.endPoint
          const { style } = cfg
          const shape = group.addShape('path', {
            attrs: {
              stroke: style.stroke,
              lineWidth: 2,// 描边粗细
              lineCap: 'round', // 设置线条的结束端点样式
              lineJoin: 'round', // 两条线相交时,所创建的拐角形状(bevel斜角、round圆角、miter尖角)
              endArrow: style.endArrow,
              path: [
                ['M', startPoint.x, startPoint.y],
                ['V', (startPoint.y) + 150], // 画一条垂直线到指定x坐标
                ['H', endPoint.x - 0],
                ['L', endPoint.x, endPoint.y],
              ],
              // path: [
              //   ['M', startPoint.x, startPoint.y],
              //   ['L', startPoint.x, (startPoint.y + endPoint.y) / 2],
              //   ['L', endPoint.x, (startPoint.y + endPoint.y) / 2],
              //   ['L', endPoint.x, endPoint.y],
              // ],
            },
          })
          return shape
        },
      })

      // 机构树 DOM 节点
      const container = document.getElementById("org-tree")
      const width = container.scrollWidth
      const height = container.scrollHeight
      
      // 实例化树图
      const that = this
      that.graph = new G6.TreeGraph({
        container: 'org-tree',
        width,
        height,
        linkCenter: true, // 设置边连入节点的中心,以保证美观性
        modes: {
          default: ['drag-canvas', 'zoom-canvas'],
          // default: ['drag-canvas', 'drag-node'],
        }, // 树图模式

        // 节点总览:https://g6.antv.antgroup.com/manual/middle/elements/nodes/default-node
        defaultNode: {
          type: 'icon-node', // 指定节点类型 icon-node、circle、rect
          // size: [120, 40],
          size: [40, 216], // 节点的大小 [120, 40]、[48, 216]
          style: {
            fill: '#000',
            stroke: 'l(30) 0:#ffffff 0.5:#7ec2f3 1:#1890ff', // #40a9ff、l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff
            radius: 4, // 描边圆角
            lineWidth: 2,// 描边粗细
            // lineDash: [3, 5], // 描边虚线,数组代表实、虚长度
          },
          labelCfg: {
            style: {
              fill: '#fff',
              fontSize: 14,
              fontWeight: 'bolder',
              textAlign:'center',
            },
          }, // 文本配置项
        },

        // 边总览:https://g6.antv.antgroup.com/manual/middle/elements/edges/default-edge
        defaultEdge: {
          type: 'flow-line', // 指定边的类型 line、flow-line
          style: {
            stroke: 'l(45) 0:#ffffff 0.5:#27bda8 1:#5e7ce0', // #5294FF、l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff
            fillOpacity: 0.5,
            lineOpacity: 0.1,
            shadowBlur: 10,
            // endArrow: {
            //   path: "M 0,0 L 12, 6 L 9,0 L 12, -6 Z",
            //   fill: "#5294FF",
            //   d: -110,
            // }, // 结束箭头
          }, // 边的样式属性
        },

        // 节点状态:https://g6.antv.antgroup.com/manual/middle/states/state
        nodeStateStyles: {
          type: 'polyline-edge',
          hover: {
            stroke: "#1890ff",
            lineWidth: 2,
          },
        },

        // 边状态:https://g6.antv.antgroup.com/manual/middle/states/state
        edgeStateStyles: {},

        layout: {
          type: 'compactBox', // 布局类型,支持 dendrogram、compactBox、mindmap、indeted
          direction: 'TB', // 布局方向,有  LR , RL , TB , BT , H , V ,说明(L:左;R:右;T:上;B:下;H:垂直;V:水平)
          getId: (d) => {return d.id},
          getWidth: () => {return 80}, // 每个节点的宽度
          getHeight: () => {return 200}, // 每个节点的高度
          getVGap: () => {return 50}, // 每个节点的水平间隙
          getHGap: () => {return 40}, // 每个节点的垂直间隙
        },
      })

      that.graph.data(this.treeData) // 载入数据
      that.graph.render() // 渲染视图
      that.graph.fitView() // 自适应画布

      // 监听鼠标移入事件
      that.graph.on('node:mouseenter', (evt) => {
        const { item } = evt
        that.graph.setItemState(item, 'hover', true)
      })

      // 监听鼠标移出事件
      that.graph.on('node:mouseleave', (evt) => {
        const { item } = evt
        that.graph.setItemState(item, 'hover', false)
      })

      // 监听画布缩放事件
      if (typeof window !== 'undefined')
        window.addEventListener('resize', () => {
          if (!that.graph || that.graph.get('destroyed'))
            return

          if (!container || !container.scrollWidth || !container.scrollHeight)
            return

          that.graph.changeSize(0, 0)
          that.graph.changeSize(container.scrollWidth, container.scrollHeight-3)
          that.graph.fitView()
        })
    },
  },
}
</script>

<style lang="less" scoped>
.index {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: #000;

  #org-tree {
    width: 100%;
    height: 100%;
  }
}
</style>

二、运行效果

// Todo

相关推荐
ekskef_sef37 分钟前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6411 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云1 小时前
npm淘宝镜像
前端·npm·node.js
dz88i81 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr2 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
顾平安3 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网3 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工3 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染
不是鱼3 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js