G6可视化流程编辑器

一、说明


本例使用V4.1.12版本。最新版本4.2.6版本有问题,节点间连线数据获取不到,建议不要轻易升级。

gitee项目地址

1. 长啥样

image.png

2. 目录说明

├─public
│  └─data
├─src
|   ├─assets               # 样式
|   │  └─style
|   ├─behavior             # g6行为定义
|   ├─components           # 组件
|   │  ├─menu              # 左侧菜单
|   │  ├─props             # 右侧属性配置
|   │  ├─svg-icon          # svg组件
|   │  └─toolbar-panel
|   ├─libs                 # 库(发布订阅模式)
|   ├─plugins              # toolbar命令注册
|   ├─services             # axios封装
|   ├─shape                # g6图形定义
|   │  ├─edges             # g6边相关定义
|   │  └─nodes             # g6节点相关定义
|   └─utils                # 工具函数
└─Index.vue                # 入口
复制代码

3. 功能点

1.  拖拽组件到画布区域
2.  画布区节点hover,selected
3.  画布区节点拖拽
4.  锚点绘制
5.  节点连线
6.  连线hover,selected
7.  toolbar操作
8.  画布拖拽
9.  minimap
复制代码

二、功能实现


1. 初始化G6实例

image.png

G6的渲染数据是有权重的:
使用 graph.node(nodeFn) 配置 > 数据中动态配置 > 实例化图时全局配置

// 注册自定义节点、边
registerFactory(G6);
// 画布
const canvasMap = this.$refs['canvasRef'];
this.graph = new G6.Graph({
    container: canvasMap,
    width: 600,
    height: 400,
    // 编辑模式下行为
    modes: {},
    // 默认节点配置
    defaultNode: {},
    // 默认边配置
    defaultEdge: {},
    // 全局样式
    nodeStateStyles: {},
    edgeStateStyles: {}
});
// 设置graph模式
this.graph.setMode(this.mode);
// 设置自适应比例
this.graph.fitView = true;
// 初始化canvas事件
this.initEvents();
复制代码

2. 节点和边的交互行为

节点和边的行为,分为点击选中、hover、双击等。以节点的交互事件为例:

// hover-node.js
export default G6 => {
    G6.registerBehavior('hover-node', {
        getDefaultCfg() {
            return {
                multiple: false // 多选
            }
        },
        getEvents() {
            return {
                'node:mouseenter': 'onNodeMouseEnter',
                'node:mousemove': 'onNodeMouseMove',
                'node:mouseleave': 'onNodeMouseLeave'
            }
        },
        onNodeMouseEnter(e) {
            const graph = this.graph;
            const item = e.item;
            if (!item.hasState('nodeState:selected')) {
                graph.setItemState(item, 'nodeState', 'hover');
            }
            // 锚点显示
            graph.setItemState(item, 'anchorShow', true); // 二值状态
            // mouseenter回调
            graph.emit('on-node-mouseenter', e);
        },
        onNodeMouseMove(e) {
            this.graph.emit('on-node-mousemove', e);
        },
        onNodeMouseLeave(e) {
            const graph = this.graph;
            const item = e.item;
            if (item.hasState('nodeState:hover')) {
                graph.setItemState(item, 'nodeState', 'default');
            }
            // 锚点显示
            graph.setItemState(item, 'anchorShow', false);
            // mouseleave回调
            graph.emit('on-node-mouseleave', e);
        }
    })
}
复制代码
// select-node.js
export default G6 => {
    G6.registerBehavior('select-node', {
        getDefaultCfg() {
            return {
                multiple: false // 多选
            }
        },
        getEvents() {
            return {
                'node:click': 'onNodeClick',
                'node:dblclick': 'onNodeDblClick',
                'node:contextmenu': 'onNodeContextmenu'
            }
        },
        onNodeClick(e) {
            const graph = this.graph;
            const item = e.item;
            this._clearSelected();
            graph.setItemState(item, 'nodeState', 'selected');
            graph.emit('after-node-selected', e);
        },
        onNodeDblClick(e) {
            const graph = this.graph;
            const item = e.item;
            this._clearSelected();
            // item.toFront(); // 提高层级
            graph.setItemState(item, 'nodeState', 'selected');
            graph.emit('after-node-dblclick', e);
        },
        onNodeContextmenu(e) {
            this.graph.emit('on-editor-contextmenu', {
                type: 'node',
                event: e
            })
        }, // 清除选中的节点和边样式
        _clearSelected() {
            let graph = this.graph;
            const selectNodes = this.graph.findAllByState('node', 'nodeState:selected');
            selectNodes.forEach(node => {
                graph.setItemState(node, 'nodeState', 'default');
            })
            const selectEdges = this.graph.findAllByState('edge', 'edgeState:selected');
            selectEdges.forEach(edge => {
                graph.setItemState(edge, 'edgeState', 'default');
            })
        }
    })
}
复制代码

