feature:添加泄漏区功能部分
This commit is contained in:
+28
-29
@@ -1,3 +1,4 @@
|
||||
import { Model } from "../Model/modelData";
|
||||
import { SV } from "../StructV";
|
||||
|
||||
|
||||
@@ -5,37 +6,32 @@ import { SV } from "../StructV";
|
||||
/**
|
||||
* 动画表
|
||||
*/
|
||||
export class Animations {
|
||||
private duration: number;
|
||||
private timingFunction: string;
|
||||
private mat3 = SV.G6.Util.mat3;
|
||||
|
||||
constructor(duration: number, timingFunction: string) {
|
||||
this.duration = duration;
|
||||
this.timingFunction = timingFunction;
|
||||
}
|
||||
|
||||
export const Animations = {
|
||||
|
||||
/**
|
||||
* 添加节点 / 边时的动画效果
|
||||
* @param G6Item
|
||||
* @param model
|
||||
* @param duration
|
||||
* @param timingFunction
|
||||
* @param callback
|
||||
*/
|
||||
append(G6Item, callback: Function = null) {
|
||||
const type = G6Item.getType(),
|
||||
animate_append(model: Model, duration: number, timingFunction: string, callback: Function = null) {
|
||||
const G6Item = model.G6Item,
|
||||
type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = SV.Mat3,
|
||||
animateCfg = {
|
||||
duration: this.duration,
|
||||
easing: this.timingFunction,
|
||||
duration: duration,
|
||||
easing: timingFunction,
|
||||
callback
|
||||
};
|
||||
|
||||
if(type === 'node') {
|
||||
let mat3 = this.mat3,
|
||||
matrix = group.getMatrix(),
|
||||
targetMatrix = mat3.clone(matrix);
|
||||
let matrix = group.getMatrix(),
|
||||
targetMatrix = Mat3.clone(matrix);
|
||||
|
||||
mat3.scale(matrix, matrix, [0, 0]);
|
||||
mat3.scale(targetMatrix, targetMatrix, [1, 1]);
|
||||
Mat3.scale(matrix, matrix, [0, 0]);
|
||||
Mat3.scale(targetMatrix, targetMatrix, [1, 1]);
|
||||
|
||||
group.attr({ opacity: 0, matrix });
|
||||
group.animate({ opacity: 1, matrix: targetMatrix }, animateCfg);
|
||||
@@ -48,27 +44,30 @@ export class Animations {
|
||||
line.attr({ lineDash: [0, length], opacity: 0 });
|
||||
line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除节点 / 边时的动画效果
|
||||
* @param G6Item
|
||||
* @param model
|
||||
* @param duration
|
||||
* @param timingFunction
|
||||
* @param callback
|
||||
*/
|
||||
remove(G6Item, callback: Function = null) {
|
||||
const type = G6Item.getType(),
|
||||
animate_remove(model: Model, duration: number, timingFunction: string, callback: Function = null) {
|
||||
const G6Item = model.G6Item,
|
||||
type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = SV.Mat3,
|
||||
animateCfg = {
|
||||
duration: this.duration,
|
||||
easing: this.timingFunction,
|
||||
duration: duration,
|
||||
easing: timingFunction,
|
||||
callback
|
||||
};
|
||||
|
||||
if(type === 'node') {
|
||||
let mat3 = this.mat3,
|
||||
matrix = mat3.clone(group.getMatrix());
|
||||
let matrix = Mat3.clone(group.getMatrix());
|
||||
|
||||
mat3.scale(matrix, matrix, [0, 0]);
|
||||
Mat3.scale(matrix, matrix, [0, 0]);
|
||||
group.animate({ opacity: 0, matrix }, animateCfg);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
import { Engine } from "../engine";
|
||||
import { Util } from "./../Common/util";
|
||||
|
||||
export class Behavior {
|
||||
private engine: Engine;
|
||||
private graphInstance;
|
||||
|
||||
constructor(engine: Engine, graphInstance) {
|
||||
this.engine = engine;
|
||||
this.graphInstance = graphInstance;
|
||||
|
||||
const interactionOptions = this.engine.interactionOptions,
|
||||
selectNode: boolean | string[] = interactionOptions.selectNode;
|
||||
|
||||
if(interactionOptions.dragNode) {
|
||||
this.initDragNode();
|
||||
}
|
||||
|
||||
if(interactionOptions.selectNode) {
|
||||
this.initSelectNode(selectNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化节点拖拽事件
|
||||
*/
|
||||
private initDragNode() {
|
||||
let pointer = null,
|
||||
pointerX = null,
|
||||
pointerY = null,
|
||||
dragStartX = null,
|
||||
dragStartY = null;
|
||||
|
||||
this.graphInstance.on('node:dragstart', ev => {
|
||||
pointer = this.graphInstance.findById(ev.item.getModel().externalPointerId);
|
||||
|
||||
if(pointer) {
|
||||
pointerX = pointer.getModel().x,
|
||||
pointerY = pointer.getModel().y;
|
||||
dragStartX = ev.canvasX;
|
||||
dragStartY = ev.canvasY;
|
||||
}
|
||||
});
|
||||
|
||||
this.graphInstance.on('node:dragend', ev => {
|
||||
pointer = null;
|
||||
pointerX = null,
|
||||
pointerY = null,
|
||||
dragStartX = null,
|
||||
dragStartY = null;
|
||||
});
|
||||
|
||||
this.graphInstance.on('node:drag', ev => {
|
||||
if(!pointer) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dx = ev.canvasX - dragStartX,
|
||||
dy = ev.canvasY - dragStartY,
|
||||
zoom = this.graphInstance.getZoom();
|
||||
|
||||
pointer.updatePosition({
|
||||
x: pointerX + dx / zoom,
|
||||
y: pointerY + dy / zoom
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化节/边选中
|
||||
* @param selectNode
|
||||
*/
|
||||
private initSelectNode(selectNode: boolean | string[]) {
|
||||
let defaultHighlightColor = '#f08a5d',
|
||||
curSelectItem = null,
|
||||
curSelectItemStyle = null;
|
||||
|
||||
if(selectNode === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectCallback = ev => {
|
||||
const item = ev.item,
|
||||
model = item.getModel(),
|
||||
type = item.getType(),
|
||||
name = model.modelName,
|
||||
highlightColor = model.style.selectedColor;
|
||||
|
||||
if(Array.isArray(selectNode) && selectNode.find(item => item === name) === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(curSelectItem && curSelectItem !== item) {
|
||||
curSelectItem.update({
|
||||
style: curSelectItemStyle
|
||||
});
|
||||
}
|
||||
|
||||
curSelectItem = item;
|
||||
curSelectItemStyle = Util.objectClone(curSelectItem.getModel().style);
|
||||
curSelectItem.update({
|
||||
style: {
|
||||
...curSelectItemStyle,
|
||||
[type === 'node'? 'fill': 'stroke']: highlightColor || defaultHighlightColor
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.graphInstance.on('node:click', selectCallback);
|
||||
this.graphInstance.on('edge:click', selectCallback);
|
||||
this.graphInstance.on('click', ev => {
|
||||
if(curSelectItem === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
curSelectItem.update({
|
||||
style: curSelectItemStyle
|
||||
});
|
||||
|
||||
curSelectItem = null;
|
||||
curSelectItemStyle = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定 G6 事件
|
||||
* @param eventName
|
||||
* @param callback
|
||||
*/
|
||||
public on(eventName: string, callback: Function) {
|
||||
if(this.graphInstance) {
|
||||
this.graphInstance.on(eventName, callback)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,140 +0,0 @@
|
||||
import { Vector } from "../Common/vector";
|
||||
|
||||
|
||||
|
||||
// 包围盒类型
|
||||
export type BoundingRect = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
|
||||
// 包围盒操作
|
||||
export const Bound = {
|
||||
|
||||
/**
|
||||
* 从点集生成包围盒
|
||||
* @param points
|
||||
*/
|
||||
fromPoints(points: Array<[number, number]>): BoundingRect {
|
||||
let maxX = -Infinity,
|
||||
minX = Infinity,
|
||||
maxY = -Infinity,
|
||||
minY = Infinity;
|
||||
|
||||
points.map(item => {
|
||||
if(item[0] > maxX) maxX = item[0];
|
||||
if(item[0] < minX) minX = item[0];
|
||||
if(item[1] > maxY) maxY = item[1];
|
||||
if(item[1] < minY) minY = item[1];
|
||||
});
|
||||
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 由包围盒转化为四个顶点(顺时针)
|
||||
* @param bound
|
||||
*/
|
||||
toPoints(bound: BoundingRect): Array<[number, number]> {
|
||||
return [
|
||||
[bound.x, bound.y],
|
||||
[bound.x + bound.width, bound.y],
|
||||
[bound.x + bound.width, bound.y + bound.height],
|
||||
[bound.x, bound.y + bound.height]
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* 求包围盒并集
|
||||
* @param arg
|
||||
*/
|
||||
union(...arg: BoundingRect[]): BoundingRect {
|
||||
return arg.length > 1?
|
||||
arg.reduce((total, cur) => {
|
||||
let minX = total.x < cur.x? total.x: cur.x,
|
||||
maxX = total.x + total.width < cur.x + cur.width? cur.x + cur.width: total.x + total.width,
|
||||
minY = total.y < cur.y? total.y: cur.y,
|
||||
maxY = total.y + total.height < cur.y + cur.height? cur.y + cur.height: total.y + total.height;
|
||||
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY
|
||||
};
|
||||
}): arg[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* 包围盒求交集
|
||||
* @param b1
|
||||
* @param b2
|
||||
*/
|
||||
intersect(b1: BoundingRect, b2: BoundingRect): BoundingRect {
|
||||
let x, y,
|
||||
maxX, maxY,
|
||||
overlapsX,
|
||||
overlapsY;
|
||||
|
||||
if(b1.x < b2.x + b2.width && b1.x + b1.width > b2.x) {
|
||||
x = b1.x < b2.x? b2.x: b1.x;
|
||||
maxX = b1.x + b1.width < b2.x + b2.width? b1.x + b1.width: b2.x + b2.width;
|
||||
overlapsX = maxX - x;
|
||||
}
|
||||
|
||||
if(b1.y < b2.y + b2.height && b1.y + b1.height > b2.y) {
|
||||
y = b1.y < b2.y? b2.y: b1.y;
|
||||
maxY = b1.y + b1.height < b2.y + b2.height? b1.y + b1.height: b2.y + b2.height;
|
||||
overlapsY = maxY - y;
|
||||
}
|
||||
|
||||
if(!overlapsX || !overlapsY) return null;
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: overlapsX,
|
||||
height: overlapsY
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 求包围盒旋转后新形成的包围盒
|
||||
* @param bound
|
||||
* @param rot
|
||||
*/
|
||||
rotation(bound: BoundingRect, rot: number): BoundingRect {
|
||||
let cx = bound.x + bound.width / 2,
|
||||
cy = bound.y + bound.height / 2;
|
||||
|
||||
return Bound.fromPoints(Bound.toPoints(bound).map(item => Vector.rotation(rot, item, [cx, cy])));
|
||||
},
|
||||
|
||||
/**
|
||||
* 判断两个包围盒是否相交
|
||||
* @param b1
|
||||
* @param b2
|
||||
*/
|
||||
isOverlap(b1: BoundingRect, b2: BoundingRect): boolean {
|
||||
let maxX1 = b1.x + b1.width,
|
||||
maxY1 = b1.y + b1.height,
|
||||
maxX2 = b2.x + b2.width,
|
||||
maxY2 = b2.y + b2.height;
|
||||
|
||||
if (b1.x < maxX2 && b2.x < maxX1 && b1.y < maxY2 && b2.y < maxY1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import { Bound, BoundingRect } from "../../Common/boundingRect";
|
||||
import { Engine } from "../../engine";
|
||||
import { ConstructList } from "../../Model/modelConstructor";
|
||||
import { Element, Model, Pointer } from "../../Model/modelData";
|
||||
import { AnimationOptions, InteractionOptions, LayoutOptions } from "../../options";
|
||||
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.renderer = new Renderer(engine, DOMContainer, {
|
||||
...g6Options,
|
||||
modes: {
|
||||
default: this.initBehaviors()
|
||||
}
|
||||
});
|
||||
this.prevModelList = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化交互行为
|
||||
* @returns
|
||||
*/
|
||||
protected initBehaviors(): 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 findReTargetPointer(list: Model[]): Pointer[] {
|
||||
let prevPointers = this.prevModelList.filter(item => item instanceof Pointer),
|
||||
pointers = list.filter(item => item instanceof Pointer);
|
||||
|
||||
return <Pointer[]>pointers.filter(item => prevPointers.find(prevItem => {
|
||||
return prevItem.id === item.id && (<Pointer>prevItem).target.id !== (<Pointer>item).target.id
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理新增的 G6Item(主要是动画)
|
||||
* @param appendData
|
||||
*/
|
||||
protected handleAppendModels(appendModels: Model[]) {
|
||||
let counter = 0;
|
||||
|
||||
appendModels.forEach(item => {
|
||||
Animations.animate_append(item, this.animationsOptions.duration, this.animationsOptions.timingFunction, () => {
|
||||
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, this.animationsOptions.duration, this.animationsOptions.timingFunction, () => {
|
||||
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[]) { }
|
||||
|
||||
// ------------------------------------------ hook ---------------------------------------------
|
||||
|
||||
afterAppendModels(callback: (models: Model[]) => void) {
|
||||
this.afterAppendModelsCallbacks.push(callback);
|
||||
}
|
||||
|
||||
afterRemoveModels(callback: (models: Model[]) => void) {
|
||||
this.afterRemoveModelsCallbacks.push(callback);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param modelList
|
||||
* @param layoutFn
|
||||
*/
|
||||
public render(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||
const modelList: Model[] = [...constructList.element, ...constructList.link, ...constructList.pointer],
|
||||
appendModels: Model[] = this.getAppendModels(this.prevModelList, modelList),
|
||||
removeModels: Model[] = this.getRemoveModels(this.prevModelList, modelList),
|
||||
changeModels: Model[] = [...appendModels, ...this.findReTargetPointer(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Container } from "./container";
|
||||
|
||||
/**
|
||||
* 释放区可视化视图
|
||||
*/
|
||||
export class FreedContainer extends Container { };
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Container } from "./container";
|
||||
|
||||
/**
|
||||
* 泄漏区可视化视图
|
||||
*/
|
||||
export class LeakContainer extends Container { };
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Link, Model } from "../../Model/modelData";
|
||||
import { Container } from "./container";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 主可视化视图
|
||||
*/
|
||||
export class MainContainer extends Container {
|
||||
|
||||
protected initBehaviors() {
|
||||
const interactionOptions = this.interactionOptions,
|
||||
dragNode: boolean | string[] = interactionOptions.dragNode,
|
||||
dragNodeFilter = node => {
|
||||
let model = node.item.getModel();
|
||||
|
||||
if(node.item === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(model.modelType === 'pointer') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(typeof dragNode === 'boolean') {
|
||||
return dragNode;
|
||||
}
|
||||
|
||||
if(Array.isArray(dragNode) && dragNode.indexOf(model.modelName) > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const modeMap = {
|
||||
drag: 'drag-canvas',
|
||||
zoom: 'zoom-canvas',
|
||||
dragNode: {
|
||||
type: 'drag-node',
|
||||
shouldBegin: node => dragNodeFilter(node)
|
||||
}
|
||||
},
|
||||
defaultModes = [];
|
||||
|
||||
Object.keys(interactionOptions).forEach(item => {
|
||||
if(interactionOptions[item] && modeMap[item] !== undefined) {
|
||||
defaultModes.push(modeMap[item]);
|
||||
}
|
||||
});
|
||||
|
||||
return defaultModes;
|
||||
}
|
||||
|
||||
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,101 +0,0 @@
|
||||
import { Util } from "../Common/util";
|
||||
import { BoundingRect, Bound } from "../View/boundingRect";
|
||||
import { Vector } from "../Common/vector";
|
||||
import { Element } from "../Model/modelData";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* element组
|
||||
*/
|
||||
export class Group {
|
||||
id: string;
|
||||
private elements: Array<Element | Group> = [];
|
||||
|
||||
constructor(...arg: Array<Element | Group>) {
|
||||
this.id = Util.generateId();
|
||||
|
||||
if(arg) {
|
||||
this.add(...arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加element
|
||||
* @param arg
|
||||
*/
|
||||
add(...arg: Array<Element | Group>) {
|
||||
arg.map(ele => {
|
||||
this.elements.push(ele);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除element
|
||||
* @param element
|
||||
*/
|
||||
remove(element: Element | Group) {
|
||||
Util.removeFromList(this.elements, item => item.id === element.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取group的包围盒
|
||||
*/
|
||||
getBound(): BoundingRect {
|
||||
return Bound.union(...this.elements.map(item => item.getBound()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 位移group
|
||||
* @param dx
|
||||
* @param dy
|
||||
*/
|
||||
translate(dx: number, dy: number) {
|
||||
this.elements.map(item => {
|
||||
if(item instanceof Group) {
|
||||
item.translate(dx, dy);
|
||||
}
|
||||
else {
|
||||
item.set('x', item.get('x') + dx);
|
||||
item.set('y', item.get('y') + dy);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转group
|
||||
* @param rotation
|
||||
* @param center
|
||||
*/
|
||||
rotate(rotation: number, center?: [number, number]) {
|
||||
// if(rotation === 0) return;
|
||||
|
||||
// let {x, y, width, height} = this.getBound(),
|
||||
// cx = x + width / 2,
|
||||
// cy = y + height / 2;
|
||||
|
||||
// if(center) {
|
||||
// cx = center[0];
|
||||
// cy = center[1];
|
||||
// }
|
||||
|
||||
// this.elements.map(item => {
|
||||
// if(item instanceof Group) {
|
||||
// item.rotate(rotation, [cx, cy]);
|
||||
// }
|
||||
// else {
|
||||
// let d = Vector.rotation(rotation, [item.x, item.y], [cx, cy]);
|
||||
// item.x = d[0];
|
||||
// item.y = d[1];
|
||||
// item.set('rotation', rotation);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空group
|
||||
*/
|
||||
clear() {
|
||||
this.elements.length = 0;
|
||||
}
|
||||
}
|
||||
+63
-47
@@ -1,10 +1,9 @@
|
||||
import { Engine } from "../engine";
|
||||
import { ConstructedData } from "../Model/modelConstructor";
|
||||
import { Element, Model, Pointer } from "../Model/modelData";
|
||||
import { LayoutOptions, PointerOption } from "../options";
|
||||
import { Bound, BoundingRect } from "./boundingRect";
|
||||
|
||||
|
||||
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||
import { Engine } from '../engine';
|
||||
import { ConstructList } from '../Model/modelConstructor';
|
||||
import { Element, Model, Pointer } from '../Model/modelData';
|
||||
import { LayoutOptions, PointerOption } from '../options';
|
||||
import { Container } from './container/container';
|
||||
|
||||
|
||||
export class Layouter {
|
||||
@@ -14,15 +13,54 @@ export class Layouter {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化布局参数
|
||||
* @param elements
|
||||
* @param pointers
|
||||
*/
|
||||
private initLayoutValue(elements: Element[], pointers: Pointer[]) {
|
||||
[...elements, ...pointers].forEach(item => {
|
||||
item.set('rotation', item.get('rotation'));
|
||||
item.set({ x: 0, y: 0 });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局外部指针
|
||||
* @param pointer
|
||||
*/
|
||||
private layoutPointer(pointers: Pointer[]) {
|
||||
pointers.forEach(item => {
|
||||
const options: PointerOption = this.engine.pointerOptions[item.getType()],
|
||||
offset = options.offset || 8,
|
||||
anchor = options.anchor || 0;
|
||||
|
||||
let target = item.target,
|
||||
targetBound: BoundingRect = item.target.getBound(),
|
||||
anchorPosition = item.target.G6Item.getAnchorPoints()[anchor];
|
||||
|
||||
item.set({
|
||||
x: targetBound.x + targetBound.width / 2,
|
||||
y: targetBound.y - offset
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将视图调整至画布中心
|
||||
* @param nodes
|
||||
* @param container
|
||||
* @param models
|
||||
*/
|
||||
private fitCenter(models: Model[]) {
|
||||
private fitCenter(container: Container, models: Model[]) {
|
||||
if(models.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewBound: BoundingRect = models.map(item => item.getBound()).reduce((prev, cur) => Bound.union(prev, cur));
|
||||
|
||||
let width = this.engine.getGraphInstance().getWidth(),
|
||||
height = this.engine.getGraphInstance().getHeight(),
|
||||
let width = container.getG6Instance().getWidth(),
|
||||
height = container.getG6Instance().getHeight(),
|
||||
centerX = width / 2, centerY = height / 2,
|
||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
||||
@@ -38,55 +76,33 @@ export class Layouter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局外部指针
|
||||
* @param pointer
|
||||
*/
|
||||
private layoutPointer(pointer: { [key: string]: Pointer[] }) {
|
||||
Object.keys(pointer).map(name => {
|
||||
const options: PointerOption = this.engine.pointerOptions[name],
|
||||
pointerList: Pointer[] = pointer[name],
|
||||
offset = options.offset || 8;
|
||||
|
||||
pointerList.forEach(item => {
|
||||
let targetBound: BoundingRect = item.target.getBound();
|
||||
item.set({
|
||||
x: targetBound.x + targetBound.width / 2,
|
||||
y: targetBound.y - offset
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 主布局函数
|
||||
* @param constructedData
|
||||
* @param modelList
|
||||
* 进行布局
|
||||
* @param container
|
||||
* @param constructList
|
||||
* @param layoutFn
|
||||
*/
|
||||
public layout(constructedData: ConstructedData, modelList: Model[], layoutFn: (element: { [ket: string]: Element[] }, layoutOptions: LayoutOptions) => void) {
|
||||
const options: LayoutOptions = this.engine.layoutOptions;
|
||||
|
||||
public layout(container: Container, constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||
const options: LayoutOptions = this.engine.layoutOptions,
|
||||
modelList: Model[] = [...constructList.element, ...constructList.pointer, ...constructList.link];
|
||||
|
||||
// 首先初始化所有节点的坐标为0,且设定旋转
|
||||
modelList.forEach(item => {
|
||||
item.G6Item = item.shadowG6Item;
|
||||
|
||||
if(item.modelType === 'element' || item.modelType === 'pointer') {
|
||||
item.set('rotation', item.get('rotation'));
|
||||
item.set({ x: 0, y: 0 });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 初始化布局参数
|
||||
this.initLayoutValue(constructList.element, constructList.pointer);
|
||||
// 布局节点
|
||||
layoutFn.call(this.engine, constructedData.element, options);
|
||||
|
||||
layoutFn(constructList.element, options);
|
||||
// 布局外部指针
|
||||
this.layoutPointer(constructedData.pointer);
|
||||
this.layoutPointer(constructList.pointer);
|
||||
|
||||
// 将视图调整到画布中心
|
||||
options.fitCenter && this.fitCenter(modelList);
|
||||
options.fitCenter && this.fitCenter(container, modelList);
|
||||
|
||||
modelList.forEach(item => {
|
||||
item.G6Item = item.renderG6Item;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+51
-203
@@ -1,8 +1,6 @@
|
||||
import { Engine } from '../engine';
|
||||
import { Element, G6EdgeModel, G6NodeModel, Link, Pointer } from '../Model/modelData';
|
||||
import { ConstructedData } from '../Model/modelConstructor';
|
||||
import { G6EdgeModel, G6NodeModel } from '../Model/modelData';
|
||||
import { Util } from '../Common/util';
|
||||
import { Animations } from './animation';
|
||||
import { SV } from '../StructV';
|
||||
import { Model } from './../Model/modelData';
|
||||
|
||||
@@ -14,32 +12,28 @@ export interface G6Data {
|
||||
};
|
||||
|
||||
|
||||
export type g6Behavior = string | { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function; };
|
||||
|
||||
|
||||
export class Renderer {
|
||||
private engine: Engine;
|
||||
private DOMContainer: HTMLElement;
|
||||
private animations: Animations;
|
||||
private isFirstRender: boolean;
|
||||
private prevRenderData: G6Data;
|
||||
private graphInstance;
|
||||
private shadowGraphInstance;
|
||||
private modelList: Model[];
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||
private DOMContainer: HTMLElement; // 主可视化视图容器
|
||||
private g6Instance; // g6 实例
|
||||
|
||||
private isFirstRender: boolean; // 是否为第一次渲染
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any }) {
|
||||
this.engine = engine;
|
||||
this.DOMContainer = DOMContainer;
|
||||
this.isFirstRender = true;
|
||||
this.modelList = [];
|
||||
this.prevRenderData = {
|
||||
nodes: [],
|
||||
edges: []
|
||||
};
|
||||
|
||||
const enable: boolean = this.engine.animationOptions.enable === undefined? true: this.engine.animationOptions.enable,
|
||||
const enable: boolean = this.engine.animationOptions.enable,
|
||||
duration: number = this.engine.animationOptions.duration,
|
||||
timingFunction: string = this.engine.animationOptions.timingFunction;
|
||||
|
||||
this.graphInstance = new SV.G6.Graph({
|
||||
// 初始化g6实例
|
||||
this.g6Instance = new SV.G6.Graph({
|
||||
container: DOMContainer,
|
||||
width: DOMContainer.offsetWidth,
|
||||
height: DOMContainer.offsetHeight,
|
||||
@@ -49,223 +43,77 @@ export class Renderer {
|
||||
duration: duration,
|
||||
easing: timingFunction
|
||||
},
|
||||
fitView: this.engine.layoutOptions.fitView,
|
||||
fitView: false,
|
||||
modes: {
|
||||
default: this.initBehaviors()
|
||||
}
|
||||
});
|
||||
|
||||
this.shadowGraphInstance = new SV.G6.Graph({
|
||||
container: DOMContainer.cloneNode()
|
||||
});
|
||||
|
||||
this.animations = new Animations(duration, timingFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化交互行为
|
||||
* @returns
|
||||
*/
|
||||
private initBehaviors() {
|
||||
const interactionOptions = this.engine.interactionOptions,
|
||||
dragNode: boolean | string[] = interactionOptions.dragNode,
|
||||
dragNodeFilter = node => {
|
||||
let model = node.item.getModel();
|
||||
|
||||
if(node.item === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(model.modelType === 'pointer') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(typeof dragNode === 'boolean') {
|
||||
return dragNode;
|
||||
}
|
||||
|
||||
if(Array.isArray(dragNode) && dragNode.indexOf(model.modelName) > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const modeMap = {
|
||||
drag: 'drag-canvas',
|
||||
zoom: 'zoom-canvas',
|
||||
dragNode: {
|
||||
type: 'drag-node',
|
||||
shouldBegin: node => dragNodeFilter(node)
|
||||
}
|
||||
},
|
||||
defaultModes = [];
|
||||
|
||||
Object.keys(interactionOptions).forEach(item => {
|
||||
if(interactionOptions[item] && modeMap[item] !== undefined) {
|
||||
defaultModes.push(modeMap[item]);
|
||||
}
|
||||
});
|
||||
|
||||
return defaultModes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比上一次和该次 G6Data 找出新添加的节点和边
|
||||
* @param prevData
|
||||
* @param data
|
||||
*/
|
||||
private diffAppendItems(prevData: G6Data, data: G6Data): G6Data {
|
||||
return {
|
||||
nodes: data.nodes.filter(item => !prevData.nodes.find(n => n.id === item.id)),
|
||||
edges: data.edges.filter(item => !prevData.edges.find(e => e.id === item.id))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比上一次和该次 G6Data 找出被删除的节点和边
|
||||
* @param prevData
|
||||
* @param data
|
||||
*/
|
||||
private diffRemoveItems(prevData: G6Data, data: G6Data): G6Data {
|
||||
return {
|
||||
nodes: prevData.nodes.filter(item => !data.nodes.find(n => n.id === item.id)),
|
||||
edges: prevData.edges.filter(item => !data.edges.find(e => e.id === item.id))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找被释放的节点
|
||||
* @param constructedData
|
||||
*/
|
||||
private findFreedItems(constructedData: ConstructedData): G6NodeModel[] {
|
||||
return Util.converterList(constructedData.element).filter(item => item.free).map(item => item.G6Item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理新增的 G6Item(主要是动画)
|
||||
* @param appendData
|
||||
*/
|
||||
private handleAppendItems(appendData: G6Data) {
|
||||
const appendItems = [
|
||||
...appendData.nodes.map(item => this.graphInstance.findById(item.id)),
|
||||
...appendData.edges.map(item => this.graphInstance.findById(item.id))
|
||||
];
|
||||
|
||||
appendItems.forEach(item => {
|
||||
this.animations.append(item);
|
||||
default: []
|
||||
},
|
||||
...g6Options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理被移除的 G6Item(主要是动画)
|
||||
* @param removeData
|
||||
*/
|
||||
private handleRemoveItems(removeData: G6Data) {
|
||||
const removeItems = [
|
||||
...removeData.nodes.map(item => this.graphInstance.findById(item.id)),
|
||||
...removeData.edges.map(item => this.graphInstance.findById(item.id))
|
||||
];
|
||||
public getIsFirstRender(): boolean {
|
||||
return this.isFirstRender;
|
||||
}
|
||||
|
||||
removeItems.forEach(item => {
|
||||
this.animations.remove(item, () => {
|
||||
this.graphInstance.removeItem(item);
|
||||
});
|
||||
});
|
||||
public setIsFirstRender(value: boolean) {
|
||||
this.isFirstRender = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理被 free 的 G6Item
|
||||
* @param freedItems
|
||||
* 从视图中移除一个 Model
|
||||
* @param model
|
||||
*/
|
||||
private handleFreedItems(freedItems: G6NodeModel[]) { }
|
||||
|
||||
/**
|
||||
* 构建 G6 元素
|
||||
* @param constructedData
|
||||
*/
|
||||
public build(constructedData: ConstructedData) {
|
||||
let elementList: Element[] = Util.converterList(constructedData.element),
|
||||
linkList: Link[] = Util.converterList(constructedData.link),
|
||||
pointerList: Pointer[] = Util.converterList(constructedData.pointer),
|
||||
nodeList = [...elementList.map(item => item.cloneProps()), ...pointerList.map(item => item.cloneProps())],
|
||||
edgeList = linkList.map(item => item.cloneProps());
|
||||
|
||||
this.modelList = [...elementList, ...linkList, ...pointerList];
|
||||
|
||||
const data: G6Data = {
|
||||
nodes: <G6NodeModel[]>nodeList,
|
||||
edges: <G6EdgeModel[]>edgeList
|
||||
};
|
||||
|
||||
this.shadowGraphInstance.clear();
|
||||
this.shadowGraphInstance.read(data);
|
||||
|
||||
this.modelList.forEach(item => {
|
||||
item.shadowG6Item = this.shadowGraphInstance.findById(item.id);
|
||||
});
|
||||
public removeModel(model: Model) {
|
||||
this.g6Instance.removeItem(model.renderG6Item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param constructedData
|
||||
* @param modelList
|
||||
*/
|
||||
public render(constructedData: ConstructedData) {
|
||||
let data: G6Data = Util.convertG6Data(constructedData),
|
||||
freedItems = this.findFreedItems(constructedData),
|
||||
renderData: G6Data = null,
|
||||
appendData: G6Data = null,
|
||||
removeData: G6Data = null;
|
||||
|
||||
appendData = this.diffAppendItems(this.prevRenderData, data);
|
||||
removeData = this.diffRemoveItems(this.prevRenderData, data);
|
||||
renderData = {
|
||||
nodes: [...data.nodes, ...removeData.nodes],
|
||||
edges: [...data.edges, ...removeData.edges]
|
||||
};
|
||||
|
||||
this.prevRenderData = data;
|
||||
public render(modelList: Model[], removeModels: Model[]) {
|
||||
let data: G6Data = Util.convertModelList2G6Data(modelList),
|
||||
removeData: G6Data = Util.convertModelList2G6Data(removeModels),
|
||||
renderData: G6Data = {
|
||||
nodes: [...data.nodes, ...removeData.nodes],
|
||||
edges: [...data.edges, ...removeData.edges]
|
||||
};
|
||||
|
||||
if(this.isFirstRender) {
|
||||
this.graphInstance.read(renderData);
|
||||
this.g6Instance.read(renderData);
|
||||
}
|
||||
else {
|
||||
this.graphInstance.changeData(renderData);
|
||||
this.g6Instance.changeData(renderData);
|
||||
}
|
||||
|
||||
this.handleAppendItems(appendData);
|
||||
this.handleRemoveItems(removeData);
|
||||
|
||||
if(this.engine.layoutOptions.fitView) {
|
||||
this.graphInstance.fitView();
|
||||
this.g6Instance.fitView();
|
||||
}
|
||||
|
||||
this.modelList.forEach(item => {
|
||||
item.renderG6Item = this.graphInstance.findById(item.id);
|
||||
modelList.forEach(item => {
|
||||
item.renderG6Item = this.g6Instance.findById(item.id);
|
||||
item.G6Item = item.renderG6Item;
|
||||
});
|
||||
|
||||
// 把所有连线置顶
|
||||
if(this.isFirstRender) {
|
||||
this.graphInstance.getEdges().forEach(item => item.toFront());
|
||||
this.graphInstance.paint();
|
||||
this.g6Instance.getEdges().forEach(item => item.toFront());
|
||||
this.g6Instance.paint();
|
||||
}
|
||||
|
||||
if(this.isFirstRender) {
|
||||
this.isFirstRender = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 model 队列
|
||||
*/
|
||||
getModelList(): Model[] {
|
||||
return this.modelList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 G6 实例
|
||||
*/
|
||||
public getGraphInstance() {
|
||||
return this.graphInstance;
|
||||
public getG6Instance() {
|
||||
return this.g6Instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
public destroy() {
|
||||
this.g6Instance.destroy();
|
||||
this.DOMContainer = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import { Engine } from "../engine";
|
||||
import { Element, Link } from "../Model/modelData";
|
||||
import { EngineInitOptions, LayoutOptions } from "../options";
|
||||
import { Container } from "./container/container";
|
||||
import { SV } from '../StructV';
|
||||
import { ConstructList } from "../Model/modelConstructor";
|
||||
import { MainContainer } from "./container/main";
|
||||
import { FreedContainer } from "./container/freed";
|
||||
import { LeakContainer } from "./container/leak";
|
||||
import { Layouter } from "./layouter";
|
||||
|
||||
|
||||
export class ViewManager {
|
||||
private engine: Engine;
|
||||
private layouter: Layouter;
|
||||
private mainContainer: Container;
|
||||
private freedContainer: Container;
|
||||
private leakContainer: Container;
|
||||
|
||||
private prevConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
||||
private freedConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
||||
private leakConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
||||
|
||||
private shadowG6Instance;
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||
this.engine = engine;
|
||||
this.layouter = new Layouter(engine);
|
||||
this.mainContainer = new MainContainer(engine, DOMContainer);
|
||||
|
||||
const options: EngineInitOptions = this.engine.initOptions;
|
||||
|
||||
if(options.freedContainer) {
|
||||
this.freedContainer = new FreedContainer(engine, options.freedContainer, { fitCenter: true });
|
||||
}
|
||||
|
||||
if(options.leakContainer) {
|
||||
this.leakContainer = new LeakContainer(engine, options.leakContainer, { fitCenter: true });
|
||||
}
|
||||
|
||||
this.shadowG6Instance = new SV.G6.Graph({
|
||||
container: DOMContainer.cloneNode()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
|
||||
* @param constructList
|
||||
*/
|
||||
private build(constructList: ConstructList) {
|
||||
constructList.element.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', item));
|
||||
constructList.pointer.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', item));
|
||||
constructList.link.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('edge', item));
|
||||
|
||||
constructList.element.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
});
|
||||
constructList.pointer.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
});
|
||||
constructList.link.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被 free 的节点
|
||||
* @param constructList
|
||||
* @returns
|
||||
*/
|
||||
private getFreedConstructList(constructList: ConstructList): ConstructList {
|
||||
const freedList: ConstructList = {
|
||||
element: constructList.element.filter(item => item.free),
|
||||
pointer: [],
|
||||
link: []
|
||||
};
|
||||
|
||||
freedList.element.forEach(fItem => {
|
||||
constructList.element.splice(constructList.element.findIndex(item => item.id === fItem.id), 1);
|
||||
constructList.link.splice(constructList.link.findIndex(item => item.element.id === fItem.id || item.target.id === fItem.id));
|
||||
constructList.pointer.splice(constructList.pointer.findIndex(item => item.target.id === fItem.id));
|
||||
});
|
||||
|
||||
return freedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被泄露的节点
|
||||
* @param constructList
|
||||
* @param prevConstructList
|
||||
* @returns
|
||||
*/
|
||||
private getLeakConstructList(prevConstructList: ConstructList, constructList: ConstructList): ConstructList {
|
||||
const elements: Element[] = prevConstructList.element.filter(item => !constructList.element.find(n => n.id === item.id)),
|
||||
links: Link[] = prevConstructList.link.filter(item => !constructList.link.find(n => n.id === item.id)),
|
||||
elementIds: string[] = elements.map(item => item.id);
|
||||
|
||||
elements.forEach(item => {
|
||||
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--;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
element: elements,
|
||||
link: links,
|
||||
pointer: []
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 对主视图进行重新布局
|
||||
* @param constructList
|
||||
* @param layoutFn
|
||||
*/
|
||||
reLayout(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||
this.layouter.layout(this.mainContainer, constructList, layoutFn);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 g6 实例
|
||||
*/
|
||||
getG6Instance() {
|
||||
return this.mainContainer.getG6Instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新视图
|
||||
*/
|
||||
refresh() {
|
||||
this.mainContainer.getG6Instance().refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新调整容器尺寸
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
resize(width: number, height: number) {
|
||||
this.mainContainer.getG6Instance().changeSize(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染所有视图
|
||||
* @param models
|
||||
* @param layoutFn
|
||||
*/
|
||||
renderAll(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||
this.shadowG6Instance.clear();
|
||||
|
||||
this.build(constructList);
|
||||
|
||||
this.freedConstructList = this.getFreedConstructList(constructList);
|
||||
this.leakConstructList = this.getLeakConstructList(this.prevConstructList, constructList);
|
||||
|
||||
this.build(this.leakConstructList);
|
||||
|
||||
if(this.freedContainer) {
|
||||
this.freedContainer.render(this.freedConstructList, layoutFn);
|
||||
}
|
||||
|
||||
// 进行布局(设置model的x,y)
|
||||
this.layouter.layout(this.mainContainer, constructList, layoutFn);
|
||||
this.mainContainer.render(constructList, layoutFn);
|
||||
|
||||
if(this.leakContainer) {
|
||||
this.mainContainer.afterRemoveModels(() => {
|
||||
this.leakContainer.render(this.leakConstructList, layoutFn);
|
||||
});
|
||||
}
|
||||
|
||||
this.prevConstructList = constructList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
destroy() {
|
||||
this.shadowG6Instance.destroy();
|
||||
this.mainContainer.destroy();
|
||||
this.freedContainer && this.freedContainer.destroy();
|
||||
this.leakContainer && this.leakContainer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user