diff --git a/src/BehaviorHelper/behaviorIssueHelper.ts b/src/BehaviorHelper/behaviorIssueHelper.ts index 96e5659..f48a00d 100644 --- a/src/BehaviorHelper/behaviorIssueHelper.ts +++ b/src/BehaviorHelper/behaviorIssueHelper.ts @@ -181,8 +181,8 @@ export function SolveDragCanvasWithLeak(viewContainer: ViewContainer) { let translateY = event.matrix[7], dy = translateY - viewContainer.lastLeakAreaTranslateY; - - viewContainer.lastLeakAreaTranslateY = translateY; + + viewContainer.lastLeakAreaTranslateY = translateY; viewContainer.leakAreaY = viewContainer.leakAreaY + dy; if (viewContainer.hasLeak) { diff --git a/src/Model/modelConstructor.ts b/src/Model/modelConstructor.ts index 5f516a4..f996604 100644 --- a/src/Model/modelConstructor.ts +++ b/src/Model/modelConstructor.ts @@ -48,7 +48,6 @@ export class ModelConstructor { public construct(sources: Sources): LayoutGroupTable { const layoutGroupTable = new Map(), layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout; - Object.keys(sources).forEach(group => { let sourceGroup = sources[group], @@ -303,7 +302,6 @@ export class ModelConstructor { if (!markerData) continue; let id = `[${name}(${Array.isArray(markerData) ? markerData.join('-') : markerData})]`, - marker = new SVMarker(id, name, group, layout, markerData, node, markerOptions[name]); markerList.push(marker); @@ -351,7 +349,6 @@ export class ModelConstructor { ): SVNode { let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode), id = `${sourceNodeType}(${sourceNode.id.toString()})`, - node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options); if (node.freed) { @@ -434,7 +431,7 @@ export class ModelConstructor { sourceNodeType = info.pop(); targetGroupName = info.pop(); } else { - let field = info.pop(); + let field = info.pop(); if (layoutGroupTable.get(targetGroupName).node.find(item => item.sourceType === field)) { sourceNodeType = field; } else if (layoutGroupTable.has(field)) { @@ -444,17 +441,17 @@ export class ModelConstructor { } } - // 为了可以连接到不同group的结点 - for (let layoutGroup of layoutGroupTable.values()) { - nodeList = layoutGroup.node.filter(item => item.sourceType === sourceNodeType); - if (nodeList === undefined) { - continue; - } - targetNode = nodeList.find(item => item.sourceId === targetId); - if (targetNode) { - break; - } - } + // 为了可以连接到不同group的结点 + for (let layoutGroup of layoutGroupTable.values()) { + nodeList = layoutGroup.node.filter(item => item.sourceType === sourceNodeType); + if (nodeList === undefined) { + continue; + } + targetNode = nodeList.find(item => item.sourceId === targetId); + if (targetNode) { + break; + } + } // nodeList = layoutGroupTable.get(targetGroupName).node.filter(item => item.sourceType === sourceNodeType); // // 若目标node不存在,返回null @@ -509,25 +506,25 @@ export class ModelConstructor { return node1.group === node2.group; } - /** - * 获取簇 - * - 什么为一个簇?有边相连的一堆节点,再加上这些节点各自的appendages,共同组成了一个簇 - * @param models - * @returns - */ + /** + * 获取簇 + * - 什么为一个簇?有边相连的一堆节点,再加上这些节点各自的appendages,共同组成了一个簇 + * @param models + * @returns + */ static getClusters(models: SVModel[]): Group[] { const clusterGroupList = [], idMap = {}, - idName = '__clusterId'; + idName = '__clusterId'; models.forEach(item => { idMap[item.id] = item; }); const DFS = (model: SVModel, clusterId: number, idMap): SVModel[] => { - if(model === null) { - return []; - } + if (model === null) { + return []; + } if (idMap[model.id] === undefined) { return []; @@ -560,7 +557,7 @@ export class ModelConstructor { } if (model instanceof SVNodeAppendage) { - list.push(...DFS(model.target, clusterId, idMap)); + list.push(...DFS(model.target, clusterId, idMap)); } return list; @@ -570,7 +567,7 @@ export class ModelConstructor { const model = models[i]; if (model[idName] !== undefined) { - delete model[idName]; + delete model[idName]; continue; } @@ -580,9 +577,9 @@ export class ModelConstructor { clusterList.forEach(item => { group.add(item); }); - + clusterGroupList.push(group); - delete model[idName]; + delete model[idName]; } return clusterGroupList; diff --git a/src/View/animation.ts b/src/View/animation.ts index 22ec8e1..09dc2d0 100644 --- a/src/View/animation.ts +++ b/src/View/animation.ts @@ -1,116 +1,95 @@ import { Util, Item } from '@antv/g6'; - export type animationConfig = { - duration: number; - timingFunction: string; - callback?: () => void; - [key: string]: any; -} - + duration: number; + timingFunction: string; + callback?: () => void; + [key: string]: any; +}; /** * 动画表 */ 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, + }; - /** - * 添加节点 / 边时的动画效果 - * @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(), + targetMatrix = Mat3.clone(matrix); - if (type === 'node') { - 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({ matrix, opacity: 0 }); + group.animate({ matrix: targetMatrix, opacity: 1 }, animateCfg); + } - group.attr({ matrix, opacity: 0 }); - group.animate({ matrix: targetMatrix, opacity: 1 }, animateCfg); - } + if (type === 'edge') { + const line = group.get('children')[0], + length = line.getTotalLength(); - if (type === 'edge') { - const line = group.get('children')[0], - length = line.getTotalLength(); + line.attr({ lineDash: [0, length], opacity: 0 }); + line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg); + } + }, - 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, + }; - /** - * 移除节点 / 边时的动画效果 - * @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()); - if (type === 'node') { - let matrix = Mat3.clone(group.getMatrix()); + Mat3.scale(matrix, matrix, [0, 0]); + group.animate({ opacity: 0, matrix }, animateCfg); + } - Mat3.scale(matrix, matrix, [0, 0]); - group.animate({ opacity: 0, matrix }, animateCfg); - } + if (type === 'edge') { + const line = group.get('children')[0], + length = line.getTotalLength(); - if (type === 'edge') { - const line = group.get('children')[0], - length = line.getTotalLength(); + line.animate({ lineDash: [0, length], opacity: 0 }, animateCfg); + } + }, - 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, + }; - /** - * - * @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); - } + group.attr({ opacity: 0 }); + group.animate({ opacity: 1 }, animateCfg); + }, }; - - - - - - - - - - - - - - - - - - diff --git a/src/View/layoutProvider.ts b/src/View/layoutProvider.ts index f0442eb..58bbaa8 100644 --- a/src/View/layoutProvider.ts +++ b/src/View/layoutProvider.ts @@ -10,6 +10,11 @@ import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker } from '../Model/S import { AddressLabelOption, IndexLabelOption, LayoutOptions, MarkerOption, ViewOptions } from '../options'; import { ViewContainer } from './viewContainer'; +export enum ELayoutMode { + HORIZONTAL = 'hor', + VERTICAL = 'ver', +} + export class LayoutProvider { private engine: Engine; private viewOptions: ViewOptions; @@ -155,7 +160,7 @@ export class LayoutProvider { }, left: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => { return { - x: nodeBound.x - labelBound.width / 2- offset, + x: nodeBound.x - labelBound.width / 2 - offset, y: nodeBound.y + nodeBound.height / 2, }; }, @@ -253,7 +258,7 @@ export class LayoutProvider { const clusters = ModelConstructor.getClusters(accumulateLeakModels); - // 每一个簇从左往右布局就完事了,比之前的方法简单稳定很多 + // 每一个簇从左往右布局就完事了,比之前的方法简单稳定很多 clusters.forEach(item => { const bound = item.getBound(), 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(), group: Group, prevBound: BoundingRect, bound: BoundingRect, boundList: BoundingRect[] = [], - dx = 0; + groupPadding = this.viewOptions.groupPadding, + dx = 0, + dy = 0; - // 左往右布局 + for (let i = 0; i < modelGroupList.length; i++) { group = modelGroupList[i]; - bound = group.getPaddingBound(this.viewOptions.groupPadding); + bound = group.getPaddingBound(groupPadding); - if (prevBound) { - dx = prevBound.x + prevBound.width - bound.x; - } else { - dx = bound.x; + // 左往右水平布局 + if (layoutMode === ELayoutMode.HORIZONTAL) { + if (prevBound) { + 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); wrapperGroup.add(group); prevBound = bound; @@ -333,11 +357,11 @@ export class LayoutProvider { * @param layoutGroupTable * @param accumulateLeakModels */ - public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[]) { + public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], layoutMode: ELayoutMode) { this.preLayoutProcess(layoutGroupTable); const modelGroupList: Group[] = this.layoutModels(layoutGroupTable); - const generalGroup: Group = this.layoutGroups(modelGroupList); + const generalGroup: Group = this.layoutGroups(modelGroupList, layoutMode); this.layoutLeakArea(accumulateLeakModels); diff --git a/src/View/reconcile.ts b/src/View/reconcile.ts index d6aa6dc..7f1b5d5 100644 --- a/src/View/reconcile.ts +++ b/src/View/reconcile.ts @@ -256,10 +256,10 @@ export class Reconcile { let { duration, timingFunction } = this.engine.animationOptions; appendModels.forEach(item => { - const G6Item = item.G6Item; + const G6Item = item.G6Item; if (item instanceof SVNodeAppendage) { - G6Item.enableCapture(false); + G6Item.enableCapture(false); // 先不显示泄漏区节点上面的地址文本 if (item instanceof SVAddressLabel) { @@ -395,9 +395,7 @@ export class Reconcile { isDiffLeak: boolean ): DiffResult { const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList); - const leakModels: SVModel[] = isDiffLeak - ? [] - : this.getLeakModels(layoutGroupTable, prevModelList, modelList); + const leakModels: SVModel[] = isDiffLeak ? [] : this.getLeakModels(layoutGroupTable, prevModelList, modelList); const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels); const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList, accumulateLeakModels); const updateModels: SVModel[] = [ diff --git a/src/View/viewContainer.ts b/src/View/viewContainer.ts index 258b9fb..a39a67b 100644 --- a/src/View/viewContainer.ts +++ b/src/View/viewContainer.ts @@ -1,5 +1,5 @@ import { Engine } from '../engine'; -import { LayoutProvider } from './layoutProvider'; +import { ELayoutMode, LayoutProvider } from './layoutProvider'; import { LayoutGroupTable } from '../Model/modelConstructor'; import { Util } from '../Common/util'; import { SVModel } from '../Model/SVModel'; @@ -19,141 +19,140 @@ import { import { handleUpdate } from '../sources'; export class ViewContainer { - private engine: Engine; - private layoutProvider: LayoutProvider; - private reconcile: Reconcile; - public renderer: Renderer; + private engine: Engine; + private layoutProvider: LayoutProvider; + private reconcile: Reconcile; + public renderer: Renderer; - private layoutGroupTable: LayoutGroupTable; - private prevModelList: SVModel[]; - private accumulateLeakModels: SVModel[]; + private layoutGroupTable: LayoutGroupTable; + private prevModelList: SVModel[]; + private accumulateLeakModels: SVModel[]; - public hasLeak: boolean; - public leakAreaY: number; + public hasLeak: boolean; + public leakAreaY: number; public lastLeakAreaTranslateY: number; - public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点 - public clickSelectNode: SVNode; // 点击选中的节点 + public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点 + public clickSelectNode: SVNode; // 点击选中的节点 - constructor(engine: Engine, DOMContainer: HTMLElement, isForce: boolean) { - const behaviorsModes: Modes = InitG6Behaviors(engine, this); + constructor(engine: Engine, DOMContainer: HTMLElement, isForce: boolean) { + const behaviorsModes: Modes = InitG6Behaviors(engine, this); - this.engine = engine; - this.layoutProvider = new LayoutProvider(engine, this); - this.renderer = new Renderer(engine, DOMContainer, behaviorsModes, isForce); - this.reconcile = new Reconcile(engine, this.renderer); - this.layoutGroupTable = new Map(); - this.prevModelList = []; - this.accumulateLeakModels = []; - this.hasLeak = false; // 判断是否已经发生过泄漏 - this.brushSelectedModels = []; - this.clickSelectNode = null; - this.lastLeakAreaTranslateY = 0; + this.engine = engine; + this.layoutProvider = new LayoutProvider(engine, this); + this.renderer = new Renderer(engine, DOMContainer, behaviorsModes, isForce); + this.reconcile = new Reconcile(engine, this.renderer); + this.layoutGroupTable = new Map(); + this.prevModelList = []; + this.accumulateLeakModels = []; + this.hasLeak = false; // 判断是否已经发生过泄漏 + this.brushSelectedModels = []; + this.clickSelectNode = null; + this.lastLeakAreaTranslateY = 0; - const g6Instance = this.renderer.getG6Instance(), - leakAreaHeight = this.engine.viewOptions.leakAreaHeight, - height = g6Instance.getHeight(), - { drag, zoom } = this.engine.behaviorOptions; + const g6Instance = this.renderer.getG6Instance(), + leakAreaHeight = this.engine.viewOptions.leakAreaHeight, + height = g6Instance.getHeight(), + { drag, zoom } = this.engine.behaviorOptions; - this.leakAreaY = height - leakAreaHeight; + this.leakAreaY = height - leakAreaHeight; - SolveNodeAppendagesDrag(this); - SolveBrushSelectDrag(this); - drag && SolveDragCanvasWithLeak(this); - zoom && SolveZoomCanvasWithLeak(this); - } + SolveNodeAppendagesDrag(this); + SolveBrushSelectDrag(this); + drag && SolveDragCanvasWithLeak(this); + zoom && SolveZoomCanvasWithLeak(this); + } + // ---------------------------------------------------------------------------------------------- - // ---------------------------------------------------------------------------------------------- - - /** - * 对主视图进行重新布局 - */ - reLayout() { - const g6Instance = this.getG6Instance(), + /** + * 对主视图进行重新布局 + */ + reLayout(layoutMode: ELayoutMode) { + const g6Instance = this.getG6Instance(), group = g6Instance.getGroup(), matrix = group.getMatrix(), bound = group.getCanvasBBox(); const { duration, enable, timingFunction } = this.engine.animationOptions; - if (matrix) { - let dx = matrix[6], - dy = matrix[7]; + if (matrix) { + let dx = matrix[6], + dy = matrix[7]; g6Instance.moveTo(bound.minX - dx, bound.minY - dy, enable, { duration, easing: timingFunction, }); - } + } - const leakAreaHeight = this.engine.viewOptions.leakAreaHeight, + const leakAreaHeight = this.engine.viewOptions.leakAreaHeight, height = g6Instance.getHeight(); this.leakAreaY = height - leakAreaHeight; this.lastLeakAreaTranslateY = 0; - this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels); + this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels, layoutMode); g6Instance.refresh(); EventBus.emit('onLeakAreaUpdate', { - leakAreaY: this.leakAreaY, - hasLeak: this.hasLeak, - }); - } + leakAreaY: this.leakAreaY, + hasLeak: this.hasLeak, + }); + } + + /** + * 获取 g6 实例 + */ + getG6Instance(): Graph { + return this.renderer.getG6Instance(); + } - /** - * 获取 g6 实例 - */ - getG6Instance(): Graph { - return this.renderer.getG6Instance(); - } + /** + * 获取泄漏区里面的元素 + * @returns + */ + getAccumulateLeakModels(): SVModel[] { + return this.accumulateLeakModels; + } - /** - * 获取泄漏区里面的元素 - * @returns - */ - getAccumulateLeakModels(): SVModel[] { - return this.accumulateLeakModels; - } + /** + * + */ + getLayoutGroupTable(): LayoutGroupTable { + return this.layoutGroupTable; + } - /** - * - */ - getLayoutGroupTable(): LayoutGroupTable { - return this.layoutGroupTable; - } + /** + * 刷新视图 + */ + refresh() { + this.renderer.getG6Instance().refresh(); + } - /** - * 刷新视图 - */ - refresh() { - this.renderer.getG6Instance().refresh(); - } + /** + * 重新调整容器尺寸 + * @param width + * @param height + */ + resize(width: number, height: number) { + const g6Instance = this.getG6Instance(), + prevContainerHeight = g6Instance.getHeight(), + globalGroup: Group = new Group(); - /** - * 重新调整容器尺寸 - * @param width - * @param height - */ - resize(width: number, height: number) { - const g6Instance = this.getG6Instance(), - prevContainerHeight = g6Instance.getHeight(), - globalGroup: Group = new Group(); + globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels); + this.renderer.changeSize(width, height); - globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels); - this.renderer.changeSize(width, height); + const containerHeight = g6Instance.getHeight(), + dy = containerHeight - prevContainerHeight; - const containerHeight = g6Instance.getHeight(), - dy = containerHeight - prevContainerHeight; + globalGroup.translate(0, dy); + this.renderer.refresh(); - globalGroup.translate(0, dy); - this.renderer.refresh(); - - this.leakAreaY += dy; - EventBus.emit('onLeakAreaUpdate', { - leakAreaY: this.leakAreaY, - hasLeak: this.hasLeak, - }); + this.leakAreaY += dy; + EventBus.emit('onLeakAreaUpdate', { + leakAreaY: this.leakAreaY, + hasLeak: this.hasLeak, + }); } /** @@ -174,7 +173,11 @@ export class ViewContainer { * @param models * @param layoutFn */ - render(layoutGroupTable: LayoutGroupTable, isSameSources: boolean, handleUpdate: handleUpdate,hasTriggerLastStep: boolean) { + render( + layoutGroupTable: LayoutGroupTable, + isSameSources: boolean, + handleUpdate: handleUpdate + ) { const modelList = Util.convertGroupTable2ModelList(layoutGroupTable); this.restoreHighlight([...modelList, ...this.accumulateLeakModels]); @@ -184,16 +187,16 @@ export class ViewContainer { return; } - // 判断是否需要进行泄漏区的比较 - let isDiffLeak = handleUpdate.isEnterFunction || hasTriggerLastStep; - + // 判断是否需要进行泄漏区的比较 + let isDiffLeak = handleUpdate?.isEnterFunction || handleUpdate?.hasTriggerLastStep; + const diffResult = this.reconcile.diff( this.layoutGroupTable, this.prevModelList, modelList, this.accumulateLeakModels, // handleUpdate?.isEnterFunction - isDiffLeak + isDiffLeak ), 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.renderer.build(renderModelList); // 首先在离屏canvas渲染先 - this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels); // 进行布局(设置model的x,y,样式等) + this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels, layoutMode); // 进行布局(设置model的x,y,样式等) this.beforeRender(); this.renderer.render(renderModelList); // 渲染视图 diff --git a/src/engine.ts b/src/engine.ts index e72d149..430b784 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -1,229 +1,238 @@ -import { handleUpdate, Sources } from "./sources"; -import { LayoutGroupTable, ModelConstructor } from "./Model/modelConstructor"; -import { AnimationOptions, BehaviorOptions, EngineOptions, LayoutGroupOptions, ViewOptions } from "./options"; -import { EventBus } from "./Common/eventBus"; -import { ViewContainer } from "./View/viewContainer"; -import { SVNode } from "./Model/SVNode"; -import { Util } from "./Common/util"; -import { SVModel } from "./Model/SVModel"; -import { G6Event } from "@antv/g6"; - +import { handleUpdate, Sources } from './sources'; +import { LayoutGroupTable, ModelConstructor } from './Model/modelConstructor'; +import { AnimationOptions, BehaviorOptions, EngineOptions, LayoutGroupOptions, ViewOptions } from './options'; +import { EventBus } from './Common/eventBus'; +import { ViewContainer } from './View/viewContainer'; +import { SVNode } from './Model/SVNode'; +import { Util } from './Common/util'; +import { SVModel } from './Model/SVModel'; +import { G6Event } from '@antv/g6'; +import { ELayoutMode } from './View/layoutProvider'; export class Engine { - private modelConstructor: ModelConstructor; - private viewContainer: ViewContainer; - private prevStringSource: string; + private modelConstructor: ModelConstructor; + private viewContainer: ViewContainer; + private prevStringSource: string; - public engineOptions: EngineOptions; - public viewOptions: ViewOptions; - public animationOptions: AnimationOptions; - public behaviorOptions: BehaviorOptions; + public engineOptions: EngineOptions; + public viewOptions: ViewOptions; + public animationOptions: AnimationOptions; + public behaviorOptions: BehaviorOptions; - constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean) { - this.engineOptions = Object.assign({}, engineOptions); + constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean) { + this.engineOptions = Object.assign({}, engineOptions); - this.viewOptions = Object.assign({ - fitCenter: true, - fitView: false, - groupPadding: 20, - leakAreaHeight: 150, - updateHighlight: '#fc5185' - }, engineOptions.view); + this.viewOptions = Object.assign( + { + fitCenter: true, + fitView: false, + groupPadding: 20, + leakAreaHeight: 150, + updateHighlight: '#fc5185', + layoutMode: ELayoutMode.HORIZONTAL + }, + engineOptions.view + ); - this.animationOptions = Object.assign({ - enable: true, - duration: 750, - timingFunction: 'easePolyOut' - }, engineOptions.animation); + this.animationOptions = Object.assign( + { + enable: true, + duration: 750, + timingFunction: 'easePolyOut', + }, + engineOptions.animation + ); - this.behaviorOptions = Object.assign({ - drag: true, - zoom: true, - dragNode: true, - selectNode: true - }, engineOptions.behavior); + this.behaviorOptions = Object.assign( + { + drag: true, + zoom: true, + dragNode: true, + selectNode: true, + }, + engineOptions.behavior + ); - this.modelConstructor = new ModelConstructor(this); - this.viewContainer = new ViewContainer(this, DOMContainer, isForce); - } + this.modelConstructor = new ModelConstructor(this); + this.viewContainer = new ViewContainer(this, DOMContainer, isForce); + } - /** - * 输入数据进行渲染 - * @param sources - * @param prevStep - */ - public render(source: Sources, hasTriggerLastStep: boolean) { - let isSameSources: boolean = false, - layoutGroupTable: LayoutGroupTable; + /** + * 输入数据进行渲染 + * @param sources + * @param prevStep + */ + public render(source: Sources) { + let isSameSources: boolean = false, + layoutGroupTable: LayoutGroupTable; - if (source === undefined || source === null) { - return; - } + if (source === undefined || source === null) { + return; + } - let handleUpdate: handleUpdate = source.handleUpdate, - stringSource = JSON.stringify(source); - - if (this.prevStringSource === stringSource) { - isSameSources = true; - } + let handleUpdate: handleUpdate = source.handleUpdate, + stringSource = JSON.stringify(source); - this.prevStringSource = stringSource; + if (this.prevStringSource === stringSource) { + isSameSources = true; + } + this.prevStringSource = stringSource; - if(isSameSources) { - // 若源数据两次一样的,用回上一次的layoutGroupTable - layoutGroupTable = this.modelConstructor.getLayoutGroupTable(); - } - else { - // 1 转换模型(data => model) - layoutGroupTable = this.modelConstructor.construct(source); - } + if (isSameSources) { + // 若源数据两次一样的,用回上一次的layoutGroupTable + layoutGroupTable = this.modelConstructor.getLayoutGroupTable(); + } else { + // 1 转换模型(data => model) + layoutGroupTable = this.modelConstructor.construct(source); + } - // 2 渲染(使用g6进行渲染) - this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate, hasTriggerLastStep); - } + // 2 渲染(使用g6进行渲染) + this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate); + } + /** + * 重新布局 + */ + public reLayout(layoutMode?: ELayoutMode) { + this.viewOptions.layoutMode = layoutMode || this.viewOptions.layoutMode; + this.viewContainer.reLayout(this.viewOptions.layoutMode); + } - /** - * 重新布局 - */ - public reLayout() { - this.viewContainer.reLayout(); - } + /** + * 获取 G6 实例 + */ + public getGraphInstance() { + return this.viewContainer.getG6Instance(); + } - /** - * 获取 G6 实例 - */ - public getGraphInstance() { - return this.viewContainer.getG6Instance(); - } + /** + * 隐藏某些组 + * @param groupNames + */ + public hideGroups(groupNames: string | string[]) { + const names = Array.isArray(groupNames) ? groupNames : [groupNames], + instance = this.viewContainer.getG6Instance(), + layoutGroupTable = this.modelConstructor.getLayoutGroupTable(); - /** - * 隐藏某些组 - * @param groupNames - */ - public hideGroups(groupNames: string | string[]) { - const names = Array.isArray(groupNames) ? groupNames : [groupNames], - instance = this.viewContainer.getG6Instance(), - layoutGroupTable = this.modelConstructor.getLayoutGroupTable(); + layoutGroupTable.forEach(item => { + const hasName = names.find(name => name === item.layout); - layoutGroupTable.forEach(item => { - const hasName = names.find(name => name === item.layout); + if (hasName && !item.isHide) { + item.modelList.forEach(model => instance.hideItem(model.G6Item)); + item.isHide = true; + } - if (hasName && !item.isHide) { - item.modelList.forEach(model => instance.hideItem(model.G6Item)); - item.isHide = true; - } + if (!hasName && item.isHide) { + item.modelList.forEach(model => instance.showItem(model.G6Item)); + item.isHide = false; + } + }); + } - if (!hasName && item.isHide) { - item.modelList.forEach(model => instance.showItem(model.G6Item)); - item.isHide = false; - } - }); - } + /** + * + */ + public getAllModels(): SVModel[] { + const modelList = Util.convertGroupTable2ModelList(this.modelConstructor.getLayoutGroupTable()); + const accumulateLeakModels = this.viewContainer.getAccumulateLeakModels(); - /** - * - */ - public getAllModels(): SVModel[] { - const modelList = Util.convertGroupTable2ModelList(this.modelConstructor.getLayoutGroupTable()); - const accumulateLeakModels = this.viewContainer.getAccumulateLeakModels(); + return [...modelList, ...accumulateLeakModels]; + } - return [...modelList, ...accumulateLeakModels]; - } + /** + * 根据配置变化更新视图 + * @param modelType + * @returns + */ + public updateStyle(group: string, newOptions: LayoutGroupOptions) { + const models = this.getAllModels(), + layoutGroup = this.modelConstructor.getLayoutGroupTable().get(group); - /** - * 根据配置变化更新视图 - * @param modelType - * @returns - */ - public updateStyle(group: string, newOptions: LayoutGroupOptions) { - const models = this.getAllModels(), - layoutGroup = this.modelConstructor.getLayoutGroupTable().get(group); + layoutGroup.options = newOptions; + models.forEach(item => { + if (item.group !== group) { + return; + } - layoutGroup.options = newOptions; - models.forEach(item => { - if (item.group !== group) { - return; - } + const modelType = item.getModelType(), + optionsType = layoutGroup.options[modelType]; - const modelType = item.getModelType(), - optionsType = layoutGroup.options[modelType]; + if (optionsType) { + if (modelType === 'addressLabel') { + item.updateG6ModelStyle(item.generateG6ModelProps(optionsType)); + } else { + const targetModelOption = optionsType[item.sourceType]; + if (targetModelOption) { + item.updateG6ModelStyle(item.generateG6ModelProps(targetModelOption)); + } + } + } + }); + } - if (optionsType) { - if (modelType === 'addressLabel') { - item.updateG6ModelStyle(item.generateG6ModelProps(optionsType)); - } - else { - const targetModelOption = optionsType[item.sourceType]; - if (targetModelOption) { - item.updateG6ModelStyle(item.generateG6ModelProps(targetModelOption)); - } - } - } - }); - } + /** + * 使用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; - /** - * 使用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; + return targetNode; + } - return targetNode; - } + /** + * 调整容器尺寸 + * @param width + * @param height + */ + public resize(width: number, height: number) { + this.viewContainer.resize(width, height); + } - /** - * 调整容器尺寸 - * @param width - * @param height - */ - public resize(width: number, height: number) { - this.viewContainer.resize(width, height); - } + /** + * 绑定 G6 事件 + * @param eventName + * @param callback + */ + public on(eventName: string, callback: Function) { + if (typeof callback !== 'function') { + return; + } - /** - * 绑定 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); + return; + } - if (eventName === 'onFreed' || eventName === 'onLeak') { - EventBus.on(eventName, callback); - return; - } + if (eventName === 'onLeakAreaUpdate') { + EventBus.on(eventName, callback); + return; + } - if (eventName === 'onLeakAreaUpdate') { - EventBus.on(eventName, callback); - return; - } + this.viewContainer.getG6Instance().on(eventName as G6Event, event => { + callback(event.item['SVModel']); + }); + } - this.viewContainer.getG6Instance().on(eventName as G6Event, event => { - callback(event.item['SVModel']); - }); - } + /** + * 开启/关闭框选模式 + * @param enable + */ + public switchBrushSelect(enable: boolean) { + const g6Instance = this.viewContainer.getG6Instance(); + enable ? g6Instance.setMode('brush') : g6Instance.setMode('default'); + } - /** - * 开启/关闭框选模式 - * @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(); - } -}; \ No newline at end of file + /** + * 销毁引擎 + */ + public destroy() { + this.modelConstructor.destroy(); + this.viewContainer.destroy(); + } +} diff --git a/src/options.ts b/src/options.ts index 35bae5c..cf44663 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,6 +1,7 @@ import { SVModel } from "./Model/SVModel"; import { SVNode } from "./Model/SVNode"; import { SourceNode } from "./sources"; +import { ELayoutMode } from "./View/layoutProvider"; export interface Style { @@ -104,6 +105,7 @@ export interface ViewOptions { groupPadding: number; updateHighlight: string; leakAreaHeight: number; + layoutMode: ELayoutMode; } diff --git a/src/sources.ts b/src/sources.ts index 5f08fbd..d58b10e 100644 --- a/src/sources.ts +++ b/src/sources.ts @@ -16,6 +16,7 @@ export interface SourceNode { export interface handleUpdate { isEnterFunction: boolean; isFirstDebug: boolean; + hasTriggerLastStep: boolean; } export type Sources = {