'node:click': 'onNodeClick'是G6提供的事件交互方式。

我们想要点击节点的时候更改节点的样式怎么做?

  • 定义节点不同状态时的样式
nodeStateStyles: {
    'nodeState:default': {
        lineWidth: 1,
        fill: '#fff',
        stroke: '#1890FF',
        opacity: 1
    },
    'nodeState:hover': {
        lineWidth: 2,
        fill: '#d5f1fd',
        opacity: 0.8
    },
    'nodeState:selected': {
        fill: '#caebf9',
        stroke: '#1890FF',
        opacity: 0.9
    }
}
复制代码
  • 更改状态
graph.setItemState(item, 'nodeState', 'selected');
复制代码

作用机制是:setItemState时,触发节点setState钩子函数,setState会根据节点状态应用对应的样式。

3. 节点的绘制

// base-node.js
// 基础节点,其他节点在此基础上扩展

import itemEvents from './item-event';
import anchorEvent from './anchor-event';

export default G6 => {
    G6.registerNode('base-node', {
        // 绘制图标
        drawIcon(cfg, group, attrs) {

        },
        // 初始化锚点
        initAnchor(cfg, group) {
            // 锚点图形
            group.anchorShapes = [];
            // 显示锚点
            group.showAnchor = group => {
                this.drawAnchor(cfg, group);
            }
            // 隐藏锚点
            group.clearAnchor = group => {
                if (group.anchorShapes) {
                    group.anchorShapes.forEach(anchor => anchor.remove());
                }
                group.anchorShapes = [];
            }
        },
        // 绘制锚点
        drawAnchor(cfg, group) {
            const keyShape = group.getFirst();
            const { type, direction, anchorPointStyles } = keyShape.attr();
            const node = group.get('item');
            const bBox = keyShape.getBBox();
            // 获取配置锚点
            const anchors = this.getAnchorPoints(cfg) || [];
            if (anchors.length) {
                anchors.forEach((anchorCfg, i) => {
                    const x = bBox.width * (anchorCfg[0] - 0.5);
                    const y = bBox.height * (anchorCfg[1] - 0.5);
                    // 绘制三层锚点
                    // 最底层:锚点背景
                    // 中间层:锚点
                    // 最顶层:锚点group,用于事件触发
                    const anchor = group.addShape('circle', {
                        attrs: {
                            x,
                            y,
                            ...anchorPointStyles
                        },
                        zIndex: 1,
                        className: 'node-anchor',
                        nodeId: node.get('id'),
                        draggable: true,
                        isAnchor: true,
                        index: i // 方便查找锚点
                    })
                    const anchorGroup = group.addShape('circle', {
                        attrs: {
                            x,
                            y,
                            r: 11,
                            fill: '#000',
                            opacity: 0
                        },
                        zIndex: 2,
                        className: 'node-anchor-group',
                        nodeId: node.get('id'),
                        draggable: true,
                        isAnchor: true,
                        index: i
                    })
                    // 添加绑定锚点事件,给最上层的group添加事件
                    anchorEvent(anchorGroup, group, anchorCfg);

                    // 锚点放到组中
                    group.anchorShapes.push(anchor);
                    group.anchorShapes.push(anchorGroup);
                })
                // 查找所有锚点
                group.getAllAnchors = () => {
                    return group.anchorShapes.filter(c => c.get('isAnchor') === true);
                }
                // 查找指定锚点
                group.getAnchor = i => {
                    return group.anchorShapes.filter(c => c.get('className') === 'node-anchor' && c.get('index') === i);
                }
                // 查找所有锚点背景
                group.getAllAnchorBg = () => {
                    return group.anchorShapes.filter(c => c.get('className') === 'node-anchor-bg');
                };
            }
        },
        // 绘制label文本
        drawLabel(cfg, group, attrs) {
            const { label, labelCfg, labels } = attrs;
            // 多行文本
            if (labels) {
                labels.forEach(item => {
                    const { label, labelCfg: { maxLength }, className } = item;
                    // 文本长度
                    let text = maxLength ? label.substr(0, maxLength) : label || '';
                    if (label.length > maxLength) {
                        text = `${text}...`;
                    }
                    group.addShape('text', {
                        attrs: {
                            text,
                            ...item,
                            ...item.labelCfg
                        },
                        className: `node-text ${className}`,
                        draggable: true
                    })
                })
            // 单行文本
            } else if (label) {
                const { maxLength } = labelCfg;
                // 超出显示...
                let text = maxLength ? label.substr(0, maxLength) : label || '';
                if (label.length > maxLength) {
                    text = `${text}...`;
                }
                group.addShape('text', {
                    attrs: {
                        text,
                        x: 0,
                        y: 0,
                        ...label,
                        ...labelCfg
                    },
                    className: 'node-text',
                    draggable: true
                })
            }
        },
        // 绘制节点和文本
        draw(cfg, group) {
            // 获取图形样式
            const attrs = this.getShapeStyle(cfg, group);
            const keyShape = group.addShape(this.shapeType, {
                className: `${this.shapeType}-shape`, // rect-shape
                draggable: true,
                attrs
            })
            // 通过class查找元素
            group.getItem = className => {
                return group.get('children').find(item => item.get('className') === className);
            }
            // 绘制文本节点
            this.drawLabel(cfg, group, attrs);
            // 添加锚点
            this.initAnchor(cfg, group);
            
            return keyShape;
        },
        // 更新节点和文本
        update(cfg, node) {
            
        },
        // 设置节点状态,主要是交互状态,业务状态在draw方法中实现(定义此方法,实例中配置的状态会失效)
        setState(name, value, item) { // name =>'nodeState hover' , value => true , item => Node
            const activeEvents = [
                'anchorShow', // 锚点显示隐藏
                'anchorActive', // 鼠标按下锚点,让所有node的锚点处于激活状态
                'nodeState', // nodeState:xxx都会触发nodeState事件
                'nodeState:default',
                'nodeState:hover',
                'nodeState:selected'
            ];
            const group = item.getContainer(); // 获取容器
            if (group.get('destroyed')) return; // 组已经卸载
            if (activeEvents.includes(name)) {
                // this指向当前实例
                // 调用对应更新状态的方法
                itemEvents[name].call(this, value, group);
            } else {
                console.warn(`warning: ${name} 事件回调未注册`);
            }
        },
        // 锚点配置
        getAnchorPoints(cfg) {
            return cfg.anchorPoints || [
                [0.5, 0],
                [1, 0.5],
                [0.5, 1],
                [0, 0.5]
            ]
        }
    }, 'single-node');
}
复制代码

