工作中遇到的问题之——使用G6-AntV注册DOM节点创建树形图并添加点击事件等

3,875 阅读1分钟

业务需求

无标题-2021-10-08-2313.png 如上图所示,前端需要根据后端返回的数据动态形成树状结构图,以完成流程图展示。经过几天的了解和学习,最终决定使用G6-AntV。下面就如何使用及使用中遇到的问题进行总结。

代码实现

<template>
    <div id="container" class="container"></div>
</template>

<script>
// 引入antv-G6
import G6 from "@antv/g6";
// G6的配置项,注册新的边
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: [
                    ["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;
    }
});
// 注册新的node,注意:不要继承其他节点,否则更新时会走继承的draw方法
// 这里是个坑,当时查阅官方文档的时候,registerNode注册DomNode的示例中传了第三个参数;
// 作为一名合格的CV,必然是原样复制了,但是在点击事件中,更新DomNode时会无效;在求助大佬后,大佬看了看
// 源码,说不要传第三个参数就好了;下班回家我也仔细看了看,简单来说就是传了第三个参数就会走传的参数类的
// 绘制方法,不会走自己写的绘制方法。
// const lastItem = graph.find("node", node => {
//       return node.get("model").isSelected == true;
//  });
//  graph.updateItem(lastItem, { isSelected: false });
//  const item = graph.findById(id);
//  graph.updateItem(item, { isSelected: true });
G6.registerNode("dom-node", {
    draw: (cfg, group) => {
        return group.addShape("dom", {
            attrs: {
                width: cfg.size[0],
                height: cfg.size[1],
                // 传入 DOM 的 html
                html: `
                          <div
                          class="catalog-node ${cfg.isSelected && !cfg.dataIsDeleted ? "selected-catalog" : ""} 
                          ${cfg.dataIsDeleted ? "is-deleted" : ""}"
                          style="
                          background-color: #fff;
                          border: 1px solid #C4C7D6;
                          border-radius: ${(cfg.size[1] - 5) / 2}px;
                          width: ${cfg.size[0] - 5}px;
                          height: ${cfg.size[1] - 5}px;
                          display: flex;
                          cursor:pointer;
                          user-select:none;"
                          id="${cfg.id}">
                            <div
                            class="left ${cfg.isSelected ? "is-selected" : ""}"
                            style="
                            height: 100%;
                            line-height:100%;
                            width: 45%;
                            color: #606266;
                            font-size: 8px;
                            padding: 15px 5px 10px 28px;
                            overflow: hidden;
                            text-overflow: ellipsis;
                            white-space: nowrap">
                            ${cfg.name}
                            </div>
                            <div class="line" style="height: 100%; width: 1px; background-color: #c4c7d6"></div>
                            <div class="right" style="
                            font-size:4px;
                            color:#C4C7D6;
                            padding-left:10px;
                            padding-top:6px;">
                            <p style="color:#606266;">${cfg.handleType}</p>
                            <p style="color:#8D929B;">${cfg.utime}</p>
                            <span class='icon ${cfg.handleType == "数据清洗" ? "icon-clean" : ""}'></span>
                            </div>
                          </div>
                            `
            }
        });
    }
});

// 默认边的颜色 末尾箭头
const defaultEdgeStyle = {
    stroke: "#C4C7D6"
};

