使用antv的G6展示折叠节点树的效果

安装:
npm install --save @antv/g6;
效果图展示:
在这里插入图片描述

这些代码可以直接拿过去用,这是封装的一个公共组件。

import React, { useEffect, useState } from 'react';
// 引入G6
import G6 from '@antv/g6';
// 引入antv的样式
import insertCss from 'insert-css';
const GsixManage = (props) => {
    const listObj = {
        id: 'A',
        label: 'root',
        name: "张三",
        children: [
            {
                id: 'c1',
                label: 'c1',
                name: "李四",
                children: [
                    {
                        id: 'c1-1',
                        label: 'c1-1',
                        name: "李五",
                        children: [
                            {
                                id: "c1-1-1",
                                label: "c1-1-1",
                                name: "白龙马",
                            },
                            {
                                id: "c1-1-2",
                                label: "c1-1-2",
                                name: "唐三藏",
                                children: [
                                    {
                                        id: "c1-1-1-1",
                                        label: "c1-1-1-1",
                                        name: "沙悟净",
                                    },
                                    {
                                        id: "c1-1-1-2",
                                        label: "c1-1-1-2",
                                        name: "孙悟空",
                                    },
                                ]
                            },
                        ]
                    },
                    {
                        id: 'c1-2',
                        label: 'c1-2',
                        name: "五六",
                        children: [
                            {
                                id: 'c1-2-1',
                                label: 'c1-2-1',
                                name: "六七",
                            },
                            {
                                id: 'c1-2-2',
                                label: 'c1-2-2',
                                name: "七八",
                            },
                        ],
                    },
                ],
            },
            {
                id: 'c2',
                label: 'c2',
                name: "七八",
            },
            {
                id: 'c3',
                label: 'c3',
                name: "七八",
                children: [
                    {
                        id: 'c3-1',
                        label: 'c3-1',
                        name: "七八",
                    },
                    {
                        id: 'c3-2',
                        label: 'c3-2',
                        name: "七八",
                        children: [
                            {
                                id: 'c3-2-1',
                                label: 'c3-2-1',
                                name: "七八",
                            },
                            {
                                id: 'c3-2-2',
                                label: 'c3-2-2',
                                name: "七八",
                            },
                            {
                                id: 'c3-2-3',
                                label: 'c3-2-3',
                                name: "七八",
                            },
                        ],
                    },
                    {
                        id: 'c3-3',
                        label: 'c3-3',
                        name: "七八",
                    },
                ],
            },
        ],
    };
    const [data, setData] = useState(listObj);
    const gSixCom = (data) => {
        insertCss(`
        .g6-tooltip {
          border: 1px solid #e2e2e2;
          border-radius: 4px;
          font-size: 12px;
          color: #000;
          background-color: rgba(255, 255, 255, 0.9);
          padding: 10px 8px;
          box-shadow: rgb(174, 174, 174) 0px 0px 10px;
          position: absolute;
          top: 30px;
        }
      `);
        // 绘制背景表格的插件
        const grid = new G6.Grid();
        // 这里是绘制节点的地方
        G6.registerNode(
            'icon-node',
            {
                options: {
                    size: [60, 20],
                    stroke: '#91d5ff',
                    fill: '#91d5ff',
                },
                drawShape(cfg, group) {
                    const styles = this.getShapeStyle(cfg);
                    const w = styles.width;
                    const h = styles.height;
                    const keyShape = group.addShape('rect', {
                        attrs: {
                            x: -w / 2,
                            y: -h / 2,
                            width: w,
                            height: h,
                            stroke: "#5B8FF9",
                            fill: "#fff",
                            radius: [2, 2, 0, 0],
                            textAlign: "left",
                        },
                        name: 'box',
                    });
                    group.addShape('text', {
                        attrs: {
                            x: -w / 2.4,
                            y: h / 2,
                            width: w,
                            height: h,
                            fill: "green",
                            text: `联系人:${cfg.label}\ 
电话:${cfg.category === 0 ? "公司" : "部门"}\
                        
简码:${cfg.isOpen === 0 ? "禁用" : "启用"}
                        `,
                            radius: [2, 2, 0, 0],
                            textAlign: "left",
                        },
                        name: 'header-box',
                        zIndex: 10,
                    });
                    if (cfg.name) {
                        // title的背景设置
                        group.addShape('rect', {
                            attrs: {
                                x: -w / 2,
                                y: -h / 2,
                                width: w,
                                height: h / 3,
                                fill: "#5B8FF9",
                                radius: [2, 2, 0, 0],
                                textAlign: "center",
                            },
                            name: 'title-box',
                            draggable: true,
                        });
                        // 头部标题
                        // 图标的展示
                        // 有children才会显示这个图标
                        group.addShape('text', {
                            attrs: {
                                textBaseline: 'top',
                                // x: -w / 6,
                                y: -h / 2.5,
                                width: w,
                                height: h / 3,
                                lineHeight: 20,
                                textAlign: "center",
                                text: cfg.label,
                                fill: '#fff',
                            },
                            name: 'title',
                        });
                        // 图标的展示
                        console.log(cfg.children);
                        cfg.children &&
                            group.addShape('marker', {
                                attrs: {
                                    x: 1,
                                    y: 45,
                                    r: 8,
                                    cursor: 'pointer',
                                    // 就是设置折叠展开的样式,但是必须要设置stroke颜色,
                                    // 否则 效果就是透明,误以为会没生效,当然x,y,r的定位也很重要
                                    symbol: JSON.stringify(cfg.children) === "[]" ? G6.Marker.expand : G6.Marker.collapse,
                                    stroke: '#666',
                                    lineWidth: 1,
                                    fill: '#fff',
                                },
                                name: 'collapse-icon',
                                modelId: cfg.id
                            });
                    }
                    return keyShape;
                },
                update: undefined,
                // 设置点击折叠以后显示的图标,这个setState尽量避免使用,因为我在里面的
                // 控制台输出发现这个数据一会儿一输出,像是使用了定时器一样。又似是监听
                setState(name, value, item) {
                    console.log(name, value, item);
                    const group = item.getContainer();
                    if (name === 'collapsed') {
                        const marker = item.get('group').find((ele) => ele.get('name') === 'collapse-icon');
                        let icon;
                        if(value === undefined){
                            icon = G6.Marker.expand;
                        } else {
                            icon = G6.Marker.collapse;
                        }
                        marker.attr('symbol', icon);
                    }
                    
                },
            },
            'rect',
        );
        // 绘制节点线条
        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,
                        endArrow: style.endArrow,
                        path: [ // path这里是对三角形(也就是箭头的绘制)x
                            ['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;
            },
        });
        const defaultStateStyles = {
            hover: {
                stroke: '#1890ff',
                lineWidth: 2,
            },
        };
        const defaultNodeStyle = {
            // fill: '#fff',
            fill: "red", // 背景颜色
            stroke: '#5B8FF9', // 边框
            radius: 2,
        };
        // 节点之间连线的样式
        const defaultEdgeStyle = {
            stroke: '#5B8FF9',
        };
        const defaultLayout = {
            type: 'compactBox',
            direction: 'TB', // TB 是从上到下的展示节点
            // 这个是不能去掉的,否则展开与折叠就不生效了。
            getId: function getId(d) {
                return d.id;
            },
            getHeight: function getHeight() { // 节点之间高度的距离
                return 45;
            },
            getWidth: function getWidth() { // 节点之间宽度的距离
                return 16;
            },
            getVGap: function getVGap() {
                return 40;
            },
            getHGap: function getHGap() {
                return 70;
            },
        };
        const defaultLabelCfg = { // 这个就是节点中间的字体颜色,因为都是绘制出来的所以我暂时先#fff相当于隐藏了
            style: {
                fill: 'rgb(0,0,0,0)',
                fontSize: 12,
            },
        };
        const width = document.getElementById(`container`).scrollWidth;
        const height = document.getElementById(`container`).scrollHeight || 600;
        let graph = new G6.TreeGraph({
            container: `container`,
            width,
            height,
            linkCenter: true,
            defaultLevel: 2,
            modes: {
                default: [
                    'drag-canvas',
                    // 这个是鼠标移入到节点,而显示的数据
                    {
                        type: 'tooltip',
                        formatText: function formatText(model) {
                            return model.name;
                        },
                        offset: 30,
                        style: {
                            stroke: "#DEE9FF"
                        },
                    },
                    'zoom-canvas',
                ],
            },
            // 内置节点
            defaultNode: {
                type: 'icon-node',
                size: [130, 90], // 设置节点的宽和高
                style: defaultNodeStyle,
                labelCfg: defaultLabelCfg,
            },
            defaultEdge: {
                type: 'flow-line',
                style: defaultEdgeStyle,
            },
            nodeStateStyles: defaultStateStyles,
            edgeStateStyles: defaultStateStyles,
            layout: defaultLayout,
            plugins: [grid],
            defaultLevel: 2,
            nodeClick: (item) => {
                console.log(item);
            }
        });
        let listAA = JSON.parse(JSON.stringify(data));
        const loop = (data) => {
            data.forEach(item => {
                if (item.id.length >= 2) {
                    if (item.children) {
                        item = graph.findDataById(item.id, listAA).children = [];
                    }
                }
                if (item.children) {
                    loop(item.children);
                }
            })
            return data;
        }
        loop([listAA]);
        graph.setAutoPaint(true);
        graph.read(listAA);
        graph.render();
        graph.fitCenter(true);
        graph.zoom(0.8); // 默认缩放图形大小
        graph.on('node:mouseenter', (evt) => {
            const { item } = evt;
            graph.setItemState(item, 'hover', true);
        });
        graph.on('node:mouseleave', (evt) => {
            const { item } = evt;
            graph.setItemState(item, 'hover', false);
        });
        let cloneData = JSON.parse(JSON.stringify(data));
        graph.on('node:click', (evt) => {
            const { target, item } = evt;
            const id = target.get('modelId');
            const item22 = graph.findById(id);
            console.log(item22, id);
            const nodeModel = item22 && item22.getModel();
            const name = target.get('name');
            if (name === 'collapse-icon') {
                let gsixAa = graph.findDataById(id, JSON.parse(JSON.stringify(cloneData)));
                console.log(JSON.stringify(nodeModel.children));
                if (JSON.stringify(nodeModel.children) !== "[]") {
                    gsixAa && gsixAa.children.forEach(item => {
                        nodeModel.children = nodeModel.children ? [] : null;
                    })
                    let gsixModel = JSON.parse(JSON.stringify(nodeModel));
                    graph.updateChild(gsixModel, gsixModel.id);
                    graph.setItemState(item22, 'collapsed', nodeModel.collapsed);
                } else {
                    if (!nodeModel.children) {
                        nodeModel.children = [];
                    }
                    console.log(nodeModel);
                    gsixAa && gsixAa.children.forEach(item => {
                        nodeModel.children.push(item);
                    })
                    let gsixModel = nodeModel;
                    gsixModel.children.forEach((element, index) => {
                        if (element.children) {
                            element.children = element.children ? [] : null;
                        }
                    })
                    graph.updateChild(gsixModel, gsixModel.id);
                    graph.setItemState(item22, 'collapsed', !nodeModel.collapsed);
                }
            }
        });
    }
    useEffect(() => {
        document.getElementById(`container`).innerHTML = "";
        gSixCom(data);
    }, [])
    return <>
        <div>
            1111111
            <div id={`container`}></div>
        </div>
    </>
}
export default GsixManage;