继承base-node注册rect-nodecircle-node节点。

// builtIn-node.js
export default G6 => {
    G6.registerNode('rect-node', {
        // 图形类型
        shapeType: 'rect',
        // shape样式
        getShapeStyle(cfg) {
            const width = cfg.style.width || 80;
            const height = cfg.style.height || 40;
            return getStyle.call(this, {
                width,
                height,
                radius: 5,
                x: -width / 2,
                y: -height / 2
            }, cfg);
        }
    }, 'base-node');
    
    // 扩展圆形节点
    G6.registerNode('circle-node', {
        shapeType: 'circle',
        getShapeStyle(cfg) {
            const r = cfg.style.r || 30;
            return getStyle.call(this, {
                r, // 半径
                // 将图形中心坐标移动到图形中心, 用于方便鼠标位置计算
                x: 0,
                y: 0
            }, cfg);
        }
    }, 'base-node');
}
复制代码

4. toolbar操作栏

注册命令,待用户点击toolbar按钮是触发对应方法。

// command.js
import { mix, clone, isString } from '@antv/util';

class Command {
    constructor() {
        this._cfgs = this.getDefaultCfg();
        this.list = []; // 注册的命令集合
        this.queue = []; // 用户撤销重做
    }

    getDefaultCfg() {
        return { _command: { zoomDelta: 0.1, queue: [], current: 0, clipboard: [] } };
    }

