UNPKG

@antv/g6

Version:

graph visualization frame work

1,255 lines (1,171 loc) 32.3 kB
/* * @Author: moyee * @Date: 2019-06-27 18:12:06 * @LastEditors: moyee * @LastEditTime: 2019-08-22 11:22:16 * @Description: Graph */ const { groupBy, isString } = require('lodash'); const G = require('@antv/g/lib'); const EventEmitter = G.EventEmitter; const Util = require('../util'); const Global = require('../global'); const Controller = require('./controller'); const NODE = 'node'; const EDGE = 'edge'; class Graph extends EventEmitter { /** * Access to the default configuration properties * @return {object} default configuration */ getDefaultCfg() { return { /** * Container could be dom object or dom id * @type {object|string|undefined} */ container: undefined, /** * Canvas width * @type {number|undefined} * unit pixel if undefined force fit width */ width: undefined, /** * Canvas height * @type {number|undefined} * unit pixel if undefined force fit height */ height: undefined, /** * renderer canvas or svg * @type {string} */ renderer: 'canvas', /** * control graph behaviors * @type Array */ mode: [], /** * 注册插件 */ plugins: [], /** * source data * @type object */ data: null, /** * Fit view padding (client scale) * @type {number|array} */ fitViewPadding: 10, /** * Minimum scale size * @type {number} */ minZoom: 0.2, /** * Maxmum scale size * @type {number} */ maxZoom: 10, /** * capture events * @type boolean */ event: true, /** * group node & edges into different graphic groups * @private * @type boolean */ groupByTypes: true, /** * determine if it's a directed graph * @type boolean */ directed: false, /** * when data or shape changed, should canvas draw automatically * @type boolean */ autoPaint: true, /** * store all the node instances * @type [object] */ nodes: [], /** * store all the edge instances * @type [object] */ edges: [], /** * all the instances indexed by id * @type object */ itemMap: {}, /** * 边直接连接到节点的中心,不再考虑锚点 * @type {Boolean} */ linkCenter: false, /** * 默认的节点配置,data 上定义的配置会覆盖这些配置。例如: * defaultNode: { * shape: 'rect', * size: [60, 40], * style: { * //... 样式配置项 * } * } * 若数据项为 { id: 'node', x: 100, y: 100 } * 实际创建的节点模型是 { id: 'node', x: 100, y: 100, shape: 'rect', size: [60, 40] } * 若数据项为 { id: 'node', x: 100, y: 100, shape: 'circle' } * 实际创建的节点模型是 { id: 'node', x: 100, y: 100, shape: 'circle', size: [60, 40] } */ defaultNode: {}, /** * 默认边配置,data 上定义的配置会覆盖这些配置。用法同 defaultNode */ defaultEdge: {}, /** * 节点默认样式,也可以添加状态样式 * 例如: * const graph = new G6.Graph({ * nodeStateStyle: { * selected: { fill: '#ccc', stroke: '#666' }, * active: { lineWidth: 2 } * }, * ... * }); * */ nodeStateStyles: {}, /** * 边默认样式,用法同nodeStateStyle */ edgeStateStyles: {}, /** * graph 状态 */ states: {}, /** * 是否启用全局动画 * @type {Boolean} */ animate: false, /** * 动画设置,仅在 animate 为 true 时有效 * @type {Object} */ animateCfg: { /** * 帧回调函数,用于自定义节点运动路径,为空时线性运动 * @type {Function|null} */ onFrame: null, /** * 动画时长(ms) * @type {Number} */ duration: 500, /** * 指定动画动效 * @type {String} */ easing: 'easeLinear' }, callback: null, /** * group类型 */ groupType: 'circle', /** * 各个group的BBox */ groupBBoxs: {}, /** * 每个group包含的节点,父层的包括自己的节点以及子Group的节点 */ groupNodes: {}, /** * 群组的原始数据 */ groups: [], groupStyle: {} }; } constructor(inputCfg) { super(); this._cfg = Util.deepMix(this.getDefaultCfg(), inputCfg); // merge graph configs this._init(); } _init() { this._initCanvas(); const eventController = new Controller.Event(this); const viewController = new Controller.View(this); const modeController = new Controller.Mode(this); const itemController = new Controller.Item(this); const stateController = new Controller.State(this); const layoutController = new Controller.Layout(this); // 实例化customGroup const customGroupControll = new Controller.CustomGroup(this); this.set({ eventController, viewController, modeController, itemController, stateController, customGroupControll, layoutController }); this._initPlugins(); } _initCanvas() { let container = this.get('container'); if (Util.isString(container)) { container = document.getElementById(container); this.set('container', container); } if (!container) { throw Error('invalid container'); } const canvas = new G.Canvas({ containerDOM: container, width: this.get('width'), height: this.get('height'), renderer: this.get('renderer'), pixelRatio: this.get('pixelRatio') }); this.set('canvas', canvas); this._initGroups(); } _initGroups() { const canvas = this.get('canvas'); const id = this.get('canvas').get('el').id; const group = canvas.addGroup({ id: id + '-root', className: Global.rootContainerClassName }); if (this.get('groupByTypes')) { const edgeGroup = group.addGroup({ id: id + '-edge', className: Global.edgeContainerClassName }); const nodeGroup = group.addGroup({ id: id + '-node', className: Global.nodeContainerClassName }); const delegateGroup = group.addGroup({ id: id + '-delagate', className: Global.delegateContainerClassName }); // 用于存储自定义的群组 const customGroup = group.addGroup({ id: `${id}-group`, className: Global.customGroupContainerClassName }); customGroup.toBack(); this.set({ nodeGroup, edgeGroup, customGroup, delegateGroup }); } this.set('group', group); } _initPlugins() { const self = this; Util.each(self.get('plugins'), plugin => { if (!plugin.destroyed && plugin.initPlugin) { plugin.initPlugin(self); } }); } get(key) { return this._cfg[key]; } set(key, val) { if (Util.isPlainObject(key)) { this._cfg = Util.mix({}, this._cfg, key); } else { this._cfg[key] = val; } return this; } /** * 更新元素 * @param {string|object} item 元素id或元素实例 * @param {object} cfg 需要更新的数据 */ update(item, cfg) { this.updateItem(item, cfg); } /** * 更新元素 * @param {string|object} item 元素id或元素实例 * @param {object} cfg 需要更新的数据 */ updateItem(item, cfg) { this.get('itemController').updateItem(item, cfg); } /** * 设置元素状态 * @param {string|object} item 元素id或元素实例 * @param {string} state 状态 * @param {boolean} enabled 是否启用状态 */ setItemState(item, state, enabled) { if (Util.isString(item)) { item = this.findById(item); } this.get('itemController').setItemState(item, state, enabled); this.get('stateController').updateState(item, state, enabled); } /** * 清理元素多个状态 * @param {string|object} item 元素id或元素实例 * @param {Array|String|null} states 状态 */ clearItemStates(item, states) { if (Util.isString(item)) { item = this.findById(item); } this.get('itemController').clearItemStates(item, states); if (!states) { states = item.get('states'); } this.get('stateController').updateStates(item, states, false); } /** * 新增元素 * @param {string} type 元素类型(node | edge) * @param {object} model 元素数据模型 * @return {object} 元素实例 */ add(type, model) { return this.addItem(type, model); } /** * 新增元素 或 节点分组 * @param {string} type 元素类型(node | edge | group) * @param {object} model 元素数据模型 * @return {object} 元素实例 */ addItem(type, model) { if (type === 'group') { const { groupId, nodes, type, zIndex, title } = model; let groupTitle = title; if (isString(title)) { groupTitle = { text: title }; } return this.get('customGroupControll').create(groupId, nodes, type, zIndex, true, groupTitle); } return this.get('itemController').addItem(type, model); } /** * 删除元素 * @param {string|object} item 元素id或元素实例 */ remove(item) { this.removeItem(item); } /** * 删除元素 * @param {string|object} item 元素id或元素实例 */ removeItem(item) { // 如果item是字符串,且查询的节点实例不存在,则认为是删除group let nodeItem = null; if (Util.isString(item)) { nodeItem = this.findById(item); } if (!nodeItem && Util.isString(item)) { this.get('customGroupControll').remove(item); } else { this.get('itemController').removeItem(item); } } /** * 设置视图初始化数据 * @param {object} data 初始化数据 */ data(data) { this.set('data', data); } /** * 设置各个节点样式,以及在各种状态下节点 keyShape 的样式。 * 若是自定义节点切在各种状态下 * graph.node(node => { * return { * { shape: 'rect', label: node.id, style: { fill: '#666' }, stateStyles: { selected: { fill: 'blue' }, custom: { fill: 'green' } } } * } * }); * @param {function} nodeFn 指定每个节点样式 */ node(nodeFn) { if (typeof nodeFn === 'function') { this.set('nodeMapper', nodeFn); } } /** * 设置各个边样式 * @param {function} edgeFn 指定每个边的样式,用法同 node */ edge(edgeFn) { if (typeof edgeFn === 'function') { this.set('edgeMapper', edgeFn); } } /** * 刷新元素 * @param {string|object} item 元素id或元素实例 */ refreshItem(item) { this.get('itemController').refreshItem(item); } /** * 当源数据在外部发生变更时,根据新数据刷新视图。但是不刷新节点位置 */ refresh() { const self = this; const autoPaint = self.get('autoPaint'); self.setAutoPaint(false); self.emit('beforegraphrefresh'); if (self.get('animate')) { self.positionsAnimate(); } else { const nodes = self.get('nodes'); const edges = self.get('edges'); Util.each(nodes, node => { node.refresh(); }); Util.each(edges, edge => { edge.refresh(); }); } self.setAutoPaint(autoPaint); self.emit('aftergraphrefresh'); self.autoPaint(); } /** * 当节点位置在外部发生改变时,刷新所有节点位置,重计算边 */ refreshPositions() { const self = this; self.emit('beforegraphrefreshposition'); const nodes = self.get('nodes'); const edges = self.get('edges'); let model; Util.each(nodes, node => { model = node.getModel(); node.updatePosition(model); }); Util.each(edges, edge => { edge.refresh(); }); self.emit('aftergraphrefreshposition'); self.autoPaint(); } /** * 根据data接口的数据渲染视图 */ render() { const self = this; const data = this.get('data'); if (!data) { throw new Error('data must be defined first'); } this.clear(); this.emit('beforerender'); const autoPaint = this.get('autoPaint'); this.setAutoPaint(false); Util.each(data.nodes, node => { self.add(NODE, node); }); Util.each(data.edges, edge => { self.add(EDGE, edge); }); // 防止传入的数据不存在nodes if (data.nodes) { // 获取所有有groupID的node const nodeInGroup = data.nodes.filter(node => node.groupId); // 所有node中存在groupID,则说明需要群组 if (nodeInGroup.length > 0) { // 渲染群组 const groupType = self.get('groupType'); this.renderCustomGroup(data, groupType); } } if (!this.get('groupByTypes')) { // 为提升性能,选择数量少的进行操作 if (data.nodes.length < data.edges.length) { const nodes = this.getNodes(); // 遍历节点实例,将所有节点提前。 nodes.forEach(node => { node.toFront(); }); } else { const edges = this.getEdges(); // 遍历节点实例,将所有节点提前。 edges.forEach(edge => { edge.toBack(); }); } } // layout const layoutController = self.get('layoutController'); layoutController.layout(); self.refreshPositions(); if (self.get('fitView')) { self.get('viewController')._fitView(); } self.paint(); self.setAutoPaint(autoPaint); self.emit('afterrender'); } /** * 根据数据渲染群组 * @param {object} data 渲染图的数据 * @param {string} groupType group类型 */ renderCustomGroup(data, groupType) { const { groups, nodes } = data; // 第一种情况,,不存在groups,则不存在嵌套群组 let groupIndex = 10; if (!groups) { // 存在单个群组 // 获取所有有groupID的node const nodeInGroup = nodes.filter(node => node.groupId); const groupsArr = []; // 根据groupID分组 const groupIds = groupBy(nodeInGroup, 'groupId'); for (const groupId in groupIds) { const nodeIds = groupIds[groupId].map(node => node.id); this.get('customGroupControll').create(groupId, nodeIds, groupType, groupIndex); groupIndex--; // 获取所有不重复的 groupId if (!groupsArr.find(data => data.id === groupId)) { groupsArr.push({ id: groupId }); } } this.set({ groups: groupsArr }); } else { // 将groups的数据存到groups中 this.set({ groups }); // 第二种情况,存在嵌套的群组,数据中有groups字段 const groupNodes = Util.getAllNodeInGroups(data); for (const groupId in groupNodes) { const tmpNodes = groupNodes[groupId]; this.get('customGroupControll').create(groupId, tmpNodes, groupType, groupIndex); groupIndex--; } // 对所有Group排序 const customGroup = this.get('customGroup'); customGroup.sort(); } } /** * 接收数据进行渲染 * @Param {Object} data 初始化数据 */ read(data) { this.data(data); this.render(); } /** * 更改源数据,根据新数据重新渲染视图 * @param {object} data 源数据 * @return {object} this */ changeData(data) { const self = this; if (!data) { return this; } if (!self.get('data')) { self.data(data); self.render(); } const autoPaint = this.get('autoPaint'); const itemMap = this.get('itemMap'); const items = { nodes: [], edges: [] }; this.setAutoPaint(false); this._diffItems(NODE, items, data.nodes); this._diffItems(EDGE, items, data.edges); Util.each(itemMap, (item, id) => { if (items.nodes.indexOf(item) < 0 && items.edges.indexOf(item) < 0) { delete itemMap[id]; self.remove(item); } }); this.set({ nodes: items.nodes, edges: items.edges }); const layoutController = this.get('layoutController'); layoutController.changeData(); if (self.get('animate')) { self.positionsAnimate(); } else { this.paint(); } this.setAutoPaint(autoPaint); return this; } _diffItems(type, items, models) { const self = this; let item; const itemMap = this.get('itemMap'); Util.each(models, model => { item = itemMap[model.id]; if (item) { if (self.get('animate') && type === NODE) { const containerMatrix = item.getContainer().getMatrix(); item.set('originAttrs', { x: containerMatrix[6], y: containerMatrix[7] }); } self.updateItem(item, model); } else { item = self.addItem(type, model); } items[type + 's'].push(item); }); } /** * 仅画布重新绘制 */ paint() { this.emit('beforepaint'); this.get('canvas').draw(); this.emit('afterpaint'); } /** * 自动重绘 * @internal 仅供内部更新机制调用,外部根据需求调用 render 或 paint 接口 */ autoPaint() { if (this.get('autoPaint')) { this.paint(); } } /** * 导出图数据 * @return {object} data */ save() { const nodes = []; const edges = []; Util.each(this.get('nodes'), node => { nodes.push(node.getModel()); }); Util.each(this.get('edges'), edge => { edges.push(edge.getModel()); }); return { nodes, edges, groups: this.get('groups') }; } /** * 改变画布大小 * @param {number} width 画布宽度 * @param {number} height 画布高度 * @return {object} this */ changeSize(width, height) { this.get('viewController').changeSize(width, height); this.autoPaint(); return this; } /** * 平移画布 * @param {number} dx 水平方向位移 * @param {number} dy 垂直方向位移 */ translate(dx, dy) { const group = this.get('group'); group.translate(dx, dy); this.emit('viewportchange', { action: 'translate', matrix: group.getMatrix() }); this.autoPaint(); } /** * 平移画布到某点 * @param {number} x 水平坐标 * @param {number} y 垂直坐标 */ moveTo(x, y) { const group = this.get('group'); group.move(x, y); this.emit('viewportchange', { action: 'move', matrix: group.getMatrix() }); this.autoPaint(); } /** * 调整视口适应视图 * @param {object} padding 四周围边距 */ fitView(padding) { if (padding) { this.set('fitViewPadding', padding); } this.get('viewController')._fitView(); this.paint(); } /** * 新增行为 * @param {string|array} behaviors 添加的行为 * @param {string|array} modes 添加到对应的模式 * @return {object} this */ addBehaviors(behaviors, modes) { this.get('modeController').manipulateBehaviors(behaviors, modes, true); return this; } /** * 移除行为 * @param {string|array} behaviors 移除的行为 * @param {string|array} modes 从指定的模式中移除 * @return {object} this */ removeBehaviors(behaviors, modes) { this.get('modeController').manipulateBehaviors(behaviors, modes, false); return this; } /** * 切换行为模式 * @param {string} mode 指定模式 * @return {object} this */ setMode(mode) { this.set('mode', mode); this.get('modeController').setMode(mode); return this; } /** * 获取当前的行为模式 * @return {string} 当前行为模式 */ getCurrentMode() { return this.get('mode'); } /** * 获取当前视口伸缩比例 * @return {number} 比例 */ getZoom() { return this.get('group').getMatrix()[0]; } /** * 获取当前图中所有节点的item实例 * @return {array} item数组 */ getNodes() { return this.get('nodes'); } /** * 获取当前图中所有边的item实例 * @return {array} item数组 */ getEdges() { return this.get('edges'); } /** * 伸缩视口 * @param {number} ratio 伸缩比例 * @param {object} center 以center的x, y坐标为中心缩放 */ zoom(ratio, center) { const matrix = Util.clone(this.get('group').getMatrix()); const minZoom = this.get('minZoom'); const maxZoom = this.get('maxZoom'); if (center) { Util.mat3.translate(matrix, matrix, [ -center.x, -center.y ]); Util.mat3.scale(matrix, matrix, [ ratio, ratio ]); Util.mat3.translate(matrix, matrix, [ center.x, center.y ]); } else { Util.mat3.scale(matrix, matrix, [ ratio, ratio ]); } if (minZoom && matrix[0] < minZoom) { return; } if (maxZoom && matrix[0] > maxZoom) { return; } this.get('group').setMatrix(matrix); this.emit('viewportchange', { action: 'zoom', matrix }); this.autoPaint(); } /** * 伸缩视口到一固定比例 * @param {number} toRatio 伸缩比例 * @param {object} center 以center的x, y坐标为中心缩放 */ zoomTo(toRatio, center) { const ratio = toRatio / this.getZoom(); this.zoom(ratio, center); } /** * 根据 graph 上的 animateCfg 进行视图中节点位置动画接口 */ positionsAnimate() { const self = this; self.emit('beforeanimate'); const animateCfg = self.get('animateCfg'); const onFrame = animateCfg.onFrame; const nodes = self.getNodes(); const toNodes = nodes.map(node => { const model = node.getModel(); return { id: model.id, x: model.x, y: model.y }; }); if (self.isAnimating()) { self.stopAnimate(); } self.get('canvas').animate({ onFrame(ratio) { Util.each(toNodes, data => { const node = self.findById(data.id); if (!node || node.destroyed) { return; } let originAttrs = node.get('originAttrs'); const model = node.get('model'); if (!originAttrs) { const containerMatrix = node.getContainer().getMatrix(); originAttrs = { x: containerMatrix[6], y: containerMatrix[7] }; node.set('originAttrs', originAttrs); } if (onFrame) { const attrs = onFrame(node, ratio, data, originAttrs); node.set('model', Util.mix(model, attrs)); } else { model.x = originAttrs.x + (data.x - originAttrs.x) * ratio; model.y = originAttrs.y + (data.y - originAttrs.y) * ratio; } }); self.refreshPositions(); } }, animateCfg.duration, animateCfg.easing, () => { Util.each(nodes, node => { node.set('originAttrs', null); }); if (animateCfg.callback) { animateCfg.callback(); } self.emit('afteranimate'); self.animating = false; }); } stopAnimate() { this.get('canvas').stopAnimate(); } isAnimating() { return this.animating; } /** * 将元素移动到视口中心 * @param {string|object} item 指定元素 */ focusItem(item) { this.get('viewController').focus(item); this.autoPaint(); } /** * 将屏幕坐标转换为视口坐标 * @param {number} clientX 屏幕x坐标 * @param {number} clientY 屏幕y坐标 * @return {object} 视口坐标 */ getPointByClient(clientX, clientY) { return this.get('viewController').getPointByClient(clientX, clientY); } /** * 将视口坐标转换为屏幕坐标 * @param {number} x 视口x坐标 * @param {number} y 视口y坐标 * @return {object} 视口坐标 */ getClientByPoint(x, y) { return this.get('viewController').getClientByPoint(x, y); } /** * 将画布坐标转换为视口坐标 * @param {number} canvasX 屏幕x坐标 * @param {number} canvasY 屏幕y坐标 * @return {object} 视口坐标 */ getPointByCanvas(canvasX, canvasY) { return this.get('viewController').getPointByCanvas(canvasX, canvasY); } /** * 将视口坐标转换为画布坐标 * @param {number} x 屏幕x坐标 * @param {number} y 屏幕y坐标 * @return {object} 画布坐标 */ getCanvasByPoint(x, y) { return this.get('viewController').getCanvasByPoint(x, y); } /** * 显示元素 * @param {string|object} item 指定元素 */ showItem(item) { this.get('itemController').changeItemVisibility(item, true); } /** * 隐藏元素 * @param {string|object} item 指定元素 */ hideItem(item) { this.get('itemController').changeItemVisibility(item, false); } /** * 查找对应id的元素 * @param {string} id 元素id * @return {object} 元素实例 */ findById(id) { return this.get('itemMap')[id]; } /** * 根据对应规则查找单个元素 * @param {string} type 元素类型(node|edge) * @param {string} fn 指定规则 * @return {object} 元素实例 */ find(type, fn) { let result; const items = this.get(type + 's'); Util.each(items, (item, i) => { if (fn(item, i)) { result = item; return false; } }); return result; } /** * 查找所有满足规则的元素 * @param {string} type 元素类型(node|edge) * @param {string} fn 指定规则 * @return {array} 元素实例 */ findAll(type, fn) { const result = []; Util.each(this.get(type + 's'), (item, i) => { if (fn(item, i)) { result.push(item); } }); return result; } /** * 查找所有处于指定状态的元素 * @param {string} type 元素类型(node|edge) * @param {string} state z状态 * @return {object} 元素实例 */ findAllByState(type, state) { return this.findAll(type, item => { return item.hasState(state); }); } /** * 设置是否在更新/刷新后自动重绘 * @param {boolean} auto 自动重绘 */ setAutoPaint(auto) { this.set('autoPaint', auto); } /** * 返回图表的 dataUrl 用于生成图片 * @return {string/Object} 图片 dataURL */ toDataURL() { const canvas = this.get('canvas'); const renderer = canvas.getRenderer(); const canvasDom = canvas.get('el'); let dataURL = ''; if (renderer === 'svg') { const clone = canvasDom.cloneNode(true); const svgDocType = document.implementation.createDocumentType( 'svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' ); const svgDoc = document.implementation.createDocument('http://www.w3.org/2000/svg', 'svg', svgDocType); svgDoc.replaceChild(clone, svgDoc.documentElement); const svgData = (new XMLSerializer()).serializeToString(svgDoc); dataURL = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(svgData); } else if (renderer === 'canvas') { dataURL = canvasDom.toDataURL('image/png'); } return dataURL; } /** * 画布导出图片 * @param {String} name 图片的名称 */ downloadImage(name) { const self = this; if (self.isAnimating()) { self.stopAnimate(); } const canvas = self.get('canvas'); const renderer = canvas.getRenderer(); const fileName = (name || 'graph') + (renderer === 'svg' ? '.svg' : '.png'); const link = document.createElement('a'); setTimeout(() => { const dataURL = self.toDataURL(); if (typeof window !== 'undefined') { if (window.Blob && window.URL && renderer !== 'svg') { const arr = dataURL.split(','); const mime = arr[0].match(/:(.*?);/)[1]; const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } const blobObj = new Blob([ u8arr ], { type: mime }); if (window.navigator.msSaveBlob) { window.navigator.msSaveBlob(blobObj, fileName); } else { link.addEventListener('click', function() { link.download = fileName; link.href = window.URL.createObjectURL(blobObj); }); } } else { link.addEventListener('click', function() { link.download = fileName; link.href = dataURL; }); } } const e = document.createEvent('MouseEvents'); e.initEvent('click', false, false); link.dispatchEvent(e); }, 16); } /** * 添加插件 * @param {object} plugin 插件实例 */ addPlugin(plugin) { const self = this; if (plugin.destroyed) { return; } self.get('plugins').push(plugin); plugin.initPlugin(self); } /** * 添加插件 * @param {object} plugin 插件实例 */ removePlugin(plugin) { const plugins = this.get('plugins'); const index = plugins.indexOf(plugin); if (index >= 0) { plugin.destroyPlugin(); plugins.splice(index, 1); } } /** * 更换布局配置项 * @param {object} cfg 新布局配置项 * 若 cfg 含有 type 字段或为 String 类型,且与现有布局方法不同,则更换布局 * 若 cfg 不包括 type ,则保持原有布局方法,仅更新布局配置项 */ updateLayout(cfg) { const layoutController = this.get('layoutController'); let newLayoutType; if (Util.isString(cfg)) { newLayoutType = cfg; cfg = { type: newLayoutType }; } else { newLayoutType = cfg.type; } const oriLayoutCfg = this.get('layout'); const oriLayoutType = oriLayoutCfg ? oriLayoutCfg.type : undefined; if (!newLayoutType || oriLayoutType === newLayoutType) { // no type or same type, update layout const layoutCfg = {}; Util.mix(layoutCfg, cfg); layoutCfg.type = oriLayoutType ? oriLayoutType : 'random'; layoutController.updateLayoutCfg(layoutCfg); } else { // has different type, change layout this.set('layout', cfg); layoutController.changeLayout(newLayoutType); } } /** * 重新以当前示例中配置的属性进行一次布局 */ layout() { const layoutController = this.get('layoutController'); if (layoutController.layoutMethod) { layoutController.relayout(); } else { layoutController.layout(); } } /** * 清除画布元素 * @return {object} this */ clear() { const canvas = this.get('canvas'); canvas.clear(); this._initGroups(); // 清空画布时同时清除数据 this.set({ itemMap: {}, nodes: [], edges: [], groups: [] }); return this; } /** * 销毁画布 */ destroy() { this.clear(); Util.each(this.get('plugins'), plugin => { plugin.destroyPlugin(); }); this.get('eventController').destroy(); this.get('itemController').destroy(); this.get('modeController').destroy(); this.get('viewController').destroy(); this.get('stateController').destroy(); this.get('layoutController').destroy(); this.get('customGroupControll').destroy(); this.get('canvas').destroy(); this._cfg = null; this.destroyed = true; } // group 相关方法 /** * 收起分组 * @param {string} groupId 分组ID */ collapseGroup(groupId) { this.get('customGroupControll').collapseGroup(groupId); } /** * 展开分组 * @param {string} groupId 分组ID */ expandGroup(groupId) { this.get('customGroupControll').expandGroup(groupId); } } module.exports = Graph;