0
点赞
收藏
分享

微信扫一扫

vue项目实现G6双向树状图最详细教程

一脸伟人痣 2022-02-16 阅读 180

在项目中使用 NPM 包引入

npm install --save @antv/g6

AntV G6 示例图

初始化数据
在这里插入图片描述

点击节点动态加入数据
在这里插入图片描述

上代码(vue 文件)

<template>
<!-- g6 容器 -->
<!-- 以防数据量过大,加载速度慢,引入 element ui 的 loading -->
  <div 
   id="container"
   v-loading="loading" 
   element-loading-text="加载中..."
   element-loading-background="rgba(0, 0, 0, 0.8)"
  ></div>
</template>
<script>
// 导入 G6
import G6 from '@antv/g6';
// 导入 api 后台数据
import { PostGraph } from "../../api/index";

export default {
  data() {
    return {
      data: {
        id:'1',
        nood: 0,
        direction: 0,
        children: [ // 子集
          {
            id: "11",
            nood: 2, // 0:主节点, 1:统计节点, 2:普通节点
            direction: 1, // 0:主节点, 1:向左展开, 2:向右展开
            isSpread: false, // 有无子集
            text1: 'text11111',
            text2: "text2222222222",
            text3: "text33333333333333333333333333333",
            url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
          },
          {
            id: "12",
            nood: 2,
            direction: 1,
            isSpread: false,
            text1: 'text11111',
            text2: "text2222222222",
            text3: "text33333333333333333333333333333",
            url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
          },
          {
            id: "13",
            nood: 2,
            direction: 1,
            isSpread: false,
            text1: 'text11111',
            text2: "text2222222222",
            text3: "text33333333333333333333333333333",
            url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
          },
          {
            id: "14",
            nood: 2,
            direction: 1,
            isSpread: false,
            text1: 'text11111',
            text2: "text2222222222",
            text3: "text33333333333333333333333333333",
            url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
          },
          {
            id: "16",
            nood: 2,
            direction: 2,
            isSpread: true,
            text1: 'text11111',
            text2: "text2222222222",
            text3: "text33333333333333333333333333333",
            url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",//标签url
          },
        ]
      },
      data2: {
        id: '2',
        nood: 1,
        direction: 2,
        value: 1111111111,
        value1: 2222,
        value2: 3333,
        children: [
          {
            id: "21",
            nood: 2,
            direction: 2,
            isSpread: true,
            text1: 'text11111',
            text2: "text2222222222",
            text3: "text33333333333333333333333333333",
            url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
          },
          {
            id: "22",
            nood: 2,
            direction: 2,
            isSpread: true,
            text1: 'text11111',
            text2: "text2222222222",
            text3: "text33333333333333333333333333333",
            url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
          },
        ]
      },
      loading: true,
      dataList: {},
      dataList2: {},
      id: "",
      direction: 0,
    };
  },
  created (){
    // this.tradeMap();
  },
  mounted () {
    this.tradeMap();
    // this.render();
  },
  methods: {
    // 从api获取图谱初始数据
    async tradeMap(){
      let res = await PostGraph({
        id: this.id,
        direction: this.direction,
      })
      if (res.status == "1"){
        this.dataList = res.data[0];
        this.render();
      }
    },
    // 点击加载的数据
    async tradeMap2(){
      let res = await PostGraph({
        id: this.id,
        direction: this.direction,
      })
      if (res.status == "1"){
        this.dataList2 = res.data[0];
      }
    },
    // g6 配置
    async render () {
      // g6 的实例方法里 this 会变
      let _this = this;

      // 文本超出隐藏
      // fittingString(字段, 最大长度, 字体大小)
      const fittingString = (str, maxWidth, fontSize) => {
        const ellipsis = '...';
        const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
        let currentWidth = 0;
        let res = str;
        const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
        str.split('').forEach((letter, i) => {
          if (currentWidth > maxWidth - ellipsisLength) return;
          if (pattern.test(letter)) {
            // Chinese charactors
            currentWidth += fontSize;
          } else {
            // get the width of single letter according to the fontSize
            currentWidth += G6.Util.getLetterWidth(letter, fontSize);
          }
          if (currentWidth > maxWidth - ellipsisLength) {
            res = `${str.substr(0, i)}${ellipsis}`;
          }
        });
        return res;
      };
      // 自定义节点
      G6.registerNode(
        'tree-node',
        {
          drawShape: function drawShape(cfg, group) {
            const rect = group.addShape('rect', {
              attrs: {
                x: 0, // x 轴移动距离
                y: 0, // y 轴移动距离
                width: cfg.nood == 1 ? 170 : 190,// 宽
                height: 65,// 高
                // fill: '#666', // 背景色
                // stroke: '#666',// 边框色
              },
              name: 'big-rect-shape',
            });
            // 主节点
            if (cfg.nood  == 0){
              group.addShape('rect', {
                attrs: {
                  x: 0,
                  y: 0,
                },
                name: 'source-shape',
              });
              group.addShape('rect', {
                attrs: {
                  fill: '#eff4ff',
                  x: 60,
                  y: 0,
                  width: 64,
                  height: 64,
                  radius: 32,
                },
                name: 'source-rect-shape',
              });
              group.addShape('rect', {
                attrs: {
                  fill: '#2d60e0',
                  x: 78,
                  y: 18,
                  width: 28,
                  height: 28,
                  radius: 14,
                },
                name: 'source-rect-shape',
              });
              group.addShape('text', {
                attrs: {
                  fill: '#fff',
                  text: '主节点',
                  x: 63,
                  y: 90,
                },
                name: 'source-text-shape',
              });
            }
            // 统计节点
            if (cfg.nood == 1){
              let operate = '';
              if(cfg.value3 > cfg.value2){
                operate= '转出'
              }else{
                operate= '转入'
              }
              /* 盒子 */
              group.addShape('rect', {
                attrs: {
                  fill: '#eff4ff',
                  x: 5,
                  y: 18,
                  width: 170,
                  height: 30,
                  radius: 15,
                },
                name: 'stat-rect-shape',
              });
              /* 文字 */
              group.addShape('text', {
                attrs: {
                  fill: '#000',
                  text: operate + cfg.value,
                  x: 30,
                  y: 40,
                },
                name: 'stat-text-shape',
              });
            }
            // 普通节点
            if (cfg.nood == 2){
              /* 盒子 */
              group.addShape('rect', {
                attrs: {
                  fill: '#fff',
                  shadowColor: '#c1c1c0',//阴影颜色
           	      shadowBlur: 10,//阴影范围
                  x: cfg.direction == '2' ? 5 : 15,
                  y: 0,
                  width: 170,
                  height: 65,
                  radius: 5,
                },
                name: 'rect-shape',
              });
              /* 第一个图标 */
              group.addShape('image', {
                attrs: {
                  x: cfg.direction == '2' ? 13 : 23,
                  y: 8,
                  height: 14,
                  width: 14,
                  img: 'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg',
                },
                name: 'state-icon',
              });
              /* 第二个图标 */
              group.addShape('image', {
                attrs: {
                  x: cfg.direction == '2' ? 13 : 23,
                  y: 30,
                  height: 14,
                  width: 14,
                  img: cfg.url,
                },
                name: 'copy-icon',
              });
              /* 第一行字 */
              group.addShape('text', {
                attrs: {
                  text: cfg.text1,
                  x: cfg.direction == '2' ? 35 : 45,
                  y: 15,
                  fontSize: 10,
                  textAlign: 'left',
                  textBaseline: 'middle',
                  fill: cfg.direction == '2' ? '#e35e5e' : '#2ead65',
                },
                name: 'text-shape',
              });
              /* 第二行字 */
              group.addShape('text', {
                attrs: {
                  // fittingString(字段, 最大长度, 字体大小)
                  text: fittingString(cfg.text2, 130, 12),
                  x: cfg.direction == '2' ? 35 : 45,
                  y: 35,
                  fontSize: 12,
                  textAlign: 'left',
                  textBaseline: 'middle',
                  fill: '#666',
                },
                name: 'center-text-shape',
              });
              /* 第三行字 */
              group.addShape('text', {
                attrs: {
                  text: fittingString(cfg.text3, 150, 12),
                  x: cfg.direction == '2' ? 35 : 45,
                  y: 52,
                  fontSize: 10,
                  textAlign: 'left',
                  textBaseline: 'middle',
                  fill: '#9aa1b1',
                },
                name: 'bottom-text-shape',
              });
              /* 折叠展开 */
              if (cfg.isSpread) {
                group.addShape('marker', {
                  attrs: {
                    x: cfg.direction == '2' ? 182 : 8,
                    y: 32,
                    r: 6,
                    symbol: cfg.collapsed || cfg.isSpread ? G6.Marker.expand : G6.Marker.collapse,
                    stroke: '#666',
                    fill: '#fff',
                    lineWidth: 1,
                    cursor: 'pointer', // 鼠标变手
                  },
                  name: 'collapse-icon',
                });
              }
            }
            return rect;
          },
          // 更新
          update: (cfg, item) => {
            if(cfg.txhash){
              // 简单实现节流
              // 防止用户瞎点出现bug
              let previous = 0;
              let now = Date.now();
              if(now - previous > 1000){
                previous = now;
                const group = item.getContainer();
                const icon = group.find((e) => e.get('name') === 'collapse-icon');
                icon.attr('symbol', cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse);
              }
            }
          },
        },
        'single-node',
      );
      
      const container = document.getElementById('container');// 获取容器id
      const width = container.scrollWidth || 1520; // 宽
      const height = container.scrollHeight || 856; // 高
      const graph = new G6.TreeGraph({
        // 图的  DOM 容器,可以传入该 DOM 的 id 或者直接传入容器的 HTML 节点对象。
        container: 'container',
        // 指定画布宽度,单位为 'px'
        width,
        // 指定画布高度,单位为 'px'
        height,
        // 设置画布的模式
        modes: {
          default: [
            {
              // 只适用于树图,展开或收起子树
              type: 'collapse-expand',
              // onChange:收起或展开的回调函数。警告:V3.1.2 版本中将移除
              onChange: function onChange(item, collapsed) {
                const data = item.get('model');
                graph.updateItem(item, {
                  collapsed,
                });
                data.collapsed = collapsed;
                return true;
              },
            },
            // 拖拽画布;
            'drag-canvas',
            // 缩放画布;
            'zoom-canvas',
          ],
        },
        // 默认状态下节点的配置,比如 type, size, color,会被写入的 data 覆盖
        defaultNode: {
          // 节点类型,这里是自定义节点,自己取名
          type: 'tree-node',
          // 指定边连入节点的连接点的位置(相对于该节点而言),可以为空
          anchorPoints: [
            [0, 0.5],
            [1, 0.5],
          ],
        },
        // 默认状态下边的配置
        defaultEdge: {
          // 指定边的类型,可以是内置边的类型名称,也可以是自定义边的名称。默认为 'line'
          type: 'cubic-horizontal',
          // 边的样式属性
          style: {
            // 边的颜色
            stroke: '#A3B1BF',
            // 开始箭头
            startArrow: {
              path: G6.Arrow.circle(2, 2), // 内置样式
              fill: '#A3B1BF', // 颜色
              d: 4, // 偏移距离
            },
            // 结束箭头
            endArrow: {
              path: 'M 0,0 L 5,4 L 5,-4 Z', // 自定义样式
              fill: '#A3B1BF',
              d: 3,
            },
          },
        },
        // 布局配置项,使用 type 字段指定使用的布局方式
        layout: {
          // 布局名称
          type: 'compactBox',
          // layout 的方向 H / V / LR / RL / TB / BT
          // TB —— 根节点在上,往下布局
          // BT —— 根节点在下,往上布局
          // LR —— 根节点在左,往右布局
          // RL —— 根节点在右,往左布局
          // H —— 根节点在中间,水平对称布局
          // V —— 根节点在中间,垂直对称布局
          direction: 'H',
          // 判断节点向左还是向右
          getSide: (d) => { 
            if (d.data.direction == '2') {
              return 'right';
            }
            return 'left';
          },
          // 节点 id 的回调函数
          getId: function getId(d) {
            return d.id;
          },
          // 节点高度的回调函数
          getHeight: function getHeight() {
            return 40;
          },
          // 节点宽度的回调函数
          getWidth: function getWidth() {
            return 20;
          },
          // 节点纵向间距的回调函数
          getVGap: function getVGap() {
            return 18;
          },
          // 节点横向间距的回调函数
          getHGap: function getHGap() {
            return 100;
          },
        },
        // 动画
        animate: true,
      });
      // 初始化的图数据
      graph.data(this.data);
      // graph.data(this.dataList);
      // 根据提供的数据渲染视图。
      graph.render();
      // 缩放视窗窗口到一个固定比例
      graph.zoomTo(1);
      // 平移图到中心将对齐到画布中心,但不缩放
      graph.fitCenter();
      // 点击动态加入节点
      // 因为数据量过大,在用户点击展开时才请求获取数据
      // 数据源里要有唯一字段 id ,不加 id g6会自动生成id,会有一些bug
      graph.on('node:click', async (e) => {
        const item = e.item;
        const nodeId = item.get('id');
        const model = item.getModel();
        const children = model.children;
        // 没有子集加入数据
        if (!children && model.isSpread){
          _this.txhash= model.txhash;
          _this.direction= model.direction;
          await _this.tradeMap2();
          // 不加加定时器获取不到数据
          setTimeout(() =>{
            graph.addChild(_this.data2, nodeId);
            // graph.addChild(_this.dataList2, nodeId);
          },100)
        }
      });
      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);
        };
      // 数据加载完成关闭 element ui 的 loading 
	  this.loading = false;
    },
  },
};
</script>
<style scoped>
#container{
  width: 1520px;
  height: 856px;
}
</style>

在这里插入图片描述

AntV 官网

https://antv.gitee.io/zh

点击查看官网(G6)

举报

相关推荐

0 条评论