    get(key) {
        return this._cfgs[key];
    }
    set(key, val) {
        this._cfgs[key] = val;
    }
    // 初始化插件
    initPlugin(graph) {
        this.initCommands();
        // 获取命令队列
        graph.getCommands = () => {
            return this.get('_command').queue;
        };
        // 获取当前命令
        graph.getCurrentCommand = () => {
            const c = this.get('_command');
            return c.queue[c.current - 1];
        };
        // 执行命令
        graph.executeCommand = (name, cfg) => {
            this.execute(name, graph, cfg);
        };
        // 命令是否可以被执行
        graph.commandEnable = name => {
            return this.enable(name, graph);
        };
    }
    // 注册命令
    registerCommand(name, cfg) {
        if (this[name]) {
            mix(this[name], cfg);
        } else {
            const cmd = mix(
                {},
                {
                    name: name, // add、update、autoFit、redo、undo
                    shortcutCodes: [], // 键盘命令
                    queue: true, // 是否入队,支持撤销重做
                    executeTimes: 1, // 撤销次数
                    init() { // 初始化调用函数
                        console.log('init');
                    },
                    enable() { // 是否可操作
                        return true;
                    },
                    execute(graph) { // 命令执行
                        this.snapShot = graph.save(); // 获取图数据
                        this.selectedItems = graph.get('selectedItems'); // 获取当前选中的元素
                        // 执行命令
                        if (this.method) {
                            if (isString(this.method)) {
                                graph[this.method]();
                            } else {
                                this.method(graph);
                            }
                        }
                    },
                    back(graph) { // 命令撤销
                        graph.read(this.snapShot);
                        graph.set('selectedItems', this.selectedItems);
                    }
                },
                cfg
            );
            // 将命令挂载到Command实例上
            this[name] = cmd;
            // 将命令push到list中
            this.list.push(cmd);
        }
    }
    // 执行命令
    execute(name, graph, cfg) {
        const cmd = mix({}, this[name], cfg);
        const manager = this.get('_command');
        // 如果可以点击操作
        if (cmd.enable(graph)) {
            // 调用初始化函数
            cmd.init();
            if (cmd.queue) {
                manager.queue.splice(manager.current, manager.queue.length - manager.current, cmd);
                manager.current++;
            }
        }
        // 执行命令
        graph.emit('beforecommandexecute', { command: cmd });
        cmd.execute(graph);
        graph.emit('aftercommandexecute', { command: cmd });
        return cmd;
    }
    // 是否可点击
    enable(name, graph) {
        return this[name]?.enable(graph);
    }
    // 销毁插件
    destroyPlugin() {
        this._events = null;
        this._cfgs = null;
        this.list = [];
        this.queue = [];
        this.destroyed = true;
    }
    // 删除子流程节点
    _deleteSubProcessNode(graph, itemId) {
        const subProcess = graph.find('node', node => {
            if (node.get('model')) {
                const clazz = node.get('model').clazz;
                if (clazz === 'subProcess') {
                    const containerGroup = node.getContainer();
                    const subGroup = containerGroup.subGroup;
                    const item = subGroup.findById(itemId);
                    return subGroup.contain(item);
                } else {
                    return false;
                }
            } else {
                return false;
            }
        });
        if (subProcess) {
            const group = subProcess.getContainer();
            const resultModel = group.removeItem(subProcess, itemId);
            graph.updateItem(subProcess, resultModel);
        }
    }
    // 初始化命令-注册命令
    initCommands() {
        const cmdPlugin = this;
        // 删除
        cmdPlugin.registerCommand('delete', {
            enable: function (graph) {
                const mode = graph.getCurrentMode();
                const selectedItems = graph.get('selectedItems');
                return mode === 'edit' && selectedItems && selectedItems.length > 0;
            },
            method: function (graph) {
                const selectedItems = graph.get('selectedItems');
                graph.emit('beforedelete', { items: selectedItems });
                if (selectedItems && selectedItems.length > 0) {
                    selectedItems.forEach(i => {
                        const node = graph.findById(i);
                        if (node) {
                            graph.remove(i);
                        } else {
                            cmdPlugin._deleteSubProcessNode(graph, i);
                        }
                    });
                }
                graph.emit('afterdelete', { items: selectedItems });
            },
            shortcutCodes: ['Delete', 'Backspace']
        });
        // 重做
        cmdPlugin.registerCommand('redo', {
            queue: false,
            enable: function (graph) {
                const mode = graph.getCurrentMode();
                const manager = cmdPlugin.get('_command');
                return mode === 'edit' && manager.current < manager.queue.length;
            },
            execute: function (graph) {
                const manager = cmdPlugin.get('_command');
                const cmd = manager.queue[manager.current];
                if (cmd) {
                    cmd.execute(graph);
                }
                manager.current++;
            },
            shortcutCodes: [
                ['metaKey', 'shiftKey', 'z'],
                ['ctrlKey', 'shiftKey', 'z']
            ]
        });
        // 撤销
        cmdPlugin.registerCommand('undo', {
            queue: false,
            enable: function (graph) {
                const mode = graph.getCurrentMode();
                return mode === 'edit' && cmdPlugin.get('_command').current > 0;
            },
            execute: function (graph) {
                const manager = cmdPlugin.get('_command');
                const cmd = manager.queue[manager.current - 1];
                if (cmd) {
                    cmd.executeTimes++;
                    cmd.back(graph);
                }
                manager.current--;
            },
            shortcutCodes: [
                ['metaKey', 'z'],
                ['ctrlKey', 'z']
            ]
        });
        // 拷贝
        cmdPlugin.registerCommand('copy', {
            queue: false,
            enable: function (graph) {
                const mode = graph.getCurrentMode();
                const items = graph.get('selectedItems');
                return mode === 'edit' && items && items.length > 0;
            },
            method: function (graph) {
                const manager = cmdPlugin.get('_command');
                manager.clipboard = [];
                const items = graph.get('selectedItems');
                if (items && items.length > 0) {
                    const item = graph.findById(items[0]);
                    if (item) {
                        manager.clipboard.push({ type: item.get('type'), model: item.getModel() });
                    }
                }
            }
        });
        // 粘贴
        cmdPlugin.registerCommand('paste', {
            enable: function (graph) {
                const mode = graph.getCurrentMode();
                return mode === 'edit' && cmdPlugin.get('_command').clipboard.length > 0;
            },
            method: function (graph) {
                const manager = cmdPlugin.get('_command');
                this.pasteData = clone(manager.clipboard[0]);
                const addModel = this.pasteData.model;
                if (addModel.x) {
                    addModel.x += 10;
                }
                if (addModel.y) {
                    addModel.y += 10;
                }
                const { clazz = 'userTask' } = addModel;
                const timestamp = new Date().getTime();
                const id = clazz + timestamp;
                addModel.id = id;
                const item = graph.add(this.pasteData.type, addModel);
                item.toFront();
            }
        });
        // 放大
        cmdPlugin.registerCommand('zoomIn', {
            queue: false,
            enable: function (graph) {
                const zoom = graph.getZoom();
                const maxZoom = graph.get('maxZoom');
                const minZoom = graph.get('minZoom');
                return zoom <= maxZoom && zoom >= minZoom;
            },
            execute: function (graph) {
                const manager = cmdPlugin.get('_command');
                const maxZoom = graph.get('maxZoom');
                const zoom = graph.getZoom();
                this.originZoom = zoom;
                let currentZoom = zoom + manager.zoomDelta;
                if (currentZoom > maxZoom) currentZoom = maxZoom;
                graph.zoomTo(currentZoom);
            },
            back: function (graph) {
                graph.zoomTo(this.originZoom);
            },
            shortcutCodes: [
                ['metaKey', '='],
                ['ctrlKey', '=']
            ]
        });
        // 缩小
        cmdPlugin.registerCommand('zoomOut', {
            queue: false,
            enable: function (graph) {
                const zoom = graph.getZoom();
                const maxZoom = graph.get('maxZoom');
                const minZoom = graph.get('minZoom');
                return zoom <= maxZoom && zoom >= minZoom;
            },
            execute: function (graph) {
                const manager = cmdPlugin.get('_command');
                const minZoom = graph.get('minZoom');
                const zoom = graph.getZoom();
                this.originZoom = zoom;
                let currentZoom = zoom - manager.zoomDelta;
                if (currentZoom < minZoom) currentZoom = minZoom;
                graph.zoomTo(currentZoom);
            },
            back: function (graph) {
                graph.zoomTo(this.originZoom);
            },
            shortcutCodes: [
                ['metaKey', '-'],
                ['ctrlKey', '-']
            ]
        });
        // 重置缩放(自适应)
        cmdPlugin.registerCommand('resetZoom', {
            queue: false,
            execute: function (graph) {
                const zoom = graph.getZoom();
                this.originZoom = zoom;
                graph.zoomTo(1);
            },
            back: function (graph) {
                graph.zoomTo(this.originZoom);
            }
        });
        // 1:1
        cmdPlugin.registerCommand('autoFit', {
            queue: false,
            execute: function (graph) {
                const zoom = graph.getZoom();
                this.originZoom = zoom;
                graph.fitView = true;
            },
            back: function (graph) {
                graph.zoomTo(this.originZoom);
            }
        });
        // 置顶
        cmdPlugin.registerCommand('toFront', {
            queue: false,
            enable: function (graph) {
                const items = graph.get('selectedItems');
                return items && items.length > 0;
            },
            execute: function (graph) {
                const items = graph.get('selectedItems');
                if (items && items.length > 0) {
                    const item = graph.findById(items[0]);
                    item.toFront();
                    graph.paint();
                }
            },
            back: function (graph) {
                console.log(graph);
            }
        });
        // 返回
        cmdPlugin.registerCommand('toBack', {
            queue: false,
            enable: function (graph) {
                const items = graph.get('selectedItems');
                return items && items.length > 0;
            },
            execute: function (graph) {
                const items = graph.get('selectedItems');
                if (items && items.length > 0) {
                    const item = graph.findById(items[0]);
                    item.toBack();
                    graph.paint();
                }
            },
            back: function (graph) {
                console.log(graph);
            }
        });
    }
}
export default Command;
复制代码

