feat: 重构
This commit is contained in:
+42
-48
@@ -1,12 +1,11 @@
|
||||
import { Model } from "../Model/modelData";
|
||||
import { SV } from "../StructV";
|
||||
import { Util } from '@antv/g6';
|
||||
|
||||
|
||||
export type animationConfig = {
|
||||
duration: number;
|
||||
timingFunction: string;
|
||||
payload?: any;
|
||||
callback?: Function;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,39 +13,36 @@ export type animationConfig = {
|
||||
* 动画表
|
||||
*/
|
||||
export const Animations = {
|
||||
|
||||
|
||||
/**
|
||||
* 添加节点 / 边时的动画效果
|
||||
* @param model
|
||||
* @param G6Item
|
||||
* @param animationConfig
|
||||
*/
|
||||
animate_append(model: Model, animationConfig: animationConfig) {
|
||||
model.G6Item === null && console.log(model);
|
||||
APPEND(G6Item: any, animationConfig: animationConfig) {
|
||||
const type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = Util.mat3,
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
|
||||
const G6Item = model.G6Item,
|
||||
type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = SV.Mat3,
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
|
||||
if(type === 'node') {
|
||||
if (type === 'node') {
|
||||
let matrix = group.getMatrix(),
|
||||
targetMatrix = Mat3.clone(matrix);
|
||||
|
||||
Mat3.scale(matrix, matrix, [0, 0]);
|
||||
Mat3.scale(targetMatrix, targetMatrix, [1, 1]);
|
||||
|
||||
group.attr({ opacity: 0, matrix });
|
||||
group.animate({ opacity: 1, matrix: targetMatrix }, animateCfg);
|
||||
group.attr({ matrix, opacity: 0 });
|
||||
group.animate({ matrix: targetMatrix, opacity: 1 }, animateCfg);
|
||||
}
|
||||
|
||||
if(type === 'edge') {
|
||||
if (type === 'edge') {
|
||||
const line = group.get('children')[0],
|
||||
length = line.getTotalLength();
|
||||
length = line.getTotalLength();
|
||||
|
||||
line.attr({ lineDash: [0, length], opacity: 0 });
|
||||
line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg);
|
||||
@@ -55,52 +51,50 @@ export const Animations = {
|
||||
|
||||
/**
|
||||
* 移除节点 / 边时的动画效果
|
||||
* @param model
|
||||
* @param G6Item
|
||||
* @param animationConfig
|
||||
*/
|
||||
animate_remove(model: Model, animationConfig: animationConfig) {
|
||||
const G6Item = model.G6Item,
|
||||
type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = SV.Mat3,
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
REMOVE(G6Item: any, animationConfig: animationConfig) {
|
||||
const type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = Util.mat3,
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
|
||||
if(type === 'node') {
|
||||
if (type === 'node') {
|
||||
let matrix = Mat3.clone(group.getMatrix());
|
||||
|
||||
Mat3.scale(matrix, matrix, [0, 0]);
|
||||
group.animate({ opacity: 0, matrix }, animateCfg);
|
||||
}
|
||||
|
||||
if(type === 'edge') {
|
||||
if (type === 'edge') {
|
||||
const line = group.get('children')[0],
|
||||
length = line.getTotalLength();
|
||||
length = line.getTotalLength();
|
||||
|
||||
line.animate({ lineDash: [0, length], opacity: 0 }, animateCfg);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 移动节点 / 边的动画
|
||||
* @param model
|
||||
*
|
||||
* @param G6Item
|
||||
* @param animationConfig
|
||||
*/
|
||||
animate_fadeOut(model: Model, animationConfig: animationConfig) {
|
||||
const G6Item = model.G6Item,
|
||||
group = G6Item.getContainer(),
|
||||
animateCfg = {
|
||||
duration: 1200,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
FADE_IN(G6Item: any, animationConfig: animationConfig) {
|
||||
const group = G6Item.getContainer(),
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
|
||||
group.animate({ opacity: 0 }, animateCfg);
|
||||
group.attr({ opacity: 0 });
|
||||
group.animate({ opacity: 1 }, animateCfg);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
import { Engine } from "../../engine";
|
||||
import { Model, Marker } from "../../Model/modelData";
|
||||
import { AnimationOptions, InteractionOptions, LayoutGroupOptions } from "../../options";
|
||||
import { SV } from "../../StructV";
|
||||
import { Animations } from "../animation";
|
||||
import { g6Behavior, Renderer } from "../renderer";
|
||||
|
||||
|
||||
|
||||
|
||||
export class Container {
|
||||
protected engine: Engine;
|
||||
protected DOMContainer: HTMLElement; // 可视化视图容器
|
||||
protected renderer: Renderer; // 渲染器
|
||||
protected prevModelList: Model[]; // 上一次渲染的模型列表
|
||||
|
||||
protected animationsOptions: AnimationOptions;
|
||||
protected interactionOptions: InteractionOptions;
|
||||
|
||||
protected afterAppendModelsCallbacks: ((models: Model[]) => void)[] = [];
|
||||
protected afterRemoveModelsCallbacks: ((models: Model[]) => void)[] = [];
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any } = {}) {
|
||||
this.engine = engine;
|
||||
this.DOMContainer = DOMContainer;
|
||||
this.animationsOptions = engine.animationOptions;
|
||||
this.interactionOptions = engine.interactionOptions;
|
||||
this.prevModelList = [];
|
||||
|
||||
const g6Plugins = [];
|
||||
|
||||
if (g6Options.tooltip) {
|
||||
const tooltip = new SV.G6.Tooltip({
|
||||
offsetX: 10,
|
||||
offsetY: 20,
|
||||
shouldBegin(event) {
|
||||
return event.item.getModel().SVModelType === 'element';
|
||||
},
|
||||
getContent: event => this.getTooltipContent(event.item.SVModel, { address: 'sourceId', data: 'data' }),
|
||||
itemTypes: ['node']
|
||||
});
|
||||
|
||||
g6Plugins.push(tooltip);
|
||||
}
|
||||
|
||||
this.renderer = new Renderer(engine, DOMContainer, {
|
||||
fitCenter: g6Options.fitCenter,
|
||||
modes: {
|
||||
default: this.initBehaviors(this.engine.optionsTable)
|
||||
},
|
||||
plugins: g6Plugins
|
||||
});
|
||||
|
||||
this.afterInitRenderer();
|
||||
}
|
||||
|
||||
|
||||
private getTooltipContent(model: Model, items: { [key: string]: string }): HTMLElement {
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
|
||||
Object.keys(items).map(key => {
|
||||
let value = model[items[key]];
|
||||
if (value !== undefined && value !== null) {
|
||||
let item = document.createElement('div');
|
||||
item.innerHTML = `${key}:${value}`;
|
||||
wrapper.appendChild(item);
|
||||
}
|
||||
});
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化交互行为
|
||||
* @param optionsTable
|
||||
* @returns
|
||||
*/
|
||||
protected initBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }): g6Behavior[] {
|
||||
return ['drag-canvas', 'zoom-canvas'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比上一次和该次 modelList 找出新添加的节点和边
|
||||
* @param prevList
|
||||
* @param list
|
||||
*/
|
||||
protected getAppendModels(prevList: Model[], list: Model[]): Model[] {
|
||||
return list.filter(item => !prevList.find(n => n.id === item.id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比上一次和该次 modelList 找出被删除的节点和边
|
||||
* @param prevList
|
||||
* @param list
|
||||
*/
|
||||
protected getRemoveModels(prevList: Model[], list: Model[]): Model[] {
|
||||
return prevList.filter(item => !list.find(n => n.id === item.id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出重新指向的外部指针
|
||||
* @param list
|
||||
* @returns
|
||||
*/
|
||||
protected findReTargetMarkers(list: Model[]): Marker[] {
|
||||
let prevMarkers = this.prevModelList.filter(item => item instanceof Marker),
|
||||
markers = list.filter(item => item instanceof Marker);
|
||||
|
||||
return <Marker[]>markers.filter(item => prevMarkers.find(prevItem => {
|
||||
return prevItem.id === item.id && (<Marker>prevItem).target.id !== (<Marker>item).target.id
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出前后 label 发生变化的 model
|
||||
* @param list
|
||||
*/
|
||||
protected findLabelChangeModels(list: Model[]): Model[] {
|
||||
let labelChangeModels: Model[] = [];
|
||||
|
||||
list.forEach(item => {
|
||||
const prevItem = this.prevModelList.find(prevItem => prevItem.id === item.id);
|
||||
|
||||
if (prevItem === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevLabel = prevItem.get('label'),
|
||||
label = item.get('label');
|
||||
|
||||
if (prevLabel !== label) {
|
||||
labelChangeModels.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return labelChangeModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理新增的 G6Item(主要是动画)
|
||||
* @param appendData
|
||||
*/
|
||||
protected handleAppendModels(appendModels: Model[]) {
|
||||
let counter = 0;
|
||||
|
||||
appendModels.forEach(item => {
|
||||
Animations.animate_append(item, {
|
||||
duration: this.animationsOptions.duration,
|
||||
timingFunction: this.animationsOptions.timingFunction,
|
||||
callback: () => {
|
||||
counter++;
|
||||
|
||||
if (counter === appendModels.length) {
|
||||
this.afterAppendModelsCallbacks.map(item => item(appendModels));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理被移除(也就是泄露)的 G6Item(主要是动画)
|
||||
* @param removeData
|
||||
*/
|
||||
protected handleRemoveModels(removeModels: Model[]) {
|
||||
let counter = 0;
|
||||
|
||||
removeModels.forEach(item => {
|
||||
Animations.animate_remove(item, {
|
||||
duration: this.animationsOptions.duration,
|
||||
timingFunction: this.animationsOptions.timingFunction,
|
||||
callback: () => {
|
||||
if (item.isLeak === false) {
|
||||
this.renderer.removeModel(item);
|
||||
item.renderG6Item = item.G6Item = null;
|
||||
}
|
||||
|
||||
counter++;
|
||||
|
||||
if (counter === removeModels.length) {
|
||||
this.afterRemoveModelsCallbacks.map(item => item(removeModels));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理发生变化的 models
|
||||
* @param models
|
||||
*/
|
||||
protected handleChangeModels(models: Model[]) { }
|
||||
|
||||
/**
|
||||
* 初始化渲染器之后的回调
|
||||
*/
|
||||
protected afterInitRenderer() { }
|
||||
|
||||
// ------------------------------------------ hook ---------------------------------------------
|
||||
|
||||
afterAppendModels(callback: (models: Model[]) => void) {
|
||||
this.afterAppendModelsCallbacks.push(callback);
|
||||
}
|
||||
|
||||
afterRemoveModels(callback: (models: Model[]) => void) {
|
||||
this.afterRemoveModelsCallbacks.push(callback);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param modelList
|
||||
*/
|
||||
public render(modelList: Model[]) {
|
||||
const appendModels: Model[] = this.getAppendModels(this.prevModelList, modelList),
|
||||
removeModels: Model[] = this.getRemoveModels(this.prevModelList, modelList),
|
||||
changeModels: Model[] = [
|
||||
...appendModels,
|
||||
...this.findLabelChangeModels(modelList),
|
||||
...this.findReTargetMarkers(modelList)
|
||||
];
|
||||
|
||||
// 渲染视图
|
||||
this.renderer.render(modelList, removeModels);
|
||||
|
||||
// 处理副作用
|
||||
this.handleAppendModels(appendModels);
|
||||
this.handleRemoveModels(removeModels);
|
||||
this.handleChangeModels(changeModels);
|
||||
|
||||
if (this.renderer.getIsFirstRender()) {
|
||||
this.renderer.setIsFirstRender(false);
|
||||
}
|
||||
|
||||
this.prevModelList = modelList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 g6 实例
|
||||
*/
|
||||
public getG6Instance() {
|
||||
return this.renderer.getG6Instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
public destroy() {
|
||||
this.renderer.destroy();
|
||||
this.DOMContainer = null;
|
||||
this.prevModelList = [];
|
||||
this.animationsOptions = this.interactionOptions = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { BoundingRect } from "../../Common/boundingRect";
|
||||
import { Group } from "../../Common/group";
|
||||
import { Model } from "../../Model/modelData";
|
||||
import { Container } from "./container";
|
||||
|
||||
/**
|
||||
* 释放区可视化视图
|
||||
*/
|
||||
export class FreedContainer extends Container {
|
||||
|
||||
/**
|
||||
* 将释放区的节点位置移到容器中央
|
||||
* @param freedModels
|
||||
*/
|
||||
fitCenter(freedModels: Model[]) {
|
||||
freedModels.forEach(item => {
|
||||
item.G6Item = item.shadowG6Item;
|
||||
});
|
||||
|
||||
let width = this.getG6Instance().getWidth(),
|
||||
height = this.getG6Instance().getHeight(),
|
||||
group = new Group();
|
||||
|
||||
group.add(...freedModels);
|
||||
|
||||
let viewBound: BoundingRect = group.getBound(),
|
||||
centerX = width / 2, centerY = height / 2,
|
||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
||||
dx = centerX - boundCenterX,
|
||||
dy = centerY - boundCenterY;
|
||||
|
||||
group.translate(dx, dy);
|
||||
|
||||
freedModels.forEach(item => {
|
||||
item.G6Item = item.renderG6Item;
|
||||
});
|
||||
}
|
||||
|
||||
protected initBehaviors(): string[] {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Container } from "./container";
|
||||
|
||||
/**
|
||||
* 泄漏区可视化视图
|
||||
*/
|
||||
export class LeakContainer extends Container {
|
||||
|
||||
};
|
||||
@@ -1,203 +0,0 @@
|
||||
import { Engine } from "../../engine";
|
||||
import { Link, Model } from "../../Model/modelData";
|
||||
import { InteractionOptions, LayoutGroupOptions } from "../../options";
|
||||
import { Container } from "./container";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 主可视化视图
|
||||
*/
|
||||
export class MainContainer extends Container {
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any } = { }) {
|
||||
super(engine, DOMContainer, g6Options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param optionsTable
|
||||
* @returns
|
||||
*/
|
||||
protected initBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }) {
|
||||
const dragNodeTable: { [key: string]: boolean | string[] } = { },
|
||||
selectNodeTable: { [key: string]: boolean | string[] } = { },
|
||||
interactionOptions: InteractionOptions = this.engine.interactionOptions,
|
||||
defaultModes = [];
|
||||
|
||||
Object.keys(optionsTable).forEach(item => {
|
||||
dragNodeTable[item] = optionsTable[item].behavior.dragNode;
|
||||
selectNodeTable[item] = optionsTable[item].behavior.selectNode;
|
||||
});
|
||||
|
||||
if(interactionOptions.drag) {
|
||||
defaultModes.push('drag-canvas');
|
||||
}
|
||||
|
||||
if(interactionOptions.zoom) {
|
||||
defaultModes.push('zoom-canvas');
|
||||
}
|
||||
|
||||
const dragNodeFilter = node => {
|
||||
let model = node.item.getModel();
|
||||
|
||||
if(node.item === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(model.SVModelType === 'marker') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let dragNode = optionsTable[model.SVLayouter].behavior.dragNode;
|
||||
|
||||
if(typeof dragNode === 'boolean') {
|
||||
return dragNode;
|
||||
}
|
||||
|
||||
if(Array.isArray(dragNode) && dragNode.indexOf(model.SVModelName) > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const selectNodeFilter = node => {
|
||||
let model = node.item.getModel();
|
||||
|
||||
if(node.item === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(model.SVModelType === 'marker') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let selectNode = optionsTable[model.SVLayouter].behavior.selectNode;
|
||||
|
||||
if(typeof selectNode === 'boolean') {
|
||||
return selectNode;
|
||||
}
|
||||
|
||||
if(Array.isArray(selectNode) && selectNode.indexOf(model.SVModelName) > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
defaultModes.push({
|
||||
type: 'drag-node',
|
||||
shouldBegin: dragNodeFilter
|
||||
});
|
||||
|
||||
defaultModes.push({
|
||||
type: 'click-select',
|
||||
shouldBegin: selectNodeFilter
|
||||
});
|
||||
|
||||
return defaultModes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在初始化渲染器之后,修正节点拖拽时,外部指针没有跟着动的问题
|
||||
* @param dragNodeTable
|
||||
*/
|
||||
protected afterInitRenderer() {
|
||||
let g6Instance = this.getG6Instance(),
|
||||
marker = null,
|
||||
markerX = 0,
|
||||
markerY = 0,
|
||||
dragStartX = 0,
|
||||
dragStartY = 0,
|
||||
element = null;
|
||||
|
||||
g6Instance.on('node:dragstart', ev => {
|
||||
const model = ev.item.getModel();
|
||||
element = ev.item.SVModel;
|
||||
|
||||
if(model.SVModelType === 'marker') {
|
||||
return;
|
||||
}
|
||||
|
||||
const dragNode = this.engine.optionsTable[model.SVLayouter].behavior.dragNode;
|
||||
|
||||
if(dragNode === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(Array.isArray(dragNode) && dragNode.find(item => item === model.SVModelName) === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragStartX = ev.canvasX;
|
||||
dragStartY = ev.canvasY;
|
||||
marker = g6Instance.findById(model.markerId);
|
||||
|
||||
if(marker) {
|
||||
markerX = marker.getModel().x,
|
||||
markerY = marker.getModel().y;
|
||||
}
|
||||
});
|
||||
|
||||
g6Instance.on('node:dragend', ev => {
|
||||
let distanceX = ev.canvasX - dragStartX,
|
||||
distanceY = ev.canvasY - dragStartY,
|
||||
elementX = element.get('x'),
|
||||
elementY = element.get('y');
|
||||
|
||||
element.set({
|
||||
x: elementX + distanceX,
|
||||
y: elementY + distanceY
|
||||
});
|
||||
|
||||
marker = null;
|
||||
markerX = 0,
|
||||
markerY = 0,
|
||||
dragStartX = 0,
|
||||
dragStartY = 0;
|
||||
element = null;
|
||||
});
|
||||
|
||||
g6Instance.on('node:drag', ev => {
|
||||
if(!marker) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dx = ev.canvasX - dragStartX,
|
||||
dy = ev.canvasY - dragStartY,
|
||||
zoom = g6Instance.getZoom();
|
||||
|
||||
marker.updatePosition({
|
||||
x: markerX + dx / zoom,
|
||||
y: markerY + dy / zoom
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected handleChangeModels(models: Model[]) {
|
||||
const changeHighlightColor: string = this.interactionOptions.changeHighlight;
|
||||
|
||||
// 第一次渲染的时候不高亮变化的元素
|
||||
if(this.renderer.getIsFirstRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!changeHighlightColor || typeof changeHighlightColor !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
models.forEach(item => {
|
||||
if(item instanceof Link) {
|
||||
item.set('style', {
|
||||
stroke: changeHighlightColor
|
||||
});
|
||||
}
|
||||
else {
|
||||
item.set('style', {
|
||||
fill: changeHighlightColor
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,30 +1,35 @@
|
||||
import { IPoint } from '@antv/g6-core';
|
||||
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||
import { Group } from '../Common/group';
|
||||
import { Vector } from '../Common/vector';
|
||||
import { Engine } from '../engine';
|
||||
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||
import { Element, Model, Marker } from '../Model/modelData';
|
||||
import { SVMarker } from '../Model/SVMarker';
|
||||
import { SVModel } from '../Model/SVModel';
|
||||
import { SVFreedLabel, SVLeakAddress, SVNode } from '../Model/SVNode';
|
||||
import { LayoutOptions, MarkerOption, ViewOptions } from '../options';
|
||||
import { Container } from './container/container';
|
||||
import { ViewContainer } from './viewContainer';
|
||||
|
||||
|
||||
export class Layouter {
|
||||
export class LayoutProvider {
|
||||
private engine: Engine;
|
||||
private viewOptions: ViewOptions;
|
||||
private viewContainer: ViewContainer;
|
||||
|
||||
constructor(engine: Engine) {
|
||||
constructor(engine: Engine, viewContainer: ViewContainer) {
|
||||
this.engine = engine;
|
||||
this.viewOptions = this.engine.viewOptions;
|
||||
this.viewContainer = viewContainer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化布局参数
|
||||
* @param elements
|
||||
* @param nodes
|
||||
* @param markers
|
||||
*/
|
||||
private initLayoutValue(elements: Element[], markers: Marker[]) {
|
||||
[...elements, ...markers].forEach(item => {
|
||||
private initLayoutValue(nodes: SVNode[], markers: SVMarker[]) {
|
||||
[...nodes, ...markers].forEach(item => {
|
||||
item.set('rotation', item.get('rotation'));
|
||||
item.set({ x: 0, y: 0 });
|
||||
});
|
||||
@@ -35,21 +40,21 @@ export class Layouter {
|
||||
* @param marker
|
||||
* @param markerOptions
|
||||
*/
|
||||
private layoutMarker(markers: Marker[], markerOptions: { [key: string]: MarkerOption }) {
|
||||
private layoutMarker(markers: SVMarker[], markerOptions: { [key: string]: MarkerOption }) {
|
||||
markers.forEach(item => {
|
||||
const options: MarkerOption = markerOptions[item.getType()],
|
||||
const options: MarkerOption = markerOptions[item.sourceType],
|
||||
offset = options.offset ?? 8,
|
||||
anchor = item.anchor ?? 0,
|
||||
labelOffset = options.labelOffset ?? 2;
|
||||
|
||||
let target = item.target,
|
||||
targetBound: BoundingRect = target.getBound(),
|
||||
anchorPosition = item.target.G6Item.getAnchorPoints()[anchor],
|
||||
g6AnchorPosition = item.target.shadowG6Item.getAnchorPoints()[anchor] as IPoint,
|
||||
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
|
||||
markerPosition: [number, number],
|
||||
markerEndPosition: [number, number];
|
||||
|
||||
anchorPosition = [anchorPosition.x, anchorPosition.y];
|
||||
let anchorPosition: [number, number] = [g6AnchorPosition.x, g6AnchorPosition.y];
|
||||
|
||||
let anchorVector = Vector.subtract(anchorPosition, center),
|
||||
angle = 0, len = Vector.length(anchorVector) + offset;
|
||||
@@ -79,23 +84,38 @@ export class Layouter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将视图调整至画布中心
|
||||
* @param container
|
||||
* @param models
|
||||
* 布局节点的‘已释放’文本
|
||||
* @param freedLabels
|
||||
*/
|
||||
private fitCenter(container: Container, group: Group) {
|
||||
let width = container.getG6Instance().getWidth(),
|
||||
height = container.getG6Instance().getHeight(),
|
||||
viewBound: BoundingRect = group.getBound(),
|
||||
centerX = width / 2, centerY = height / 2,
|
||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
||||
dx = centerX - boundCenterX,
|
||||
dy = centerY - boundCenterY;
|
||||
private layoutFreedLabel(freedLabels: SVFreedLabel[]) {
|
||||
freedLabels.forEach(item => {
|
||||
const freedNodeBound = item.node.getBound();
|
||||
|
||||
group.translate(dx, dy);
|
||||
item.set({
|
||||
x: freedNodeBound.x + freedNodeBound.width / 2,
|
||||
y: freedNodeBound.y + freedNodeBound.height * 1.5,
|
||||
size: [freedNodeBound.width, 0]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局泄漏区节点上面的address label
|
||||
* @param leakAddress
|
||||
*/
|
||||
private layoutLeakAddress(leakAddress: SVLeakAddress[]) {
|
||||
leakAddress.forEach(item => {
|
||||
const nodeBound = item.node.getBound();
|
||||
|
||||
item.set({
|
||||
x: nodeBound.x + nodeBound.width / 2,
|
||||
y: nodeBound.y - 16,
|
||||
size: [nodeBound.width, 0]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对每个组内部的model进行布局
|
||||
* @param layoutGroupTable
|
||||
@@ -105,31 +125,57 @@ export class Layouter {
|
||||
|
||||
layoutGroupTable.forEach(group => {
|
||||
const options: LayoutOptions = group.options.layout,
|
||||
modelList: Model[] = group.modelList,
|
||||
modelList: SVModel[] = group.modelList,
|
||||
modelGroup: Group = new Group();
|
||||
|
||||
modelList.forEach(item => {
|
||||
modelGroup.add(item);
|
||||
});
|
||||
|
||||
this.initLayoutValue(group.element, group.marker); // 初始化布局参数
|
||||
group.layouter.layout(group.element, options); // 布局节点
|
||||
this.initLayoutValue(group.node, group.marker); // 初始化布局参数
|
||||
group.layoutCreator.layout(group.node, options); // 布局节点
|
||||
modelGroupList.push(modelGroup);
|
||||
});
|
||||
|
||||
layoutGroupTable.forEach(group => {
|
||||
this.layoutFreedLabel(group.freedLabel);
|
||||
this.layoutLeakAddress(group.leakAddress);
|
||||
this.layoutMarker(group.marker, group.options.marker); // 布局外部指针
|
||||
});
|
||||
|
||||
return modelGroupList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对泄漏区进行布局
|
||||
* @param leakModels
|
||||
* @param accumulateLeakModels
|
||||
*/
|
||||
private layoutLeakModels(leakModels: SVModel[], accumulateLeakModels: SVModel[]) {
|
||||
const group: Group = new Group(),
|
||||
containerHeight = this.viewContainer.getG6Instance().getHeight(),
|
||||
leakAreaHeightRatio = this.engine.viewOptions.leakAreaHeight,
|
||||
leakAreaY = containerHeight * (1 - leakAreaHeightRatio),
|
||||
xOffset = 50;
|
||||
|
||||
group.add(...leakModels);
|
||||
const currentLeakGroupBound: BoundingRect = group.getBound(),
|
||||
globalLeakGroupBound: BoundingRect = accumulateLeakModels.length ?
|
||||
Bound.union(...accumulateLeakModels.map(item => item.getBound())) :
|
||||
{ x: 0, y: leakAreaY, width: 0, height: 0 };
|
||||
|
||||
const { x: groupX, y: groupY } = currentLeakGroupBound,
|
||||
dx = globalLeakGroupBound.x + globalLeakGroupBound.width + xOffset - groupX,
|
||||
dy = globalLeakGroupBound.y - groupY;
|
||||
|
||||
group.translate(dx, dy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有组进行相互布局
|
||||
* @param container
|
||||
* @param modelGroupTable
|
||||
*/
|
||||
private layoutGroups(container: Container, modelGroupList: Group[]): Group {
|
||||
private layoutGroups(modelGroupList: Group[]): Group {
|
||||
let wrapperGroup: Group = new Group(),
|
||||
group: Group,
|
||||
prevBound: BoundingRect,
|
||||
@@ -174,69 +220,45 @@ export class Layouter {
|
||||
return wrapperGroup;
|
||||
}
|
||||
|
||||
public layoutSingleMarker(marker: Marker, markerOptions: { [key: string]: MarkerOption }) {
|
||||
const options: MarkerOption = markerOptions[marker.getType()],
|
||||
offset = options.offset ?? 8,
|
||||
anchor = marker.anchor ?? 0,
|
||||
labelOffset = options.labelOffset ?? 2;
|
||||
|
||||
let target = marker.target,
|
||||
targetBound: BoundingRect = target.getBound(),
|
||||
anchorPosition = marker.target.G6Item.getAnchorPoints()[anchor],
|
||||
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
|
||||
markerPosition: [number, number],
|
||||
markerEndPosition: [number, number];
|
||||
/**
|
||||
* 将视图调整至画布中心
|
||||
* @param models
|
||||
* @param leakAreaHeight
|
||||
*/
|
||||
private fitCenter(group: Group) {
|
||||
let width = this.viewContainer.getG6Instance().getWidth(),
|
||||
height = this.viewContainer.getG6Instance().getHeight(),
|
||||
leakAreaHeightRatio = this.engine.viewOptions.leakAreaHeight;
|
||||
|
||||
anchorPosition = [anchorPosition.x, anchorPosition.y];
|
||||
|
||||
let anchorVector = Vector.subtract(anchorPosition, center),
|
||||
angle = 0, len = Vector.length(anchorVector) + offset;
|
||||
|
||||
if (anchorVector[0] === 0) {
|
||||
angle = anchorVector[1] > 0 ? -Math.PI : 0;
|
||||
}
|
||||
else {
|
||||
angle = Math.sign(anchorVector[0]) * (Math.PI / 2 - Math.atan(anchorVector[1] / anchorVector[0]));
|
||||
if(this.viewContainer.hasLeak) {
|
||||
height = height * (1 - leakAreaHeightRatio);
|
||||
}
|
||||
|
||||
const markerHeight = marker.get('size')[1],
|
||||
labelRadius = marker.getLabelSizeRadius() / 2;
|
||||
const viewBound: BoundingRect = group.getBound(),
|
||||
centerX = width / 2, centerY = height / 2,
|
||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
||||
dx = centerX - boundCenterX,
|
||||
dy = centerY - boundCenterY;
|
||||
|
||||
anchorVector = Vector.normalize(anchorVector);
|
||||
markerPosition = Vector.location(center, anchorVector, len);
|
||||
markerEndPosition = Vector.location(center, anchorVector, markerHeight + len + labelRadius + labelOffset);
|
||||
markerEndPosition = Vector.subtract(markerEndPosition, markerPosition);
|
||||
|
||||
marker.set({
|
||||
x: markerPosition[0],
|
||||
y: markerPosition[1],
|
||||
rotation: angle,
|
||||
markerEndPosition
|
||||
});
|
||||
group.translate(dx, dy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局
|
||||
* @param container
|
||||
* @param layoutGroupTable
|
||||
* @param leakModels
|
||||
* @param hasLeak
|
||||
*/
|
||||
public layoutAll(container: Container, layoutGroupTable: LayoutGroupTable) {
|
||||
layoutGroupTable.forEach(item => {
|
||||
item.modelList.forEach(model => {
|
||||
model.G6Item = model.shadowG6Item;
|
||||
});
|
||||
});
|
||||
|
||||
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], leakModels: SVModel[]) {
|
||||
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
|
||||
const globalGroup: Group = this.layoutGroups(modelGroupList);
|
||||
|
||||
const wrapperGroup: Group = this.layoutGroups(container, modelGroupList);
|
||||
this.fitCenter(container, wrapperGroup);
|
||||
if (leakModels.length) {
|
||||
this.layoutLeakModels(leakModels, accumulateLeakModels);
|
||||
}
|
||||
|
||||
layoutGroupTable.forEach(item => {
|
||||
item.modelList.forEach(model => {
|
||||
model.G6Item = model.renderG6Item;
|
||||
});
|
||||
});
|
||||
this.fitCenter(globalGroup);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
import { EventBus } from "../Common/eventBus";
|
||||
import { Util } from "../Common/util";
|
||||
import { Engine } from "../engine";
|
||||
import { SVLink } from "../Model/SVLink";
|
||||
import { SVMarker } from "../Model/SVMarker";
|
||||
import { SVModel } from "../Model/SVModel";
|
||||
import { SVLeakAddress, SVNode } from "../Model/SVNode";
|
||||
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[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getLeakModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||
const potentialLeakModels: SVModel[] = prevModelList.filter(item =>
|
||||
!modelList.find(model => model.id === item.id) && !item.freed
|
||||
);
|
||||
const leakModels: SVModel[] = [];
|
||||
|
||||
potentialLeakModels.forEach(item => {
|
||||
if (item instanceof SVNode) {
|
||||
item.leaked = true;
|
||||
leakModels.push(item);
|
||||
|
||||
if (item.marker) {
|
||||
item.marker.leaked = true;
|
||||
leakModels.push(item.marker);
|
||||
}
|
||||
|
||||
if(item.freedLabel) {
|
||||
item.marker.leaked = true;
|
||||
leakModels.push(item.freedLabel);
|
||||
}
|
||||
|
||||
item.leakAddress.leaked = true;
|
||||
leakModels.push(item.leakAddress);
|
||||
}
|
||||
});
|
||||
|
||||
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 (prevLabel !== 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 SVLeakAddress) {
|
||||
const leakAddressG6Group = item.G6Item.getContainer();
|
||||
leakAddressG6Group.attr({ opacity: 0 });
|
||||
}
|
||||
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 SVLeakAddress) {
|
||||
Animations.FADE_IN(item.G6Item, {
|
||||
duration,
|
||||
timingFunction
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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 prevLayoutGroupTable
|
||||
* @param layoutGroupTable
|
||||
* @param accumulateLeakModels
|
||||
* @returns
|
||||
*/
|
||||
public diff(prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): DiffResult {
|
||||
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
|
||||
const leakModels: SVModel[] = this.getLeakModels(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;
|
||||
|
||||
// 第一次渲染的时候不高亮变化的元素
|
||||
if (this.isFirstPatch === false) {
|
||||
this.handleChangeModels(UPDATE);
|
||||
}
|
||||
|
||||
this.handleContinuousModels(CONTINUOUS);
|
||||
this.handleFreedModels(FREED);
|
||||
this.handleAppendModels(APPEND);
|
||||
this.handleLeakModels(LEAKED);
|
||||
this.handleRemoveModels(REMOVE);
|
||||
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
|
||||
|
||||
if(this.isFirstPatch) {
|
||||
this.isFirstPatch = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
-71
@@ -1,15 +1,16 @@
|
||||
import { Engine } from '../engine';
|
||||
import { G6EdgeModel, G6NodeModel } from '../Model/modelData';
|
||||
import { SVModel } from '../Model/SVModel';
|
||||
import { Util } from '../Common/util';
|
||||
import { SV } from '../StructV';
|
||||
import { Model } from './../Model/modelData';
|
||||
import G6 from '@antv/g6';
|
||||
import { InitViewBehaviors } from '../BehaviorHelper/initViewBehaviors';
|
||||
import { Graph, GraphData, IGroup } from '@antv/g6-pc';
|
||||
|
||||
|
||||
|
||||
export interface G6Data {
|
||||
nodes: G6NodeModel[];
|
||||
edges: G6EdgeModel[];
|
||||
};
|
||||
export interface RenderModelPack {
|
||||
leaKModels: SVModel[];
|
||||
generalModel: SVModel[];
|
||||
}
|
||||
|
||||
|
||||
export type g6Behavior = string | { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function; };
|
||||
@@ -17,26 +18,34 @@ export type g6Behavior = string | { type: string; shouldBegin?: Function; should
|
||||
|
||||
export class Renderer {
|
||||
private engine: Engine;
|
||||
private g6Instance: Graph; // g6 实例
|
||||
private shadowG6Instance: Graph;
|
||||
|
||||
private DOMContainer: HTMLElement; // 主可视化视图容器
|
||||
private g6Instance; // g6 实例
|
||||
|
||||
private prevRenderModelList: Model[] = null;
|
||||
private isFirstRender: boolean; // 是否为第一次渲染
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any }) {
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||
this.engine = engine;
|
||||
this.DOMContainer = DOMContainer;
|
||||
this.isFirstRender = true;
|
||||
|
||||
const enable: boolean = this.engine.animationOptions.enable,
|
||||
duration: number = this.engine.animationOptions.duration,
|
||||
timingFunction: string = this.engine.animationOptions.timingFunction;
|
||||
duration: number = this.engine.animationOptions.duration,
|
||||
timingFunction: string = this.engine.animationOptions.timingFunction;
|
||||
|
||||
const tooltip = new G6.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 G6.Graph({
|
||||
container: DOMContainer.cloneNode() as HTMLElement
|
||||
});
|
||||
|
||||
// 初始化g6实例
|
||||
this.g6Instance = new SV.G6.Graph({
|
||||
this.g6Instance = new G6.Graph({
|
||||
container: DOMContainer,
|
||||
width: DOMContainer.offsetWidth,
|
||||
width: DOMContainer.offsetWidth,
|
||||
height: DOMContainer.offsetHeight,
|
||||
groupByTypes: false,
|
||||
animate: enable,
|
||||
@@ -46,68 +55,74 @@ export class Renderer {
|
||||
},
|
||||
fitView: false,
|
||||
modes: {
|
||||
default: []
|
||||
default: InitViewBehaviors(this.engine.optionsTable)
|
||||
},
|
||||
...g6Options
|
||||
plugins: [tooltip]
|
||||
});
|
||||
}
|
||||
|
||||
public getIsFirstRender(): boolean {
|
||||
return this.isFirstRender;
|
||||
/**
|
||||
* 创造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;
|
||||
}
|
||||
|
||||
Object.keys(items).map(key => {
|
||||
let value = model[items[key]];
|
||||
if (value !== undefined && value !== null) {
|
||||
let item = document.createElement('div');
|
||||
item.innerHTML = `${key}:${value}`;
|
||||
wrapper.appendChild(item);
|
||||
}
|
||||
});
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public setIsFirstRender(value: boolean) {
|
||||
this.isFirstRender = value;
|
||||
/**
|
||||
* 对每一个 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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param renderModelList
|
||||
* @param isFirstRender
|
||||
*/
|
||||
public render(renderModelList: SVModel[]) {
|
||||
const renderData: GraphData = Util.convertModelList2G6Data(renderModelList);
|
||||
|
||||
this.g6Instance.changeData(renderData);
|
||||
|
||||
renderModelList.forEach(item => {
|
||||
item.G6Item = this.g6Instance.findById(item.id);
|
||||
item.G6Item['SVModel'] = item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从视图中移除一个 Model
|
||||
* @param model
|
||||
*/
|
||||
public removeModel(model: Model) {
|
||||
this.g6Instance.removeItem(model.renderG6Item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param modelList
|
||||
*/
|
||||
public render(modelList: Model[], removeModels: Model[]) {
|
||||
const renderModelList = [...modelList, ...removeModels],
|
||||
renderData: G6Data = Util.convertModelList2G6Data(renderModelList);
|
||||
|
||||
if(this.isFirstRender) {
|
||||
this.g6Instance.read(renderData);
|
||||
}
|
||||
else {
|
||||
this.g6Instance.changeData(renderData);
|
||||
}
|
||||
|
||||
if(this.engine.viewOptions.fitView) {
|
||||
this.g6Instance.fitView();
|
||||
}
|
||||
|
||||
modelList.forEach(item => {
|
||||
item.renderG6Item = this.g6Instance.findById(item.id);
|
||||
|
||||
if(item.renderG6Item) {
|
||||
item.G6Item = item.renderG6Item;
|
||||
item.renderG6Item.SVModel = item;
|
||||
}
|
||||
});
|
||||
|
||||
// 将上一次的model全部标记为已销毁
|
||||
if(this.prevRenderModelList) {
|
||||
this.prevRenderModelList.forEach(item => { item.isDestroy = true; });
|
||||
}
|
||||
|
||||
this.prevRenderModelList = renderModelList;
|
||||
|
||||
// 把所有连线置顶
|
||||
if(this.isFirstRender) {
|
||||
this.g6Instance.getEdges().forEach(item => item.toFront());
|
||||
this.g6Instance.paint();
|
||||
}
|
||||
public removeModel(model: SVModel) {
|
||||
this.g6Instance.removeItem(model.G6Item);
|
||||
this.shadowG6Instance.removeItem(model.shadowG6Item);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +136,7 @@ export class Renderer {
|
||||
* 销毁
|
||||
*/
|
||||
public destroy() {
|
||||
this.shadowG6Instance.destroy();
|
||||
this.g6Instance.destroy();
|
||||
this.DOMContainer = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
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 { FixNodeMarkerDrag } from "../BehaviorHelper/fixNodeMarkerDrag";
|
||||
import { InitDragCanvasWithLeak } from "../BehaviorHelper/dragCanavsWithLeak";
|
||||
import { EventBus } from "../Common/eventBus";
|
||||
import { Group } from "../Common/group";
|
||||
|
||||
|
||||
|
||||
export class ViewContainer {
|
||||
private engine: Engine;
|
||||
private layoutProvider: LayoutProvider;
|
||||
private reconcile: Reconcile;
|
||||
public renderer: Renderer;
|
||||
|
||||
private prevLayoutGroupTable: LayoutGroupTable;
|
||||
private prevModelList: SVModel[];
|
||||
private accumulateLeakModels: SVModel[];
|
||||
|
||||
public hasLeak: boolean;
|
||||
public leakAreaY: number;
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||
this.engine = engine;
|
||||
this.layoutProvider = new LayoutProvider(engine, this);
|
||||
this.renderer = new Renderer(engine, DOMContainer);
|
||||
this.reconcile = new Reconcile(engine, this.renderer);
|
||||
this.prevLayoutGroupTable = new Map();
|
||||
this.prevModelList = [];
|
||||
this.accumulateLeakModels = [];
|
||||
this.hasLeak = false; // 判断是否已经发生过泄漏
|
||||
|
||||
const g6Instance = this.renderer.getG6Instance(),
|
||||
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||
height = this.getG6Instance().getHeight(),
|
||||
{ drag, zoom } = this.engine.interactionOptions;
|
||||
|
||||
this.leakAreaY = height * (1 - leakAreaHeight);
|
||||
|
||||
if (drag) {
|
||||
InitDragCanvasWithLeak(this);
|
||||
}
|
||||
|
||||
if (zoom) {
|
||||
// InitZoomCanvas(g6Instance, g6GeneralGroup);
|
||||
}
|
||||
|
||||
FixNodeMarkerDrag(g6Instance, this.engine.optionsTable);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 对主视图进行重新布局
|
||||
*/
|
||||
reLayout() {
|
||||
this.layoutProvider.layoutAll(this.prevLayoutGroupTable, [], this.accumulateLeakModels);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 g6 实例
|
||||
*/
|
||||
getG6Instance() {
|
||||
return this.renderer.getG6Instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新视图
|
||||
*/
|
||||
refresh() {
|
||||
this.renderer.getG6Instance().refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新调整容器尺寸
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
resize(width: number, height: number) {
|
||||
this.renderer.getG6Instance().changeSize(width, height);
|
||||
|
||||
const containerHeight = this.getG6Instance().getHeight(),
|
||||
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||
targetY = containerHeight * (1 - leakAreaHeight);
|
||||
|
||||
const accumulateLeakGroup = new Group();
|
||||
accumulateLeakGroup.add(...this.accumulateLeakModels);
|
||||
accumulateLeakGroup.translate(0, targetY - this.leakAreaY);
|
||||
this.leakAreaY = targetY;
|
||||
|
||||
EventBus.emit('onLeakAreaUpdate', {
|
||||
leakAreaY: this.leakAreaY,
|
||||
hasLeak: this.hasLeak
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染所有视图
|
||||
* @param models
|
||||
* @param layoutFn
|
||||
*/
|
||||
render(layoutGroupTable: LayoutGroupTable) {
|
||||
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
|
||||
diffResult = this.reconcile.diff(this.prevModelList, modelList, this.accumulateLeakModels),
|
||||
renderModelList = [
|
||||
...modelList,
|
||||
...diffResult.REMOVE,
|
||||
...diffResult.LEAKED,
|
||||
...diffResult.ACCUMULATE_LEAK
|
||||
];
|
||||
|
||||
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
|
||||
this.hasLeak = false;
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
|
||||
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels, diffResult.LEAKED); // 进行布局(设置model的x,y,样式等)
|
||||
|
||||
this.beforeRender();
|
||||
this.renderer.render(renderModelList); // 渲染视图
|
||||
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
|
||||
this.afterRender();
|
||||
|
||||
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行累积
|
||||
|
||||
this.prevLayoutGroupTable = layoutGroupTable;
|
||||
this.prevModelList = modelList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* 把渲染前要触发的逻辑放在这里
|
||||
*/
|
||||
private afterRender() {
|
||||
const g6Instance = this.renderer.getG6Instance();
|
||||
|
||||
// 把所有连线置顶
|
||||
g6Instance.getEdges().forEach(item => item.toFront());
|
||||
g6Instance.paint();
|
||||
|
||||
this.prevModelList.forEach(item => {
|
||||
if (item.leaked === false) {
|
||||
item.discarded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 把渲染后要触发的逻辑放在这里
|
||||
*/
|
||||
private beforeRender() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user