feat:增加布局模式
This commit is contained in:
parent
54a4a98e75
commit
f85f8beecd
@ -49,7 +49,6 @@ export class ModelConstructor {
|
||||
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
||||
layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout;
|
||||
|
||||
|
||||
Object.keys(sources).forEach(group => {
|
||||
let sourceGroup = sources[group],
|
||||
layout = sourceGroup.layouter,
|
||||
@ -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) {
|
||||
@ -525,7 +522,7 @@ export class ModelConstructor {
|
||||
});
|
||||
|
||||
const DFS = (model: SVModel, clusterId: number, idMap): SVModel[] => {
|
||||
if(model === null) {
|
||||
if (model === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
import { Util, Item } from '@antv/g6';
|
||||
|
||||
|
||||
export type animationConfig = {
|
||||
duration: number;
|
||||
timingFunction: string;
|
||||
callback?: () => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 动画表
|
||||
*/
|
||||
export const Animations = {
|
||||
|
||||
/**
|
||||
* 添加节点 / 边时的动画效果
|
||||
* @param G6Item
|
||||
@ -26,7 +23,7 @@ export const Animations = {
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
callback: animationConfig.callback,
|
||||
};
|
||||
|
||||
if (type === 'node') {
|
||||
@ -61,7 +58,7 @@ export const Animations = {
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
callback: animationConfig.callback,
|
||||
};
|
||||
|
||||
if (type === 'node') {
|
||||
@ -89,28 +86,10 @@ export const Animations = {
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
callback: animationConfig.callback,
|
||||
};
|
||||
|
||||
group.attr({ opacity: 0 });
|
||||
group.animate({ opacity: 1 }, animateCfg);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
},
|
||||
@ -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(),
|
||||
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 (layoutMode === ELayoutMode.HORIZONTAL) {
|
||||
if (prevBound) {
|
||||
dx = prevBound.x + prevBound.width - bound.x;
|
||||
} else {
|
||||
@ -291,6 +301,20 @@ export class LayoutProvider {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@ -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[] = [
|
||||
|
||||
@ -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';
|
||||
@ -62,13 +62,12 @@ export class ViewContainer {
|
||||
zoom && SolveZoomCanvasWithLeak(this);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 对主视图进行重新布局
|
||||
*/
|
||||
reLayout() {
|
||||
reLayout(layoutMode: ELayoutMode) {
|
||||
const g6Instance = this.getG6Instance(),
|
||||
group = g6Instance.getGroup(),
|
||||
matrix = group.getMatrix(),
|
||||
@ -91,7 +90,7 @@ export class ViewContainer {
|
||||
|
||||
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', {
|
||||
@ -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]);
|
||||
@ -185,7 +188,7 @@ export class ViewContainer {
|
||||
}
|
||||
|
||||
// 判断是否需要进行泄漏区的比较
|
||||
let isDiffLeak = handleUpdate.isEnterFunction || hasTriggerLastStep;
|
||||
let isDiffLeak = handleUpdate?.isEnterFunction || handleUpdate?.hasTriggerLastStep;
|
||||
|
||||
const diffResult = this.reconcile.diff(
|
||||
this.layoutGroupTable,
|
||||
@ -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); // 渲染视图
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
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;
|
||||
@ -22,26 +22,36 @@ export class Engine {
|
||||
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean) {
|
||||
this.engineOptions = Object.assign({}, engineOptions);
|
||||
|
||||
this.viewOptions = Object.assign({
|
||||
this.viewOptions = Object.assign(
|
||||
{
|
||||
fitCenter: true,
|
||||
fitView: false,
|
||||
groupPadding: 20,
|
||||
leakAreaHeight: 150,
|
||||
updateHighlight: '#fc5185'
|
||||
}, engineOptions.view);
|
||||
updateHighlight: '#fc5185',
|
||||
layoutMode: ELayoutMode.HORIZONTAL
|
||||
},
|
||||
engineOptions.view
|
||||
);
|
||||
|
||||
this.animationOptions = Object.assign({
|
||||
this.animationOptions = Object.assign(
|
||||
{
|
||||
enable: true,
|
||||
duration: 750,
|
||||
timingFunction: 'easePolyOut'
|
||||
}, engineOptions.animation);
|
||||
timingFunction: 'easePolyOut',
|
||||
},
|
||||
engineOptions.animation
|
||||
);
|
||||
|
||||
this.behaviorOptions = Object.assign({
|
||||
this.behaviorOptions = Object.assign(
|
||||
{
|
||||
drag: true,
|
||||
zoom: true,
|
||||
dragNode: true,
|
||||
selectNode: true
|
||||
}, engineOptions.behavior);
|
||||
selectNode: true,
|
||||
},
|
||||
engineOptions.behavior
|
||||
);
|
||||
|
||||
this.modelConstructor = new ModelConstructor(this);
|
||||
this.viewContainer = new ViewContainer(this, DOMContainer, isForce);
|
||||
@ -52,7 +62,7 @@ export class Engine {
|
||||
* @param sources
|
||||
* @param prevStep
|
||||
*/
|
||||
public render(source: Sources, hasTriggerLastStep: boolean) {
|
||||
public render(source: Sources) {
|
||||
let isSameSources: boolean = false,
|
||||
layoutGroupTable: LayoutGroupTable;
|
||||
|
||||
@ -69,26 +79,24 @@ export class Engine {
|
||||
|
||||
this.prevStringSource = stringSource;
|
||||
|
||||
|
||||
if(isSameSources) {
|
||||
if (isSameSources) {
|
||||
// 若源数据两次一样的,用回上一次的layoutGroupTable
|
||||
layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// 1 转换模型(data => model)
|
||||
layoutGroupTable = this.modelConstructor.construct(source);
|
||||
}
|
||||
|
||||
// 2 渲染(使用g6进行渲染)
|
||||
this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate, hasTriggerLastStep);
|
||||
this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重新布局
|
||||
*/
|
||||
public reLayout() {
|
||||
this.viewContainer.reLayout();
|
||||
public reLayout(layoutMode?: ELayoutMode) {
|
||||
this.viewOptions.layoutMode = layoutMode || this.viewOptions.layoutMode;
|
||||
this.viewContainer.reLayout(this.viewOptions.layoutMode);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,8 +161,7 @@ export class Engine {
|
||||
if (optionsType) {
|
||||
if (modelType === 'addressLabel') {
|
||||
item.updateG6ModelStyle(item.generateG6ModelProps(optionsType));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const targetModelOption = optionsType[item.sourceType];
|
||||
if (targetModelOption) {
|
||||
item.updateG6ModelStyle(item.generateG6ModelProps(targetModelOption));
|
||||
@ -171,7 +178,9 @@ export class Engine {
|
||||
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;
|
||||
const targetNode: SVNode = modelList.find(
|
||||
item => item instanceof SVNode && item.sourceId === stringId
|
||||
) as SVNode;
|
||||
|
||||
return targetNode;
|
||||
}
|
||||
@ -226,4 +235,4 @@ export class Engine {
|
||||
this.modelConstructor.destroy();
|
||||
this.viewContainer.destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ export interface SourceNode {
|
||||
export interface handleUpdate {
|
||||
isEnterFunction: boolean;
|
||||
isFirstDebug: boolean;
|
||||
hasTriggerLastStep: boolean;
|
||||
}
|
||||
|
||||
export type Sources = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user