import { Engine } from "../engine"; import { Element, Link, Marker, Model } from "../Model/modelData"; import { EngineOptions } from "../options"; import { Container } from "./container/container"; import { SV } from '../StructV'; import { MainContainer } from "./container/main"; import { FreedContainer } from "./container/freed"; import { LeakContainer } from "./container/leak"; import { Layouter } from "./layouter"; import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor"; import { Util } from "../Common/util"; import { EventBus } from "../Common/eventBus"; export class ViewManager { private engine: Engine; private layouter: Layouter; private mainContainer: Container; private freedContainer: FreedContainer; private leakContainer: LeakContainer; private prevLayoutGroupTable: LayoutGroupTable; private prevModelList: Model[]; private prevFreedElements: Element[]; private shadowG6Instance; constructor(engine: Engine, DOMContainer: HTMLElement) { this.engine = engine; this.layouter = new Layouter(engine); this.mainContainer = new MainContainer(engine, DOMContainer, { tooltip: true }); this.prevLayoutGroupTable = new Map(); this.prevFreedElements = []; this.prevModelList = []; const options: EngineOptions = this.engine.engineOptions; if (options.freedContainer) { this.freedContainer = new FreedContainer(engine, options.freedContainer, { fitCenter: true, tooltip: true }); } if (options.leakContainer) { this.leakContainer = new LeakContainer(engine, options.leakContainer, { fitCenter: true, tooltip: false }); } this.shadowG6Instance = new SV.G6.Graph({ container: DOMContainer.cloneNode() }); } /** * 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局 * @param constructList */ private build(modelList: Model[]) { modelList.forEach(item => { const type = item instanceof Link ? 'edge' : 'node'; this.shadowG6Instance.addItem(type, item.cloneProps()); item.shadowG6Item = this.shadowG6Instance.findById(item.id); }); } /** * * @param freedElement * @param prevLayoutGroup */ private handleFreedLabel(freedElement: Element[], prevLayoutGroup: LayoutGroup) { if (prevLayoutGroup === undefined) { return; } const prevElementList: Element[] = prevLayoutGroup.element; freedElement.map(item => { let prevElement = prevElementList.find(el => el.id === item.id), prevLabel = prevElement.get('label') ?? ''; item.set('label', prevLabel); }); } /** * 获取被 free 的节点 * @param layoutGroupTable * @returns */ private getFreedModelList(prevLayoutGroupTable: LayoutGroupTable, layoutGroupTable: LayoutGroupTable): Model[] { let freedElements: Element[] = [], freedMarkers: Marker[] = [], removeModels: Model[] = [], freedGroupName: string; layoutGroupTable.forEach((group, key) => { let targetElements: Element[] = group.element.filter(item => item.freed); targetElements.forEach(fItem => { removeModels.push( ...Util.removeFromList(group.element, item => item.id === fItem.id), ...Util.removeFromList(group.link, item => item.element.id === fItem.id || item.target.id === fItem.id), ...Util.removeFromList(group.marker, item => item.target.id === fItem.id), ); }); removeModels.map(model => { Util.removeFromList(group.modelList, item => item.id === model.id); }); // 找出最新的freed节点(防止上一次的freed节点被遗留在该次渲染,此时会出现大于一个freed节点) targetElements.forEach(item => { if (this.prevFreedElements.find(prevEle => prevEle.id === item.id) === undefined) { freedGroupName = key; freedElements.push(item); } }); }); freedElements.map(item => { const markers = Object.keys(item.markers).map(name => item.markers[name]); freedMarkers.push(...markers); }); this.handleFreedLabel(freedElements, prevLayoutGroupTable.get(freedGroupName)); this.prevFreedElements = freedElements; const freedItems = [...freedElements, ...freedMarkers]; freedItems.forEach(item => { item.set('style', { fill: '#ccc' }); }); return freedItems; } /** * 获取被泄露的节点 * @param prevModelList * @param modelList * @returns */ private getLeakModelList(prevModelList: Model[], modelList: Model[]): Model[] { const leakModelList: Model[] = prevModelList.filter(item => !modelList.find(n => n.id === item.id)), elements: Element[] = leakModelList.filter(item => item instanceof Element && item.freed === false), links: Link[] = leakModelList.filter(item => item instanceof Link), elementIds: string[] = [], res: Model[] = []; elements.forEach(item => { elementIds.push(item.id); item.set('style', { fill: '#ccc' }); }); for (let i = 0; i < links.length; i++) { let sourceId = links[i].element.id, targetId = links[i].target.id; links[i].set('style', { stroke: '#333' }); if (elementIds.find(item => item === sourceId) === undefined || elementIds.find(item => item === targetId) === undefined) { links.splice(i, 1); i--; } } res.push(...elements, ...links); res.map(item => { item.isLeak = true; }); return res; } // ---------------------------------------------------------------------------------------------- /** * 对主视图进行重新布局 * @param layoutGroupTable */ reLayout(layoutGroupTable: LayoutGroupTable) { this.layouter.layoutAll(this.mainContainer, layoutGroupTable); } /** * 获取 g6 实例 */ getG6Instance() { return this.mainContainer.getG6Instance(); } /** * 刷新视图 */ refresh() { this.mainContainer.getG6Instance().refresh(); } /** * 重新调整容器尺寸 * @param containerName * @param width * @param height */ resize(containerName: string, width: number, height: number) { if (containerName === 'main') { this.mainContainer.getG6Instance().changeSize(width, height); } if (containerName === 'freed') { this.freedContainer.getG6Instance().changeSize(width, height); } if (containerName === 'leak') { this.leakContainer.getG6Instance().changeSize(width, height); } } /** * 渲染所有视图 * @param models * @param layoutFn */ renderAll(layoutGroupTable: LayoutGroupTable) { this.shadowG6Instance.clear(); let modelList = Util.convertGroupTable2ModelList(layoutGroupTable), leakModelList = [], freedModelList = []; this.build(modelList); if (this.leakContainer) { leakModelList = this.getLeakModelList(this.prevModelList, modelList); this.build(leakModelList); } // 进行布局(设置model的x,y) this.layouter.layoutAll(this.mainContainer, layoutGroupTable); freedModelList = this.getFreedModelList(this.prevLayoutGroupTable, layoutGroupTable); if (this.freedContainer && freedModelList.length) { this.freedContainer.fitCenter(freedModelList); this.freedContainer.render(freedModelList); EventBus.emit('onFreed', freedModelList); } // 从新获取一次,因为第一次获取没有把freed节点筛选出去 modelList = Util.convertGroupTable2ModelList(layoutGroupTable); this.mainContainer.render(modelList); if (this.leakContainer && leakModelList.length) { this.leakContainer.render(leakModelList); EventBus.emit('onLeak', leakModelList); } this.prevLayoutGroupTable = layoutGroupTable; this.prevModelList = modelList; } /** * 销毁 */ destroy() { this.shadowG6Instance.destroy(); this.mainContainer.destroy(); this.freedContainer && this.freedContainer.destroy(); this.leakContainer && this.leakContainer.destroy(); } }