遇坑:

  1. 就是最初要把数据中的children数据截取出来,只展示一个主节点一个子节点
    解决: 通过递归去循环拿到数据,然后使用antv中的graph.findDataById(id, 展示的数据).children(注:这个就像是js里面的getElementById是一个意思)
    例:item = graph.findDataById(item.id, listAA).children = [];
  2. 然后通过点击不同的数据,展示节点内的数据
    这个用到了graph.on(‘node:click’, (evt) => {})
    示例:
    graph.on(‘node:click’, (evt) => {
    const { target, item } = evt;
    const id = target.get(‘modelId’); // 这个就是绘制icon中给的modelId属性,对应的就是各自的id,来代表每个数据的唯一性。
    const item22 = graph.findById(id);
    const nodeModel = item22 && item22.getModel();
    const name = target.get(‘name’);
    if (name === ‘collapse-icon’) { // 判断点击的是不是折叠的图标
    let aa = graph.findDataById(id, JSON.parse(JSON.stringify(cc)));
    console.log(JSON.stringify(nodeModel.children));
    if (JSON.stringify(nodeModel.children) !== “[]”) {
    aa && aa.children.forEach(item => {
    nodeModel.children = nodeModel.children ? [] : null; // 这里不建议使用antv中的removeChildren来删除元素,因为会导致折叠展示的图标,而且会引起一些其他的bug。
    })
    let gg = JSON.parse(JSON.stringify(nodeModel));
    graph.updateChild(gg, gg.id);
    graph.setItemState(item22, ‘collapsed’, !nodeModel.collapsed);
    } else {
    if (!nodeModel.children) {
    nodeModel.children = [];
    }
    console.log(nodeModel);
    aa && aa.children.forEach(item => {
    nodeModel.children.push(item);
    })
    let gg = nodeModel;
    gg.children.forEach((element, index) => {
    if (element.children) {
    element.children = element.children ? [] : null;
    }
    })
    graph.updateChild(gg, gg.id);
    graph.setItemState(item22, ‘collapsed’, nodeModel.collapsed); // 这里不能是!nodeModel.collapsed因为会导致图标后期的展示bug
    }
    }
    })
    注:上方标红的代码那是关键点,意味着是否能正常折叠展示,注意不能使用graph.layout()会引起bug
  3. 图标展示问题的判断,最初图标无论折叠数据还是展示数据都只展示+图标,但是经过修改了graph.setItemState以后就正常展示,可是当展开多个数据的时候,折叠一个孙子节点,再折叠一个其他的子节点,完了,影响到了折叠展示的孙子节点。这个的
    原因经排除就在于drawShape这个函数中的setState的判断,因为在log输出中,在展示的数据是value就是undefined,在折叠的时候数据的value是children的数据。
    代码示例 :
    setState(name, value, item) {
    console.log(name, value, item);
    const group = item.getContainer();
    if (name === ‘collapsed’) {
    const marker = item.get(‘group’).find((ele) => ele.get(‘name’) === ‘collapse-icon’);
    let icon;
    if(value === undefined){
    icon = G6.Marker.expand;
    } else {
    icon = G6.Marker.collapse;
    }
    marker.attr(‘symbol’, icon);
    }
    }

参考:
antv中style样式:https://g6.antv.vision/zh/docs/api/nodeEdge/shapeProperties

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值