feat: 重写泄漏区逻辑

This commit is contained in:
黎智洲
2022-03-02 22:07:27 +08:00
parent 5893ef2938
commit 3170bd77ec
21 changed files with 2209 additions and 1999 deletions
+33 -39
View File
@@ -1,10 +1,10 @@
import { IPoint } from '@antv/g6-core';
import { IPoint, INode } from '@antv/g6-core';
import { Bound, BoundingRect } from '../Common/boundingRect';
import { Group } from '../Common/group';
import { Util } from '../Common/util';
import { Vector } from '../Common/vector';
import { Engine } from '../engine';
import { LayoutGroupTable } from '../Model/modelConstructor';
import { LayoutGroupTable, ModelConstructor } from '../Model/modelConstructor';
import { SVModel } from '../Model/SVModel';
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker } from '../Model/SVNodeAppendage';
import { AddressLabelOption, IndexLabelOption, LayoutOptions, MarkerOption, ViewOptions } from '../options';
@@ -14,6 +14,8 @@ export class LayoutProvider {
private engine: Engine;
private viewOptions: ViewOptions;
private viewContainer: ViewContainer;
private leakAreaXoffset: number = 20;
private leakClusterXInterval = 25;
constructor(engine: Engine, viewContainer: ViewContainer) {
this.engine = engine;
@@ -67,7 +69,7 @@ export class LayoutProvider {
let target = item.target,
targetBound: BoundingRect = target.getBound(),
g6AnchorPosition = item.target.shadowG6Item.getAnchorPoints()[anchor] as IPoint,
g6AnchorPosition = (<INode>item.target.shadowG6Item).getAnchorPoints()[anchor] as IPoint,
center: [number, number] = [
targetBound.x + targetBound.width / 2,
targetBound.y + targetBound.height / 2,
@@ -214,10 +216,15 @@ export class LayoutProvider {
indexLabelOptions = group.options.indexLabel || {},
addressLabelOption = group.options.addressLabel || {};
this.layoutIndexLabel(group.indexLabel, indexLabelOptions);
this.layoutFreedLabel(group.freedLabel);
this.layoutAddressLabel(group.addressLabel, addressLabelOption);
this.layoutMarker(group.marker, markerOptions); // 布局外部指针
const indexLabel = group.appendage.filter(item => item instanceof SVIndexLabel) as SVIndexLabel[],
freedLabel = group.appendage.filter(item => item instanceof SVFreedLabel) as SVFreedLabel[],
addressLabel = group.appendage.filter(item => item instanceof SVAddressLabel) as SVAddressLabel[],
marker = group.appendage.filter(item => item instanceof SVMarker) as SVMarker[];
this.layoutIndexLabel(indexLabel, indexLabelOptions);
this.layoutFreedLabel(freedLabel);
this.layoutAddressLabel(addressLabel, addressLabelOption);
this.layoutMarker(marker, markerOptions); // 布局外部指针
});
return modelGroupList;
@@ -225,45 +232,36 @@ export class LayoutProvider {
/**
* 对泄漏区进行布局
* @param leakModels
* @param accumulateLeakModels
* // todo: 部分元素被抽离后的泄漏区
*/
private layoutLeakModels(leakModels: SVModel[], accumulateLeakModels: SVModel[]) {
const group: Group = new Group(),
containerHeight = this.viewContainer.getG6Instance().getHeight(),
private layoutLeakArea(accumulateLeakModels: SVModel[]) {
const containerHeight = this.viewContainer.getG6Instance().getHeight(),
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
leakAreaY = containerHeight - leakAreaHeight,
xOffset = 60;
leakAreaY = containerHeight - leakAreaHeight;
let prevBound: BoundingRect;
// 避免在泄漏前拖拽节点导致的位置变化,先把节点位置重置为布局后的标准位置
leakModels.forEach(item => {
accumulateLeakModels.forEach(item => {
item.set({
x: item.layoutX,
y: item.layoutY,
});
});
const globalLeakGroupBound: BoundingRect = accumulateLeakModels.length
? Bound.union(...accumulateLeakModels.map(item => item.getBound()))
: { x: 0, y: leakAreaY, width: 0, height: 0 };
const clusters = ModelConstructor.getClusters(accumulateLeakModels);
const layoutGroups = Util.groupBy(leakModels, 'group');
Object.keys(layoutGroups).forEach(key => {
group.add(...layoutGroups[key]);
// 每一个簇从左往右布局就完事了,比之前的方法简单稳定很多
clusters.forEach(item => {
const bound = item.getBound(),
x = prevBound ? prevBound.x + prevBound.width + this.leakClusterXInterval : this.leakAreaXoffset,
dx = x - bound.x,
dy = leakAreaY - bound.y;
const currentBound: BoundingRect = group.getBound(),
prevBoundEnd = prevBound ? prevBound.x + prevBound.width : 0,
{ x: groupX, y: groupY } = currentBound,
dx = globalLeakGroupBound.x + globalLeakGroupBound.width + prevBoundEnd + xOffset - groupX,
dy = globalLeakGroupBound.y - groupY;
group.translate(dx, dy);
group.clear();
Bound.translate(currentBound, dx, dy);
prevBound = currentBound;
item.translate(dx, dy);
Bound.translate(bound, dx, dy);
prevBound = bound;
});
}
@@ -320,7 +318,7 @@ export class LayoutProvider {
if (this.viewContainer.hasLeak) {
const boundBottomY = viewBound.y + viewBound.height;
dy = height - leakAreaHeight - 100 - boundBottomY;
dy = height - leakAreaHeight - 130 - boundBottomY;
} else {
const boundCenterY = viewBound.y + viewBound.height / 2;
dy = centerY - boundCenterY;
@@ -332,19 +330,15 @@ export class LayoutProvider {
/**
* 布局
* @param layoutGroupTable
* @param leakModels
* @param hasLeak
* @param needFitCenter
* @param accumulateLeakModels
*/
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], leakModels: SVModel[]) {
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[]) {
this.preLayoutProcess(layoutGroupTable);
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
const generalGroup: Group = this.layoutGroups(modelGroupList);
if (leakModels.length) {
this.layoutLeakModels(leakModels, accumulateLeakModels);
}
this.layoutLeakArea(accumulateLeakModels);
this.fitCenter(generalGroup);
this.postLayoutProcess(layoutGroupTable);
+456 -425
View File
@@ -1,430 +1,461 @@
import { EventBus } from "../Common/eventBus";
import { Util } from "../Common/util";
import { Engine } from "../engine";
import { LayoutGroupTable } from "../Model/modelConstructor";
import { SVLink } from "../Model/SVLink";
import { SVModel } from "../Model/SVModel";
import { SVNode } from "../Model/SVNode";
import { SVAddressLabel, SVMarker, SVNodeAppendage } from "../Model/SVNodeAppendage";
import { Animations } from "./animation";
import { Renderer } from "./renderer";
import { EventBus } from '../Common/eventBus';
import { Util } from '../Common/util';
import { Engine } from '../engine';
import { LayoutGroupTable } from '../Model/modelConstructor';
import { SVLink } from '../Model/SVLink';
import { SVModel } from '../Model/SVModel';
import { SVNode } from '../Model/SVNode';
import { SVAddressLabel, SVMarker, SVNodeAppendage } from '../Model/SVNodeAppendage';
import { Animations } from './animation';
import { Renderer } from './renderer';
export interface DiffResult {
CONTINUOUS: SVModel[];
APPEND: SVModel[];
REMOVE: SVModel[];
FREED: SVNode[];
LEAKED: SVModel[];
UPDATE: SVModel[];
ACCUMULATE_LEAK: SVModel[];
CONTINUOUS: SVModel[];
APPEND: SVModel[];
REMOVE: SVModel[];
FREED: SVNode[];
LEAKED: SVModel[];
UPDATE: SVModel[];
ACCUMULATE_LEAK: SVModel[];
}
export class Reconcile {
private engine: Engine;
private renderer: Renderer;
private isFirstPatch: boolean;
constructor(engine: Engine, renderer: Renderer) {
this.engine = engine;
this.renderer = renderer;
this.isFirstPatch = true;
}
/**
* 获取上一次渲染存在的,这一次渲染也存在的models
* @param prevModelList
* @param modelList
*/
private getContinuousModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
const continuousModels = modelList.filter(item => prevModelList.find(prevModel => item.id === prevModel.id));
return continuousModels;
}
/**
* 获取新增的节点,这些节点有可能来自泄漏区(上一步的情况)
* @param prevModelList
* @param modelList
* @param accumulateLeakModels
* @returns
*/
private getAppendModels(prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): SVModel[] {
const appendModels = modelList.filter(item => !prevModelList.find(model => model.id === item.id));
appendModels.forEach(item => {
let removeIndex = accumulateLeakModels.findIndex(leakModel => item.id === leakModel.id);
if (removeIndex > -1) {
accumulateLeakModels.splice(removeIndex, 1);
}
});
return appendModels;
}
/**
* 获取被泄露的节点
* @param layoutGroupTable
* @param prevModelList
* @param modelList
* @returns
*/
private getLeakModels(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
let potentialLeakModels: SVModel[] = prevModelList.filter(item =>
!modelList.find(model => model.id === item.id) && !item.freed
);
const leakModels: SVModel[] = [];
// 先把节点拿出来
const potentialLeakNodes = potentialLeakModels.filter(item => item.isNode()) as SVNode[],
groups = Util.groupBy<SVNode>(potentialLeakNodes, 'group');
// 再把非节点的model拿出来
potentialLeakModels = potentialLeakModels.filter(item => item.isNode() === false);
Object.keys(groups).forEach(key => {
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
if(leakRule && typeof leakRule === 'function') {
potentialLeakModels.push(...leakRule(groups[key]));
}
});
potentialLeakModels.forEach(item => {
if (item instanceof SVNode) {
item.leaked = true;
leakModels.push(item);
item.appendages.forEach(appendage => {
appendage.leaked = true;
leakModels.push(appendage);
});
}
});
potentialLeakModels.forEach(item => {
if (item instanceof SVLink && item.node.leaked !== false && item.target.leaked !== false) {
item.leaked = true;
leakModels.push(item);
}
});
leakModels.forEach(item => {
// 不能用上次的G6item了,不然布局的时候会没有动画
item.G6Item = null;
});
return leakModels;
}
/**
* 找出需要移除的节点
* @param prevModelList
* @param modelList
*/
private getRemoveModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
let removedModels: SVModel[] = [];
for (let i = 0; i < prevModelList.length; i++) {
let prevModel = prevModelList[i],
target = modelList.find(item => item.id === prevModel.id);
if (target === undefined && !prevModel.leaked) {
removedModels.push(prevModel);
}
}
return removedModels;
}
/**
* 找出重新指向的外部指针
* @param prevModelList
* @param modelList
* @returns
*/
private getReTargetMarkers(prevModelList: SVModel[], modelList: SVModel[]): SVMarker[] {
const prevMarkers: SVMarker[] = prevModelList.filter(item => item instanceof SVMarker) as SVMarker[],
markers: SVMarker[] = modelList.filter(item => item instanceof SVMarker) as SVMarker[];
return markers.filter(item => prevMarkers.find(prevItem => {
return prevItem.id === item.id && prevItem.target.id !== item.target.id
}));
}
/**
* 找出前后 label 发生变化的 model
* @param prevModelList
* @param modelList
* @returns
*/
private getLabelChangeModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
let labelChangeModels: SVModel[] = [];
modelList.forEach(item => {
const prevItem = prevModelList.find(prevItem => prevItem.id === item.id);
if (prevItem === undefined) {
return;
}
const prevLabel = prevItem.get('label'),
label = item.get('label');
if (String(prevLabel) !== String(label)) {
labelChangeModels.push(item);
}
});
return labelChangeModels;
}
/**
* 获取被 free 的节点
* @param prevModelList
* @param modelList
* @returns
*/
private getFreedModels(prevModelList: SVModel[], modelList: SVModel[]): SVNode[] {
const freedNodes = modelList.filter(item => item instanceof SVNode && item.freed) as SVNode[];
freedNodes.forEach(item => {
const prev = prevModelList.find(prevModel => item.id === prevModel.id);
if (prev) {
item.set('label', prev.get('label'));
}
});
return freedNodes;
}
// ------------------------------------------------------------------------------------------------
/**
* 处理不变的models
* @param continuousModels
*/
private handleContinuousModels(continuousModels: SVModel[]) {
for (let i = 0; i < continuousModels.length; i++) {
let model = continuousModels[i];
if (model instanceof SVNode) {
const group = model.G6Item.getContainer();
group.attr({ opacity: 1 });
}
}
}
/**
* 处理新增的 models
* @param appendData
*/
private handleAppendModels(appendModels: SVModel[]) {
let { duration, timingFunction } = this.engine.animationOptions;
appendModels.forEach(item => {
if (item instanceof SVNodeAppendage) {
// 先不显示泄漏区节点上面的地址文本
if (item instanceof SVAddressLabel) {
// 先将透明度改为0,隐藏掉
const AddressLabelG6Group = item.G6Item.getContainer();
AddressLabelG6Group.attr({ opacity: 0 });
}
else {
Animations.FADE_IN(item.G6Item, {
duration,
timingFunction
});
}
}
else {
Animations.APPEND(item.G6Item, {
duration,
timingFunction
});
}
});
}
/**
* 处理被移除 models
* @param removeData
*/
private handleRemoveModels(removeModels: SVModel[]) {
let { duration, timingFunction } = this.engine.animationOptions;
removeModels.forEach(item => {
Animations.REMOVE(item.G6Item, {
duration,
timingFunction,
callback: () => {
this.renderer.removeModel(item);
}
});
});
}
/**
* 处理泄漏区 models
* @param leakModels
*/
private handleLeakModels(leakModels: SVModel[]) {
let { duration, timingFunction } = this.engine.animationOptions;
leakModels.forEach(item => {
if (item instanceof SVAddressLabel) {
Animations.FADE_IN(item.G6Item, {
duration,
timingFunction
});
}
item.G6Item.enableCapture(false);
});
EventBus.emit('onLeak', leakModels);
}
/**
* 处理已经堆积的泄漏区 models
* @param accumulateModels
*/
private handleAccumulateLeakModels(accumulateModels: SVModel[]) {
accumulateModels.forEach(item => {
if (item.generalStyle) {
item.set('style', { ...item.generalStyle });
}
});
}
/**
* 处理被释放的节点 models
* @param freedModes
*/
private handleFreedModels(freedModes: SVNode[]) {
const { duration, timingFunction } = this.engine.animationOptions,
alpha = 0.4;
freedModes.forEach(item => {
const nodeGroup = item.G6Item.getContainer();
item.set('style', { fill: '#ccc' });
nodeGroup.attr({ opacity: alpha });
if (item.marker) {
const markerGroup = item.marker.G6Item.getContainer();
item.marker.set('style', { fill: '#ccc' });
markerGroup.attr({ opacity: alpha + 0.5 });
}
item.freedLabel.G6Item.toFront();
Animations.FADE_IN(item.freedLabel.G6Item, { duration, timingFunction });
});
EventBus.emit('onFreed', freedModes);
}
/**
* 处理发生变化的 models
* @param models
*/
private handleChangeModels(models: SVModel[]) {
const changeHighlightColor: string = this.engine.viewOptions.updateHighlight;
if (!changeHighlightColor || typeof changeHighlightColor !== 'string') {
return;
}
models.forEach(item => {
if (item.generalStyle === undefined) {
item.generalStyle = Util.objectClone(item.G6ModelProps.style);
}
if (item instanceof SVLink) {
item.set('style', {
stroke: changeHighlightColor
});
}
else {
item.set('style', {
fill: changeHighlightColor
});
}
});
}
/**
* 进行diff
* @param layoutGroupTable
* @param prevModelList
* @param modelList
* @param accumulateLeakModels
* @returns
*/
public diff(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[], isEnterFunction: boolean): DiffResult {
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
const leakModels: SVModel[] = isEnterFunction? []: this.getLeakModels(layoutGroupTable, prevModelList, modelList);
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels);
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList);
const updateModels: SVModel[] = [
...this.getReTargetMarkers(prevModelList, modelList),
...this.getLabelChangeModels(prevModelList, modelList),
...appendModels,
...leakModels
];
const freedModels: SVNode[] = this.getFreedModels(prevModelList, modelList);
return {
CONTINUOUS: continuousModels,
APPEND: appendModels,
REMOVE: removeModels,
FREED: freedModels,
LEAKED: leakModels,
UPDATE: updateModels,
ACCUMULATE_LEAK: [...accumulateLeakModels]
};
}
/**
* 执行调和操作
* @param diffResult
* @param isFirstRender
*/
public patch(diffResult: DiffResult) {
const {
APPEND,
REMOVE,
FREED,
LEAKED,
UPDATE,
CONTINUOUS,
ACCUMULATE_LEAK
} = diffResult;
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
// 第一次渲染的时候不高亮变化的元素
if (this.isFirstPatch === false) {
this.handleChangeModels(UPDATE);
}
this.handleContinuousModels(CONTINUOUS);
this.handleFreedModels(FREED);
this.handleAppendModels(APPEND);
this.handleLeakModels(LEAKED);
this.handleRemoveModels(REMOVE);
if (this.isFirstPatch) {
this.isFirstPatch = false;
}
}
public destroy() { }
}
private engine: Engine;
private renderer: Renderer;
private isFirstPatch: boolean;
constructor(engine: Engine, renderer: Renderer) {
this.engine = engine;
this.renderer = renderer;
this.isFirstPatch = true;
}
/**
* 获取上一次渲染存在的,这一次渲染也存在的models
* @param prevModelList
* @param modelList
*/
private getContinuousModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
const continuousModels = modelList.filter(item => prevModelList.find(prevModel => item.id === prevModel.id));
return continuousModels;
}
/**
* 获取新增的节点,这些节点有可能来自泄漏区(上一步的情况)
* @param prevModelList
* @param modelList
* @param accumulateLeakModels
* @returns
*/
private getAppendModels(
prevModelList: SVModel[],
modelList: SVModel[],
accumulateLeakModels: SVModel[],
prevStep: boolean
): SVModel[] {
const appendModels = modelList.filter(item => !prevModelList.find(model => model.id === item.id));
// 看看新增的节点是不是从泄漏区来的
// 目前的判断方式比较傻:看看泄漏区有没有相同id的节点,但是发现这个方法可能不可靠,不知道还有没有更好的办法
appendModels.forEach(item => {
const removeIndex = accumulateLeakModels.findIndex(leakModel => item.id === leakModel.id),
svModel = accumulateLeakModels[removeIndex];
if (removeIndex > -1) {
svModel.leaked = false;
accumulateLeakModels.splice(removeIndex, 1);
}
});
return appendModels;
}
/**
* 获取被泄露的节点
* @param layoutGroupTable
* @param prevModelList
* @param modelList
* @returns
*/
private getLeakModels(
layoutGroupTable: LayoutGroupTable,
prevModelList: SVModel[],
modelList: SVModel[]
): SVModel[] {
let potentialLeakModels: SVModel[] = prevModelList.filter(
item => !modelList.find(model => model.id === item.id) && !item.freed
);
const leakModels: SVModel[] = [];
// 先把节点拿出来
const potentialLeakNodes = potentialLeakModels.filter(item => item.isNode()) as SVNode[],
groups = Util.groupBy<SVNode>(potentialLeakNodes, 'group');
// 再把非节点的model拿出来
potentialLeakModels = potentialLeakModels.filter(item => item.isNode() === false);
Object.keys(groups).forEach(key => {
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
if (leakRule && typeof leakRule === 'function') {
potentialLeakModels.push(...leakRule(groups[key]));
}
});
potentialLeakModels.forEach(item => {
if (item instanceof SVNode) {
item.leaked = true;
leakModels.push(item);
item.getAppendagesList().forEach(appendage => {
// 外部指针先不加入泄漏区(这个需要讨论一下,我觉得不应该)
if (appendage instanceof SVMarker) {
return;
}
appendage.leaked = true;
leakModels.push(appendage);
});
}
});
potentialLeakModels.forEach(item => {
if (item instanceof SVLink && item.node.leaked !== false && item.target.leaked !== false) {
item.leaked = true;
leakModels.push(item);
}
});
return leakModels;
}
/**
* 找出需要移除的节点
* @param prevModelList
* @param modelList
*/
private getRemoveModels(
prevModelList: SVModel[],
modelList: SVModel[],
accumulateLeakModels: SVModel[]
): SVModel[] {
let removedModels: SVModel[] = [];
for (let i = 0; i < prevModelList.length; i++) {
let prevModel = prevModelList[i],
target = modelList.find(item => item.id === prevModel.id);
if (target === undefined && !prevModel.leaked) {
removedModels.push(prevModel);
}
}
// 假如某个节点从泄漏区移回可视化区域,那么与原来泄漏结构的连线应该消失掉
for (let i = 0; i < accumulateLeakModels.length; i++) {
let leakModel = accumulateLeakModels[i];
if (leakModel instanceof SVLink) {
if (leakModel.node.leaked === false || leakModel.target.leaked === false) {
accumulateLeakModels.splice(i, 1);
i--;
removedModels.push(leakModel);
}
}
}
removedModels.forEach(item => {
item.beforeDestroy();
});
return removedModels;
}
/**
* 找出重新指向的外部指针
* @param prevModelList
* @param modelList
* @returns
*/
private getReTargetMarkers(prevModelList: SVModel[], modelList: SVModel[]): SVMarker[] {
const prevMarkers: SVMarker[] = prevModelList.filter(item => item instanceof SVMarker) as SVMarker[],
markers: SVMarker[] = modelList.filter(item => item instanceof SVMarker) as SVMarker[];
return markers.filter(item =>
prevMarkers.find(prevItem => {
return prevItem.id === item.id && prevItem.target.id !== item.target.id;
})
);
}
/**
* 找出前后 label 发生变化的 model
* @param prevModelList
* @param modelList
* @returns
*/
private getLabelChangeModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
let labelChangeModels: SVModel[] = [];
modelList.forEach(item => {
const prevItem = prevModelList.find(prevItem => prevItem.id === item.id);
if (prevItem === undefined) {
return;
}
const prevLabel = prevItem.get('label'),
label = item.get('label');
if (String(prevLabel) !== String(label)) {
labelChangeModels.push(item);
}
});
return labelChangeModels;
}
/**
* 获取被 free 的节点
* @param prevModelList
* @param modelList
* @returns
*/
private getFreedModels(prevModelList: SVModel[], modelList: SVModel[]): SVNode[] {
const freedNodes = modelList.filter(item => item instanceof SVNode && item.freed) as SVNode[];
freedNodes.forEach(item => {
const prev = prevModelList.find(prevModel => item.id === prevModel.id);
if (prev) {
item.set('label', prev.get('label'));
}
});
return freedNodes;
}
// ------------------------------------------------------------------------------------------------
/**
* 处理不变的models
* @param continuousModels
*/
private handleContinuousModels(continuousModels: SVModel[]) {
for (let i = 0; i < continuousModels.length; i++) {
let model = continuousModels[i];
if (model instanceof SVNode) {
const group = model.G6Item.getContainer();
group.attr({ opacity: 1 });
}
}
}
/**
* 处理新增的 models
* @param appendData
*/
private handleAppendModels(appendModels: SVModel[]) {
let { duration, timingFunction } = this.engine.animationOptions;
appendModels.forEach(item => {
if (item instanceof SVNodeAppendage) {
// 先不显示泄漏区节点上面的地址文本
if (item instanceof SVAddressLabel) {
// 先将透明度改为0,隐藏掉
const AddressLabelG6Group = item.G6Item.getContainer();
AddressLabelG6Group.attr({ opacity: 0 });
} else {
Animations.FADE_IN(item.G6Item, {
duration,
timingFunction,
});
}
} else {
Animations.APPEND(item.G6Item, {
duration,
timingFunction,
});
}
});
}
/**
* 处理被移除 models
* @param removeData
*/
private handleRemoveModels(removeModels: SVModel[]) {
let { duration, timingFunction } = this.engine.animationOptions;
removeModels.forEach(item => {
Animations.REMOVE(item.G6Item, {
duration,
timingFunction,
callback: () => {
this.renderer.removeModel(item);
},
});
});
}
/**
* 处理泄漏区 models
* @param leakModels
*/
private handleLeakModels(leakModels: SVModel[]) {
let { duration, timingFunction } = this.engine.animationOptions;
leakModels.forEach(item => {
if (item instanceof SVAddressLabel) {
Animations.FADE_IN(item.G6Item, {
duration,
timingFunction,
});
}
item.G6Item.enableCapture(false);
});
EventBus.emit('onLeak', leakModels);
}
/**
* 处理已经堆积的泄漏区 models
* @param accumulateModels
*/
private handleAccumulateLeakModels(accumulateModels: SVModel[]) {
accumulateModels.forEach(item => {
if (item.generalStyle) {
item.set('style', { ...item.generalStyle });
}
});
}
/**
* 处理被释放的节点 models
* @param freedModes
*/
private handleFreedModels(freedModes: SVNode[]) {
const { duration, timingFunction } = this.engine.animationOptions,
alpha = 0.4;
freedModes.forEach(item => {
const nodeGroup = item.G6Item.getContainer();
item.set('style', { fill: '#ccc' });
nodeGroup.attr({ opacity: alpha });
if (item.appendages.marker) {
item.appendages.marker.forEach(marker => {
const markerGroup = marker.G6Item.getContainer();
marker.set('style', { fill: '#ccc' });
markerGroup.attr({ opacity: alpha + 0.5 });
});
}
if (item.appendages.freedLabel) {
item.appendages.freedLabel.forEach(freedLabel => {
freedLabel.G6Item.toFront();
Animations.FADE_IN(freedLabel.G6Item, { duration, timingFunction });
});
}
});
EventBus.emit('onFreed', freedModes);
}
/**
* 处理发生变化的 models
* @param models
*/
private handleChangeModels(models: SVModel[]) {
const changeHighlightColor: string = this.engine.viewOptions.updateHighlight;
if (!changeHighlightColor || typeof changeHighlightColor !== 'string') {
return;
}
models.forEach(item => {
if (item.generalStyle === undefined) {
item.generalStyle = Util.objectClone(item.G6ModelProps.style);
}
if (item instanceof SVLink) {
item.set('style', {
stroke: changeHighlightColor,
});
} else {
item.set('style', {
fill: changeHighlightColor,
});
}
});
}
/**
* 进行diff
* @param layoutGroupTable
* @param prevModelList
* @param modelList
* @param accumulateLeakModels
* @returns
*/
public diff(
layoutGroupTable: LayoutGroupTable,
prevModelList: SVModel[],
modelList: SVModel[],
accumulateLeakModels: SVModel[],
isEnterFunction: boolean,
prevStep: boolean
): DiffResult {
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
const leakModels: SVModel[] = isEnterFunction
? []
: this.getLeakModels(layoutGroupTable, prevModelList, modelList);
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels, prevStep);
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList, accumulateLeakModels);
const updateModels: SVModel[] = [
...this.getReTargetMarkers(prevModelList, modelList),
...this.getLabelChangeModels(prevModelList, modelList),
...appendModels,
...leakModels,
];
const freedModels: SVNode[] = this.getFreedModels(prevModelList, modelList);
return {
CONTINUOUS: continuousModels,
APPEND: appendModels,
REMOVE: removeModels,
FREED: freedModels,
LEAKED: leakModels,
UPDATE: updateModels,
ACCUMULATE_LEAK: [...accumulateLeakModels],
};
}
/**
* 执行调和操作
* @param diffResult
* @param isFirstRender
*/
public patch(diffResult: DiffResult) {
const { APPEND, REMOVE, FREED, LEAKED, UPDATE, CONTINUOUS, ACCUMULATE_LEAK } = diffResult;
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
// 第一次渲染的时候不高亮变化的元素
if (this.isFirstPatch === false) {
this.handleChangeModels(UPDATE);
}
this.handleContinuousModels(CONTINUOUS);
this.handleFreedModels(FREED);
this.handleAppendModels(APPEND);
this.handleLeakModels(LEAKED);
this.handleRemoveModels(REMOVE);
if (this.isFirstPatch) {
this.isFirstPatch = false;
}
}
public destroy() {}
}
+135 -136
View File
@@ -4,165 +4,164 @@ import { Util } from '../Common/util';
import { Tooltip, Graph, GraphData, Modes } from '@antv/g6';
import { InitG6Behaviors } from '../BehaviorHelper/initG6Behaviors';
export interface RenderModelPack {
leaKModels: SVModel[];
generalModel: SVModel[];
leaKModels: SVModel[];
generalModel: SVModel[];
}
export type g6Behavior = string | { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function; };
export type g6Behavior =
| string
| { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function };
export class Renderer {
private engine: Engine;
private g6Instance: Graph; // g6 实例
private shadowG6Instance: Graph;
private engine: Engine;
private g6Instance: Graph; // g6 实例
private shadowG6Instance: Graph;
constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes) {
this.engine = engine;
constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes) {
this.engine = engine;
const enable: boolean = this.engine.animationOptions.enable,
duration: number = this.engine.animationOptions.duration,
timingFunction: string = this.engine.animationOptions.timingFunction;
const enable: boolean = this.engine.animationOptions.enable,
duration: number = this.engine.animationOptions.duration,
timingFunction: string = this.engine.animationOptions.timingFunction;
const tooltip = new Tooltip({
offsetX: 10,
offsetY: 20,
shouldBegin(event) {
return event.item['SVModel'].isNode();
},
getContent: event => this.getTooltipContent(event.item['SVModel'], { address: 'sourceId', data: 'data' }),
itemTypes: ['node']
});
const tooltip = new Tooltip({
offsetX: 10,
offsetY: 20,
shouldBegin(event) {
return event.item['SVModel'].isNode();
},
getContent: event => this.getTooltipContent(event.item['SVModel'], { address: 'sourceId', data: 'data' }),
itemTypes: ['node'],
});
this.shadowG6Instance = new Graph({
container: DOMContainer.cloneNode() as HTMLElement
});
this.shadowG6Instance = new Graph({
container: DOMContainer.cloneNode() as HTMLElement,
});
// 初始化g6实例
this.g6Instance = new Graph({
container: DOMContainer,
width: DOMContainer.offsetWidth,
height: DOMContainer.offsetHeight,
groupByTypes: false,
animate: enable,
animateCfg: {
duration: duration,
easing: timingFunction
},
fitView: false,
modes: behaviorsModes,
plugins: [tooltip]
});
}
// 初始化g6实例
this.g6Instance = new Graph({
container: DOMContainer,
width: DOMContainer.offsetWidth,
height: DOMContainer.offsetHeight,
groupByTypes: false,
animate: enable,
animateCfg: {
duration: duration,
easing: timingFunction,
},
fitView: false,
modes: behaviorsModes,
plugins: [tooltip],
});
}
/**
* 创造tooltip元素
* @param model
* @param items
* @returns
*/
private getTooltipContent(model: SVModel, items: { [key: string]: string }): HTMLDivElement {
const wrapper = document.createElement('div');
/**
* 创造tooltip元素
* @param model
* @param items
* @returns
*/
private getTooltipContent(model: SVModel, items: { [key: string]: string }): HTMLDivElement {
const wrapper = document.createElement('div');
if (model === null || model === undefined) {
return wrapper;
}
if (model === null || model === undefined) {
return wrapper;
}
Object.keys(items).map(key => {
let value = model[items[key]];
if (value !== undefined && value !== null) {
let item = document.createElement('div');
item.innerHTML = `${key}${value !== '' ? value : model.G6ModelProps['label']}`;
wrapper.appendChild(item);
}
});
Object.keys(items).map(key => {
let value = model[items[key]];
if (value !== undefined && value !== null) {
let item = document.createElement('div');
item.innerHTML = `${key}${value !== '' ? value : model.G6ModelProps['label']}`;
wrapper.appendChild(item);
}
});
if (model.freed) {
let item = document.createElement('div');
item.innerHTML = '(freed)';
wrapper.appendChild(item);
}
if (model.freed) {
let item = document.createElement('div');
item.innerHTML = '(freed)';
wrapper.appendChild(item);
}
return wrapper;
}
return wrapper;
}
/**
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
* @param renderModelList
*/
public build(renderModelList: SVModel[]) {
const g6Data: GraphData = Util.convertModelList2G6Data(renderModelList);
/**
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
* @param renderModelList
*/
public build(renderModelList: SVModel[]) {
const g6Data: GraphData = Util.convertModelList2G6Data(renderModelList);
this.shadowG6Instance.clear();
this.shadowG6Instance.read(g6Data);
renderModelList.forEach(item => {
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
item.shadowG6Instance = this.shadowG6Instance;
});
}
this.shadowG6Instance.clear();
this.shadowG6Instance.read(g6Data);
renderModelList.forEach(item => {
item.G6Item = null;
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
item.shadowG6Instance = this.shadowG6Instance;
});
}
/**
* 渲染函数
* @param renderModelList
* @param isFirstRender
*/
public render(renderModelList: SVModel[]) {
const renderData: GraphData = Util.convertModelList2G6Data(renderModelList);
/**
* 渲染函数
* @param renderModelList
* @param isFirstRender
*/
public render(renderModelList: SVModel[]) {
const renderData: GraphData = Util.convertModelList2G6Data(renderModelList);
this.g6Instance.changeData(renderData);
this.g6Instance.changeData(renderData);
renderModelList.forEach(item => {
item.g6Instance = this.g6Instance;
item.G6Item = this.g6Instance.findById(item.id);
item.G6Item['SVModel'] = item;
});
renderModelList.forEach(item => {
item.g6Instance = this.g6Instance;
item.G6Item = this.g6Instance.findById(item.id);
item.G6Item['SVModel'] = item;
});
this.g6Instance.getEdges().forEach(item => item.toFront());
this.g6Instance.paint();
}
this.g6Instance.getEdges().forEach(item => item.toFront());
this.g6Instance.paint();
}
/**
* 从视图中移除一个 Model
* @param model
*/
public removeModel(model: SVModel) {
this.g6Instance.removeItem(model.G6Item);
this.shadowG6Instance.removeItem(model.shadowG6Item);
}
/**
* 从视图中移除一个 Model
* @param model
*/
public removeModel(model: SVModel) {
this.g6Instance.removeItem(model.G6Item);
this.shadowG6Instance.removeItem(model.shadowG6Item);
}
/**
* 获取 G6 实例
*/
public getG6Instance() {
return this.g6Instance;
}
/**
* 获取 G6 实例
*/
public getG6Instance() {
return this.g6Instance;
}
/**
*
*/
public refresh() {
this.g6Instance.refresh();
this.shadowG6Instance.refresh();
}
/**
*
*/
public refresh() {
this.g6Instance.refresh();
this.shadowG6Instance.refresh();
}
/**
*
* @param width
* @param height
*/
public changeSize(width: number, height: number) {
this.g6Instance.changeSize(width, height);
this.shadowG6Instance.changeSize(width, height);
}
/**
*
* @param width
* @param height
*/
public changeSize(width: number, height: number) {
this.g6Instance.changeSize(width, height);
this.shadowG6Instance.changeSize(width, height);
}
/**
* 销毁
*/
public destroy() {
this.shadowG6Instance.destroy();
this.g6Instance.destroy();
}
}
/**
* 销毁
*/
public destroy() {
this.shadowG6Instance.destroy();
this.g6Instance.destroy();
}
}
+180 -192
View File
@@ -1,226 +1,214 @@
import { Engine } from "../engine";
import { LayoutProvider } from "./layoutProvider";
import { LayoutGroupTable } from "../Model/modelConstructor";
import { Util } from "../Common/util";
import { SVModel } from "../Model/SVModel";
import { Renderer } from "./renderer";
import { Reconcile } from "./reconcile";
import { EventBus } from "../Common/eventBus";
import { Group } from "../Common/group";
import { Graph, Modes } from "@antv/g6-pc";
import { InitG6Behaviors } from "../BehaviorHelper/initG6Behaviors";
import { SVNode } from "../Model/SVNode";
import { SolveBrushSelectDrag, SolveDragCanvasWithLeak, SolveNodeAppendagesDrag, SolveZoomCanvasWithLeak } from "../BehaviorHelper/behaviorIssueHelper";
import { Engine } from '../engine';
import { LayoutProvider } from './layoutProvider';
import { LayoutGroupTable } from '../Model/modelConstructor';
import { Util } from '../Common/util';
import { SVModel } from '../Model/SVModel';
import { Renderer } from './renderer';
import { Reconcile } from './reconcile';
import { EventBus } from '../Common/eventBus';
import { Group } from '../Common/group';
import { Graph, Modes } from '@antv/g6-pc';
import { InitG6Behaviors } from '../BehaviorHelper/initG6Behaviors';
import { SVNode } from '../Model/SVNode';
import {
SolveBrushSelectDrag,
SolveDragCanvasWithLeak,
SolveNodeAppendagesDrag,
SolveZoomCanvasWithLeak,
} from '../BehaviorHelper/behaviorIssueHelper';
export class ViewContainer {
private engine: Engine;
private layoutProvider: LayoutProvider;
private reconcile: Reconcile;
public renderer: Renderer;
private engine: Engine;
private layoutProvider: LayoutProvider;
private reconcile: Reconcile;
public renderer: Renderer;
private layoutGroupTable: LayoutGroupTable;
private prevModelList: SVModel[];
private accumulateLeakModels: SVModel[];
private layoutGroupTable: LayoutGroupTable;
private prevModelList: SVModel[];
private accumulateLeakModels: SVModel[];
public hasLeak: boolean;
public leakAreaY: number;
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
public clickSelectNode: SVNode; // 点击选中的节点
public hasLeak: boolean;
public leakAreaY: number;
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
public clickSelectNode: SVNode; // 点击选中的节点
constructor(engine: Engine, DOMContainer: HTMLElement) {
const behaviorsModes: Modes = InitG6Behaviors(engine, this);
constructor(engine: Engine, DOMContainer: HTMLElement) {
const behaviorsModes: Modes = InitG6Behaviors(engine, this);
this.engine = engine;
this.layoutProvider = new LayoutProvider(engine, this);
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes);
this.reconcile = new Reconcile(engine, this.renderer);
this.layoutGroupTable = new Map();
this.prevModelList = [];
this.accumulateLeakModels = [];
this.hasLeak = false; // 判断是否已经发生过泄漏
this.brushSelectedModels = [];
this.clickSelectNode = null;
this.engine = engine;
this.layoutProvider = new LayoutProvider(engine, this);
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes);
this.reconcile = new Reconcile(engine, this.renderer);
this.layoutGroupTable = new Map();
this.prevModelList = [];
this.accumulateLeakModels = [];
this.hasLeak = false; // 判断是否已经发生过泄漏
this.brushSelectedModels = [];
this.clickSelectNode = null;
const g6Instance = this.renderer.getG6Instance(),
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
height = this.getG6Instance().getHeight(),
{ drag, zoom } = this.engine.behaviorOptions;
const g6Instance = this.renderer.getG6Instance(),
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
height = this.getG6Instance().getHeight(),
{ drag, zoom } = this.engine.behaviorOptions;
this.leakAreaY = height - leakAreaHeight;
this.leakAreaY = height - leakAreaHeight;
SolveNodeAppendagesDrag(this);
SolveBrushSelectDrag(this);
drag && SolveDragCanvasWithLeak(this);
zoom && SolveZoomCanvasWithLeak(this);
}
SolveNodeAppendagesDrag(this);
SolveBrushSelectDrag(this);
drag && SolveDragCanvasWithLeak(this);
zoom && SolveZoomCanvasWithLeak(this);
}
// ----------------------------------------------------------------------------------------------
/**
* 对主视图进行重新布局
*/
reLayout() {
const g6Instance = this.getG6Instance(),
group = g6Instance.getGroup(),
matrix = group.getMatrix();
// ----------------------------------------------------------------------------------------------
if (matrix) {
let dx = matrix[6],
dy = matrix[7];
/**
* 对主视图进行重新布局
*/
reLayout() {
const g6Instance = this.getG6Instance(),
group = g6Instance.getGroup(),
matrix = group.getMatrix();
g6Instance.translate(-dx, -dy);
}
if (matrix) {
let dx = matrix[6],
dy = matrix[7];
this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels);
g6Instance.refresh();
}
g6Instance.translate(-dx, -dy);
}
/**
* 获取 g6 实例
*/
getG6Instance(): Graph {
return this.renderer.getG6Instance();
}
this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels, []);
g6Instance.refresh();
}
/**
* 获取泄漏区里面的元素
* @returns
*/
getAccumulateLeakModels(): SVModel[] {
return this.accumulateLeakModels;
}
/**
*
*/
getLayoutGroupTable(): LayoutGroupTable {
return this.layoutGroupTable;
}
/**
* 获取 g6 实例
*/
getG6Instance(): Graph {
return this.renderer.getG6Instance();
}
/**
* 刷新视图
*/
refresh() {
this.renderer.getG6Instance().refresh();
}
/**
* 获取泄漏区里面的元素
* @returns
*/
getAccumulateLeakModels(): SVModel[] {
return this.accumulateLeakModels;
}
/**
* 重新调整容器尺寸
* @param width
* @param height
*/
resize(width: number, height: number) {
const g6Instance = this.getG6Instance(),
prevContainerHeight = g6Instance.getHeight(),
globalGroup: Group = new Group();
/**
*
*/
getLayoutGroupTable(): LayoutGroupTable {
return this.layoutGroupTable;
}
globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels);
this.renderer.changeSize(width, height);
/**
* 刷新视图
*/
refresh() {
this.renderer.getG6Instance().refresh();
}
const containerHeight = g6Instance.getHeight(),
dy = containerHeight - prevContainerHeight;
/**
* 重新调整容器尺寸
* @param width
* @param height
*/
resize(width: number, height: number) {
const g6Instance = this.getG6Instance(),
prevContainerHeight = g6Instance.getHeight(),
globalGroup: Group = new Group();
globalGroup.translate(0, dy);
this.renderer.refresh();
globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels);
this.renderer.changeSize(width, height);
this.leakAreaY += dy;
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: this.leakAreaY,
hasLeak: this.hasLeak,
});
}
const containerHeight = g6Instance.getHeight(),
dy = containerHeight - prevContainerHeight;
/**
* 渲染所有视图
* @param models
* @param layoutFn
*/
render(layoutGroupTable: LayoutGroupTable, isEnterFunction: boolean, prevStep: boolean) {
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
diffResult = this.reconcile.diff(
this.layoutGroupTable,
this.prevModelList,
modelList,
this.accumulateLeakModels,
isEnterFunction,
prevStep
),
renderModelList = [...modelList, ...diffResult.REMOVE, ...diffResult.LEAKED, ...diffResult.ACCUMULATE_LEAK];
globalGroup.translate(0, dy);
this.renderer.refresh();
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
this.hasLeak = false;
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: this.leakAreaY,
hasLeak: this.hasLeak,
});
}
this.leakAreaY += dy;
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: this.leakAreaY,
hasLeak: this.hasLeak
});
}
if (diffResult.LEAKED.length) {
this.hasLeak = true;
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: this.leakAreaY,
hasLeak: this.hasLeak,
});
}
/**
* 渲染所有视图
* @param models
* @param layoutFn
*/
render(layoutGroupTable: LayoutGroupTable, isEnterFunction: boolean) {
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
diffResult = this.reconcile.diff(this.layoutGroupTable, this.prevModelList, modelList, this.accumulateLeakModels, isEnterFunction),
renderModelList = [
...modelList,
...diffResult.REMOVE,
...diffResult.LEAKED,
...diffResult.ACCUMULATE_LEAK
];
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行向后累积
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels); // 进行布局(设置model的x,y,样式等)
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
this.hasLeak = false;
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: this.leakAreaY,
hasLeak: this.hasLeak
});
}
this.beforeRender();
this.renderer.render(renderModelList); // 渲染视图
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
this.afterRender();
if (diffResult.LEAKED.length) {
this.hasLeak = true;
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: this.leakAreaY,
hasLeak: this.hasLeak
});
}
this.layoutGroupTable = layoutGroupTable;
this.prevModelList = modelList;
}
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels, diffResult.LEAKED); // 进行布局(设置model的x,y,样式等)
/**
* 销毁
*/
destroy() {
this.renderer.destroy();
this.reconcile.destroy();
this.layoutProvider = null;
this.layoutGroupTable = null;
this.prevModelList.length = 0;
this.accumulateLeakModels.length = 0;
this.brushSelectedModels.length = 0;
}
this.beforeRender();
this.renderer.render(renderModelList); // 渲染视图
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
this.afterRender();
// ------------------------------------------------------------------------------
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行累积
/**
* 把渲染后要触发的逻辑放在这里
*/
private afterRender() {
this.prevModelList.forEach(item => {
if (item.leaked === false) {
item.discarded = true;
}
});
}
this.layoutGroupTable = layoutGroupTable;
this.prevModelList = modelList;
}
/**
* 销毁
*/
destroy() {
this.renderer.destroy();
this.reconcile.destroy();
this.layoutProvider = null;
this.layoutGroupTable = null;
this.prevModelList.length = 0;
this.accumulateLeakModels.length = 0;
this.brushSelectedModels.length = 0;
}
// ------------------------------------------------------------------------------
/**
* 把渲染后要触发的逻辑放在这里
*/
private afterRender() {
this.prevModelList.forEach(item => {
if (item.leaked === false) {
item.discarded = true;
}
});
}
/**
* 把渲染前要触发的逻辑放在这里
*/
private beforeRender() { }
/**
* 把渲染前要触发的逻辑放在这里
*/
private beforeRender() {}
}