// 默认布局
// compactBox 紧凑树布局
// 从根节点开始,同一深度的节点在同一层,并且布局时会将节点大小考虑进去。
const defaultLayout = {
    type: "compactBox", // 布局类型树
    direction: "TB", // TB 根节点在上,往下布局
    getId: function getId(d) {
        // 节点 id 的回调函数
        return d.id;
    },
    getHeight: function getHeight() {
        // 节点高度的回调函数
        return 16;
    },
    getWidth: function getWidth() {
        // 节点宽度的回调函数
        return 16;
    },
    getVGap: function getVGap() {
        // 节点纵向间距的回调函数
        return 40;
    },
    getHGap: function getHGap() {
        // 节点横向间距的回调函数
        return 150;
    }
};
let graph;
// vue部分
export default {
    name: "catalogInShow",
    data() {
        return {
            directoryManagement: this.datasetInfo.directoryManagement,
            dataForShow: {},
            selectedId: ""
        };
    },
    methods: {
        G6init(data) {
            if (!this.directoryManagement) return;
            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);
                };
            }
            // 获取容器
            const container = document.getElementById("container");
            // 获取容器的宽高
            const width = container.clientWidth;
            const height = container.clientHeight || 500;

            // Graph 是 G6 图表的载体-实例化
            graph = new G6.TreeGraph({
                container: "container", // 图的 DOM 容器
                width,
                height,
                linkCenter: true, // 指定边是否连入节点的中心
                modes: {
                    // 交互模式
                    // default 模式中包含点击选中节点行为和拖拽画布行为;
                    default: ["drag-canvas", "zoom-canvas"]
                },
                // 使用 Dom node 的时候需要使用 svg 的渲染形式 **重要
                renderer: "svg",
                // 默认状态下节点的配置
                defaultNode: {
                    type: "dom-node",
                    size: [280, 50]
                },
                // 默认状态下边的配置,
                defaultEdge: {
                    type: "flow-line",
                    style: defaultEdgeStyle
                },
                // 布局配置项
                layout: defaultLayout
            });

            graph.data(data);
            // 根据提供的数据渲染视图。
            graph.render();
            graph.stopAnimate();
            graph.fitView();
        },

        changeDataAdaptFront(params, selectedId) {
            params.id = params.id + "";
            if (!selectedId) {
                params.isSelected = params.parentId ? false : true;
            } else {
                params.isSelected = params.id == selectedId ? true : false;
            }
            let handleType = null;
            switch (params.handleType) {
                case 0:
                    handleType = "数据预处理";
                    break;
                case 1:
                    handleType = "数据清洗";
                    break;
                case 2:
                    handleType = "数据校验";
                    break;
                case 3:
                    handleType = "数据增强";
                    break;
                case 4:
                    handleType = "数据均衡";
                    break;
                default:
                    return;
            }
            params.handleType = handleType;
            if (params.children && params.children.length !== 0) {
                params.children.forEach(item => {
                    this.changeDataAdaptFront(item);
                });
            }
        }
    },
    props: {
        datasetInfo: {}
    },
    created() {
        if (!this.directoryManagement) return;
        this.selectedId = this.directoryManagement.id;
        this.$emit("getSelectedId", this.selectedId);
        this.dataForShow = JSON.parse(JSON.stringify(this.directoryManagement).replace(/childs/g, "children"));
        this.dataForShow = JSON.parse(JSON.stringify(this.dataForShow).replace(/type/g, "handleType"));
        this.changeDataAdaptFront(this.dataForShow);
        // console.log(this.dataForShow);
    },
    mounted() {
        let data = this.dataForShow;
        this.changeDataAdaptFront(data);
        this.G6init(data);
        let container = document.getElementById("container");
        container.onclick = e => {
            let id = "";
            let catalogNode;
            function findCatalogNode(target) {
                if (target.nodeName == "svg") return;
                if ([].indexOf.call(target.classList, "catalog-node") !== -1) {
                    catalogNode = target;
                    return;
                } else {
                    findCatalogNode(target.parentNode);
                }
            }
            findCatalogNode(e.target);
            // 如果已删除,则点击事件无效
            if (catalogNode && catalogNode.classList.contains("is-deleted")) return;
            if (!catalogNode || !catalogNode.id) return;
            id = catalogNode.id ? catalogNode.id : "";
            this.selectedId = id;
            this.$emit("getSelectedId", this.selectedId);
            // 获取当前isSelected为true的节点,并改为false
            const lastItem = graph.find("node", node => {
                return node.get("model").isSelected == true;
            });
            graph.updateItem(lastItem, { isSelected: false });
            // 根据id获取当前点击的元素,并更新其样式
            const item = graph.findById(id);
            graph.updateItem(item, { isSelected: true });
        };
    }
};
</script>
<style lang="scss">
#container {
    height: 700px;
    width: 100%;
    .catalog-node {
        .left {
            position: relative;
            &::before {
                content: "";
                position: absolute;
                background: url("~@workbench/assets/images/datasets/Data-analysis-platform-contents @2x.png") center center
                    no-repeat;
                background-size: 100%;
                width: 12px;
                height: 12px;
                left: 10px;
                top: 50%;
                transform: translateY(-60%);
            }
        }
        .is-selected {
            &::before {
                content: "";
                position: absolute;
                background: url("~@workbench/assets/images/datasets/Data-analysis-platform-contents-click@2x.png") center center
                    no-repeat;
                background-size: 100%;
                width: 12px;
                height: 12px;
                left: 10px;
                top: 50%;
                transform: translateY(-60%);
            }
        }
        .icon-clean {
            &::after {
                content: "";
                position: absolute;
                background: url("~@workbench/assets/images/datasets/Data-analysis-platform-label@2x.png") center center no-repeat;
                background-size: 100%;
                width: 12px;
                height: 12px;
                right: 15px;
                top: 50%;
                transform: translateY(-60%);
            }
        }
    }
    .selected-catalog {
        border: 1px solid #475aff !important;
        .line {
            background-color: #475aff !important;
        }
    }
    // 删除状态下样式
    .is-deleted {
        border: 1px dashed #eee !important;
        .line {
            background-color: #eee !important;
        }
    }
}
.show-whole-title {
    position: fixed;
    line-height: 20px;
    font-size: 12px;
    padding: 2px 5px;
    color: #fff;
    background: rgba(37, 37, 54, 0.6);
    border-radius: 4px;
    z-index: 100;
    text-align: justify;
}
</style>

最终效果:

image.png 以上就是全部代码了,借鉴了网上好多大佬的文章,在此表示感谢!不足的地方还望朋友们能够指出!感激不尽!