vue 里使用 antv g6 实现脑图左右布局、文本超出隐藏处理、自定义边、自定义节点、自定义事件功能

简介: vue 里使用 antv g6 实现脑图左右布局、文本超出隐藏处理、自定义边、自定义节点、自定义事件功能

文档

antv g6 文档:https://antv-g6.gitee.io/zh/docs/manual/introduction



实现效果

效果如下:只是简单的实现一下,有问题在所难免,有好的方案欢迎分享。

ae0f73f14b574735ac2ccd9d7981a69d.png


数据

这里需要对数据进行一个处理,state: 'left', 表示在左边渲染,anchorPoints 根据左边还是右边,以及是否有 children 有关

export const TREELIST = {
  id: "1",
  name: 'kiamo313 测试第一层',
  "children": [
    {
      "id": "1-1",
      name: '第二层1',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-2",
      name: '第二层2',
      state: 'left',
      "children": [
        {
          "id": "1-2-1",
          name: '第三层1',
          state: 'left',
          children: [
            {
              "id": "1-2-1-1",
              name: '第四层1第四层1第四层1第四层1第四层1第四层1',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-1-2",
              name: '第四层2',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-1-3",
              name: '第四层3第四层3第四层3第四层3第四层3第四层3',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            }
          ],
          anchorPoints: [
            [1, 0.5],
            [0, 0.5]
          ]
        },
        {
          "id": "1-2-2",
          name: '第三层2第三层2第三层2第三层2第三层2第三层2',
          state: 'left',
          children: [
            {
              "id": "1-2-2-1",
              name: '第四层4',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-2-2",
              name: '第四层5第四层5第四层5第四层5第四层5第四层5',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            }
          ],
          anchorPoints: [
            [1, 0.5],
            [0, 0.5]
          ]
        },
        {
          "id": "1-2-3",
          name: '第三层3',
          state: 'left',
          children: [
            {
              "id": "1-2-3-1",
              name: '第四层6第四层6第四层6第四层6第四层6',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-3-2",
              name: '第四层7',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-3-3",
              name: '第四层8',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-3-4",
              name: '第四层9第四层9第四层9第四层9第四层9第四层9',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-3-5",
              name: '第四层10',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            }
          ],
          anchorPoints: [
            [1, 0.5],
            [0, 0.5]
          ]
        },
        {
          "id": "1-2-4",
          name: '第三层4',
          state: 'left',
          children: [
            {
              "id": "1-2-4-1",
              name: '第四层11',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-4-2",
              name: '第四层12',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-4-3",
              name: '第四层13',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            },
            {
              "id": "1-2-4-4",
              name: '第四层14',
              state: 'left',
              anchorPoints: [[1, 0.5]]
            }
          ],
          anchorPoints: [
            [1, 0.5],
            [0, 0.5]
          ]
        }
      ],
      anchorPoints: [
        [1, 0.5],
        [0, 0.5]
      ]
    },
    {
      "id": "1-3",
      name: '第二层3',
      state: 'left',
      anchorPoints: [[1, 0.5]]
    },
    {
      "id": "1-4",
      name: '第二层4',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-5",
      name: '第二层5',
      "children": [
        {
          "id": "1-5-1",
          name: '第三层1',
          children: [
            {
              "id": "1-5-1-1",
              name: '第四层1第四层1第四层1第四层1第四层1',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-1-2",
              name: '第四层2',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-1-3",
              name: '第四层3第四层3第四层3',
              anchorPoints: [[0, 0.5]]
            }
          ],
          anchorPoints: [
            [1, 0.5],
            [0, 0.5]
          ]
        },
        {
          "id": "1-5-2",
          name: '第三层2',
          children: [
            {
              "id": "1-5-2-1",
              name: '第四层4',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-2-2",
              name: '第四层5',
              anchorPoints: [[0, 0.5]]
            }
          ],
          anchorPoints: [
            [1, 0.5],
            [0, 0.5]
          ]
        },
        {
          "id": "1-5-3",
          name: '第三层3',
          children: [
            {
              "id": "1-5-3-1",
              name: '第四层6',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-3-2",
              name: '第四层7',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-3-3",
              name: '第四层8',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-3-4",
              name: '第四层9第四层9第四层9第四层9第四层9',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-3-5",
              name: '第四层10',
              anchorPoints: [[0, 0.5]]
            }
          ],
          anchorPoints: [
            [1, 0.5],
            [0, 0.5]
          ]
        },
        {
          "id": "1-5-4",
          name: '第三层4',
          children: [
            {
              "id": "1-5-4-1",
              name: '第四层11',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-4-2",
              name: '第四层12',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-4-3",
              name: '第四层13',
              anchorPoints: [[0, 0.5]]
            },
            {
              "id": "1-5-4-4",
              name: '第四层14',
              anchorPoints: [[0, 0.5]]
            }
          ],
          anchorPoints: [
            [1, 0.5],
            [0, 0.5]
          ]
        }
      ],
      anchorPoints: [
        [1, 0.5],
        [0, 0.5]
      ]
    },
    {
      "id": "1-6",
      name: '第二层6',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-7",
      name: '第二层7',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-8",
      name: '第二层8',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-9",
      name: '第二层9',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-10",
      name: '第二层10',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-11",
      name: '第二层11',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-12",
      name: '第二层12',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-13",
      name: '第二层13第二层13第二层13第二层13',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-14",
      name: '第二层1-14',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-15",
      name: '第二层15第二层15第二层15第二层15',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-16",
      name: '第二层16',
      anchorPoints: [[0, 0.5]]
    },
    {
      "id": "1-27",
      name: '第二层27',
      state: 'left',
      anchorPoints: [[1, 0.5]]
    },
    {
      "id": "1-28",
      name: '第二层28',
      state: 'left',
      anchorPoints: [[1, 0.5]]
    },
    {
      "id": "1-29",
      name: '第二层29',
      state: 'left',
      anchorPoints: [[1, 0.5]]
    },
    {
      "id": "1-30",
      name: '第二层30',
      state: 'left',
      anchorPoints: [[1, 0.5]]
    },
  ],
  anchorPoints: [
    [1, 0.5],
    [0, 0.5]
  ]
}



