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

@ -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) {

View File

@ -1,19 +1,16 @@
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 G6Item
@ -26,7 +23,7 @@ export const Animations = {
animateCfg = { animateCfg = {
duration: animationConfig.duration, duration: animationConfig.duration,
easing: animationConfig.timingFunction, easing: animationConfig.timingFunction,
callback: animationConfig.callback callback: animationConfig.callback,
}; };
if (type === 'node') { if (type === 'node') {
@ -61,7 +58,7 @@ export const Animations = {
animateCfg = { animateCfg = {
duration: animationConfig.duration, duration: animationConfig.duration,
easing: animationConfig.timingFunction, easing: animationConfig.timingFunction,
callback: animationConfig.callback callback: animationConfig.callback,
}; };
if (type === 'node') { if (type === 'node') {
@ -89,28 +86,10 @@ export const Animations = {
animateCfg = { animateCfg = {
duration: animationConfig.duration, duration: animationConfig.duration,
easing: animationConfig.timingFunction, easing: animationConfig.timingFunction,
callback: animationConfig.callback callback: animationConfig.callback,
}; };
group.attr({ opacity: 0 }); group.attr({ opacity: 0 });
group.animate({ opacity: 1 }, animateCfg); 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;
@ -268,21 +273,26 @@ 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 (layoutMode === ELayoutMode.HORIZONTAL) {
if (prevBound) { if (prevBound) {
dx = prevBound.x + prevBound.width - bound.x; dx = prevBound.x + prevBound.width - bound.x;
} else { } else {
@ -291,6 +301,20 @@ export class LayoutProvider {
group.translate(dx, 0); group.translate(dx, 0);
Bound.translate(bound, 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);
}
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

@ -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';
@ -62,13 +62,12 @@ export class ViewContainer {
zoom && SolveZoomCanvasWithLeak(this); zoom && SolveZoomCanvasWithLeak(this);
} }
// ---------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------
/** /**
* *
*/ */
reLayout() { reLayout(layoutMode: ELayoutMode) {
const g6Instance = this.getG6Instance(), const g6Instance = this.getG6Instance(),
group = g6Instance.getGroup(), group = g6Instance.getGroup(),
matrix = group.getMatrix(), matrix = group.getMatrix(),
@ -91,7 +90,7 @@ export class ViewContainer {
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', {
@ -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]);
@ -185,7 +188,7 @@ export class ViewContainer {
} }
// 判断是否需要进行泄漏区的比较 // 判断是否需要进行泄漏区的比较
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,
@ -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,13 +1,13 @@
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;
@ -22,26 +22,36 @@ export class Engine {
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, fitCenter: true,
fitView: false, fitView: false,
groupPadding: 20, groupPadding: 20,
leakAreaHeight: 150, leakAreaHeight: 150,
updateHighlight: '#fc5185' updateHighlight: '#fc5185',
}, engineOptions.view); layoutMode: ELayoutMode.HORIZONTAL
},
engineOptions.view
);
this.animationOptions = Object.assign({ this.animationOptions = Object.assign(
{
enable: true, enable: true,
duration: 750, duration: 750,
timingFunction: 'easePolyOut' timingFunction: 'easePolyOut',
}, engineOptions.animation); },
engineOptions.animation
);
this.behaviorOptions = Object.assign({ this.behaviorOptions = Object.assign(
{
drag: true, drag: true,
zoom: true, zoom: true,
dragNode: true, dragNode: true,
selectNode: true selectNode: true,
}, engineOptions.behavior); },
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);
@ -52,7 +62,7 @@ export class Engine {
* @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;
@ -69,26 +79,24 @@ export class Engine {
this.prevStringSource = stringSource; this.prevStringSource = stringSource;
if (isSameSources) { if (isSameSources) {
// 若源数据两次一样的用回上一次的layoutGroupTable // 若源数据两次一样的用回上一次的layoutGroupTable
layoutGroupTable = this.modelConstructor.getLayoutGroupTable(); layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
} } else {
else {
// 1 转换模型data => model // 1 转换模型data => model
layoutGroupTable = this.modelConstructor.construct(source); layoutGroupTable = this.modelConstructor.construct(source);
} }
// 2 渲染使用g6进行渲染 // 2 渲染使用g6进行渲染
this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate, hasTriggerLastStep); this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate);
} }
/** /**
* *
*/ */
public reLayout() { public reLayout(layoutMode?: ELayoutMode) {
this.viewContainer.reLayout(); this.viewOptions.layoutMode = layoutMode || this.viewOptions.layoutMode;
this.viewContainer.reLayout(this.viewOptions.layoutMode);
} }
/** /**
@ -153,8 +161,7 @@ export class Engine {
if (optionsType) { if (optionsType) {
if (modelType === 'addressLabel') { if (modelType === 'addressLabel') {
item.updateG6ModelStyle(item.generateG6ModelProps(optionsType)); item.updateG6ModelStyle(item.generateG6ModelProps(optionsType));
} } else {
else {
const targetModelOption = optionsType[item.sourceType]; const targetModelOption = optionsType[item.sourceType];
if (targetModelOption) { if (targetModelOption) {
item.updateG6ModelStyle(item.generateG6ModelProps(targetModelOption)); item.updateG6ModelStyle(item.generateG6ModelProps(targetModelOption));
@ -171,7 +178,9 @@ export class Engine {
public findNode(id: string): SVNode { public findNode(id: string): SVNode {
const modelList = this.getAllModels(); const modelList = this.getAllModels();
const stringId = id.toString(); const stringId = id.toString();
const targetNode: SVNode = modelList.find(item => item instanceof SVNode && item.sourceId === stringId) as SVNode; const targetNode: SVNode = modelList.find(
item => item instanceof SVNode && item.sourceId === stringId
) as SVNode;
return targetNode; return targetNode;
} }
@ -226,4 +235,4 @@ export class Engine {
this.modelConstructor.destroy(); this.modelConstructor.destroy();
this.viewContainer.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 = {