如果操作可点击,调用注册好的命令

// toolbar.js

import { deepMix, each, wrapBehavior } from '@antv/util';
import { modifyCSS } from '@antv/dom-util';

class Toolbar {
    constructor(cfgs) {
        this._cfgs = deepMix(this.getDefaultCfg(), cfgs);
    }

    getDefaultCfg() {
        return { container: null };
    }

    get(key) {
        return this._cfgs[key];
    }
    set(key, val) {
        this._cfgs[key] = val;
    }
    // 初始化插件
    initPlugin(graph) {
        const self = this;
        this.set('graph', graph);
        const events = self.getEvents();
        const bindEvents = {};
        each(events, (v, k) => {
            const event = wrapBehavior(self, v);
            bindEvents[k] = event;
            graph.on(k, event); // 事件监听,更新toolbar
        });
        // 挂载到实例上
        this._events = bindEvents;

        this.initEvents();
        this.updateToolbar();
    }
    // 点击节点,命令执行后,更新toolbar
    getEvents() {
        return { 'after-node-selected': 'updateToolbar', aftercommandexecute: 'updateToolbar' };
    }
    // toolbar按钮点击,调用事件
    initEvents() {
        const graph = this.get('graph');
        const parentNode = this.get('container');
        const children = parentNode.querySelectorAll('.flow-toolbar > div[data-command]');
        each(children, child => {
            const cmdName = child.getAttribute('data-command');
            child.addEventListener('click', () => {
                // 如果此toolbar是启用状态
                if (graph.commandEnable(cmdName)) {
                    graph.executeCommand(cmdName);
                }
            });
        });
    }
    // 点击节点,命令执行后,更新toolbar
    updateToolbar() {
        const graph = this.get('graph');
        const parentNode = this.get('container');
        const children = parentNode.querySelectorAll('.flow-toolbar > div[data-command]');
        each(children, child => {
            const cmdName = child.getAttribute('data-command');
            // 按钮可被点击
            if (graph.commandEnable(cmdName)) {
                modifyCSS(child, {
                    cursor: 'pointer'
                });
                modifyCSS(child.children[0], {
                    fill: '#333',
                    cursor: 'pointer'
                });
                child.children[0].setAttribute('fill', '#333');
            // 按钮不可被点击
            } else {
                modifyCSS(child, {
                    cursor: 'default'
                });
                modifyCSS(child.children[0], {
                    fill: 'rgba(0, 0, 0, 0.25)',
                    cursor: 'default'
                });
                child.children[0].setAttribute('fill', 'rgba(0, 0, 0, 0.25)');
            }
        });
    }
    // 销毁插件
    destroyPlugin() {
        const graph = this.get('graph');
        graph.get('canvas').destroy();
        const container = graph.getContainer();
        container.parentNode.removeChild(container);
    }
}

export default Toolbar;
复制代码

三、G6文档


G6官方文档

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享