代码实现

<template>
    <div id="g6-kaimo"></div>
</template>
<script>
import G6 from '@antv/g6';
import { TREELIST } from "./data/list.js";
export default {
    name: 'G6Demo',
    mounted() {
        this.initTree(TREELIST);
    },
    methods: {
        initTree (data) {
            // 文本超出隐藏 (字段, 最大长度, 字体大小)
            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;
            };
            // 获取文本的长度
            const getTextSize = (str, maxWidth, fontSize) => {
                let width = G6.Util.getTextSize(str, fontSize)[0];
                return width > maxWidth ? maxWidth : width;
            }
            // 自定义节点
            G6.registerNode(
                'tree-node',
                {
                    draw(cfg, group) {
                        let rect;
                        if(cfg.state === "left") {
                            rect = group.addShape('rect', {
                                attrs: {
                                    x: 210 - getTextSize(cfg.name, 210, 16), // x 轴移动距离
                                    y: 0, // y 轴移动距离
                                    width: getTextSize(cfg.name, 210, 16) + 20,// 宽
                                    height: 40,// 高
                                    fill: '#e35e5e',
                                    stroke: '#666',// 边框色
                                    fontSize: 16,
                                    fontWeight: 600,
                                    radius: 4,
                                },
                                name: 'big-rect-shape',
                            });
                            // 左文本点
                            group.addShape('text', {
                                attrs: {
                                    text: fittingString(cfg.name, 210, 16),
                                    x: 210 - getTextSize(cfg.name, 210, 16) + 10,
                                    y: 20,
                                    fontSize: 16,
                                    textAlign: 'left',
                                    textBaseline: 'middle',
                                    fill: "#fff"
                                },
                                name: 'text-shape',
                            });
                        }
                        if(cfg.state !== "left") {
                            rect = group.addShape('rect', {
                                attrs: {
                                    x: 0, // x 轴移动距离
                                    y: 0, // y 轴移动距离
                                    width: getTextSize(cfg.name, 210, 16) + 20,// 宽
                                    height: 40,// 高
                                    fill: '#2ead65',
                                    stroke: '#666',// 边框色
                                    fontSize: 16,
                                    fontWeight: 600,
                                    radius: 4,
                                },
                                name: 'big-rect-shape',
                            });
                            group.addShape('text', {
                                attrs: {
                                    text: fittingString(cfg.name, 210, 16),
                                    x: 10,
                                    y: 20,
                                    fontSize: 16,
                                    textAlign: 'left',
                                    textBaseline: 'middle',
                                    fill: "#fff"
                                },
                                name: 'text-shape',
                            });
                            if(cfg.depth === 0) {
                                rect = group.addShape('rect', {
                                    attrs: {
                                        x: 0, // x 轴移动距离
                                        y: 0, // y 轴移动距离
                                        width: getTextSize(cfg.name, 210, 16) + 20,// 宽
                                        height: 40,// 高
                                        fill: '#873bf4',
                                        stroke: '#666',// 边框色
                                        fontSize: 16,
                                        fontWeight: 600,
                                        radius: 4,
                                    },
                                    name: 'big-rect-shape',
                                });
                                group.addShape('text', {
                                    attrs: {
                                        text: fittingString(cfg.name, 210, 16),
                                        x: 10,
                                        y: 20,
                                        fontSize: 16,
                                        textAlign: 'left',
                                        textBaseline: 'middle',
                                        fill: "#fff"
                                    },
                                    name: 'text-shape',
                                });
                            }
                        }
                        // 是否有子节点
                        if(cfg.children && cfg.children.length > 0) {
                            if(cfg.state === "left") {
                                group.addShape('marker', {
                                    attrs: {
                                        x: 210 - getTextSize(cfg.name, 210, 16),
                                        y: 20,
                                        r: 6,
                                        symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,
                                        stroke: '#666',
                                        fill: '#fff',
                                        lineWidth: 1,
                                        cursor: 'pointer', // 鼠标变手
                                    },
                                    name: 'collapse-icon',
                                });
                            }else{
                                group.addShape('marker', {
                                    attrs: {
                                        x: getTextSize(cfg.name, 210, 16) + 20,
                                        y: 20,
                                        r: 6,
                                        symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,
                                        stroke: '#666',
                                        fill: '#fff',
                                        lineWidth: 1,
                                        cursor: 'pointer', // 鼠标变手
                                    },
                                    name: 'collapse-icon',
                                });
                                if(cfg.depth === 0) {
                                    console.log("cfg---->", cfg)
                                    group.addShape('marker', {
                                        attrs: {
                                            x: 0,
                                            y: 20,
                                            r: 6,
                                            symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,
                                            stroke: '#666',
                                            fill: '#fff',
                                            lineWidth: 1,
                                            cursor: 'pointer', // 鼠标变手
                                        },
                                        name: 'collapse-icon',
                                    });
                                }
                            }
                        }
                        return rect;
                    },
                    setState: (name, value, item) => {
                        console.log("自定义节点--->setState:", name, value, item);
                        if (name === "collapsed") {
                            console.log("item-group", item.get("group"))
                            const marker = item.get("group").findAll(ele => ele.get("name") === "collapse-icon");
                            console.log("marker--->", marker)
                            marker[0] && marker[0].attr("symbol", value ? G6.Marker.collapse : G6.Marker.expand);
                            // 如果是根节点需要处理两个marker
                            if(item._cfg.model.depth === 0) {
                                marker[1] && marker[1].attr("symbol", value ? G6.Marker.collapse : G6.Marker.expand);
                            }
                        }
                    }
                },
            );
            // 自定义边
            G6.registerEdge(
                'kaimo-line',
                {
                    /**
                     * 绘制边,包含文本
                     * @param  {Object} cfg 边的配置项
                     * @param  {G.Group} group 图形分组,边中的图形对象的容器
                     * @return {G.Shape} 绘制的图形,通过 node.get('keyShape') 可以获取到
                     */
                    draw(cfg, group) {
                        const startPoint = cfg.startPoint;
                        const endPoint = cfg.endPoint;
                        let shape;
                        if(cfg.state === 'left') {
                            shape = group.addShape('path', {//线条
                                attrs: {
                                    stroke: 'pink',
                                    path: [
                                        ['M', startPoint.x, startPoint.y], 
                                        ['L', 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: 'pink',
                                        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'
                            });
                        }else{
                            shape = group.addShape('path', {//线条
                                attrs: {
                                    stroke: 'pink',
                                    path: [
                                        ['M', startPoint.x, startPoint.y], 
                                        ['L', 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: 'pink',
                                        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;
                    },
                },
            );
            // 自定义事件
            G6.registerBehavior("kaimo-behavior", {
                getEvents() {
                    return {
                        "node:click": "onNodeClick"
                    };
                },
                // 自定义单击事件
                onNodeClick: (evt) => {
                    // 单击展开/收起
                    console.log("自定义单击事件--->", evt, graph)
                    evt.item._cfg.model.collapsed = !evt.item._cfg.model.collapsed;
                    // 更改 item 的状态,触发自定义节点:setState
                    graph.setItemState(evt.item, "collapsed", !evt.item.getModel().collapsed);
                    graph.layout();
                },
            });
            // 定义画布的宽高
            const width = document.getElementById('g6-kaimo').scrollWidth || 1600;
            const height = document.getElementById('g6-kaimo').scrollHeight || 960;
            // 实例化G6  
            // 因为我们用的是树图,所以这里是G6.TreeGraph(),还有其他,像是普通图的配置G6.Graph(),一般y用的比较多的就像是树图这种,还有组织架构图一类的。
            const graph = new G6.TreeGraph({
                // 图的  DOM 容器,对应上面我们定义的id
                container: 'g6-kaimo',
                width,
                height,
                // 设置画布的交互模式
                modes: {
                    default: [
                        // 自定义事件
                        'kaimo-behavior',
                        // 拖拽画布
                        'drag-canvas',
                        // 缩放画布
                        'zoom-canvas',
                    ],
                },
                // 配置节点的属性
                defaultNode: {
                    // 节点类型,cicle:圆形,rect:矩形,ellipse:椭圆,diamond:菱形,triangle:三角形,star:五角星,image:图片,modelRect:卡片
                    type: 'tree-node',
                    // 节点样式
                    style: {
                        // 鼠标经过是的形状,跟css是一样的。
                        cursor:'pointer',
                        // 圆角
                        radius: 4,
                    },
                },
                // 配置边的属性
                defaultEdge: {
                    // 指定边的类型,可以是内置边的类型名称,也可以是自定义边的名称。
                    // line:直线,polyline:折线,arc:圆弧线,quadratic:二阶贝塞尔曲线,cubic:三阶贝塞尔曲线,cubic-vertica:垂直方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点。cubic-horizontal:水平方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点。loop:自环
                    type: 'kaimo-line',
                },
                // 布局配置项
                layout: {
                    // 布局名称,这个可就太多了,这个只是树图结构中的一种。比如还有compactBox:紧凑树布局,dendrogram:生态树布局,indented:缩紧树布局。
                    type: 'compactBox', // 脑图树布局
                    direction: 'H', // H / V / LR / RL / TB / BT 这些是控制节点分布位置,从左往右、从右往左、从中间往上下延伸、从中间往左右延伸...具体可以看官网
                    // 节点 id 的回调函数
                    getId: function getId(d) {
                        return d.id;
                    },
                    // 下面都是一些控制节点与节点间距离的回调函数,具体可以试着修改一下值。
                    // 节点高度的回调函数
                    getHeight: function getHeight() {
                        return 16;
                    },
                    // 节点宽度的回调函数
                    getWidth: function getWidth(cfg) {
                        return 60;
                    },
                    // 节点纵向间距的回调函数
                    getVGap: function getVGap() {
                        return 20;
                    },
                    // 节点横向间距的回调函数
                    getHGap: function getHGap(val) {
                        return 100;
                    },
                    // 这个getSide就是控制节点位置的属性了,通过数据结构中定义的值做判断,来控制左右,
                    // 注意的是这个官方写的只能return 'left'和'right',当我们的树结构是竖着的呢?难道是用top和bottom?这里我也试过了,用top和bottom是不好使的,因为人家官方确确实实的只有left和right,通过尝试,其实left就对应top,right对应bottom,所以要控制节点在上面就写left,在下面就写right。
                    getSide: (node) => {
                        if(node.data.state === 'left') {
                            return 'left'
                        }
                        return 'right'
                    }
                },
                // 动画属性
                animate: true,
            });
            // 默认全部展开
            G6.Util.traverseTree(data, function (item) {
                item.collapsed = false;
            });
            // 初始化的图数据
            graph.data(data);
            // 根据提供的数据渲染视图。
            graph.render();
            // 让画布内容适应视口
            graph.fitView();
        }
    },
};
</script>
目录
相关文章
|
4天前
|
前端开发 JavaScript
Vue底层实现原理总结
Vue底层实现原理总结
8 0
|
6天前
|
JavaScript
|
7天前
|
存储 JavaScript API
Vue状态管理深度剖析:Vuex vs Pinia —— 从原理到实践的全面对比
Vue状态管理深度剖析:Vuex vs Pinia —— 从原理到实践的全面对比
13 2
|
3天前
|
JavaScript 前端开发
Vue躬行记(7)——渲染函数和JSX
Vue躬行记(7)——渲染函数和JSX
8 0
|
18天前
|
JavaScript 前端开发 开发者
vue3+ts配置跨域报错问题解决:> newpro2@0.1.0 serve > vue-cli-service serve ERROR Invalid options in vue.
【6月更文挑战第3天】在 Vue CLI 项目中遇到 &quot;ERROR Invalid options in vue.config.js: ‘server’ is not allowed&quot; 错误是因为尝试在 `vue.config.js` 中使用不被支持的 `server` 选项。正确配置开发服务器(如代理)应使用 `devServer` 对象,例如设置代理到 `http://xxx.com/`: ```javascript module.exports = { devServer: {
30 1
|
8天前
|
JavaScript 前端开发 测试技术
使用 Vue CLI 脚手架生成 Vue 项目
通过 Vue CLI 创建 Vue 项目可以极大地提高开发效率。它不仅提供了一整套标准化的项目结构,还集成了常用的开发工具和配置,使得开发者可以专注于业务逻辑的实现,而不需要花费大量时间在项目配置上。
67 7
使用 Vue CLI 脚手架生成 Vue 项目
|
10天前
|
JavaScript 算法
“Error: error:0308010C:digital envelope routines::unsupported”启动vue项目遇到一个错误【已解决
“Error: error:0308010C:digital envelope routines::unsupported”启动vue项目遇到一个错误【已解决
11 1
|
10天前
|
JavaScript
error Component name “Login“ should always be multi-word vue/multi-word-component-names【已解决】
error Component name “Login“ should always be multi-word vue/multi-word-component-names【已解决】
25 1
|
12天前
|
JavaScript API
【vue实战项目】通用管理系统:信息列表,信息录入
【vue实战项目】通用管理系统:信息列表,信息录入
19 3
|
12天前
|
JavaScript API
【vue实战项目】通用管理系统:信息列表,信息的编辑和删除
【vue实战项目】通用管理系统:信息列表,信息的编辑和删除
26 2