feat:增加布局模式

This commit is contained in:
黎智洲 2022-04-19 16:23:28 +08:00
parent 54a4a98e75
commit f85f8beecd
9 changed files with 457 additions and 443 deletions

View File

@ -182,7 +182,7 @@ export function SolveDragCanvasWithLeak(viewContainer: ViewContainer) {
let translateY = event.matrix[7], let translateY = event.matrix[7],
dy = translateY - viewContainer.lastLeakAreaTranslateY; dy = translateY - viewContainer.lastLeakAreaTranslateY;
viewContainer.lastLeakAreaTranslateY = translateY; viewContainer.lastLeakAreaTranslateY = translateY;
viewContainer.leakAreaY = viewContainer.leakAreaY + dy; viewContainer.leakAreaY = viewContainer.leakAreaY + dy;
if (viewContainer.hasLeak) { if (viewContainer.hasLeak) {

View File

@ -49,7 +49,6 @@ export class ModelConstructor {
const layoutGroupTable = new Map<string, LayoutGroup>(), const layoutGroupTable = new Map<string, LayoutGroup>(),
layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout; layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout;
Object.keys(sources).forEach(group => { Object.keys(sources).forEach(group => {
let sourceGroup = sources[group], let sourceGroup = sources[group],
layout = sourceGroup.layouter, layout = sourceGroup.layouter,
@ -303,7 +302,6 @@ export class ModelConstructor {
if (!markerData) continue; if (!markerData) continue;
let id = `[${name}(${Array.isArray(markerData) ? markerData.join('-') : markerData})]`, let id = `[${name}(${Array.isArray(markerData) ? markerData.join('-') : markerData})]`,
marker = new SVMarker(id, name, group, layout, markerData, node, markerOptions[name]); marker = new SVMarker(id, name, group, layout, markerData, node, markerOptions[name]);
markerList.push(marker); markerList.push(marker);
@ -351,7 +349,6 @@ export class ModelConstructor {
): SVNode { ): SVNode {
let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode), let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode),
id = `${sourceNodeType}(${sourceNode.id.toString()})`, id = `${sourceNodeType}(${sourceNode.id.toString()})`,
node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options); node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options);
if (node.freed) { if (node.freed) {
@ -444,17 +441,17 @@ export class ModelConstructor {
} }
} }
// 为了可以连接到不同group的结点 // 为了可以连接到不同group的结点
for (let layoutGroup of layoutGroupTable.values()) { for (let layoutGroup of layoutGroupTable.values()) {
nodeList = layoutGroup.node.filter(item => item.sourceType === sourceNodeType); nodeList = layoutGroup.node.filter(item => item.sourceType === sourceNodeType);
if (nodeList === undefined) { if (nodeList === undefined) {
continue; continue;
} }
targetNode = nodeList.find(item => item.sourceId === targetId); targetNode = nodeList.find(item => item.sourceId === targetId);
if (targetNode) { if (targetNode) {
break; break;
} }
} }
// nodeList = layoutGroupTable.get(targetGroupName).node.filter(item => item.sourceType === sourceNodeType); // nodeList = layoutGroupTable.get(targetGroupName).node.filter(item => item.sourceType === sourceNodeType);
// // 若目标node不存在返回null // // 若目标node不存在返回null
@ -509,25 +506,25 @@ export class ModelConstructor {
return node1.group === node2.group; return node1.group === node2.group;
} }
/** /**
* *
* - appendages * - appendages
* @param models * @param models
* @returns * @returns
*/ */
static getClusters(models: SVModel[]): Group[] { static getClusters(models: SVModel[]): Group[] {
const clusterGroupList = [], const clusterGroupList = [],
idMap = {}, idMap = {},
idName = '__clusterId'; idName = '__clusterId';
models.forEach(item => { models.forEach(item => {
idMap[item.id] = item; idMap[item.id] = item;
}); });
const DFS = (model: SVModel, clusterId: number, idMap): SVModel[] => { const DFS = (model: SVModel, clusterId: number, idMap): SVModel[] => {
if(model === null) { if (model === null) {
return []; return [];
} }
if (idMap[model.id] === undefined) { if (idMap[model.id] === undefined) {
return []; return [];
@ -560,7 +557,7 @@ export class ModelConstructor {
} }
if (model instanceof SVNodeAppendage) { if (model instanceof SVNodeAppendage) {
list.push(...DFS(model.target, clusterId, idMap)); list.push(...DFS(model.target, clusterId, idMap));
} }
return list; return list;
@ -570,7 +567,7 @@ export class ModelConstructor {
const model = models[i]; const model = models[i];
if (model[idName] !== undefined) { if (model[idName] !== undefined) {
delete model[idName]; delete model[idName];
continue; continue;
} }
@ -582,7 +579,7 @@ export class ModelConstructor {
}); });
clusterGroupList.push(group); clusterGroupList.push(group);
delete model[idName]; delete model[idName];
} }
return clusterGroupList; return clusterGroupList;

View File

@ -1,116 +1,95 @@
import { Util, Item } from '@antv/g6'; import { Util, Item } from '@antv/g6';
export type animationConfig = { export type animationConfig = {
duration: number; duration: number;
timingFunction: string; timingFunction: string;
callback?: () => void; callback?: () => void;
[key: string]: any; [key: string]: any;
} };
/** /**
* *
*/ */
export const Animations = { export const Animations = {
/**
* /
* @param G6Item
* @param animationConfig
*/
APPEND(G6Item: Item, 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') {
* / let matrix = group.getMatrix(),
* @param G6Item targetMatrix = Mat3.clone(matrix);
* @param animationConfig
*/
APPEND(G6Item: Item, 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') { Mat3.scale(matrix, matrix, [0, 0]);
let matrix = group.getMatrix(), Mat3.scale(targetMatrix, targetMatrix, [1, 1]);
targetMatrix = Mat3.clone(matrix);
Mat3.scale(matrix, matrix, [0, 0]); group.attr({ matrix, opacity: 0 });
Mat3.scale(targetMatrix, targetMatrix, [1, 1]); group.animate({ matrix: targetMatrix, opacity: 1 }, animateCfg);
}
group.attr({ matrix, opacity: 0 }); if (type === 'edge') {
group.animate({ matrix: targetMatrix, opacity: 1 }, animateCfg); const line = group.get('children')[0],
} length = line.getTotalLength();
if (type === 'edge') { line.attr({ lineDash: [0, length], opacity: 0 });
const line = group.get('children')[0], line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg);
length = line.getTotalLength(); }
},
line.attr({ lineDash: [0, length], opacity: 0 }); /**
line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg); * /
} * @param G6Item
}, * @param animationConfig
*/
REMOVE(G6Item: Item, 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') {
* / let matrix = Mat3.clone(group.getMatrix());
* @param G6Item
* @param animationConfig
*/
REMOVE(G6Item: Item, 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') { Mat3.scale(matrix, matrix, [0, 0]);
let matrix = Mat3.clone(group.getMatrix()); group.animate({ opacity: 0, matrix }, animateCfg);
}
Mat3.scale(matrix, matrix, [0, 0]); if (type === 'edge') {
group.animate({ opacity: 0, matrix }, animateCfg); const line = group.get('children')[0],
} length = line.getTotalLength();
if (type === 'edge') { line.animate({ lineDash: [0, length], opacity: 0 }, animateCfg);
const line = group.get('children')[0], }
length = line.getTotalLength(); },
line.animate({ lineDash: [0, length], opacity: 0 }, animateCfg); /**
} *
}, * @param G6Item
* @param animationConfig
*/
FADE_IN(G6Item: Item, animationConfig: animationConfig) {
const group = G6Item.getContainer(),
animateCfg = {
duration: animationConfig.duration,
easing: animationConfig.timingFunction,
callback: animationConfig.callback,
};
/** group.attr({ opacity: 0 });
* group.animate({ opacity: 1 }, animateCfg);
* @param G6Item },
* @param animationConfig
*/
FADE_IN(G6Item: Item, animationConfig: animationConfig) {
const group = G6Item.getContainer(),
animateCfg = {
duration: animationConfig.duration,
easing: animationConfig.timingFunction,
callback: animationConfig.callback
};
group.attr({ opacity: 0 });
group.animate({ opacity: 1 }, animateCfg);
}
}; };

View File

@ -10,6 +10,11 @@ import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker } from '../Model/S
import { AddressLabelOption, IndexLabelOption, LayoutOptions, MarkerOption, ViewOptions } from '../options'; import { AddressLabelOption, IndexLabelOption, LayoutOptions, MarkerOption, ViewOptions } from '../options';
import { ViewContainer } from './viewContainer'; import { ViewContainer } from './viewContainer';
export enum ELayoutMode {
HORIZONTAL = 'hor',
VERTICAL = 'ver',
}
export class LayoutProvider { export class LayoutProvider {
private engine: Engine; private engine: Engine;
private viewOptions: ViewOptions; private viewOptions: ViewOptions;
@ -155,7 +160,7 @@ export class LayoutProvider {
}, },
left: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => { left: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
return { return {
x: nodeBound.x - labelBound.width / 2- offset, x: nodeBound.x - labelBound.width / 2 - offset,
y: nodeBound.y + nodeBound.height / 2, y: nodeBound.y + nodeBound.height / 2,
}; };
}, },
@ -253,7 +258,7 @@ export class LayoutProvider {
const clusters = ModelConstructor.getClusters(accumulateLeakModels); const clusters = ModelConstructor.getClusters(accumulateLeakModels);
// 每一个簇从左往右布局就完事了,比之前的方法简单稳定很多 // 每一个簇从左往右布局就完事了,比之前的方法简单稳定很多
clusters.forEach(item => { clusters.forEach(item => {
const bound = item.getBound(), const bound = item.getBound(),
x = prevBound ? prevBound.x + prevBound.width + this.leakClusterXInterval : this.leakAreaXoffset, x = prevBound ? prevBound.x + prevBound.width + this.leakClusterXInterval : this.leakAreaXoffset,
@ -268,29 +273,48 @@ export class LayoutProvider {
/** /**
* *
* @param modelGroupTable * @param modelGroupList
* @param layoutMode /
*/ */
private layoutGroups(modelGroupList: Group[]): Group { private layoutGroups(modelGroupList: Group[], layoutMode: ELayoutMode): Group {
let wrapperGroup: Group = new Group(), let wrapperGroup: Group = new Group(),
group: Group, group: Group,
prevBound: BoundingRect, prevBound: BoundingRect,
bound: BoundingRect, bound: BoundingRect,
boundList: BoundingRect[] = [], boundList: BoundingRect[] = [],
dx = 0; groupPadding = this.viewOptions.groupPadding,
dx = 0,
dy = 0;
// 左往右布局
for (let i = 0; i < modelGroupList.length; i++) { for (let i = 0; i < modelGroupList.length; i++) {
group = modelGroupList[i]; group = modelGroupList[i];
bound = group.getPaddingBound(this.viewOptions.groupPadding); bound = group.getPaddingBound(groupPadding);
if (prevBound) { // 左往右水平布局
dx = prevBound.x + prevBound.width - bound.x; if (layoutMode === ELayoutMode.HORIZONTAL) {
} else { if (prevBound) {
dx = bound.x; dx = prevBound.x + prevBound.width - bound.x;
} else {
dx = bound.x;
}
group.translate(dx, 0);
Bound.translate(bound, dx, 0);
}
// 上到下垂直布局
if (layoutMode === ELayoutMode.VERTICAL) {
if (prevBound) {
dy = prevBound.y + prevBound.height - bound.y - groupPadding;
} else {
dy = bound.y;
}
group.translate(0, dy);
Bound.translate(bound, 0, dy);
} }
group.translate(dx, 0);
Bound.translate(bound, dx, 0);
boundList.push(bound); boundList.push(bound);
wrapperGroup.add(group); wrapperGroup.add(group);
prevBound = bound; prevBound = bound;
@ -333,11 +357,11 @@ export class LayoutProvider {
* @param layoutGroupTable * @param layoutGroupTable
* @param accumulateLeakModels * @param accumulateLeakModels
*/ */
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[]) { public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], layoutMode: ELayoutMode) {
this.preLayoutProcess(layoutGroupTable); this.preLayoutProcess(layoutGroupTable);
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable); const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
const generalGroup: Group = this.layoutGroups(modelGroupList); const generalGroup: Group = this.layoutGroups(modelGroupList, layoutMode);
this.layoutLeakArea(accumulateLeakModels); this.layoutLeakArea(accumulateLeakModels);

View File

@ -256,10 +256,10 @@ export class Reconcile {
let { duration, timingFunction } = this.engine.animationOptions; let { duration, timingFunction } = this.engine.animationOptions;
appendModels.forEach(item => { appendModels.forEach(item => {
const G6Item = item.G6Item; const G6Item = item.G6Item;
if (item instanceof SVNodeAppendage) { if (item instanceof SVNodeAppendage) {
G6Item.enableCapture(false); G6Item.enableCapture(false);
// 先不显示泄漏区节点上面的地址文本 // 先不显示泄漏区节点上面的地址文本
if (item instanceof SVAddressLabel) { if (item instanceof SVAddressLabel) {
@ -395,9 +395,7 @@ export class Reconcile {
isDiffLeak: boolean isDiffLeak: boolean
): DiffResult { ): DiffResult {
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList); const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
const leakModels: SVModel[] = isDiffLeak const leakModels: SVModel[] = isDiffLeak ? [] : this.getLeakModels(layoutGroupTable, prevModelList, modelList);
? []
: this.getLeakModels(layoutGroupTable, prevModelList, modelList);
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels); const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels);
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList, accumulateLeakModels); const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList, accumulateLeakModels);
const updateModels: SVModel[] = [ const updateModels: SVModel[] = [

View File

@ -1,5 +1,5 @@
import { Engine } from '../engine'; import { Engine } from '../engine';
import { LayoutProvider } from './layoutProvider'; import { ELayoutMode, LayoutProvider } from './layoutProvider';
import { LayoutGroupTable } from '../Model/modelConstructor'; import { LayoutGroupTable } from '../Model/modelConstructor';
import { Util } from '../Common/util'; import { Util } from '../Common/util';
import { SVModel } from '../Model/SVModel'; import { SVModel } from '../Model/SVModel';
@ -19,141 +19,140 @@ import {
import { handleUpdate } from '../sources'; import { handleUpdate } from '../sources';
export class ViewContainer { export class ViewContainer {
private engine: Engine; private engine: Engine;
private layoutProvider: LayoutProvider; private layoutProvider: LayoutProvider;
private reconcile: Reconcile; private reconcile: Reconcile;
public renderer: Renderer; public renderer: Renderer;
private layoutGroupTable: LayoutGroupTable; private layoutGroupTable: LayoutGroupTable;
private prevModelList: SVModel[]; private prevModelList: SVModel[];
private accumulateLeakModels: SVModel[]; private accumulateLeakModels: SVModel[];
public hasLeak: boolean; public hasLeak: boolean;
public leakAreaY: number; public leakAreaY: number;
public lastLeakAreaTranslateY: number; public lastLeakAreaTranslateY: number;
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点 public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
public clickSelectNode: SVNode; // 点击选中的节点 public clickSelectNode: SVNode; // 点击选中的节点
constructor(engine: Engine, DOMContainer: HTMLElement, isForce: boolean) { constructor(engine: Engine, DOMContainer: HTMLElement, isForce: boolean) {
const behaviorsModes: Modes = InitG6Behaviors(engine, this); const behaviorsModes: Modes = InitG6Behaviors(engine, this);
this.engine = engine; this.engine = engine;
this.layoutProvider = new LayoutProvider(engine, this); this.layoutProvider = new LayoutProvider(engine, this);
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes, isForce); this.renderer = new Renderer(engine, DOMContainer, behaviorsModes, isForce);
this.reconcile = new Reconcile(engine, this.renderer); this.reconcile = new Reconcile(engine, this.renderer);
this.layoutGroupTable = new Map(); this.layoutGroupTable = new Map();
this.prevModelList = []; this.prevModelList = [];
this.accumulateLeakModels = []; this.accumulateLeakModels = [];
this.hasLeak = false; // 判断是否已经发生过泄漏 this.hasLeak = false; // 判断是否已经发生过泄漏
this.brushSelectedModels = []; this.brushSelectedModels = [];
this.clickSelectNode = null; this.clickSelectNode = null;
this.lastLeakAreaTranslateY = 0; this.lastLeakAreaTranslateY = 0;
const g6Instance = this.renderer.getG6Instance(), const g6Instance = this.renderer.getG6Instance(),
leakAreaHeight = this.engine.viewOptions.leakAreaHeight, leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
height = g6Instance.getHeight(), height = g6Instance.getHeight(),
{ drag, zoom } = this.engine.behaviorOptions; { drag, zoom } = this.engine.behaviorOptions;
this.leakAreaY = height - leakAreaHeight; this.leakAreaY = height - leakAreaHeight;
SolveNodeAppendagesDrag(this); SolveNodeAppendagesDrag(this);
SolveBrushSelectDrag(this); SolveBrushSelectDrag(this);
drag && SolveDragCanvasWithLeak(this); drag && SolveDragCanvasWithLeak(this);
zoom && SolveZoomCanvasWithLeak(this); zoom && SolveZoomCanvasWithLeak(this);
} }
// ----------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------- /**
*
/** */
* reLayout(layoutMode: ELayoutMode) {
*/ const g6Instance = this.getG6Instance(),
reLayout() {
const g6Instance = this.getG6Instance(),
group = g6Instance.getGroup(), group = g6Instance.getGroup(),
matrix = group.getMatrix(), matrix = group.getMatrix(),
bound = group.getCanvasBBox(); bound = group.getCanvasBBox();
const { duration, enable, timingFunction } = this.engine.animationOptions; const { duration, enable, timingFunction } = this.engine.animationOptions;
if (matrix) { if (matrix) {
let dx = matrix[6], let dx = matrix[6],
dy = matrix[7]; dy = matrix[7];
g6Instance.moveTo(bound.minX - dx, bound.minY - dy, enable, { g6Instance.moveTo(bound.minX - dx, bound.minY - dy, enable, {
duration, duration,
easing: timingFunction, easing: timingFunction,
}); });
} }
const leakAreaHeight = this.engine.viewOptions.leakAreaHeight, const leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
height = g6Instance.getHeight(); height = g6Instance.getHeight();
this.leakAreaY = height - leakAreaHeight; this.leakAreaY = height - leakAreaHeight;
this.lastLeakAreaTranslateY = 0; this.lastLeakAreaTranslateY = 0;
this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels); this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels, layoutMode);
g6Instance.refresh(); g6Instance.refresh();
EventBus.emit('onLeakAreaUpdate', { EventBus.emit('onLeakAreaUpdate', {
leakAreaY: this.leakAreaY, leakAreaY: this.leakAreaY,
hasLeak: this.hasLeak, hasLeak: this.hasLeak,
}); });
} }
/** /**
* g6 * g6
*/ */
getG6Instance(): Graph { getG6Instance(): Graph {
return this.renderer.getG6Instance(); return this.renderer.getG6Instance();
} }
/** /**
* *
* @returns * @returns
*/ */
getAccumulateLeakModels(): SVModel[] { getAccumulateLeakModels(): SVModel[] {
return this.accumulateLeakModels; return this.accumulateLeakModels;
} }
/** /**
* *
*/ */
getLayoutGroupTable(): LayoutGroupTable { getLayoutGroupTable(): LayoutGroupTable {
return this.layoutGroupTable; return this.layoutGroupTable;
} }
/** /**
* *
*/ */
refresh() { refresh() {
this.renderer.getG6Instance().refresh(); this.renderer.getG6Instance().refresh();
} }
/** /**
* *
* @param width * @param width
* @param height * @param height
*/ */
resize(width: number, height: number) { resize(width: number, height: number) {
const g6Instance = this.getG6Instance(), const g6Instance = this.getG6Instance(),
prevContainerHeight = g6Instance.getHeight(), prevContainerHeight = g6Instance.getHeight(),
globalGroup: Group = new Group(); globalGroup: Group = new Group();
globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels); globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels);
this.renderer.changeSize(width, height); this.renderer.changeSize(width, height);
const containerHeight = g6Instance.getHeight(), const containerHeight = g6Instance.getHeight(),
dy = containerHeight - prevContainerHeight; dy = containerHeight - prevContainerHeight;
globalGroup.translate(0, dy); globalGroup.translate(0, dy);
this.renderer.refresh(); this.renderer.refresh();
this.leakAreaY += dy; this.leakAreaY += dy;
EventBus.emit('onLeakAreaUpdate', { EventBus.emit('onLeakAreaUpdate', {
leakAreaY: this.leakAreaY, leakAreaY: this.leakAreaY,
hasLeak: this.hasLeak, hasLeak: this.hasLeak,
}); });
} }
/** /**
@ -174,7 +173,11 @@ export class ViewContainer {
* @param models * @param models
* @param layoutFn * @param layoutFn
*/ */
render(layoutGroupTable: LayoutGroupTable, isSameSources: boolean, handleUpdate: handleUpdate,hasTriggerLastStep: boolean) { render(
layoutGroupTable: LayoutGroupTable,
isSameSources: boolean,
handleUpdate: handleUpdate
) {
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable); const modelList = Util.convertGroupTable2ModelList(layoutGroupTable);
this.restoreHighlight([...modelList, ...this.accumulateLeakModels]); this.restoreHighlight([...modelList, ...this.accumulateLeakModels]);
@ -184,8 +187,8 @@ export class ViewContainer {
return; return;
} }
// 判断是否需要进行泄漏区的比较 // 判断是否需要进行泄漏区的比较
let isDiffLeak = handleUpdate.isEnterFunction || hasTriggerLastStep; let isDiffLeak = handleUpdate?.isEnterFunction || handleUpdate?.hasTriggerLastStep;
const diffResult = this.reconcile.diff( const diffResult = this.reconcile.diff(
this.layoutGroupTable, this.layoutGroupTable,
@ -193,7 +196,7 @@ export class ViewContainer {
modelList, modelList,
this.accumulateLeakModels, this.accumulateLeakModels,
// handleUpdate?.isEnterFunction // handleUpdate?.isEnterFunction
isDiffLeak isDiffLeak
), ),
renderModelList = [...modelList, ...diffResult.REMOVE, ...diffResult.LEAKED, ...this.accumulateLeakModels]; renderModelList = [...modelList, ...diffResult.REMOVE, ...diffResult.LEAKED, ...this.accumulateLeakModels];
@ -215,9 +218,10 @@ export class ViewContainer {
}); });
} }
const layoutMode = this.engine.viewOptions.layoutMode;
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行向后累积 this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行向后累积
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先 this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels); // 进行布局设置model的xy样式等 this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels, layoutMode); // 进行布局设置model的xy样式等
this.beforeRender(); this.beforeRender();
this.renderer.render(renderModelList); // 渲染视图 this.renderer.render(renderModelList); // 渲染视图

View File

@ -1,229 +1,238 @@
import { handleUpdate, Sources } from "./sources"; import { handleUpdate, Sources } from './sources';
import { LayoutGroupTable, ModelConstructor } from "./Model/modelConstructor"; import { LayoutGroupTable, ModelConstructor } from './Model/modelConstructor';
import { AnimationOptions, BehaviorOptions, EngineOptions, LayoutGroupOptions, ViewOptions } from "./options"; import { AnimationOptions, BehaviorOptions, EngineOptions, LayoutGroupOptions, ViewOptions } from './options';
import { EventBus } from "./Common/eventBus"; import { EventBus } from './Common/eventBus';
import { ViewContainer } from "./View/viewContainer"; import { ViewContainer } from './View/viewContainer';
import { SVNode } from "./Model/SVNode"; import { SVNode } from './Model/SVNode';
import { Util } from "./Common/util"; import { Util } from './Common/util';
import { SVModel } from "./Model/SVModel"; import { SVModel } from './Model/SVModel';
import { G6Event } from "@antv/g6"; import { G6Event } from '@antv/g6';
import { ELayoutMode } from './View/layoutProvider';
export class Engine { export class Engine {
private modelConstructor: ModelConstructor; private modelConstructor: ModelConstructor;
private viewContainer: ViewContainer; private viewContainer: ViewContainer;
private prevStringSource: string; private prevStringSource: string;
public engineOptions: EngineOptions; public engineOptions: EngineOptions;
public viewOptions: ViewOptions; public viewOptions: ViewOptions;
public animationOptions: AnimationOptions; public animationOptions: AnimationOptions;
public behaviorOptions: BehaviorOptions; public behaviorOptions: BehaviorOptions;
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean) { constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean) {
this.engineOptions = Object.assign({}, engineOptions); this.engineOptions = Object.assign({}, engineOptions);
this.viewOptions = Object.assign({ this.viewOptions = Object.assign(
fitCenter: true, {
fitView: false, fitCenter: true,
groupPadding: 20, fitView: false,
leakAreaHeight: 150, groupPadding: 20,
updateHighlight: '#fc5185' leakAreaHeight: 150,
}, engineOptions.view); updateHighlight: '#fc5185',
layoutMode: ELayoutMode.HORIZONTAL
},
engineOptions.view
);
this.animationOptions = Object.assign({ this.animationOptions = Object.assign(
enable: true, {
duration: 750, enable: true,
timingFunction: 'easePolyOut' duration: 750,
}, engineOptions.animation); timingFunction: 'easePolyOut',
},
engineOptions.animation
);
this.behaviorOptions = Object.assign({ this.behaviorOptions = Object.assign(
drag: true, {
zoom: true, drag: true,
dragNode: true, zoom: true,
selectNode: true dragNode: true,
}, engineOptions.behavior); selectNode: true,
},
engineOptions.behavior
);
this.modelConstructor = new ModelConstructor(this); this.modelConstructor = new ModelConstructor(this);
this.viewContainer = new ViewContainer(this, DOMContainer, isForce); this.viewContainer = new ViewContainer(this, DOMContainer, isForce);
} }
/** /**
* *
* @param sources * @param sources
* @param prevStep * @param prevStep
*/ */
public render(source: Sources, hasTriggerLastStep: boolean) { public render(source: Sources) {
let isSameSources: boolean = false, let isSameSources: boolean = false,
layoutGroupTable: LayoutGroupTable; layoutGroupTable: LayoutGroupTable;
if (source === undefined || source === null) { if (source === undefined || source === null) {
return; return;
} }
let handleUpdate: handleUpdate = source.handleUpdate, let handleUpdate: handleUpdate = source.handleUpdate,
stringSource = JSON.stringify(source); stringSource = JSON.stringify(source);
if (this.prevStringSource === stringSource) { if (this.prevStringSource === stringSource) {
isSameSources = true; isSameSources = true;
} }
this.prevStringSource = stringSource; this.prevStringSource = stringSource;
if (isSameSources) {
// 若源数据两次一样的用回上一次的layoutGroupTable
layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
} else {
// 1 转换模型data => model
layoutGroupTable = this.modelConstructor.construct(source);
}
if(isSameSources) { // 2 渲染使用g6进行渲染
// 若源数据两次一样的用回上一次的layoutGroupTable this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate);
layoutGroupTable = this.modelConstructor.getLayoutGroupTable(); }
}
else {
// 1 转换模型data => model
layoutGroupTable = this.modelConstructor.construct(source);
}
// 2 渲染使用g6进行渲染 /**
this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate, hasTriggerLastStep); *
} */
public reLayout(layoutMode?: ELayoutMode) {
this.viewOptions.layoutMode = layoutMode || this.viewOptions.layoutMode;
this.viewContainer.reLayout(this.viewOptions.layoutMode);
}
/**
* G6
*/
public getGraphInstance() {
return this.viewContainer.getG6Instance();
}
/** /**
* *
*/ * @param groupNames
public reLayout() { */
this.viewContainer.reLayout(); public hideGroups(groupNames: string | string[]) {
} const names = Array.isArray(groupNames) ? groupNames : [groupNames],
instance = this.viewContainer.getG6Instance(),
layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
/** layoutGroupTable.forEach(item => {
* G6 const hasName = names.find(name => name === item.layout);
*/
public getGraphInstance() {
return this.viewContainer.getG6Instance();
}
/** if (hasName && !item.isHide) {
* item.modelList.forEach(model => instance.hideItem(model.G6Item));
* @param groupNames item.isHide = true;
*/ }
public hideGroups(groupNames: string | string[]) {
const names = Array.isArray(groupNames) ? groupNames : [groupNames],
instance = this.viewContainer.getG6Instance(),
layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
layoutGroupTable.forEach(item => { if (!hasName && item.isHide) {
const hasName = names.find(name => name === item.layout); item.modelList.forEach(model => instance.showItem(model.G6Item));
item.isHide = false;
}
});
}
if (hasName && !item.isHide) { /**
item.modelList.forEach(model => instance.hideItem(model.G6Item)); *
item.isHide = true; */
} public getAllModels(): SVModel[] {
const modelList = Util.convertGroupTable2ModelList(this.modelConstructor.getLayoutGroupTable());
const accumulateLeakModels = this.viewContainer.getAccumulateLeakModels();
if (!hasName && item.isHide) { return [...modelList, ...accumulateLeakModels];
item.modelList.forEach(model => instance.showItem(model.G6Item)); }
item.isHide = false;
}
});
}
/** /**
* *
*/ * @param modelType
public getAllModels(): SVModel[] { * @returns
const modelList = Util.convertGroupTable2ModelList(this.modelConstructor.getLayoutGroupTable()); */
const accumulateLeakModels = this.viewContainer.getAccumulateLeakModels(); public updateStyle(group: string, newOptions: LayoutGroupOptions) {
const models = this.getAllModels(),
layoutGroup = this.modelConstructor.getLayoutGroupTable().get(group);
return [...modelList, ...accumulateLeakModels]; layoutGroup.options = newOptions;
} models.forEach(item => {
if (item.group !== group) {
return;
}
/** const modelType = item.getModelType(),
* optionsType = layoutGroup.options[modelType];
* @param modelType
* @returns
*/
public updateStyle(group: string, newOptions: LayoutGroupOptions) {
const models = this.getAllModels(),
layoutGroup = this.modelConstructor.getLayoutGroupTable().get(group);
layoutGroup.options = newOptions; if (optionsType) {
models.forEach(item => { if (modelType === 'addressLabel') {
if (item.group !== group) { item.updateG6ModelStyle(item.generateG6ModelProps(optionsType));
return; } else {
} const targetModelOption = optionsType[item.sourceType];
if (targetModelOption) {
item.updateG6ModelStyle(item.generateG6ModelProps(targetModelOption));
}
}
}
});
}
const modelType = item.getModelType(), /**
optionsType = layoutGroup.options[modelType]; * 使id查找某个节点
* @param id
*/
public findNode(id: string): SVNode {
const modelList = this.getAllModels();
const stringId = id.toString();
const targetNode: SVNode = modelList.find(
item => item instanceof SVNode && item.sourceId === stringId
) as SVNode;
if (optionsType) { return targetNode;
if (modelType === 'addressLabel') { }
item.updateG6ModelStyle(item.generateG6ModelProps(optionsType));
}
else {
const targetModelOption = optionsType[item.sourceType];
if (targetModelOption) {
item.updateG6ModelStyle(item.generateG6ModelProps(targetModelOption));
}
}
}
});
}
/** /**
* 使id查找某个节点 *
* @param id * @param width
*/ * @param height
public findNode(id: string): SVNode { */
const modelList = this.getAllModels(); public resize(width: number, height: number) {
const stringId = id.toString(); this.viewContainer.resize(width, height);
const targetNode: SVNode = modelList.find(item => item instanceof SVNode && item.sourceId === stringId) as SVNode; }
return targetNode; /**
} * G6
* @param eventName
* @param callback
*/
public on(eventName: string, callback: Function) {
if (typeof callback !== 'function') {
return;
}
/** if (eventName === 'onFreed' || eventName === 'onLeak') {
* EventBus.on(eventName, callback);
* @param width return;
* @param height }
*/
public resize(width: number, height: number) {
this.viewContainer.resize(width, height);
}
/** if (eventName === 'onLeakAreaUpdate') {
* G6 EventBus.on(eventName, callback);
* @param eventName return;
* @param callback }
*/
public on(eventName: string, callback: Function) {
if (typeof callback !== 'function') {
return;
}
if (eventName === 'onFreed' || eventName === 'onLeak') { this.viewContainer.getG6Instance().on(eventName as G6Event, event => {
EventBus.on(eventName, callback); callback(event.item['SVModel']);
return; });
} }
if (eventName === 'onLeakAreaUpdate') { /**
EventBus.on(eventName, callback); * /
return; * @param enable
} */
public switchBrushSelect(enable: boolean) {
const g6Instance = this.viewContainer.getG6Instance();
enable ? g6Instance.setMode('brush') : g6Instance.setMode('default');
}
this.viewContainer.getG6Instance().on(eventName as G6Event, event => { /**
callback(event.item['SVModel']); *
}); */
} public destroy() {
this.modelConstructor.destroy();
/** this.viewContainer.destroy();
* / }
* @param enable }
*/
public switchBrushSelect(enable: boolean) {
const g6Instance = this.viewContainer.getG6Instance();
enable ? g6Instance.setMode('brush') : g6Instance.setMode('default');
}
/**
*
*/
public destroy() {
this.modelConstructor.destroy();
this.viewContainer.destroy();
}
};

View File

@ -1,6 +1,7 @@
import { SVModel } from "./Model/SVModel"; import { SVModel } from "./Model/SVModel";
import { SVNode } from "./Model/SVNode"; import { SVNode } from "./Model/SVNode";
import { SourceNode } from "./sources"; import { SourceNode } from "./sources";
import { ELayoutMode } from "./View/layoutProvider";
export interface Style { export interface Style {
@ -104,6 +105,7 @@ export interface ViewOptions {
groupPadding: number; groupPadding: number;
updateHighlight: string; updateHighlight: string;
leakAreaHeight: number; leakAreaHeight: number;
layoutMode: ELayoutMode;
} }

View File

@ -16,6 +16,7 @@ export interface SourceNode {
export interface handleUpdate { export interface handleUpdate {
isEnterFunction: boolean; isEnterFunction: boolean;
isFirstDebug: boolean; isFirstDebug: boolean;
hasTriggerLastStep: boolean;
} }
export type Sources = { export type Sources = {