292 lines
8.8 KiB
TypeScript
292 lines
8.8 KiB
TypeScript
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[] = <Element[]>leakModelList.filter(item => item instanceof Element && item.freed === false),
|
||
links: Link[] = <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();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|