feat:添加多种数据结构同时渲染的支持
This commit is contained in:
parent
4c1510da1f
commit
efdb325adc
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
|||||||
node_modules
|
node_modules
|
||||||
test
|
test
|
||||||
|
demoV2
|
||||||
|
demo
|
||||||
@ -238,6 +238,15 @@ const data = {
|
|||||||
node: []
|
node: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let d = [
|
||||||
|
{ id: 10, type: 'head', name: 'QPtr', label: 'front', external: ['lq'], front: 0 },
|
||||||
|
{ id: 11, type: 'head', name: 'QPtr', label: 'rear', external: null, rear: 2 },
|
||||||
|
{ id: 0, next: 1 },
|
||||||
|
{ id: 1, next: 2 },
|
||||||
|
{ id: 2 }
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const LQueue = function(container) {
|
const LQueue = function(container) {
|
||||||
return{
|
return{
|
||||||
|
|||||||
@ -82,6 +82,8 @@ const Engine = SV.Engine,
|
|||||||
<script src="./dataStruct/GeneralizedList.js"></script>
|
<script src="./dataStruct/GeneralizedList.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const engines = {
|
const engines = {
|
||||||
0: BTree,
|
0: BTree,
|
||||||
1: LList,
|
1: LList,
|
||||||
@ -98,7 +100,7 @@ const engines = {
|
|||||||
|
|
||||||
let dataCounter = 0;
|
let dataCounter = 0;
|
||||||
|
|
||||||
let cur = engines[3](document.getElementById('container'), {
|
let cur = engines[1](document.getElementById('container'), {
|
||||||
freedContainer: document.getElementById('freed'),
|
freedContainer: document.getElementById('freed'),
|
||||||
leakContainer: document.getElementById('leak')
|
leakContainer: document.getElementById('leak')
|
||||||
});
|
});
|
||||||
@ -113,12 +115,9 @@ document.getElementById('btn-next').addEventListener('click', e => {
|
|||||||
cur.engine.reLayout();
|
cur.engine.reLayout();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('btn-set').addEventListener('click', e => {
|
|
||||||
let els = cur.engine.getElements();
|
|
||||||
|
|
||||||
els.map(item => {
|
cur.engine.on('node:mouseover', evt => {
|
||||||
item.set('style', { fill: 'red' });
|
console.log(evt);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
dist/sv.js
vendored
2
dist/sv.js
vendored
File diff suppressed because one or more lines are too long
@ -1,154 +0,0 @@
|
|||||||
import { Engine } from "../engine";
|
|
||||||
import { Util } from "../Common/util";
|
|
||||||
|
|
||||||
export class Behavior {
|
|
||||||
private engine: Engine;
|
|
||||||
private graphInstance;
|
|
||||||
|
|
||||||
constructor(engine: Engine, graphInstance) {
|
|
||||||
this.engine = engine;
|
|
||||||
this.graphInstance = graphInstance;
|
|
||||||
|
|
||||||
const interactionOptions = this.engine.interactionOptions,
|
|
||||||
selectNode: boolean | string[] = interactionOptions.selectNode,
|
|
||||||
dragNode: boolean | string[] = interactionOptions.dragNode;
|
|
||||||
|
|
||||||
if(interactionOptions.dragNode) {
|
|
||||||
this.initDragNode(dragNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(interactionOptions.selectNode) {
|
|
||||||
this.initSelectNode(selectNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化节点拖拽事件
|
|
||||||
*/
|
|
||||||
private initDragNode(dragNode: boolean | string[]) {
|
|
||||||
let pointer = null,
|
|
||||||
pointerX = null,
|
|
||||||
pointerY = null,
|
|
||||||
dragStartX = null,
|
|
||||||
dragStartY = null;
|
|
||||||
|
|
||||||
this.graphInstance.on('node:dragstart', ev => {
|
|
||||||
if(dragNode === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let model = ev.item.getModel();
|
|
||||||
|
|
||||||
if(Array.isArray(dragNode) && dragNode.find(item => item === model.modelName) === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pointer = this.graphInstance.findById(model.externalPointerId);
|
|
||||||
|
|
||||||
if(pointer) {
|
|
||||||
pointerX = pointer.getModel().x,
|
|
||||||
pointerY = pointer.getModel().y;
|
|
||||||
dragStartX = ev.canvasX;
|
|
||||||
dragStartY = ev.canvasY;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.graphInstance.on('node:dragend', ev => {
|
|
||||||
pointer = null;
|
|
||||||
pointerX = null,
|
|
||||||
pointerY = null,
|
|
||||||
dragStartX = null,
|
|
||||||
dragStartY = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.graphInstance.on('node:drag', ev => {
|
|
||||||
if(!pointer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dx = ev.canvasX - dragStartX,
|
|
||||||
dy = ev.canvasY - dragStartY,
|
|
||||||
zoom = this.graphInstance.getZoom();
|
|
||||||
|
|
||||||
pointer.updatePosition({
|
|
||||||
x: pointerX + dx / zoom,
|
|
||||||
y: pointerY + dy / zoom
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化节/边选中
|
|
||||||
* @param selectNode
|
|
||||||
*/
|
|
||||||
private initSelectNode(selectNode: boolean | string[]) {
|
|
||||||
let defaultHighlightColor = '#f08a5d',
|
|
||||||
curSelectItem = null,
|
|
||||||
curSelectItemStyle = null;
|
|
||||||
|
|
||||||
if(selectNode === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectCallback = ev => {
|
|
||||||
const item = ev.item,
|
|
||||||
model = item.getModel(),
|
|
||||||
type = item.getType(),
|
|
||||||
name = model.modelName,
|
|
||||||
highlightColor = model.style.selectedColor;
|
|
||||||
|
|
||||||
if(Array.isArray(selectNode) && selectNode.find(item => item === name) === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(model.isDynamic) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(curSelectItem && curSelectItem !== item) {
|
|
||||||
curSelectItem.update({
|
|
||||||
style: curSelectItemStyle
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
curSelectItem = item;
|
|
||||||
curSelectItemStyle = Util.objectClone(curSelectItem.getModel().style);
|
|
||||||
curSelectItem.update({
|
|
||||||
style: {
|
|
||||||
...curSelectItemStyle,
|
|
||||||
[type === 'node'? 'fill': 'stroke']: highlightColor || defaultHighlightColor
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.graphInstance.on('node:click', selectCallback);
|
|
||||||
this.graphInstance.on('edge:click', selectCallback);
|
|
||||||
this.graphInstance.on('click', ev => {
|
|
||||||
if(curSelectItem === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
curSelectItem.update({
|
|
||||||
style: curSelectItemStyle
|
|
||||||
});
|
|
||||||
|
|
||||||
curSelectItem = null;
|
|
||||||
curSelectItemStyle = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绑定 G6 事件
|
|
||||||
* @param eventName
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public on(eventName: string, callback: Function) {
|
|
||||||
if(this.graphInstance === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.graphInstance.on(eventName, evt => {
|
|
||||||
callback(evt.item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -106,6 +106,17 @@ export const Bound = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位移包围盒
|
||||||
|
* @param bound
|
||||||
|
* @param dx
|
||||||
|
* @param dy
|
||||||
|
*/
|
||||||
|
translate(bound: BoundingRect, dx: number, dy: number) {
|
||||||
|
bound.x += dx;
|
||||||
|
bound.y += dy;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 求包围盒旋转后新形成的包围盒
|
* 求包围盒旋转后新形成的包围盒
|
||||||
* @param bound
|
* @param bound
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
import { Util } from "./util";
|
import { Util } from "./util";
|
||||||
import { BoundingRect, Bound } from "./boundingRect";
|
import { BoundingRect, Bound } from "./boundingRect";
|
||||||
import { Vector } from "./vector";
|
import { Element, Model } from "../Model/modelData";
|
||||||
import { Element } from "../Model/modelData";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* element组
|
* model 集合组
|
||||||
*/
|
*/
|
||||||
export class Group {
|
export class Group {
|
||||||
id: string;
|
id: string;
|
||||||
private elements: Array<Element | Group> = [];
|
private models: Array<Model | Group> = [];
|
||||||
|
|
||||||
constructor(...arg: Array<Element | Group>) {
|
constructor(...arg: Array<Model | Group>) {
|
||||||
this.id = Util.generateId();
|
this.id = Util.generateId();
|
||||||
|
|
||||||
if(arg) {
|
if(arg) {
|
||||||
@ -24,25 +23,43 @@ export class Group {
|
|||||||
* 添加element
|
* 添加element
|
||||||
* @param arg
|
* @param arg
|
||||||
*/
|
*/
|
||||||
add(...arg: Array<Element | Group>) {
|
add(...arg: Array<Model | Group>) {
|
||||||
arg.map(ele => {
|
arg.map(ele => {
|
||||||
this.elements.push(ele);
|
this.models.push(ele);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除element
|
* 移除 model
|
||||||
* @param element
|
* @param element
|
||||||
*/
|
*/
|
||||||
remove(element: Element | Group) {
|
remove(model: Model | Group) {
|
||||||
Util.removeFromList(this.elements, item => item.id === element.id);
|
Util.removeFromList(this.models, item => item.id === model.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取group的包围盒
|
* 获取group的包围盒
|
||||||
*/
|
*/
|
||||||
getBound(): BoundingRect {
|
getBound(): BoundingRect {
|
||||||
return Bound.union(...this.elements.map(item => item.getBound()));
|
return this.models.length?
|
||||||
|
Bound.union(...this.models.map(item => item.getBound())):
|
||||||
|
{ x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取具有一定内边距的包围盒
|
||||||
|
* @param padding
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getPaddingBound(padding: number = 0): BoundingRect {
|
||||||
|
const bound = this.getBound();
|
||||||
|
|
||||||
|
bound.x -= padding;
|
||||||
|
bound.y -= padding;
|
||||||
|
bound.width += padding * 2;
|
||||||
|
bound.height += padding * 2;
|
||||||
|
|
||||||
|
return bound;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,7 +68,7 @@ export class Group {
|
|||||||
* @param dy
|
* @param dy
|
||||||
*/
|
*/
|
||||||
translate(dx: number, dy: number) {
|
translate(dx: number, dy: number) {
|
||||||
this.elements.map(item => {
|
this.models.map(item => {
|
||||||
if(item instanceof Group) {
|
if(item instanceof Group) {
|
||||||
item.translate(dx, dy);
|
item.translate(dx, dy);
|
||||||
}
|
}
|
||||||
@ -62,40 +79,10 @@ export class Group {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 旋转group
|
|
||||||
* @param rotation
|
|
||||||
* @param center
|
|
||||||
*/
|
|
||||||
rotate(rotation: number, center?: [number, number]) {
|
|
||||||
// if(rotation === 0) return;
|
|
||||||
|
|
||||||
// let {x, y, width, height} = this.getBound(),
|
|
||||||
// cx = x + width / 2,
|
|
||||||
// cy = y + height / 2;
|
|
||||||
|
|
||||||
// if(center) {
|
|
||||||
// cx = center[0];
|
|
||||||
// cy = center[1];
|
|
||||||
// }
|
|
||||||
|
|
||||||
// this.elements.map(item => {
|
|
||||||
// if(item instanceof Group) {
|
|
||||||
// item.rotate(rotation, [cx, cy]);
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// let d = Vector.rotation(rotation, [item.x, item.y], [cx, cy]);
|
|
||||||
// item.x = d[0];
|
|
||||||
// item.y = d[1];
|
|
||||||
// item.set('rotation', rotation);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空group
|
* 清空group
|
||||||
*/
|
*/
|
||||||
clear() {
|
clear() {
|
||||||
this.elements.length = 0;
|
this.models.length = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { ConstructList } from "../Model/modelConstructor";
|
import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor";
|
||||||
import { G6EdgeModel, G6NodeModel, Link, Model } from "../Model/modelData";
|
import { G6EdgeModel, G6NodeModel, Link, Model } from "../Model/modelData";
|
||||||
import { SV } from "../StructV";
|
import { SV } from "../StructV";
|
||||||
import { G6Data } from "../View/renderer";
|
import { G6Data } from "../View/renderer";
|
||||||
@ -78,21 +78,27 @@ export const Util = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param constructListType
|
* @param groupTable
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
converterList(modelContainer: { [key: string]: ConstructList[keyof ConstructList]}) {
|
convertGroupTable2ModelList(groupTable: LayoutGroupTable): Model[] {
|
||||||
return [].concat(...Object.keys(modelContainer).map(item => modelContainer[item]));
|
const list: Model[] = [];
|
||||||
|
|
||||||
|
groupTable.forEach(item => {
|
||||||
|
list.push(...item.modelList);
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* G6 data 转换器
|
* G6 data 转换器
|
||||||
* @param constructList
|
* @param layoutGroup
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
convertG6Data(constructList: ConstructList): G6Data {
|
convertG6Data(layoutGroup: LayoutGroup): G6Data {
|
||||||
let nodes = [...constructList.element, ...constructList.pointer],
|
let nodes = [...layoutGroup.element, ...layoutGroup.pointer],
|
||||||
edges = constructList.link;
|
edges = layoutGroup.link;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: nodes.map(item => item.cloneProps()) as G6NodeModel[],
|
nodes: nodes.map(item => item.cloneProps()) as G6NodeModel[],
|
||||||
|
|||||||
@ -1,58 +1,120 @@
|
|||||||
import { Util } from "../Common/util";
|
import { Util } from "../Common/util";
|
||||||
import { Engine } from "../engine";
|
import { Engine } from "../engine";
|
||||||
import { LinkOption, PointerOption } from "../options";
|
import { ElementOption, Layouter, LayoutGroupOptions, LinkOption, PointerOption } from "../options";
|
||||||
import { sourceLinkData, SourceElement, LinkTarget } from "../sources";
|
import { sourceLinkData, SourceElement, LinkTarget, Sources } from "../sources";
|
||||||
import { Element, Link, Pointer } from "./modelData";
|
import { SV } from "../StructV";
|
||||||
|
import { Element, Link, Model, Pointer } from "./modelData";
|
||||||
|
|
||||||
|
|
||||||
export interface ConstructList {
|
export type LayoutGroup = {
|
||||||
element: Element[];
|
element: Element[];
|
||||||
link: Link[];
|
link: Link[];
|
||||||
pointer: Pointer[];
|
pointer: Pointer[];
|
||||||
|
layouter: Layouter;
|
||||||
|
options: LayoutGroupOptions;
|
||||||
|
modelList: Model[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type LayoutGroupTable = Map<string, LayoutGroup>;
|
||||||
|
|
||||||
|
|
||||||
export class ModelConstructor {
|
export class ModelConstructor {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private constructList: ConstructList;
|
private layoutGroupTable: LayoutGroupTable;
|
||||||
|
private prevSourcesStringMap: { [key: string]: string }; // 保存上一次源数据转换为字符串之后的值,用作比较该次源数据和上一次源数据是否有差异,若相同,则可跳过重复构建过程
|
||||||
|
|
||||||
constructor(engine: Engine) {
|
constructor(engine: Engine) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
|
this.prevSourcesStringMap = { };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建element,link和pointer
|
* 构建element,link和pointer
|
||||||
* @param sourceList
|
* @param sourceList
|
||||||
*/
|
*/
|
||||||
public construct(sourceList: SourceElement[]): ConstructList {
|
public construct(sources: Sources): LayoutGroupTable {
|
||||||
let elementContainer = this.constructElements(sourceList),
|
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
||||||
linkContainer = this.constructLinks(this.engine.linkOptions, elementContainer),
|
layouterMap: { [key: string]: Layouter } = SV.registeredLayouter,
|
||||||
pointerContainer = this.constructPointers(this.engine.pointerOptions, elementContainer);
|
optionsTable = this.engine.optionsTable;
|
||||||
|
|
||||||
this.constructList = {
|
Object.keys(sources).forEach(name => {
|
||||||
element: Util.converterList(elementContainer),
|
let sourceGroup = sources[name],
|
||||||
link: Util.converterList(linkContainer),
|
layouterName = sourceGroup.layouter;
|
||||||
pointer: Util.converterList(pointerContainer)
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.constructList;
|
if(!layouterName) {
|
||||||
|
layoutGroupTable.set(name, {
|
||||||
|
element: [],
|
||||||
|
link: [],
|
||||||
|
pointer: [],
|
||||||
|
options: null,
|
||||||
|
layouter: null,
|
||||||
|
modelList: []
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sourceDataString: string = JSON.stringify(sourceGroup.data),
|
||||||
|
prevString: string = this.prevSourcesStringMap[name],
|
||||||
|
layouter: Layouter = null,
|
||||||
|
options: LayoutGroupOptions = null,
|
||||||
|
elementList: Element[] = [],
|
||||||
|
pointerList: Pointer[] = [];
|
||||||
|
|
||||||
|
if(prevString === sourceDataString) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
layouter = layouterMap[sourceGroup.layouter];
|
||||||
|
options = optionsTable[layouterName];
|
||||||
|
|
||||||
|
const sourceData = layouter.sourcesPreprocess? layouter.sourcesPreprocess(sourceGroup.data): sourceGroup.data;
|
||||||
|
|
||||||
|
elementList = this.constructElements(options.element, name, sourceData, layouterName);
|
||||||
|
pointerList = this.constructPointers(options.pointer, elementList);
|
||||||
|
|
||||||
|
layoutGroupTable.set(name, {
|
||||||
|
element: elementList,
|
||||||
|
link: [],
|
||||||
|
pointer: pointerList,
|
||||||
|
options: options,
|
||||||
|
layouter: layouter,
|
||||||
|
modelList: [...elementList, ...pointerList]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
layoutGroupTable.forEach((layoutGroup: LayoutGroup) => {
|
||||||
|
const linkList: Link[] = this.constructLinks(layoutGroup.options.link, layoutGroup.element, layoutGroupTable);
|
||||||
|
|
||||||
|
layoutGroup.link = linkList;
|
||||||
|
layoutGroup.modelList.push(...linkList);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.layoutGroupTable = layoutGroupTable;
|
||||||
|
|
||||||
|
return this.layoutGroupTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public getConstructList(): ConstructList {
|
public getLayoutGroupTable(): LayoutGroupTable {
|
||||||
return this.constructList;
|
return this.layoutGroupTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从源数据构建 element 集
|
* 从源数据构建 element 集
|
||||||
|
* @param elementOptions
|
||||||
|
* @param groupName
|
||||||
* @param sourceList
|
* @param sourceList
|
||||||
|
* @param layouterName
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructElements(sourceList: SourceElement[]): { [key: string]: Element[] } {
|
private constructElements(elementOptions: { [key: string]: ElementOption }, groupName: string, sourceList: SourceElement[], layouterName: string): Element[] {
|
||||||
let defaultElementType: string = 'default',
|
let defaultElementType: string = 'default',
|
||||||
elementContainer: { [key: string]: Element[] } = { };
|
elementList: Element[] = [];
|
||||||
|
|
||||||
sourceList.forEach(item => {
|
sourceList.forEach(item => {
|
||||||
if(item === null) {
|
if(item === null) {
|
||||||
@ -63,37 +125,26 @@ export class ModelConstructor {
|
|||||||
item.type = defaultElementType;
|
item.type = defaultElementType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(elementContainer[item.type] === undefined) {
|
elementList.push(this.createElement(item, item.type, groupName, layouterName, elementOptions[item.type]));
|
||||||
elementContainer[item.type] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
elementContainer[item.type].push(this.createElement(item, item.type));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return elementContainer;
|
return elementList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从配置和 element 集构建 link 集
|
* 从配置和 element 集构建 link 集
|
||||||
* @param linkOptions
|
* @param linkOptions
|
||||||
* @param elementContainer
|
* @param elements
|
||||||
|
* @param layoutGroupTable
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructLinks(linkOptions: { [key: string]: LinkOption }, elementContainer: { [key: string]: Element[] }): { [key: string]: Link[] } {
|
private constructLinks(linkOptions: { [key: string]: LinkOption }, elements: Element[], layoutGroupTable: LayoutGroupTable): Link[] {
|
||||||
let linkContainer: { [key: string]: Link[] } = { },
|
let linkList: Link[] = [],
|
||||||
elementList: Element[] = Object
|
|
||||||
.keys(elementContainer)
|
|
||||||
.map(item => elementContainer[item])
|
|
||||||
.reduce((prev, cur) => [...prev, ...cur]),
|
|
||||||
linkNames = Object.keys(linkOptions);
|
linkNames = Object.keys(linkOptions);
|
||||||
|
|
||||||
linkNames.forEach(name => {
|
linkNames.forEach(name => {
|
||||||
linkContainer[name] = [];
|
for(let i = 0; i < elements.length; i++) {
|
||||||
});
|
let element: Element = elements[i],
|
||||||
|
|
||||||
linkNames.forEach(name => {
|
|
||||||
for(let i = 0; i < elementList.length; i++) {
|
|
||||||
let element: Element = elementList[i],
|
|
||||||
sourceLinkData: sourceLinkData = element.sourceElement[name],
|
sourceLinkData: sourceLinkData = element.sourceElement[name],
|
||||||
targetElement: Element | Element[] = null,
|
targetElement: Element | Element[] = null,
|
||||||
link: Link = null;
|
link: Link = null;
|
||||||
@ -106,22 +157,22 @@ export class ModelConstructor {
|
|||||||
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 Element -------------------
|
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 Element -------------------
|
||||||
if(Array.isArray(sourceLinkData)) {
|
if(Array.isArray(sourceLinkData)) {
|
||||||
element[name] = sourceLinkData.map((item, index) => {
|
element[name] = sourceLinkData.map((item, index) => {
|
||||||
targetElement = this.fetchTargetElements(elementContainer, element, item);
|
targetElement = this.fetchTargetElements(layoutGroupTable, element, item);
|
||||||
|
|
||||||
if(targetElement) {
|
if(targetElement) {
|
||||||
link = this.createLink(name, element, targetElement, index);
|
link = this.createLink(name, element, targetElement, index, linkOptions[name]);
|
||||||
linkContainer[name].push(link);
|
linkList.push(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetElement;
|
return targetElement;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
targetElement = this.fetchTargetElements(elementContainer, element, sourceLinkData);
|
targetElement = this.fetchTargetElements(layoutGroupTable, element, sourceLinkData);
|
||||||
|
|
||||||
if(targetElement) {
|
if(targetElement) {
|
||||||
link = this.createLink(name, element, targetElement, null);
|
link = this.createLink(name, element, targetElement, null, linkOptions[name]);
|
||||||
linkContainer[name].push(link);
|
linkList.push(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
element[name] = targetElement;
|
element[name] = targetElement;
|
||||||
@ -129,64 +180,57 @@ export class ModelConstructor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return linkContainer;
|
return linkList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从配置和 element 集构建 pointer 集
|
* 从配置和 element 集构建 pointer 集
|
||||||
* @param pointerOptions
|
* @param pointerOptions
|
||||||
* @param elementContainer
|
* @param elements
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructPointers(pointerOptions: { [key: string]: PointerOption }, elementContainer: { [key: string]: Element[] }): { [key: string]: Pointer[] } {
|
private constructPointers(pointerOptions: { [key: string]: PointerOption }, elements: Element[]): Pointer[] {
|
||||||
let pointerContainer: { [key: string]: Pointer[] } = { },
|
let pointerList: Pointer[] = [],
|
||||||
elementList: Element[] = Object
|
|
||||||
.keys(elementContainer)
|
|
||||||
.map(item => elementContainer[item])
|
|
||||||
.reduce((prev, cur) => [...prev, ...cur]),
|
|
||||||
pointerNames = Object.keys(pointerOptions);
|
pointerNames = Object.keys(pointerOptions);
|
||||||
|
|
||||||
pointerNames.forEach(name => {
|
pointerNames.forEach(name => {
|
||||||
pointerContainer[name] = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
pointerNames.forEach(name => {
|
for(let i = 0; i < elements.length; i++) {
|
||||||
|
let element = elements[i],
|
||||||
|
|
||||||
for(let i = 0; i < elementList.length; i++) {
|
|
||||||
let element = elementList[i],
|
|
||||||
pointerData = element[name];
|
pointerData = element[name];
|
||||||
|
|
||||||
// 若没有指针字段的结点则跳过
|
// 若没有指针字段的结点则跳过
|
||||||
if(!pointerData) continue;
|
if(!pointerData) continue;
|
||||||
|
|
||||||
let id = name + '.' + (Array.isArray(pointerData)? pointerData.join('-'): pointerData),
|
let id = name + '.' + (Array.isArray(pointerData)? pointerData.join('-'): pointerData),
|
||||||
pointer = this.createPointer(id, name, pointerData, element);
|
pointer = this.createPointer(id, name, pointerData, element, pointerOptions[name]);
|
||||||
|
|
||||||
pointerContainer[name].push(pointer);
|
pointerList.push(pointer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return pointerContainer;
|
return pointerList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 元素工厂,创建Element
|
* 元素工厂,创建Element
|
||||||
* @param sourceElement
|
* @param sourceElement
|
||||||
* @param elementName
|
* @param elementName
|
||||||
|
* @param groupName
|
||||||
|
* @param layouterName
|
||||||
|
* @param options
|
||||||
*/
|
*/
|
||||||
private createElement(sourceElement: SourceElement, elementName: string): Element {
|
private createElement(sourceElement: SourceElement, elementName: string, groupName: string, layouterName: string, options: ElementOption): Element {
|
||||||
let elementOption = this.engine.elementOptions[elementName],
|
let element: Element = undefined,
|
||||||
element: Element = undefined,
|
label = options.label? this.parserElementContent(sourceElement, options.label): '',
|
||||||
label = elementOption.label? this.parserElementContent(sourceElement, elementOption.label): '',
|
|
||||||
id = elementName + '.' + sourceElement.id.toString();
|
id = elementName + '.' + sourceElement.id.toString();
|
||||||
|
|
||||||
if(label === null || label === 'undefined') {
|
if(label === null || label === 'undefined') {
|
||||||
label = '';
|
label = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
element = new Element(id, elementName, sourceElement);
|
element = new Element(id, elementName, groupName, layouterName, sourceElement);
|
||||||
element.initProps(elementOption);
|
element.initProps(options);
|
||||||
element.set('label', label);
|
element.set('label', label);
|
||||||
element.sourceElement = sourceElement;
|
element.sourceElement = sourceElement;
|
||||||
|
|
||||||
@ -199,10 +243,10 @@ export class ModelConstructor {
|
|||||||
* @param pointerName
|
* @param pointerName
|
||||||
* @param label
|
* @param label
|
||||||
* @param target
|
* @param target
|
||||||
|
* @param options
|
||||||
*/
|
*/
|
||||||
private createPointer(id: string, pointerName: string, pointerData: string | string[], target: Element): Pointer {
|
private createPointer(id: string, pointerName: string, pointerData: string | string[], target: Element, options: PointerOption): Pointer {
|
||||||
let options = this.engine.pointerOptions[pointerName],
|
let pointer = undefined;
|
||||||
pointer = undefined;
|
|
||||||
|
|
||||||
pointer = new Pointer(id, pointerName, pointerData, target);
|
pointer = new Pointer(id, pointerName, pointerData, target);
|
||||||
pointer.initProps(options);
|
pointer.initProps(options);
|
||||||
@ -216,10 +260,10 @@ export class ModelConstructor {
|
|||||||
* @param element
|
* @param element
|
||||||
* @param target
|
* @param target
|
||||||
* @param index
|
* @param index
|
||||||
|
* @param options
|
||||||
*/
|
*/
|
||||||
private createLink(linkName: string, element: Element, target: Element, index: number): Link {
|
private createLink(linkName: string, element: Element, target: Element, index: number, options: LinkOption): Link {
|
||||||
let options: LinkOption = this.engine.linkOptions[linkName],
|
let link = undefined,
|
||||||
link = undefined,
|
|
||||||
id = `${element.id}-${target.id}`;
|
id = `${element.id}-${target.id}`;
|
||||||
|
|
||||||
link = new Link(id, linkName, element, target, index);
|
link = new Link(id, linkName, element, target, index);
|
||||||
@ -233,7 +277,7 @@ export class ModelConstructor {
|
|||||||
* @param sourceElement
|
* @param sourceElement
|
||||||
* @param formatLabel
|
* @param formatLabel
|
||||||
*/
|
*/
|
||||||
private parserElementContent(sourceElement: SourceElement, formatLabel: string): string {
|
private parserElementContent(sourceElement: SourceElement, formatLabel: string): string {
|
||||||
let fields = Util.textParser(formatLabel);
|
let fields = Util.textParser(formatLabel);
|
||||||
|
|
||||||
if(Array.isArray(fields)) {
|
if(Array.isArray(fields)) {
|
||||||
@ -253,31 +297,44 @@ export class ModelConstructor {
|
|||||||
* @param element
|
* @param element
|
||||||
* @param linkTarget
|
* @param linkTarget
|
||||||
*/
|
*/
|
||||||
private fetchTargetElements(
|
private fetchTargetElements(layoutGroupTable: LayoutGroupTable, element: Element, linkTarget: LinkTarget): Element {
|
||||||
elementContainer: { [key: string]: Element[] } ,
|
let groupName: string = element.groupName,
|
||||||
element: Element,
|
elementName = element.type,
|
||||||
linkTarget: LinkTarget
|
|
||||||
): Element {
|
|
||||||
let elementName = element.getType(),
|
|
||||||
elementList: Element[],
|
elementList: Element[],
|
||||||
targetId = linkTarget,
|
targetId = linkTarget,
|
||||||
|
targetGroupName = groupName,
|
||||||
targetElement = null;
|
targetElement = null;
|
||||||
|
|
||||||
if(linkTarget === null || linkTarget === undefined) {
|
if(linkTarget === null || linkTarget === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof linkTarget === 'string' && linkTarget.includes('#')) {
|
if(typeof linkTarget === 'number' || (typeof linkTarget === 'string' && !linkTarget.includes('#'))) {
|
||||||
let info = linkTarget.split('#');
|
linkTarget = 'default#' + linkTarget;
|
||||||
elementName = info[0];
|
|
||||||
targetId = info[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof targetId === 'number') {
|
let info = linkTarget.split('#');
|
||||||
targetId = targetId.toString();
|
|
||||||
|
targetId = info.pop();
|
||||||
|
|
||||||
|
if(info.length > 1) {
|
||||||
|
elementName = info.pop();
|
||||||
|
targetGroupName = info.pop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let field = info.pop();
|
||||||
|
if(layoutGroupTable.get(targetGroupName).element.find(item => item.type === field)) {
|
||||||
|
elementName = field;
|
||||||
|
}
|
||||||
|
else if(layoutGroupTable.has(field)) {
|
||||||
|
targetGroupName = field;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
elementList = elementContainer[elementName];
|
elementList = layoutGroupTable.get(targetGroupName).element.filter(item => item.type === elementName);
|
||||||
|
|
||||||
// 若目标element不存在,返回null
|
// 若目标element不存在,返回null
|
||||||
if(elementList === undefined) {
|
if(elementList === undefined) {
|
||||||
@ -292,6 +349,6 @@ export class ModelConstructor {
|
|||||||
* 销毁
|
* 销毁
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
this.constructList = null;
|
this.layoutGroupTable = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -17,6 +17,7 @@ export interface G6NodeModel {
|
|||||||
style: Style;
|
style: Style;
|
||||||
labelCfg: ElementLabelOption;
|
labelCfg: ElementLabelOption;
|
||||||
externalPointerId: string;
|
externalPointerId: string;
|
||||||
|
SVLayouter: string;
|
||||||
SVModelType: string;
|
SVModelType: string;
|
||||||
SVModelName: string;
|
SVModelName: string;
|
||||||
};
|
};
|
||||||
@ -156,22 +157,30 @@ export class Model {
|
|||||||
getType(): string {
|
getType(): string {
|
||||||
return this.type;
|
return this.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getId(): string {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Element extends Model {
|
export class Element extends Model {
|
||||||
sourceElement: SourceElement;
|
sourceElement: SourceElement;
|
||||||
sourceId: string;
|
sourceId: string;
|
||||||
free: boolean;
|
groupName: string;
|
||||||
|
layouterName: string;
|
||||||
|
freed: boolean;
|
||||||
|
|
||||||
constructor(id: string, type: string, sourceElement: SourceElement) {
|
constructor(id: string, type: string, group: string, layouter: string, sourceElement: SourceElement) {
|
||||||
super(id, type);
|
super(id, type);
|
||||||
|
|
||||||
if(type === null) {
|
if(type === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.free = false;
|
this.groupName = group;
|
||||||
|
this.layouterName = layouter;
|
||||||
|
this.freed = false;
|
||||||
|
|
||||||
Object.keys(sourceElement).map(prop => {
|
Object.keys(sourceElement).map(prop => {
|
||||||
if(prop !== 'id') {
|
if(prop !== 'id') {
|
||||||
@ -197,6 +206,7 @@ export class Element extends Model {
|
|||||||
style: Util.objectClone<Style>(option.style),
|
style: Util.objectClone<Style>(option.style),
|
||||||
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
||||||
externalPointerId: null,
|
externalPointerId: null,
|
||||||
|
SVLayouter: this.layouterName,
|
||||||
SVModelType: 'element',
|
SVModelType: 'element',
|
||||||
SVModelName: this.type
|
SVModelName: this.type
|
||||||
};
|
};
|
||||||
@ -274,6 +284,7 @@ export class Pointer extends Model {
|
|||||||
style: Util.objectClone<Style>(option.style),
|
style: Util.objectClone<Style>(option.style),
|
||||||
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
||||||
externalPointerId: null,
|
externalPointerId: null,
|
||||||
|
SVLayouter: null,
|
||||||
SVModelType: 'pointer',
|
SVModelType: 'pointer',
|
||||||
SVModelName: this.type
|
SVModelName: this.type
|
||||||
};
|
};
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export default G6.registerNode('binary-tree-node', {
|
|||||||
y: height / 2,
|
y: height / 2,
|
||||||
width: width / 2,
|
width: width / 2,
|
||||||
height: height,
|
height: height,
|
||||||
fill: cfg.style.fill,
|
fill: cfg.color || cfg.style.fill,
|
||||||
stroke: cfg.style.stroke || '#333',
|
stroke: cfg.style.stroke || '#333',
|
||||||
cursor: cfg.style.cursor
|
cursor: cfg.style.cursor
|
||||||
},
|
},
|
||||||
|
|||||||
@ -16,7 +16,8 @@ export default G6.registerNode('indexed-node', {
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
stroke: cfg.style.stroke || '#333',
|
stroke: cfg.style.stroke || '#333',
|
||||||
fill: disable? '#ccc': cfg.style.fill
|
fill: disable? '#ccc': cfg.style.fill,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
},
|
},
|
||||||
name: 'wrapper'
|
name: 'wrapper'
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,7 +15,8 @@ export default G6.registerNode('link-list-node', {
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
stroke: cfg.style.stroke || '#333',
|
stroke: cfg.style.stroke || '#333',
|
||||||
fill: '#eee'
|
fill: '#eee',
|
||||||
|
cursor: cfg.style.cursor
|
||||||
},
|
},
|
||||||
name: 'wrapper'
|
name: 'wrapper'
|
||||||
});
|
});
|
||||||
@ -27,7 +28,8 @@ export default G6.registerNode('link-list-node', {
|
|||||||
width: width * (2 / 3),
|
width: width * (2 / 3),
|
||||||
height: height,
|
height: height,
|
||||||
fill: cfg.style.fill,
|
fill: cfg.style.fill,
|
||||||
stroke: cfg.style.stroke || '#333'
|
stroke: cfg.style.stroke || '#333',
|
||||||
|
cursor: cfg.style.cursor
|
||||||
},
|
},
|
||||||
name: 'main-rect',
|
name: 'main-rect',
|
||||||
draggable: true
|
draggable: true
|
||||||
|
|||||||
@ -44,7 +44,8 @@ export default G6.registerNode('two-cell-node', {
|
|||||||
textBaseline: 'middle',
|
textBaseline: 'middle',
|
||||||
text: cfg.label,
|
text: cfg.label,
|
||||||
fill: style.fill || '#000',
|
fill: style.fill || '#000',
|
||||||
fontSize: style.fontSize || 16
|
fontSize: style.fontSize || 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
},
|
},
|
||||||
name: 'text',
|
name: 'text',
|
||||||
draggable: true
|
draggable: true
|
||||||
|
|||||||
@ -10,6 +10,10 @@ import { Vector } from "./Common/vector";
|
|||||||
import indexedNode from "./RegisteredShape/indexedNode";
|
import indexedNode from "./RegisteredShape/indexedNode";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const SV = {
|
export const SV = {
|
||||||
Engine: Engine,
|
Engine: Engine,
|
||||||
Group: Group,
|
Group: Group,
|
||||||
@ -17,6 +21,7 @@ export const SV = {
|
|||||||
Vector: Vector,
|
Vector: Vector,
|
||||||
Mat3: G6.Util.mat3,
|
Mat3: G6.Util.mat3,
|
||||||
G6,
|
G6,
|
||||||
|
|
||||||
registeredShape: [
|
registeredShape: [
|
||||||
externalPointer,
|
externalPointer,
|
||||||
linkListNode,
|
linkListNode,
|
||||||
@ -24,6 +29,18 @@ export const SV = {
|
|||||||
twoCellNode,
|
twoCellNode,
|
||||||
indexedNode
|
indexedNode
|
||||||
],
|
],
|
||||||
registerShape: G6.registerNode
|
|
||||||
|
registeredLayouter: { },
|
||||||
|
|
||||||
|
registerShape: G6.registerNode,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册一个布局器
|
||||||
|
* @param name
|
||||||
|
* @param layouter
|
||||||
|
*/
|
||||||
|
registerLayouter(name: string, layouter) {
|
||||||
|
SV.registeredLayouter[name] = layouter;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { Bound, BoundingRect } from "../../Common/boundingRect";
|
|
||||||
import { Engine } from "../../engine";
|
import { Engine } from "../../engine";
|
||||||
import { ConstructList } from "../../Model/modelConstructor";
|
import { Model, Pointer } from "../../Model/modelData";
|
||||||
import { Element, Model, Pointer } from "../../Model/modelData";
|
import { AnimationOptions, InteractionOptions, LayoutGroupOptions, LayoutOptions } from "../../options";
|
||||||
import { AnimationOptions, InteractionOptions, LayoutOptions } from "../../options";
|
import { SV } from "../../StructV";
|
||||||
import { Animations } from "../animation";
|
import { Animations } from "../animation";
|
||||||
import { g6Behavior, Renderer } from "../renderer";
|
import { g6Behavior, Renderer } from "../renderer";
|
||||||
|
|
||||||
@ -25,20 +24,45 @@ export class Container {
|
|||||||
this.DOMContainer = DOMContainer;
|
this.DOMContainer = DOMContainer;
|
||||||
this.animationsOptions = engine.animationOptions;
|
this.animationsOptions = engine.animationOptions;
|
||||||
this.interactionOptions = engine.interactionOptions;
|
this.interactionOptions = engine.interactionOptions;
|
||||||
|
this.prevModelList = [];
|
||||||
|
|
||||||
|
const tooltip = new SV.G6.Tooltip({
|
||||||
|
offsetX: 10,
|
||||||
|
offsetY: 20,
|
||||||
|
shouldBegin(event) {
|
||||||
|
return event.item.getModel().SVModelType === 'element';
|
||||||
|
},
|
||||||
|
getContent(event) {
|
||||||
|
const data = event.item.SVModel.data,
|
||||||
|
wrapper = document.createElement('div');
|
||||||
|
|
||||||
|
wrapper.style.padding = '0 4px 0 4px';
|
||||||
|
wrapper.innerHTML = `
|
||||||
|
<h5>id: ${ event.item.SVModel.sourceId }</h5>
|
||||||
|
<h5>data: ${ data? data: '' }</h5>
|
||||||
|
`
|
||||||
|
return wrapper;
|
||||||
|
},
|
||||||
|
itemTypes: ['node']
|
||||||
|
});
|
||||||
|
|
||||||
this.renderer = new Renderer(engine, DOMContainer, {
|
this.renderer = new Renderer(engine, DOMContainer, {
|
||||||
...g6Options,
|
...g6Options,
|
||||||
modes: {
|
modes: {
|
||||||
default: this.initBehaviors()
|
default: this.initBehaviors(this.engine.optionsTable)
|
||||||
}
|
},
|
||||||
|
plugins: [tooltip]
|
||||||
});
|
});
|
||||||
this.prevModelList = [];
|
|
||||||
|
this.afterInitRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化交互行为
|
* 初始化交互行为
|
||||||
|
* @param optionsTable
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
protected initBehaviors(): g6Behavior[] {
|
protected initBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }): g6Behavior[] {
|
||||||
return ['drag-canvas', 'zoom-canvas'];
|
return ['drag-canvas', 'zoom-canvas'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +143,11 @@ export class Container {
|
|||||||
*/
|
*/
|
||||||
protected handleChangeModels(models: Model[]) { }
|
protected handleChangeModels(models: Model[]) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化渲染器之后的回调
|
||||||
|
*/
|
||||||
|
protected afterInitRenderer() { }
|
||||||
|
|
||||||
// ------------------------------------------ hook ---------------------------------------------
|
// ------------------------------------------ hook ---------------------------------------------
|
||||||
|
|
||||||
afterAppendModels(callback: (models: Model[]) => void) {
|
afterAppendModels(callback: (models: Model[]) => void) {
|
||||||
@ -135,11 +164,9 @@ export class Container {
|
|||||||
/**
|
/**
|
||||||
* 渲染函数
|
* 渲染函数
|
||||||
* @param modelList
|
* @param modelList
|
||||||
* @param layoutFn
|
|
||||||
*/
|
*/
|
||||||
public render(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
public render(modelList: Model[]) {
|
||||||
const modelList: Model[] = [...constructList.element, ...constructList.link, ...constructList.pointer],
|
const appendModels: Model[] = this.getAppendModels(this.prevModelList, modelList),
|
||||||
appendModels: Model[] = this.getAppendModels(this.prevModelList, modelList),
|
|
||||||
removeModels: Model[] = this.getRemoveModels(this.prevModelList, modelList),
|
removeModels: Model[] = this.getRemoveModels(this.prevModelList, modelList),
|
||||||
changeModels: Model[] = [...appendModels, ...this.findReTargetPointer(modelList)];
|
changeModels: Model[] = [...appendModels, ...this.findReTargetPointer(modelList)];
|
||||||
|
|
||||||
|
|||||||
@ -3,4 +3,9 @@ import { Container } from "./container";
|
|||||||
/**
|
/**
|
||||||
* 释放区可视化视图
|
* 释放区可视化视图
|
||||||
*/
|
*/
|
||||||
export class FreedContainer extends Container { };
|
export class FreedContainer extends Container {
|
||||||
|
|
||||||
|
protected initBehaviors(): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { Link, Model } from "../../Model/modelData";
|
import { Link, Model } from "../../Model/modelData";
|
||||||
|
import { InteractionOptions, LayoutGroupOptions } from "../../options";
|
||||||
import { Container } from "./container";
|
import { Container } from "./container";
|
||||||
|
|
||||||
|
|
||||||
@ -8,50 +9,142 @@ import { Container } from "./container";
|
|||||||
*/
|
*/
|
||||||
export class MainContainer extends Container {
|
export class MainContainer extends Container {
|
||||||
|
|
||||||
protected initBehaviors() {
|
protected initBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }) {
|
||||||
const interactionOptions = this.interactionOptions,
|
const dragNodeTable: { [key: string]: boolean | string[] } = { },
|
||||||
dragNode: boolean | string[] = interactionOptions.dragNode,
|
selectNodeTable: { [key: string]: boolean | string[] } = { },
|
||||||
dragNodeFilter = node => {
|
interactionOptions: InteractionOptions = this.engine.interactionOptions,
|
||||||
let model = node.item.getModel();
|
defaultModes = [];
|
||||||
|
|
||||||
if(node.item === null) {
|
Object.keys(optionsTable).forEach(item => {
|
||||||
return false;
|
dragNodeTable[item] = optionsTable[item].behavior.dragNode;
|
||||||
}
|
selectNodeTable[item] = optionsTable[item].behavior.selectNode;
|
||||||
|
});
|
||||||
|
|
||||||
if(model.modelType === 'pointer') {
|
if(interactionOptions.drag) {
|
||||||
return false;
|
defaultModes.push('drag-canvas');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof dragNode === 'boolean') {
|
if(interactionOptions.zoom) {
|
||||||
return dragNode;
|
defaultModes.push('zoom-canvas');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Array.isArray(dragNode) && dragNode.indexOf(model.modelName) > -1) {
|
const dragNodeFilter = node => {
|
||||||
return true;
|
let model = node.item.getModel();
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
if(node.item === null) {
|
||||||
}
|
return false;
|
||||||
|
|
||||||
const modeMap = {
|
|
||||||
drag: 'drag-canvas',
|
|
||||||
zoom: 'zoom-canvas',
|
|
||||||
dragNode: {
|
|
||||||
type: 'drag-node',
|
|
||||||
shouldBegin: node => dragNodeFilter(node)
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
defaultModes = [];
|
|
||||||
|
|
||||||
Object.keys(interactionOptions).forEach(item => {
|
if(model.SVModelType === 'pointer') {
|
||||||
if(interactionOptions[item] && modeMap[item] !== undefined) {
|
return false;
|
||||||
defaultModes.push(modeMap[item]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dragNode = optionsTable[model.SVLayouter].behavior.dragNode;
|
||||||
|
|
||||||
|
if(typeof dragNode === 'boolean') {
|
||||||
|
return dragNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Array.isArray(dragNode) && dragNode.indexOf(model.SVModelName) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectNodeFilter = node => {
|
||||||
|
let model = node.item.getModel();
|
||||||
|
|
||||||
|
if(node.item === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(model.SVModelType === 'pointer') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectNode = optionsTable[model.SVLayouter].behavior.selectNode;
|
||||||
|
|
||||||
|
if(typeof selectNode === 'boolean') {
|
||||||
|
return selectNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Array.isArray(selectNode) && selectNode.indexOf(model.SVModelName) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultModes.push({
|
||||||
|
type: 'drag-node',
|
||||||
|
shouldBegin: dragNodeFilter
|
||||||
|
});
|
||||||
|
|
||||||
|
defaultModes.push({
|
||||||
|
type: 'click-select',
|
||||||
|
shouldBegin: selectNodeFilter
|
||||||
});
|
});
|
||||||
|
|
||||||
return defaultModes;
|
return defaultModes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在初始化渲染器之后,修正节点拖拽时,外部指针没有跟着动的问题
|
||||||
|
* @param dragNodeTable
|
||||||
|
*/
|
||||||
|
protected afterInitRenderer() {
|
||||||
|
let g6Instance = this.getG6Instance(),
|
||||||
|
pointerY = null,
|
||||||
|
dragStartX = null,
|
||||||
|
dragStartY = null;
|
||||||
|
|
||||||
|
g6Instance.on('node:dragstart', ev => {
|
||||||
|
const model = ev.item.getModel(),
|
||||||
|
dragNode = this.engine.optionsTable[model.SVLayouter].behavior.dragNode;
|
||||||
|
|
||||||
|
if(dragNode === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Array.isArray(dragNode) && dragNode.find(item => item === model.SVModelName) === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer = g6Instance.findById(model.externalPointerId);
|
||||||
|
|
||||||
|
if(pointer) {
|
||||||
|
pointerX = pointer.getModel().x,
|
||||||
|
pointerY = pointer.getModel().y;
|
||||||
|
dragStartX = ev.canvasX;
|
||||||
|
dragStartY = ev.canvasY;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
g6Instance.on('node:dragend', ev => {
|
||||||
|
pointer = null;
|
||||||
|
pointerX = null,
|
||||||
|
pointerY = null,
|
||||||
|
dragStartX = null,
|
||||||
|
dragStartY = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
g6Instance.on('node:drag', ev => {
|
||||||
|
if(!pointer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dx = ev.canvasX - dragStartX,
|
||||||
|
dy = ev.canvasY - dragStartY,
|
||||||
|
zoom = g6Instance.getZoom();
|
||||||
|
|
||||||
|
pointer.updatePosition({
|
||||||
|
x: pointerX + dx / zoom,
|
||||||
|
y: pointerY + dy / zoom
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected handleChangeModels(models: Model[]) {
|
protected handleChangeModels(models: Model[]) {
|
||||||
const changeHighlightColor: string = this.interactionOptions.changeHighlight;
|
const changeHighlightColor: string = this.interactionOptions.changeHighlight;
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
import { Bound, BoundingRect } from '../Common/boundingRect';
|
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||||
|
import { Group } from '../Common/group';
|
||||||
import { Engine } from '../engine';
|
import { Engine } from '../engine';
|
||||||
import { ConstructList } from '../Model/modelConstructor';
|
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||||
import { Element, Model, Pointer } from '../Model/modelData';
|
import { Element, Model, Pointer } from '../Model/modelData';
|
||||||
import { LayoutOptions, PointerOption } from '../options';
|
import { LayoutOptions, PointerOption, ViewOptions } from '../options';
|
||||||
import { Container } from './container/container';
|
import { Container } from './container/container';
|
||||||
|
|
||||||
|
|
||||||
export class Layouter {
|
export class Layouter {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
|
private viewOptions: ViewOptions;
|
||||||
|
|
||||||
constructor(engine: Engine) {
|
constructor(engine: Engine) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
|
this.viewOptions = this.engine.viewOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -19,7 +22,7 @@ export class Layouter {
|
|||||||
* @param elements
|
* @param elements
|
||||||
* @param pointers
|
* @param pointers
|
||||||
*/
|
*/
|
||||||
private initLayoutValue(elements: Element[], pointers: Pointer[]) {
|
private initLayoutValue(elements: Element[], pointers: Pointer[]) {
|
||||||
[...elements, ...pointers].forEach(item => {
|
[...elements, ...pointers].forEach(item => {
|
||||||
item.set('rotation', item.get('rotation'));
|
item.set('rotation', item.get('rotation'));
|
||||||
item.set({ x: 0, y: 0 });
|
item.set({ x: 0, y: 0 });
|
||||||
@ -29,10 +32,11 @@ export class Layouter {
|
|||||||
/**
|
/**
|
||||||
* 布局外部指针
|
* 布局外部指针
|
||||||
* @param pointer
|
* @param pointer
|
||||||
|
* @param pointerOptions
|
||||||
*/
|
*/
|
||||||
private layoutPointer(pointers: Pointer[]) {
|
private layoutPointer(pointers: Pointer[], pointerOptions: { [key: string]: PointerOption }) {
|
||||||
pointers.forEach(item => {
|
pointers.forEach(item => {
|
||||||
const options: PointerOption = this.engine.pointerOptions[item.getType()],
|
const options: PointerOption = pointerOptions[item.getType()],
|
||||||
offset = options.offset || 8,
|
offset = options.offset || 8,
|
||||||
anchor = options.anchor || 0;
|
anchor = options.anchor || 0;
|
||||||
|
|
||||||
@ -52,57 +56,118 @@ export class Layouter {
|
|||||||
* @param container
|
* @param container
|
||||||
* @param models
|
* @param models
|
||||||
*/
|
*/
|
||||||
private fitCenter(container: Container, models: Model[]) {
|
private fitCenter(container: Container, group: Group) {
|
||||||
if(models.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewBound: BoundingRect = models.map(item => item.getBound()).reduce((prev, cur) => Bound.union(prev, cur));
|
|
||||||
|
|
||||||
let width = container.getG6Instance().getWidth(),
|
let width = container.getG6Instance().getWidth(),
|
||||||
height = container.getG6Instance().getHeight(),
|
height = container.getG6Instance().getHeight(),
|
||||||
|
viewBound: BoundingRect = group.getBound(),
|
||||||
centerX = width / 2, centerY = height / 2,
|
centerX = width / 2, centerY = height / 2,
|
||||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
boundCenterX = viewBound.x + viewBound.width / 2,
|
||||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
boundCenterY = viewBound.y + viewBound.height / 2,
|
||||||
dx = centerX - boundCenterX,
|
dx = centerX - boundCenterX,
|
||||||
dy = centerY - boundCenterY;
|
dy = centerY - boundCenterY;
|
||||||
|
|
||||||
models.forEach(item => {
|
group.translate(dx, dy);
|
||||||
item.set({
|
}
|
||||||
x: item.get('x') + dx,
|
|
||||||
y: item.get('y') + dy
|
/**
|
||||||
|
* 对每个组内部的model进行布局
|
||||||
|
* @param layoutGroupTable
|
||||||
|
*/
|
||||||
|
private layoutModels(layoutGroupTable: LayoutGroupTable): Group[] {
|
||||||
|
const modelGroupList: Group[] = [];
|
||||||
|
|
||||||
|
layoutGroupTable.forEach((group, groupName) => {
|
||||||
|
const options: LayoutOptions = group.options.layout,
|
||||||
|
modelList: Model[] = group.modelList,
|
||||||
|
modelGroup: Group = new Group();
|
||||||
|
|
||||||
|
modelList.forEach(item => {
|
||||||
|
modelGroup.add(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.initLayoutValue(group.element, group.pointer); // 初始化布局参数
|
||||||
|
group.layouter.layout(group.element, options); // 布局节点
|
||||||
|
this.layoutPointer(group.pointer, group.options.pointer); // 布局外部指针
|
||||||
|
|
||||||
|
modelGroupList.push(modelGroup);
|
||||||
|
});
|
||||||
|
|
||||||
|
return modelGroupList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对所有组进行相互布局
|
||||||
|
* @param container
|
||||||
|
* @param modelGroupTable
|
||||||
|
*/
|
||||||
|
private layoutGroups(container: Container, modelGroupList: Group[]): Group {
|
||||||
|
let wrapperGroup: Group = new Group(),
|
||||||
|
group: Group,
|
||||||
|
prevBound: BoundingRect,
|
||||||
|
bound: BoundingRect,
|
||||||
|
boundList: BoundingRect[] = [],
|
||||||
|
maxHeight: number = -Infinity,
|
||||||
|
dx = 0, dy = 0;
|
||||||
|
|
||||||
|
// 左往右布局
|
||||||
|
for(let i = 0; i < modelGroupList.length; i++) {
|
||||||
|
group = modelGroupList[i],
|
||||||
|
bound = group.getPaddingBound(this.viewOptions.groupPadding);
|
||||||
|
|
||||||
|
if(prevBound) {
|
||||||
|
dx = prevBound.x + prevBound.width - bound.x;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dx = bound.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bound.height > maxHeight) {
|
||||||
|
maxHeight = bound.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
group.translate(dx, 0);
|
||||||
|
Bound.translate(bound, dx, 0);
|
||||||
|
boundList.push(bound);
|
||||||
|
wrapperGroup.add(group);
|
||||||
|
prevBound = bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 居中对齐布局
|
||||||
|
for(let i = 0; i < modelGroupList.length; i++) {
|
||||||
|
group = modelGroupList[i];
|
||||||
|
bound = boundList[i];
|
||||||
|
|
||||||
|
dy = maxHeight / 2 - bound.height / 2;
|
||||||
|
group.translate(0, dy);
|
||||||
|
Bound.translate(bound, 0, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapperGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布局
|
||||||
|
* @param container
|
||||||
|
* @param layoutGroupTable
|
||||||
|
*/
|
||||||
|
public layoutAll(container: Container, layoutGroupTable: LayoutGroupTable) {
|
||||||
|
layoutGroupTable.forEach(item => {
|
||||||
|
item.modelList.forEach(model => {
|
||||||
|
model.G6Item = model.shadowG6Item;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable),
|
||||||
|
wrapperGroup: Group = this.layoutGroups(container, modelGroupList);
|
||||||
|
|
||||||
|
this.fitCenter(container, wrapperGroup);
|
||||||
|
|
||||||
|
layoutGroupTable.forEach(item => {
|
||||||
|
item.modelList.forEach(model => {
|
||||||
|
model.G6Item = model.renderG6Item;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 进行布局
|
|
||||||
* @param container
|
|
||||||
* @param constructList
|
|
||||||
* @param layoutFn
|
|
||||||
*/
|
|
||||||
public layout(container: Container, constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
|
||||||
const options: LayoutOptions = this.engine.layoutOptions,
|
|
||||||
modelList: Model[] = [...constructList.element, ...constructList.pointer, ...constructList.link];
|
|
||||||
|
|
||||||
// 首先初始化所有节点的坐标为0,且设定旋转
|
|
||||||
modelList.forEach(item => {
|
|
||||||
item.G6Item = item.shadowG6Item;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化布局参数
|
|
||||||
this.initLayoutValue(constructList.element, constructList.pointer);
|
|
||||||
// 布局节点
|
|
||||||
layoutFn(constructList.element, options);
|
|
||||||
// 布局外部指针
|
|
||||||
this.layoutPointer(constructList.pointer);
|
|
||||||
|
|
||||||
// 将视图调整到画布中心
|
|
||||||
options.fitCenter && this.fitCenter(container, modelList);
|
|
||||||
|
|
||||||
modelList.forEach(item => {
|
|
||||||
item.G6Item = item.renderG6Item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -86,13 +86,14 @@ export class Renderer {
|
|||||||
this.g6Instance.changeData(renderData);
|
this.g6Instance.changeData(renderData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.engine.layoutOptions.fitView) {
|
if(this.engine.viewOptions.fitView) {
|
||||||
this.g6Instance.fitView();
|
this.g6Instance.fitView();
|
||||||
}
|
}
|
||||||
|
|
||||||
modelList.forEach(item => {
|
modelList.forEach(item => {
|
||||||
item.renderG6Item = this.g6Instance.findById(item.id);
|
item.renderG6Item = this.g6Instance.findById(item.id);
|
||||||
item.G6Item = item.renderG6Item;
|
item.G6Item = item.renderG6Item;
|
||||||
|
item.renderG6Item.SVModel = item;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 把所有连线置顶
|
// 把所有连线置顶
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { Engine } from "../engine";
|
import { Engine } from "../engine";
|
||||||
import { Element, Link } from "../Model/modelData";
|
import { Element, Link, Model } from "../Model/modelData";
|
||||||
import { EngineInitOptions, LayoutOptions } from "../options";
|
import { EngineOptions } from "../options";
|
||||||
import { Container } from "./container/container";
|
import { Container } from "./container/container";
|
||||||
import { SV } from '../StructV';
|
import { SV } from '../StructV';
|
||||||
import { ConstructList } from "../Model/modelConstructor";
|
|
||||||
import { MainContainer } from "./container/main";
|
import { MainContainer } from "./container/main";
|
||||||
import { FreedContainer } from "./container/freed";
|
import { FreedContainer } from "./container/freed";
|
||||||
import { LeakContainer } from "./container/leak";
|
import { LeakContainer } from "./container/leak";
|
||||||
import { Layouter } from "./layouter";
|
import { Layouter } from "./layouter";
|
||||||
|
import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor";
|
||||||
|
import { Util } from "../Common/util";
|
||||||
|
|
||||||
|
|
||||||
export class ViewManager {
|
export class ViewManager {
|
||||||
@ -17,9 +18,7 @@ export class ViewManager {
|
|||||||
private freedContainer: Container;
|
private freedContainer: Container;
|
||||||
private leakContainer: Container;
|
private leakContainer: Container;
|
||||||
|
|
||||||
private prevConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
private prevLayoutGroupTable: LayoutGroupTable;
|
||||||
private freedConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
|
||||||
private leakConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
|
||||||
|
|
||||||
private shadowG6Instance;
|
private shadowG6Instance;
|
||||||
|
|
||||||
@ -27,8 +26,9 @@ export class ViewManager {
|
|||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.layouter = new Layouter(engine);
|
this.layouter = new Layouter(engine);
|
||||||
this.mainContainer = new MainContainer(engine, DOMContainer);
|
this.mainContainer = new MainContainer(engine, DOMContainer);
|
||||||
|
this.prevLayoutGroupTable = null;
|
||||||
|
|
||||||
const options: EngineInitOptions = this.engine.initOptions;
|
const options: EngineOptions = this.engine.engineOptions;
|
||||||
|
|
||||||
if(options.freedContainer) {
|
if(options.freedContainer) {
|
||||||
this.freedContainer = new FreedContainer(engine, options.freedContainer, { fitCenter: true });
|
this.freedContainer = new FreedContainer(engine, options.freedContainer, { fitCenter: true });
|
||||||
@ -47,38 +47,49 @@ export class ViewManager {
|
|||||||
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
|
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
|
||||||
* @param constructList
|
* @param constructList
|
||||||
*/
|
*/
|
||||||
private build(constructList: ConstructList) {
|
private build(layoutGroupTable: LayoutGroupTable) {
|
||||||
constructList.element.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', item));
|
layoutGroupTable.forEach(group => {
|
||||||
constructList.pointer.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', item));
|
group.element.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', item));
|
||||||
constructList.link.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('edge', item));
|
group.pointer.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', item));
|
||||||
|
group.link.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('edge', item));
|
||||||
|
|
||||||
constructList.element.forEach(item => {
|
group.element.forEach(item => {
|
||||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||||
});
|
});
|
||||||
constructList.pointer.forEach(item => {
|
group.pointer.forEach(item => {
|
||||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||||
});
|
});
|
||||||
constructList.link.forEach(item => {
|
group.link.forEach(item => {
|
||||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取被 free 的节点
|
* 获取被 free 的节点
|
||||||
* @param constructList
|
* @param layoutGroupTable
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private getFreedConstructList(constructList: ConstructList): ConstructList {
|
private getFreedConstructList(layoutGroupTable: LayoutGroupTable): Model[] {
|
||||||
const freedList: ConstructList = {
|
let freedList: Model[] = [],
|
||||||
element: constructList.element.filter(item => item.free),
|
freedGroup = null,
|
||||||
pointer: [],
|
freedGroupName = null;
|
||||||
link: []
|
|
||||||
};
|
|
||||||
|
|
||||||
freedList.element.forEach(fItem => {
|
for(let group in layoutGroupTable) {
|
||||||
constructList.element.splice(constructList.element.findIndex(item => item.id === fItem.id), 1);
|
let freedElements: Model[] = layoutGroupTable[group].element.filter(item => item.freed);
|
||||||
constructList.link.splice(constructList.link.findIndex(item => item.element.id === fItem.id || item.target.id === fItem.id));
|
|
||||||
constructList.pointer.splice(constructList.pointer.findIndex(item => item.target.id === fItem.id));
|
if(freedElements.length) {
|
||||||
|
freedGroupName = group;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
freedGroup = layoutGroupTable[freedGroupName];
|
||||||
|
|
||||||
|
freedList.forEach(fItem => {
|
||||||
|
freedGroup.element.splice(freedGroup.element.findIndex(item => item.id === fItem.id), 1);
|
||||||
|
freedGroup.link.splice(freedGroup.link.findIndex(item => item.element.id === fItem.id || item.target.id === fItem.id));
|
||||||
|
freedGroup.pointer.splice(freedGroup.pointer.findIndex(item => item.target.id === fItem.id));
|
||||||
});
|
});
|
||||||
|
|
||||||
return freedList;
|
return freedList;
|
||||||
@ -90,47 +101,63 @@ export class ViewManager {
|
|||||||
* @param prevConstructList
|
* @param prevConstructList
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private getLeakConstructList(prevConstructList: ConstructList, constructList: ConstructList): ConstructList {
|
private getLeakConstructList(prevLayoutGroupTable: LayoutGroupTable, layoutGroupTable: LayoutGroupTable): LayoutGroupTable {
|
||||||
const elements: Element[] = prevConstructList.element.filter(item => !constructList.element.find(n => n.id === item.id)),
|
const leakLayoutGroupTable = new Map<string, LayoutGroup>();
|
||||||
links: Link[] = prevConstructList.link.filter(item => !constructList.link.find(n => n.id === item.id)),
|
|
||||||
elementIds: string[] = elements.map(item => item.id);
|
|
||||||
|
|
||||||
elements.forEach(item => {
|
prevLayoutGroupTable.forEach((item, groupName) => {
|
||||||
item.set('style', {
|
let prevGroup = item,
|
||||||
fill: '#ccc'
|
curGroup = layoutGroupTable.get(groupName),
|
||||||
|
elements: Element[] = [],
|
||||||
|
links: Link[] = [],
|
||||||
|
elementIds: string[] = [];
|
||||||
|
|
||||||
|
if(curGroup) {
|
||||||
|
elements = prevGroup.element.filter(item => !curGroup.element.find(n => n.id === item.id)).filter(item => item.freed === false),
|
||||||
|
links = prevGroup.link.filter(item => !curGroup.link.find(n => n.id === item.id)),
|
||||||
|
elementIds = elements.map(item => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.forEach(item => {
|
||||||
|
item.set('style', {
|
||||||
|
fill: '#ccc'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
for(let i = 0; i < links.length; i++) {
|
||||||
|
let sourceId = links[i].element.id,
|
||||||
|
targetId = links[i].target.id;
|
||||||
|
|
||||||
|
links[i].set('style', {
|
||||||
|
stroke: '#333'
|
||||||
|
});
|
||||||
|
|
||||||
|
if(elementIds.find(item => item === sourceId) === undefined || elementIds.find(item => item === targetId) === undefined) {
|
||||||
|
links.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leakLayoutGroupTable.set(groupName, {
|
||||||
|
element: elements,
|
||||||
|
link: links,
|
||||||
|
pointer: [],
|
||||||
|
layouter: prevGroup.layouter,
|
||||||
|
options: prevGroup.options,
|
||||||
|
modelList: [...elements, ...links]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
for(let i = 0; i < links.length; i++) {
|
return leakLayoutGroupTable;
|
||||||
let sourceId = links[i].element.id,
|
|
||||||
targetId = links[i].target.id;
|
|
||||||
|
|
||||||
links[i].set('style', {
|
|
||||||
stroke: '#333'
|
|
||||||
});
|
|
||||||
|
|
||||||
if(elementIds.find(item => item === sourceId) === undefined || elementIds.find(item => item === targetId) === undefined) {
|
|
||||||
links.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
element: elements,
|
|
||||||
link: links,
|
|
||||||
pointer: []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对主视图进行重新布局
|
* 对主视图进行重新布局
|
||||||
* @param constructList
|
* @param layoutGroupTable
|
||||||
* @param layoutFn
|
|
||||||
*/
|
*/
|
||||||
reLayout(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
reLayout(layoutGroupTable: LayoutGroupTable) {
|
||||||
this.layouter.layout(this.mainContainer, constructList, layoutFn);
|
this.layouter.layoutAll(this.mainContainer, layoutGroupTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -150,11 +177,23 @@ export class ViewManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新调整容器尺寸
|
* 重新调整容器尺寸
|
||||||
|
* @param containerName
|
||||||
* @param width
|
* @param width
|
||||||
* @param height
|
* @param height
|
||||||
*/
|
*/
|
||||||
resize(width: number, height: number) {
|
resize(containerName: string, width: number, height: number) {
|
||||||
this.mainContainer.getG6Instance().changeSize(width, height);
|
if(containerName === 'main') {
|
||||||
|
this.mainContainer.getG6Instance().changeSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(containerName === 'freed') {
|
||||||
|
this.freedContainer.getG6Instance().changeSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(containerName === 'leak') {
|
||||||
|
this.leakContainer.getG6Instance().changeSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,31 +201,36 @@ export class ViewManager {
|
|||||||
* @param models
|
* @param models
|
||||||
* @param layoutFn
|
* @param layoutFn
|
||||||
*/
|
*/
|
||||||
renderAll(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
renderAll(layoutGroupTable: LayoutGroupTable) {
|
||||||
this.shadowG6Instance.clear();
|
this.shadowG6Instance.clear();
|
||||||
|
|
||||||
this.build(constructList);
|
this.build(layoutGroupTable);
|
||||||
|
|
||||||
this.freedConstructList = this.getFreedConstructList(constructList);
|
let freedList = this.getFreedConstructList(layoutGroupTable),
|
||||||
this.leakConstructList = this.getLeakConstructList(this.prevConstructList, constructList);
|
leakLayoutGroupTable = null;
|
||||||
|
|
||||||
this.build(this.leakConstructList);
|
if(this.leakContainer && this.prevLayoutGroupTable) {
|
||||||
|
leakLayoutGroupTable = this.getLeakConstructList(this.prevLayoutGroupTable, layoutGroupTable);
|
||||||
|
this.build(leakLayoutGroupTable);
|
||||||
|
}
|
||||||
|
|
||||||
if(this.freedContainer) {
|
if(this.freedContainer) {
|
||||||
this.freedContainer.render(this.freedConstructList, layoutFn);
|
this.freedContainer.render(freedList);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 进行布局(设置model的x,y)
|
// 进行布局(设置model的x,y)
|
||||||
this.layouter.layout(this.mainContainer, constructList, layoutFn);
|
this.layouter.layoutAll(this.mainContainer, layoutGroupTable);
|
||||||
this.mainContainer.render(constructList, layoutFn);
|
|
||||||
|
|
||||||
if(this.leakContainer) {
|
const modelList: Model[] = Util.convertGroupTable2ModelList(layoutGroupTable);
|
||||||
|
this.mainContainer.render(modelList);
|
||||||
|
|
||||||
|
if(this.leakContainer && this.prevLayoutGroupTable) {
|
||||||
this.mainContainer.afterRemoveModels(() => {
|
this.mainContainer.afterRemoveModels(() => {
|
||||||
this.leakContainer.render(this.leakConstructList, layoutFn);
|
this.leakContainer.render(Util.convertGroupTable2ModelList(leakLayoutGroupTable));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.prevConstructList = constructList;
|
this.prevLayoutGroupTable = layoutGroupTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
214
src/engine.ts
214
src/engine.ts
@ -1,44 +1,42 @@
|
|||||||
import { Element, Pointer } from "./Model/modelData";
|
import { Element, Link, Pointer } from "./Model/modelData";
|
||||||
import { SourceElement } from "./sources";
|
import { Sources } from "./sources";
|
||||||
import { ModelConstructor, ConstructList } from "./Model/modelConstructor";
|
import { ModelConstructor } from "./Model/modelConstructor";
|
||||||
import { AnimationOptions, ElementOption, EngineInitOptions, InteractionOptions, LayoutOptions, LinkOption, Options, PointerOption } from "./options";
|
import { AnimationOptions, EngineOptions, InteractionOptions, LayoutGroupOptions, ViewOptions } from "./options";
|
||||||
import { Behavior } from "./Behavior.ts/behavior";
|
|
||||||
import { ViewManager } from "./View/viewManager";
|
import { ViewManager } from "./View/viewManager";
|
||||||
|
import { SV } from "./StructV";
|
||||||
|
|
||||||
|
|
||||||
export class Engine {
|
export class Engine {
|
||||||
private stringifySources: string = null; // 序列化的源数据
|
|
||||||
|
|
||||||
private modelConstructor: ModelConstructor = null;
|
private modelConstructor: ModelConstructor = null;
|
||||||
private viewManager: ViewManager
|
private viewManager: ViewManager
|
||||||
private behavior: Behavior;
|
private prevStringSourceData: string;
|
||||||
|
|
||||||
public initOptions: EngineInitOptions;
|
public engineOptions: EngineOptions;
|
||||||
public elementOptions: { [key: string]: ElementOption } = { };
|
public viewOptions: ViewOptions;
|
||||||
public linkOptions: { [key: string]: LinkOption } = { };
|
public animationOptions: AnimationOptions;
|
||||||
public pointerOptions: { [key: string]: PointerOption } = { };
|
public interactionOptions: InteractionOptions;
|
||||||
public layoutOptions: LayoutOptions = null;
|
|
||||||
public animationOptions: AnimationOptions = null;
|
|
||||||
public interactionOptions: InteractionOptions = null;
|
|
||||||
|
|
||||||
constructor(DOMContainer: HTMLElement, initOptions: EngineInitOptions = { }) {
|
public optionsTable: { [key: string]: LayoutGroupOptions };
|
||||||
const options: Options = this.defineOptions();
|
|
||||||
|
|
||||||
this.initOptions = initOptions;
|
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions = { }) {
|
||||||
this.elementOptions = options.element;
|
this.optionsTable = {};
|
||||||
this.linkOptions = options.link || { };
|
|
||||||
this.pointerOptions = options.pointer || { };
|
|
||||||
|
|
||||||
this.layoutOptions = Object.assign({
|
this.engineOptions = Object.assign({
|
||||||
|
freedContainer: null,
|
||||||
|
leakContainer: null
|
||||||
|
}, engineOptions);
|
||||||
|
|
||||||
|
this.viewOptions = Object.assign({
|
||||||
fitCenter: true,
|
fitCenter: true,
|
||||||
fitView: false
|
fitView: false,
|
||||||
}, options.layout);
|
groupPadding: 20
|
||||||
|
}, engineOptions.view);
|
||||||
|
|
||||||
this.animationOptions = Object.assign({
|
this.animationOptions = Object.assign({
|
||||||
enable: true,
|
enable: true,
|
||||||
duration: 750,
|
duration: 750,
|
||||||
timingFunction: 'easePolyOut'
|
timingFunction: 'easePolyOut'
|
||||||
}, options.animation);
|
}, engineOptions.animation);
|
||||||
|
|
||||||
this.interactionOptions = Object.assign({
|
this.interactionOptions = Object.assign({
|
||||||
drag: true,
|
drag: true,
|
||||||
@ -46,106 +44,67 @@ export class Engine {
|
|||||||
dragNode: true,
|
dragNode: true,
|
||||||
selectNode: true,
|
selectNode: true,
|
||||||
changeHighlight: '#fc5185'
|
changeHighlight: '#fc5185'
|
||||||
}, options.interaction);
|
}, engineOptions.interaction);
|
||||||
|
|
||||||
this.initOptions = Object.assign({
|
// 初始化布局器配置项
|
||||||
freedContainer: null,
|
Object.keys(SV.registeredLayouter).forEach(layouter => {
|
||||||
leakContainer: null
|
if(this.optionsTable[layouter] === undefined) {
|
||||||
}, initOptions);
|
const options: LayoutGroupOptions = SV.registeredLayouter[layouter].defineOptions();
|
||||||
|
|
||||||
|
options.behavior = Object.assign({
|
||||||
|
dragNode: true,
|
||||||
|
selectNode: true
|
||||||
|
}, options.behavior);
|
||||||
|
|
||||||
|
this.optionsTable[layouter] = options;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.modelConstructor = new ModelConstructor(this);
|
this.modelConstructor = new ModelConstructor(this);
|
||||||
|
|
||||||
this.viewManager = new ViewManager(this, DOMContainer);
|
this.viewManager = new ViewManager(this, DOMContainer);
|
||||||
this.behavior = new Behavior(this, this.viewManager.getG6Instance());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入数据进行渲染
|
* 输入数据进行渲染
|
||||||
* @param sourceData
|
* @param sourcesData
|
||||||
*/
|
*/
|
||||||
public render(sourceData: SourceElement[] | { [key: string]: SourceElement[] }) {
|
public render(sourceData: Sources) {
|
||||||
if(sourceData === undefined || sourceData === null) {
|
if(sourceData === undefined || sourceData === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 若前后数据没有发生变化,什么也不干(将json字符串化后比较)
|
let stringSourceData = JSON.stringify(sourceData);
|
||||||
let stringifySources = JSON.stringify(sourceData);
|
if(this.prevStringSourceData === stringSourceData) {
|
||||||
if(stringifySources === this.stringifySources) return;
|
return;
|
||||||
this.stringifySources = stringifySources;
|
|
||||||
|
|
||||||
let processedSourcesData = this.sourcesPreprocess(sourceData);
|
|
||||||
if(processedSourcesData) {
|
|
||||||
sourceData = processedSourcesData;
|
|
||||||
}
|
}
|
||||||
|
this.prevStringSourceData = stringSourceData;
|
||||||
const sourceList: SourceElement[] = this.sourcesProcess(sourceData);
|
|
||||||
|
|
||||||
// 1 转换模型(data => model)
|
// 1 转换模型(data => model)
|
||||||
const constructList: ConstructList = this.modelConstructor.construct(sourceList);
|
const layoutGroupTable = this.modelConstructor.construct(sourceData);
|
||||||
|
|
||||||
// 2 渲染(使用g6进行渲染)
|
// 2 渲染(使用g6进行渲染)
|
||||||
this.viewManager.renderAll(constructList, this.layout.bind(this));
|
this.viewManager.renderAll(layoutGroupTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 源数据处理
|
|
||||||
* @param sourceData
|
|
||||||
*/
|
|
||||||
private sourcesProcess(sourceData: SourceElement[] | { [key: string]: SourceElement[] }): SourceElement[] {
|
|
||||||
if(Array.isArray(sourceData)) {
|
|
||||||
return sourceData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceList: SourceElement[] = [];
|
|
||||||
|
|
||||||
Object.keys(sourceData).forEach(name => {
|
|
||||||
sourceData[name].forEach(item => {
|
|
||||||
item.type = name;
|
|
||||||
});
|
|
||||||
|
|
||||||
sourceList.push(...sourceData[name]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sourceList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 定义配置项
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
protected defineOptions(): Options {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对源数据进行预处理
|
|
||||||
* @param sourceData
|
|
||||||
*/
|
|
||||||
protected sourcesPreprocess(sourceData: SourceElement[] | { [key: string]: SourceElement[] }): SourceElement[] | { [key: string]: SourceElement[] } | void {
|
|
||||||
return sourceData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置布局函数
|
|
||||||
* @overwrite
|
|
||||||
*/
|
|
||||||
protected layout(elements: Element[], layoutOptions: LayoutOptions) { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新布局
|
* 重新布局
|
||||||
*/
|
*/
|
||||||
public reLayout() {
|
public reLayout() {
|
||||||
const constructList: ConstructList = this.modelConstructor.getConstructList();
|
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
|
|
||||||
this.viewManager.reLayout(constructList, this.layout.bind(this));
|
this.viewManager.reLayout(layoutGroupTable);
|
||||||
|
|
||||||
[...constructList.element, ...constructList.pointer].forEach(item => {
|
layoutGroupTable.forEach(group => {
|
||||||
let model = item.G6Item.getModel(),
|
group.modelList.forEach(item => {
|
||||||
x = item.get('x'),
|
if(item instanceof Link) return;
|
||||||
y = item.get('y');
|
|
||||||
|
|
||||||
model.x = x;
|
let model = item.G6Item.getModel(),
|
||||||
model.y = y;
|
x = item.get('x'),
|
||||||
|
y = item.get('y');
|
||||||
|
|
||||||
|
model.x = x;
|
||||||
|
model.y = y;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.viewManager.refresh();
|
this.viewManager.refresh();
|
||||||
@ -160,35 +119,69 @@ export class Engine {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有 element
|
* 获取所有 element
|
||||||
|
* @param group
|
||||||
*/
|
*/
|
||||||
public getElements(): Element[] {
|
public getElements(group?: string): Element[] {
|
||||||
const constructList = this.modelConstructor.getConstructList();
|
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
return constructList.element;
|
|
||||||
|
if(group && layoutGroupTable.has('group')) {
|
||||||
|
return layoutGroupTable.get('group').element;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements: Element[] = [];
|
||||||
|
layoutGroupTable.forEach(item => {
|
||||||
|
elements.push(...item.element);
|
||||||
|
})
|
||||||
|
|
||||||
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有 pointer
|
* 获取所有 pointer
|
||||||
|
* @param group
|
||||||
*/
|
*/
|
||||||
public getPointers(): Pointer[] {
|
public getPointers(group?: string): Pointer[] {
|
||||||
const constructList = this.modelConstructor.getConstructList();
|
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
return constructList.pointer;
|
|
||||||
|
if(group && layoutGroupTable.has('group')) {
|
||||||
|
return layoutGroupTable.get('group').pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointers: Pointer[] = [];
|
||||||
|
layoutGroupTable.forEach(item => {
|
||||||
|
pointers.push(...item.pointer);
|
||||||
|
})
|
||||||
|
|
||||||
|
return pointers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有 link
|
* 获取所有 link
|
||||||
|
* @param group
|
||||||
*/
|
*/
|
||||||
public getLinks() {
|
public getLinks(group?: string): Link[] {
|
||||||
const constructList = this.modelConstructor.getConstructList();
|
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
return constructList.link;
|
|
||||||
|
if(group && layoutGroupTable.has('group')) {
|
||||||
|
return layoutGroupTable.get('group').link;
|
||||||
|
}
|
||||||
|
|
||||||
|
const links: Link[] = [];
|
||||||
|
layoutGroupTable.forEach(item => {
|
||||||
|
links.push(...item.link);
|
||||||
|
})
|
||||||
|
|
||||||
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调整容器尺寸
|
* 调整容器尺寸
|
||||||
|
* @param containerName
|
||||||
* @param width
|
* @param width
|
||||||
* @param height
|
* @param height
|
||||||
*/
|
*/
|
||||||
public resize(width: number, height: number) {
|
public resize(containerName: string, width: number, height: number) {
|
||||||
this.viewManager.resize(width, height);
|
this.viewManager.resize(containerName, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,7 +190,7 @@ export class Engine {
|
|||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
public on(eventName: string, callback: Function) {
|
public on(eventName: string, callback: Function) {
|
||||||
this.behavior.on(eventName, callback);
|
this.viewManager.getG6Instance().on(eventName, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -206,6 +199,5 @@ export class Engine {
|
|||||||
public destroy() {
|
public destroy() {
|
||||||
this.modelConstructor.destroy();
|
this.modelConstructor.destroy();
|
||||||
this.viewManager.destroy();
|
this.viewManager.destroy();
|
||||||
this.behavior = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { Element } from "./Model/modelData";
|
||||||
|
import { SourceElement } from "./sources";
|
||||||
|
|
||||||
|
|
||||||
export interface Style {
|
export interface Style {
|
||||||
@ -59,12 +61,38 @@ export interface PointerOption extends ElementOption {
|
|||||||
|
|
||||||
|
|
||||||
export interface LayoutOptions {
|
export interface LayoutOptions {
|
||||||
fitCenter: boolean;
|
|
||||||
fitView: boolean;
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export interface BehaviorOptions {
|
||||||
|
dragNode: boolean | string[];
|
||||||
|
selectNode: boolean | string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export interface LayoutGroupOptions {
|
||||||
|
element: { [key: string]: ElementOption };
|
||||||
|
link?: { [key: string]: LinkOption }
|
||||||
|
pointer?: { [key: string]: PointerOption };
|
||||||
|
layout?: LayoutOptions;
|
||||||
|
behavior?: BehaviorOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ---------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
* -------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
* ------------------------------------------------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ViewOptions {
|
||||||
|
fitCenter: boolean;
|
||||||
|
fitView: boolean;
|
||||||
|
groupPadding: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface AnimationOptions {
|
export interface AnimationOptions {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
duration: number;
|
duration: number;
|
||||||
@ -73,29 +101,24 @@ export interface AnimationOptions {
|
|||||||
|
|
||||||
|
|
||||||
export interface InteractionOptions {
|
export interface InteractionOptions {
|
||||||
|
changeHighlight: string;
|
||||||
drag: boolean;
|
drag: boolean;
|
||||||
zoom: boolean;
|
zoom: boolean;
|
||||||
dragNode: boolean | string[];
|
}
|
||||||
selectNode: boolean | string[];
|
|
||||||
changeHighlight: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export interface EngineOptions {
|
||||||
export interface Options {
|
freedContainer?: HTMLElement;
|
||||||
element: { [key: string]: ElementOption };
|
leakContainer?: HTMLElement;
|
||||||
link?: { [key: string]: LinkOption }
|
view?: ViewOptions;
|
||||||
pointer?: { [key: string]: PointerOption };
|
|
||||||
layout?: LayoutOptions;
|
|
||||||
animation?: AnimationOptions;
|
animation?: AnimationOptions;
|
||||||
interaction?: InteractionOptions;
|
interaction?: InteractionOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export interface EngineInitOptions {
|
export interface Layouter {
|
||||||
freedContainer?: HTMLElement;
|
defineOptions(): LayoutGroupOptions;
|
||||||
leakContainer?: HTMLElement;
|
sourcesPreprocess?(sources: SourceElement[]): SourceElement[];
|
||||||
};
|
layout(elements: Element[], layoutOptions: LayoutOptions);
|
||||||
|
[key: string]: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -15,4 +15,9 @@ export interface SourceElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type Sources = {
|
||||||
|
[key: string]: { data: SourceElement[]; layouter: string; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user