feat:添加多种数据结构同时渲染的支持
This commit is contained in:
parent
4c1510da1f
commit
efdb325adc
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
node_modules
|
||||
test
|
||||
test
|
||||
demoV2
|
||||
demo
|
@ -238,6 +238,15 @@ const data = {
|
||||
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) {
|
||||
return{
|
||||
|
@ -82,6 +82,8 @@ const Engine = SV.Engine,
|
||||
<script src="./dataStruct/GeneralizedList.js"></script>
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
const engines = {
|
||||
0: BTree,
|
||||
1: LList,
|
||||
@ -98,7 +100,7 @@ const engines = {
|
||||
|
||||
let dataCounter = 0;
|
||||
|
||||
let cur = engines[3](document.getElementById('container'), {
|
||||
let cur = engines[1](document.getElementById('container'), {
|
||||
freedContainer: document.getElementById('freed'),
|
||||
leakContainer: document.getElementById('leak')
|
||||
});
|
||||
@ -113,12 +115,9 @@ document.getElementById('btn-next').addEventListener('click', e => {
|
||||
cur.engine.reLayout();
|
||||
});
|
||||
|
||||
document.getElementById('btn-set').addEventListener('click', e => {
|
||||
let els = cur.engine.getElements();
|
||||
|
||||
els.map(item => {
|
||||
item.set('style', { fill: 'red' });
|
||||
});
|
||||
cur.engine.on('node:mouseover', evt => {
|
||||
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
|
||||
|
@ -1,18 +1,17 @@
|
||||
import { Util } from "./util";
|
||||
import { BoundingRect, Bound } from "./boundingRect";
|
||||
import { Vector } from "./vector";
|
||||
import { Element } from "../Model/modelData";
|
||||
import { Element, Model } from "../Model/modelData";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* element组
|
||||
* model 集合组
|
||||
*/
|
||||
export class Group {
|
||||
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();
|
||||
|
||||
if(arg) {
|
||||
@ -24,25 +23,43 @@ export class Group {
|
||||
* 添加element
|
||||
* @param arg
|
||||
*/
|
||||
add(...arg: Array<Element | Group>) {
|
||||
add(...arg: Array<Model | Group>) {
|
||||
arg.map(ele => {
|
||||
this.elements.push(ele);
|
||||
this.models.push(ele);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除element
|
||||
* 移除 model
|
||||
* @param element
|
||||
*/
|
||||
remove(element: Element | Group) {
|
||||
Util.removeFromList(this.elements, item => item.id === element.id);
|
||||
remove(model: Model | Group) {
|
||||
Util.removeFromList(this.models, item => item.id === model.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取group的包围盒
|
||||
*/
|
||||
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
|
||||
*/
|
||||
translate(dx: number, dy: number) {
|
||||
this.elements.map(item => {
|
||||
this.models.map(item => {
|
||||
if(item instanceof Group) {
|
||||
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
|
||||
*/
|
||||
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 { SV } from "../StructV";
|
||||
import { G6Data } from "../View/renderer";
|
||||
@ -78,21 +78,27 @@ export const Util = {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param constructListType
|
||||
* @param groupTable
|
||||
* @returns
|
||||
*/
|
||||
converterList(modelContainer: { [key: string]: ConstructList[keyof ConstructList]}) {
|
||||
return [].concat(...Object.keys(modelContainer).map(item => modelContainer[item]));
|
||||
convertGroupTable2ModelList(groupTable: LayoutGroupTable): Model[] {
|
||||
const list: Model[] = [];
|
||||
|
||||
groupTable.forEach(item => {
|
||||
list.push(...item.modelList);
|
||||
});
|
||||
|
||||
return list;
|
||||
},
|
||||
|
||||
/**
|
||||
* G6 data 转换器
|
||||
* @param constructList
|
||||
* @param layoutGroup
|
||||
* @returns
|
||||
*/
|
||||
convertG6Data(constructList: ConstructList): G6Data {
|
||||
let nodes = [...constructList.element, ...constructList.pointer],
|
||||
edges = constructList.link;
|
||||
convertG6Data(layoutGroup: LayoutGroup): G6Data {
|
||||
let nodes = [...layoutGroup.element, ...layoutGroup.pointer],
|
||||
edges = layoutGroup.link;
|
||||
|
||||
return {
|
||||
nodes: nodes.map(item => item.cloneProps()) as G6NodeModel[],
|
||||
|
@ -1,58 +1,120 @@
|
||||
import { Util } from "../Common/util";
|
||||
import { Engine } from "../engine";
|
||||
import { LinkOption, PointerOption } from "../options";
|
||||
import { sourceLinkData, SourceElement, LinkTarget } from "../sources";
|
||||
import { Element, Link, Pointer } from "./modelData";
|
||||
import { ElementOption, Layouter, LayoutGroupOptions, LinkOption, PointerOption } from "../options";
|
||||
import { sourceLinkData, SourceElement, LinkTarget, Sources } from "../sources";
|
||||
import { SV } from "../StructV";
|
||||
import { Element, Link, Model, Pointer } from "./modelData";
|
||||
|
||||
|
||||
export interface ConstructList {
|
||||
export type LayoutGroup = {
|
||||
element: Element[];
|
||||
link: Link[];
|
||||
pointer: Pointer[];
|
||||
layouter: Layouter;
|
||||
options: LayoutGroupOptions;
|
||||
modelList: Model[];
|
||||
};
|
||||
|
||||
|
||||
export type LayoutGroupTable = Map<string, LayoutGroup>;
|
||||
|
||||
|
||||
export class ModelConstructor {
|
||||
private engine: Engine;
|
||||
private constructList: ConstructList;
|
||||
private layoutGroupTable: LayoutGroupTable;
|
||||
private prevSourcesStringMap: { [key: string]: string }; // 保存上一次源数据转换为字符串之后的值,用作比较该次源数据和上一次源数据是否有差异,若相同,则可跳过重复构建过程
|
||||
|
||||
constructor(engine: Engine) {
|
||||
this.engine = engine;
|
||||
this.prevSourcesStringMap = { };
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建element,link和pointer
|
||||
* @param sourceList
|
||||
*/
|
||||
public construct(sourceList: SourceElement[]): ConstructList {
|
||||
let elementContainer = this.constructElements(sourceList),
|
||||
linkContainer = this.constructLinks(this.engine.linkOptions, elementContainer),
|
||||
pointerContainer = this.constructPointers(this.engine.pointerOptions, elementContainer);
|
||||
|
||||
this.constructList = {
|
||||
element: Util.converterList(elementContainer),
|
||||
link: Util.converterList(linkContainer),
|
||||
pointer: Util.converterList(pointerContainer)
|
||||
};
|
||||
public construct(sources: Sources): LayoutGroupTable {
|
||||
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
||||
layouterMap: { [key: string]: Layouter } = SV.registeredLayouter,
|
||||
optionsTable = this.engine.optionsTable;
|
||||
|
||||
return this.constructList;
|
||||
Object.keys(sources).forEach(name => {
|
||||
let sourceGroup = sources[name],
|
||||
layouterName = sourceGroup.layouter;
|
||||
|
||||
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
|
||||
*/
|
||||
public getConstructList(): ConstructList {
|
||||
return this.constructList;
|
||||
public getLayoutGroupTable(): LayoutGroupTable {
|
||||
return this.layoutGroupTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从源数据构建 element 集
|
||||
* @param sourceList
|
||||
* @param elementOptions
|
||||
* @param groupName
|
||||
* @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',
|
||||
elementContainer: { [key: string]: Element[] } = { };
|
||||
elementList: Element[] = [];
|
||||
|
||||
sourceList.forEach(item => {
|
||||
if(item === null) {
|
||||
@ -63,37 +125,26 @@ export class ModelConstructor {
|
||||
item.type = defaultElementType;
|
||||
}
|
||||
|
||||
if(elementContainer[item.type] === undefined) {
|
||||
elementContainer[item.type] = [];
|
||||
}
|
||||
|
||||
elementContainer[item.type].push(this.createElement(item, item.type));
|
||||
elementList.push(this.createElement(item, item.type, groupName, layouterName, elementOptions[item.type]));
|
||||
});
|
||||
|
||||
return elementContainer;
|
||||
return elementList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置和 element 集构建 link 集
|
||||
* @param linkOptions
|
||||
* @param elementContainer
|
||||
* @param elements
|
||||
* @param layoutGroupTable
|
||||
* @returns
|
||||
*/
|
||||
private constructLinks(linkOptions: { [key: string]: LinkOption }, elementContainer: { [key: string]: Element[] }): { [key: string]: Link[] } {
|
||||
let linkContainer: { [key: string]: Link[] } = { },
|
||||
elementList: Element[] = Object
|
||||
.keys(elementContainer)
|
||||
.map(item => elementContainer[item])
|
||||
.reduce((prev, cur) => [...prev, ...cur]),
|
||||
private constructLinks(linkOptions: { [key: string]: LinkOption }, elements: Element[], layoutGroupTable: LayoutGroupTable): Link[] {
|
||||
let linkList: Link[] = [],
|
||||
linkNames = Object.keys(linkOptions);
|
||||
|
||||
linkNames.forEach(name => {
|
||||
linkContainer[name] = [];
|
||||
});
|
||||
|
||||
linkNames.forEach(name => {
|
||||
for(let i = 0; i < elementList.length; i++) {
|
||||
let element: Element = elementList[i],
|
||||
for(let i = 0; i < elements.length; i++) {
|
||||
let element: Element = elements[i],
|
||||
sourceLinkData: sourceLinkData = element.sourceElement[name],
|
||||
targetElement: Element | Element[] = null,
|
||||
link: Link = null;
|
||||
@ -106,22 +157,22 @@ export class ModelConstructor {
|
||||
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 Element -------------------
|
||||
if(Array.isArray(sourceLinkData)) {
|
||||
element[name] = sourceLinkData.map((item, index) => {
|
||||
targetElement = this.fetchTargetElements(elementContainer, element, item);
|
||||
targetElement = this.fetchTargetElements(layoutGroupTable, element, item);
|
||||
|
||||
if(targetElement) {
|
||||
link = this.createLink(name, element, targetElement, index);
|
||||
linkContainer[name].push(link);
|
||||
link = this.createLink(name, element, targetElement, index, linkOptions[name]);
|
||||
linkList.push(link);
|
||||
}
|
||||
|
||||
return targetElement;
|
||||
});
|
||||
}
|
||||
else {
|
||||
targetElement = this.fetchTargetElements(elementContainer, element, sourceLinkData);
|
||||
targetElement = this.fetchTargetElements(layoutGroupTable, element, sourceLinkData);
|
||||
|
||||
if(targetElement) {
|
||||
link = this.createLink(name, element, targetElement, null);
|
||||
linkContainer[name].push(link);
|
||||
link = this.createLink(name, element, targetElement, null, linkOptions[name]);
|
||||
linkList.push(link);
|
||||
}
|
||||
|
||||
element[name] = targetElement;
|
||||
@ -129,64 +180,57 @@ export class ModelConstructor {
|
||||
}
|
||||
});
|
||||
|
||||
return linkContainer;
|
||||
return linkList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置和 element 集构建 pointer 集
|
||||
* @param pointerOptions
|
||||
* @param elementContainer
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
private constructPointers(pointerOptions: { [key: string]: PointerOption }, elementContainer: { [key: string]: Element[] }): { [key: string]: Pointer[] } {
|
||||
let pointerContainer: { [key: string]: Pointer[] } = { },
|
||||
elementList: Element[] = Object
|
||||
.keys(elementContainer)
|
||||
.map(item => elementContainer[item])
|
||||
.reduce((prev, cur) => [...prev, ...cur]),
|
||||
private constructPointers(pointerOptions: { [key: string]: PointerOption }, elements: Element[]): Pointer[] {
|
||||
let pointerList: Pointer[] = [],
|
||||
pointerNames = Object.keys(pointerOptions);
|
||||
|
||||
pointerNames.forEach(name => {
|
||||
pointerContainer[name] = [];
|
||||
});
|
||||
|
||||
pointerNames.forEach(name => {
|
||||
|
||||
|
||||
for(let i = 0; i < elementList.length; i++) {
|
||||
let element = elementList[i],
|
||||
for(let i = 0; i < elements.length; i++) {
|
||||
let element = elements[i],
|
||||
pointerData = element[name];
|
||||
|
||||
// 若没有指针字段的结点则跳过
|
||||
if(!pointerData) continue;
|
||||
|
||||
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
|
||||
* @param sourceElement
|
||||
* @param elementName
|
||||
* @param groupName
|
||||
* @param layouterName
|
||||
* @param options
|
||||
*/
|
||||
private createElement(sourceElement: SourceElement, elementName: string): Element {
|
||||
let elementOption = this.engine.elementOptions[elementName],
|
||||
element: Element = undefined,
|
||||
label = elementOption.label? this.parserElementContent(sourceElement, elementOption.label): '',
|
||||
private createElement(sourceElement: SourceElement, elementName: string, groupName: string, layouterName: string, options: ElementOption): Element {
|
||||
let element: Element = undefined,
|
||||
label = options.label? this.parserElementContent(sourceElement, options.label): '',
|
||||
id = elementName + '.' + sourceElement.id.toString();
|
||||
|
||||
if(label === null || label === 'undefined') {
|
||||
label = '';
|
||||
}
|
||||
|
||||
element = new Element(id, elementName, sourceElement);
|
||||
element.initProps(elementOption);
|
||||
element = new Element(id, elementName, groupName, layouterName, sourceElement);
|
||||
element.initProps(options);
|
||||
element.set('label', label);
|
||||
element.sourceElement = sourceElement;
|
||||
|
||||
@ -199,10 +243,10 @@ export class ModelConstructor {
|
||||
* @param pointerName
|
||||
* @param label
|
||||
* @param target
|
||||
* @param options
|
||||
*/
|
||||
private createPointer(id: string, pointerName: string, pointerData: string | string[], target: Element): Pointer {
|
||||
let options = this.engine.pointerOptions[pointerName],
|
||||
pointer = undefined;
|
||||
private createPointer(id: string, pointerName: string, pointerData: string | string[], target: Element, options: PointerOption): Pointer {
|
||||
let pointer = undefined;
|
||||
|
||||
pointer = new Pointer(id, pointerName, pointerData, target);
|
||||
pointer.initProps(options);
|
||||
@ -216,10 +260,10 @@ export class ModelConstructor {
|
||||
* @param element
|
||||
* @param target
|
||||
* @param index
|
||||
* @param options
|
||||
*/
|
||||
private createLink(linkName: string, element: Element, target: Element, index: number): Link {
|
||||
let options: LinkOption = this.engine.linkOptions[linkName],
|
||||
link = undefined,
|
||||
private createLink(linkName: string, element: Element, target: Element, index: number, options: LinkOption): Link {
|
||||
let link = undefined,
|
||||
id = `${element.id}-${target.id}`;
|
||||
|
||||
link = new Link(id, linkName, element, target, index);
|
||||
@ -233,7 +277,7 @@ export class ModelConstructor {
|
||||
* @param sourceElement
|
||||
* @param formatLabel
|
||||
*/
|
||||
private parserElementContent(sourceElement: SourceElement, formatLabel: string): string {
|
||||
private parserElementContent(sourceElement: SourceElement, formatLabel: string): string {
|
||||
let fields = Util.textParser(formatLabel);
|
||||
|
||||
if(Array.isArray(fields)) {
|
||||
@ -253,31 +297,44 @@ export class ModelConstructor {
|
||||
* @param element
|
||||
* @param linkTarget
|
||||
*/
|
||||
private fetchTargetElements(
|
||||
elementContainer: { [key: string]: Element[] } ,
|
||||
element: Element,
|
||||
linkTarget: LinkTarget
|
||||
): Element {
|
||||
let elementName = element.getType(),
|
||||
private fetchTargetElements(layoutGroupTable: LayoutGroupTable, element: Element, linkTarget: LinkTarget): Element {
|
||||
let groupName: string = element.groupName,
|
||||
elementName = element.type,
|
||||
elementList: Element[],
|
||||
targetId = linkTarget,
|
||||
targetGroupName = groupName,
|
||||
targetElement = null;
|
||||
|
||||
if(linkTarget === null || linkTarget === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(typeof linkTarget === 'string' && linkTarget.includes('#')) {
|
||||
let info = linkTarget.split('#');
|
||||
elementName = info[0];
|
||||
targetId = info[1];
|
||||
if(typeof linkTarget === 'number' || (typeof linkTarget === 'string' && !linkTarget.includes('#'))) {
|
||||
linkTarget = 'default#' + linkTarget;
|
||||
}
|
||||
|
||||
if(typeof targetId === 'number') {
|
||||
targetId = targetId.toString();
|
||||
let info = linkTarget.split('#');
|
||||
|
||||
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
|
||||
if(elementList === undefined) {
|
||||
@ -292,6 +349,6 @@ export class ModelConstructor {
|
||||
* 销毁
|
||||
*/
|
||||
destroy() {
|
||||
this.constructList = null;
|
||||
this.layoutGroupTable = null;
|
||||
}
|
||||
};
|
@ -17,6 +17,7 @@ export interface G6NodeModel {
|
||||
style: Style;
|
||||
labelCfg: ElementLabelOption;
|
||||
externalPointerId: string;
|
||||
SVLayouter: string;
|
||||
SVModelType: string;
|
||||
SVModelName: string;
|
||||
};
|
||||
@ -156,22 +157,30 @@ export class Model {
|
||||
getType(): string {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Element extends Model {
|
||||
sourceElement: SourceElement;
|
||||
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);
|
||||
|
||||
if(type === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.free = false;
|
||||
this.groupName = group;
|
||||
this.layouterName = layouter;
|
||||
this.freed = false;
|
||||
|
||||
Object.keys(sourceElement).map(prop => {
|
||||
if(prop !== 'id') {
|
||||
@ -197,6 +206,7 @@ export class Element extends Model {
|
||||
style: Util.objectClone<Style>(option.style),
|
||||
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
||||
externalPointerId: null,
|
||||
SVLayouter: this.layouterName,
|
||||
SVModelType: 'element',
|
||||
SVModelName: this.type
|
||||
};
|
||||
@ -274,6 +284,7 @@ export class Pointer extends Model {
|
||||
style: Util.objectClone<Style>(option.style),
|
||||
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
||||
externalPointerId: null,
|
||||
SVLayouter: null,
|
||||
SVModelType: 'pointer',
|
||||
SVModelName: this.type
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ export default G6.registerNode('binary-tree-node', {
|
||||
y: height / 2,
|
||||
width: width / 2,
|
||||
height: height,
|
||||
fill: cfg.style.fill,
|
||||
fill: cfg.color || cfg.style.fill,
|
||||
stroke: cfg.style.stroke || '#333',
|
||||
cursor: cfg.style.cursor
|
||||
},
|
||||
|
@ -16,7 +16,8 @@ export default G6.registerNode('indexed-node', {
|
||||
width: width,
|
||||
height: height,
|
||||
stroke: cfg.style.stroke || '#333',
|
||||
fill: disable? '#ccc': cfg.style.fill
|
||||
fill: disable? '#ccc': cfg.style.fill,
|
||||
cursor: cfg.style.cursor,
|
||||
},
|
||||
name: 'wrapper'
|
||||
});
|
||||
|
@ -15,7 +15,8 @@ export default G6.registerNode('link-list-node', {
|
||||
width: width,
|
||||
height: height,
|
||||
stroke: cfg.style.stroke || '#333',
|
||||
fill: '#eee'
|
||||
fill: '#eee',
|
||||
cursor: cfg.style.cursor
|
||||
},
|
||||
name: 'wrapper'
|
||||
});
|
||||
@ -27,7 +28,8 @@ export default G6.registerNode('link-list-node', {
|
||||
width: width * (2 / 3),
|
||||
height: height,
|
||||
fill: cfg.style.fill,
|
||||
stroke: cfg.style.stroke || '#333'
|
||||
stroke: cfg.style.stroke || '#333',
|
||||
cursor: cfg.style.cursor
|
||||
},
|
||||
name: 'main-rect',
|
||||
draggable: true
|
||||
|
@ -44,7 +44,8 @@ export default G6.registerNode('two-cell-node', {
|
||||
textBaseline: 'middle',
|
||||
text: cfg.label,
|
||||
fill: style.fill || '#000',
|
||||
fontSize: style.fontSize || 16
|
||||
fontSize: style.fontSize || 16,
|
||||
cursor: cfg.style.cursor,
|
||||
},
|
||||
name: 'text',
|
||||
draggable: true
|
||||
|
@ -10,6 +10,10 @@ import { Vector } from "./Common/vector";
|
||||
import indexedNode from "./RegisteredShape/indexedNode";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const SV = {
|
||||
Engine: Engine,
|
||||
Group: Group,
|
||||
@ -17,6 +21,7 @@ export const SV = {
|
||||
Vector: Vector,
|
||||
Mat3: G6.Util.mat3,
|
||||
G6,
|
||||
|
||||
registeredShape: [
|
||||
externalPointer,
|
||||
linkListNode,
|
||||
@ -24,6 +29,18 @@ export const SV = {
|
||||
twoCellNode,
|
||||
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 { ConstructList } from "../../Model/modelConstructor";
|
||||
import { Element, Model, Pointer } from "../../Model/modelData";
|
||||
import { AnimationOptions, InteractionOptions, LayoutOptions } from "../../options";
|
||||
import { Model, Pointer } from "../../Model/modelData";
|
||||
import { AnimationOptions, InteractionOptions, LayoutGroupOptions, LayoutOptions } from "../../options";
|
||||
import { SV } from "../../StructV";
|
||||
import { Animations } from "../animation";
|
||||
import { g6Behavior, Renderer } from "../renderer";
|
||||
|
||||
@ -25,20 +24,45 @@ export class Container {
|
||||
this.DOMContainer = DOMContainer;
|
||||
this.animationsOptions = engine.animationOptions;
|
||||
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, {
|
||||
...g6Options,
|
||||
modes: {
|
||||
default: this.initBehaviors()
|
||||
}
|
||||
default: this.initBehaviors(this.engine.optionsTable)
|
||||
},
|
||||
plugins: [tooltip]
|
||||
});
|
||||
this.prevModelList = [];
|
||||
|
||||
this.afterInitRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化交互行为
|
||||
* @param optionsTable
|
||||
* @returns
|
||||
*/
|
||||
protected initBehaviors(): g6Behavior[] {
|
||||
protected initBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }): g6Behavior[] {
|
||||
return ['drag-canvas', 'zoom-canvas'];
|
||||
}
|
||||
|
||||
@ -119,6 +143,11 @@ export class Container {
|
||||
*/
|
||||
protected handleChangeModels(models: Model[]) { }
|
||||
|
||||
/**
|
||||
* 初始化渲染器之后的回调
|
||||
*/
|
||||
protected afterInitRenderer() { }
|
||||
|
||||
// ------------------------------------------ hook ---------------------------------------------
|
||||
|
||||
afterAppendModels(callback: (models: Model[]) => void) {
|
||||
@ -135,11 +164,9 @@ export class Container {
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param modelList
|
||||
* @param layoutFn
|
||||
*/
|
||||
public render(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||
const modelList: Model[] = [...constructList.element, ...constructList.link, ...constructList.pointer],
|
||||
appendModels: Model[] = this.getAppendModels(this.prevModelList, modelList),
|
||||
public render(modelList: Model[]) {
|
||||
const appendModels: Model[] = this.getAppendModels(this.prevModelList, modelList),
|
||||
removeModels: Model[] = this.getRemoveModels(this.prevModelList, 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 { InteractionOptions, LayoutGroupOptions } from "../../options";
|
||||
import { Container } from "./container";
|
||||
|
||||
|
||||
@ -8,50 +9,142 @@ import { Container } from "./container";
|
||||
*/
|
||||
export class MainContainer extends Container {
|
||||
|
||||
protected initBehaviors() {
|
||||
const interactionOptions = this.interactionOptions,
|
||||
dragNode: boolean | string[] = interactionOptions.dragNode,
|
||||
dragNodeFilter = node => {
|
||||
let model = node.item.getModel();
|
||||
protected initBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }) {
|
||||
const dragNodeTable: { [key: string]: boolean | string[] } = { },
|
||||
selectNodeTable: { [key: string]: boolean | string[] } = { },
|
||||
interactionOptions: InteractionOptions = this.engine.interactionOptions,
|
||||
defaultModes = [];
|
||||
|
||||
if(node.item === null) {
|
||||
return false;
|
||||
}
|
||||
Object.keys(optionsTable).forEach(item => {
|
||||
dragNodeTable[item] = optionsTable[item].behavior.dragNode;
|
||||
selectNodeTable[item] = optionsTable[item].behavior.selectNode;
|
||||
});
|
||||
|
||||
if(model.modelType === 'pointer') {
|
||||
return false;
|
||||
}
|
||||
if(interactionOptions.drag) {
|
||||
defaultModes.push('drag-canvas');
|
||||
}
|
||||
|
||||
if(typeof dragNode === 'boolean') {
|
||||
return dragNode;
|
||||
}
|
||||
if(interactionOptions.zoom) {
|
||||
defaultModes.push('zoom-canvas');
|
||||
}
|
||||
|
||||
if(Array.isArray(dragNode) && dragNode.indexOf(model.modelName) > -1) {
|
||||
return true;
|
||||
}
|
||||
const dragNodeFilter = node => {
|
||||
let model = node.item.getModel();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const modeMap = {
|
||||
drag: 'drag-canvas',
|
||||
zoom: 'zoom-canvas',
|
||||
dragNode: {
|
||||
type: 'drag-node',
|
||||
shouldBegin: node => dragNodeFilter(node)
|
||||
if(node.item === null) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
defaultModes = [];
|
||||
|
||||
Object.keys(interactionOptions).forEach(item => {
|
||||
if(interactionOptions[item] && modeMap[item] !== undefined) {
|
||||
defaultModes.push(modeMap[item]);
|
||||
if(model.SVModelType === 'pointer') {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在初始化渲染器之后,修正节点拖拽时,外部指针没有跟着动的问题
|
||||
* @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[]) {
|
||||
const changeHighlightColor: string = this.interactionOptions.changeHighlight;
|
||||
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||
import { Group } from '../Common/group';
|
||||
import { Engine } from '../engine';
|
||||
import { ConstructList } from '../Model/modelConstructor';
|
||||
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||
import { Element, Model, Pointer } from '../Model/modelData';
|
||||
import { LayoutOptions, PointerOption } from '../options';
|
||||
import { LayoutOptions, PointerOption, ViewOptions } from '../options';
|
||||
import { Container } from './container/container';
|
||||
|
||||
|
||||
export class Layouter {
|
||||
private engine: Engine;
|
||||
private engine: Engine;
|
||||
private viewOptions: ViewOptions;
|
||||
|
||||
constructor(engine: Engine) {
|
||||
this.engine = engine;
|
||||
this.viewOptions = this.engine.viewOptions;
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +22,7 @@ export class Layouter {
|
||||
* @param elements
|
||||
* @param pointers
|
||||
*/
|
||||
private initLayoutValue(elements: Element[], pointers: Pointer[]) {
|
||||
private initLayoutValue(elements: Element[], pointers: Pointer[]) {
|
||||
[...elements, ...pointers].forEach(item => {
|
||||
item.set('rotation', item.get('rotation'));
|
||||
item.set({ x: 0, y: 0 });
|
||||
@ -29,10 +32,11 @@ export class Layouter {
|
||||
/**
|
||||
* 布局外部指针
|
||||
* @param pointer
|
||||
* @param pointerOptions
|
||||
*/
|
||||
private layoutPointer(pointers: Pointer[]) {
|
||||
private layoutPointer(pointers: Pointer[], pointerOptions: { [key: string]: PointerOption }) {
|
||||
pointers.forEach(item => {
|
||||
const options: PointerOption = this.engine.pointerOptions[item.getType()],
|
||||
const options: PointerOption = pointerOptions[item.getType()],
|
||||
offset = options.offset || 8,
|
||||
anchor = options.anchor || 0;
|
||||
|
||||
@ -52,57 +56,118 @@ export class Layouter {
|
||||
* @param container
|
||||
* @param models
|
||||
*/
|
||||
private fitCenter(container: Container, models: Model[]) {
|
||||
if(models.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewBound: BoundingRect = models.map(item => item.getBound()).reduce((prev, cur) => Bound.union(prev, cur));
|
||||
|
||||
private fitCenter(container: Container, group: Group) {
|
||||
let width = container.getG6Instance().getWidth(),
|
||||
height = container.getG6Instance().getHeight(),
|
||||
viewBound: BoundingRect = group.getBound(),
|
||||
centerX = width / 2, centerY = height / 2,
|
||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
||||
dx = centerX - boundCenterX,
|
||||
dy = centerY - boundCenterY;
|
||||
|
||||
models.forEach(item => {
|
||||
item.set({
|
||||
x: item.get('x') + dx,
|
||||
y: item.get('y') + dy
|
||||
group.translate(dx, 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);
|
||||
}
|
||||
|
||||
if(this.engine.layoutOptions.fitView) {
|
||||
if(this.engine.viewOptions.fitView) {
|
||||
this.g6Instance.fitView();
|
||||
}
|
||||
|
||||
modelList.forEach(item => {
|
||||
item.renderG6Item = this.g6Instance.findById(item.id);
|
||||
item.G6Item = item.renderG6Item;
|
||||
item.renderG6Item.SVModel = item;
|
||||
});
|
||||
|
||||
// 把所有连线置顶
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { Engine } from "../engine";
|
||||
import { Element, Link } from "../Model/modelData";
|
||||
import { EngineInitOptions, LayoutOptions } from "../options";
|
||||
import { Element, Link, Model } from "../Model/modelData";
|
||||
import { EngineOptions } from "../options";
|
||||
import { Container } from "./container/container";
|
||||
import { SV } from '../StructV';
|
||||
import { ConstructList } from "../Model/modelConstructor";
|
||||
import { MainContainer } from "./container/main";
|
||||
import { FreedContainer } from "./container/freed";
|
||||
import { LeakContainer } from "./container/leak";
|
||||
import { Layouter } from "./layouter";
|
||||
import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor";
|
||||
import { Util } from "../Common/util";
|
||||
|
||||
|
||||
export class ViewManager {
|
||||
@ -17,9 +18,7 @@ export class ViewManager {
|
||||
private freedContainer: Container;
|
||||
private leakContainer: Container;
|
||||
|
||||
private prevConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
||||
private freedConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
||||
private leakConstructList: ConstructList = { element:[], pointer: [], link: [] };
|
||||
private prevLayoutGroupTable: LayoutGroupTable;
|
||||
|
||||
private shadowG6Instance;
|
||||
|
||||
@ -27,8 +26,9 @@ export class ViewManager {
|
||||
this.engine = engine;
|
||||
this.layouter = new Layouter(engine);
|
||||
this.mainContainer = new MainContainer(engine, DOMContainer);
|
||||
this.prevLayoutGroupTable = null;
|
||||
|
||||
const options: EngineInitOptions = this.engine.initOptions;
|
||||
const options: EngineOptions = this.engine.engineOptions;
|
||||
|
||||
if(options.freedContainer) {
|
||||
this.freedContainer = new FreedContainer(engine, options.freedContainer, { fitCenter: true });
|
||||
@ -47,38 +47,49 @@ export class ViewManager {
|
||||
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
|
||||
* @param constructList
|
||||
*/
|
||||
private build(constructList: ConstructList) {
|
||||
constructList.element.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', item));
|
||||
constructList.pointer.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', item));
|
||||
constructList.link.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('edge', item));
|
||||
|
||||
constructList.element.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
});
|
||||
constructList.pointer.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
});
|
||||
constructList.link.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
private build(layoutGroupTable: LayoutGroupTable) {
|
||||
layoutGroupTable.forEach(group => {
|
||||
group.element.map(item => item.cloneProps()).forEach(item => this.shadowG6Instance.addItem('node', 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));
|
||||
|
||||
group.element.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
});
|
||||
group.pointer.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
});
|
||||
group.link.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被 free 的节点
|
||||
* @param constructList
|
||||
* @param layoutGroupTable
|
||||
* @returns
|
||||
*/
|
||||
private getFreedConstructList(constructList: ConstructList): ConstructList {
|
||||
const freedList: ConstructList = {
|
||||
element: constructList.element.filter(item => item.free),
|
||||
pointer: [],
|
||||
link: []
|
||||
};
|
||||
private getFreedConstructList(layoutGroupTable: LayoutGroupTable): Model[] {
|
||||
let freedList: Model[] = [],
|
||||
freedGroup = null,
|
||||
freedGroupName = null;
|
||||
|
||||
freedList.element.forEach(fItem => {
|
||||
constructList.element.splice(constructList.element.findIndex(item => item.id === fItem.id), 1);
|
||||
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));
|
||||
for(let group in layoutGroupTable) {
|
||||
let freedElements: Model[] = layoutGroupTable[group].element.filter(item => item.freed);
|
||||
|
||||
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;
|
||||
@ -90,47 +101,63 @@ export class ViewManager {
|
||||
* @param prevConstructList
|
||||
* @returns
|
||||
*/
|
||||
private getLeakConstructList(prevConstructList: ConstructList, constructList: ConstructList): ConstructList {
|
||||
const elements: Element[] = prevConstructList.element.filter(item => !constructList.element.find(n => n.id === item.id)),
|
||||
links: Link[] = prevConstructList.link.filter(item => !constructList.link.find(n => n.id === item.id)),
|
||||
elementIds: string[] = elements.map(item => item.id);
|
||||
private getLeakConstructList(prevLayoutGroupTable: LayoutGroupTable, layoutGroupTable: LayoutGroupTable): LayoutGroupTable {
|
||||
const leakLayoutGroupTable = new Map<string, LayoutGroup>();
|
||||
|
||||
elements.forEach(item => {
|
||||
item.set('style', {
|
||||
fill: '#ccc'
|
||||
prevLayoutGroupTable.forEach((item, groupName) => {
|
||||
let prevGroup = item,
|
||||
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++) {
|
||||
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: []
|
||||
};
|
||||
return leakLayoutGroupTable;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 对主视图进行重新布局
|
||||
* @param constructList
|
||||
* @param layoutFn
|
||||
* @param layoutGroupTable
|
||||
*/
|
||||
reLayout(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||
this.layouter.layout(this.mainContainer, constructList, layoutFn);
|
||||
reLayout(layoutGroupTable: LayoutGroupTable) {
|
||||
this.layouter.layoutAll(this.mainContainer, layoutGroupTable);
|
||||
}
|
||||
|
||||
|
||||
@ -150,11 +177,23 @@ export class ViewManager {
|
||||
|
||||
/**
|
||||
* 重新调整容器尺寸
|
||||
* @param containerName
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
resize(width: number, height: number) {
|
||||
this.mainContainer.getG6Instance().changeSize(width, height);
|
||||
resize(containerName: string, width: number, height: number) {
|
||||
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 layoutFn
|
||||
*/
|
||||
renderAll(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||
renderAll(layoutGroupTable: LayoutGroupTable) {
|
||||
this.shadowG6Instance.clear();
|
||||
|
||||
this.build(constructList);
|
||||
this.build(layoutGroupTable);
|
||||
|
||||
this.freedConstructList = this.getFreedConstructList(constructList);
|
||||
this.leakConstructList = this.getLeakConstructList(this.prevConstructList, constructList);
|
||||
|
||||
this.build(this.leakConstructList);
|
||||
let freedList = this.getFreedConstructList(layoutGroupTable),
|
||||
leakLayoutGroupTable = null;
|
||||
|
||||
if(this.leakContainer && this.prevLayoutGroupTable) {
|
||||
leakLayoutGroupTable = this.getLeakConstructList(this.prevLayoutGroupTable, layoutGroupTable);
|
||||
this.build(leakLayoutGroupTable);
|
||||
}
|
||||
|
||||
if(this.freedContainer) {
|
||||
this.freedContainer.render(this.freedConstructList, layoutFn);
|
||||
this.freedContainer.render(freedList);
|
||||
}
|
||||
|
||||
// 进行布局(设置model的x,y)
|
||||
this.layouter.layout(this.mainContainer, constructList, layoutFn);
|
||||
this.mainContainer.render(constructList, layoutFn);
|
||||
this.layouter.layoutAll(this.mainContainer, layoutGroupTable);
|
||||
|
||||
if(this.leakContainer) {
|
||||
const modelList: Model[] = Util.convertGroupTable2ModelList(layoutGroupTable);
|
||||
this.mainContainer.render(modelList);
|
||||
|
||||
if(this.leakContainer && this.prevLayoutGroupTable) {
|
||||
this.mainContainer.afterRemoveModels(() => {
|
||||
this.leakContainer.render(this.leakConstructList, layoutFn);
|
||||
this.leakContainer.render(Util.convertGroupTable2ModelList(leakLayoutGroupTable));
|
||||
});
|
||||
}
|
||||
|
||||
this.prevConstructList = constructList;
|
||||
this.prevLayoutGroupTable = layoutGroupTable;
|
||||
}
|
||||
|
||||
/**
|
||||
|
216
src/engine.ts
216
src/engine.ts
@ -1,44 +1,42 @@
|
||||
import { Element, Pointer } from "./Model/modelData";
|
||||
import { SourceElement } from "./sources";
|
||||
import { ModelConstructor, ConstructList } from "./Model/modelConstructor";
|
||||
import { AnimationOptions, ElementOption, EngineInitOptions, InteractionOptions, LayoutOptions, LinkOption, Options, PointerOption } from "./options";
|
||||
import { Behavior } from "./Behavior.ts/behavior";
|
||||
import { Element, Link, Pointer } from "./Model/modelData";
|
||||
import { Sources } from "./sources";
|
||||
import { ModelConstructor } from "./Model/modelConstructor";
|
||||
import { AnimationOptions, EngineOptions, InteractionOptions, LayoutGroupOptions, ViewOptions } from "./options";
|
||||
import { ViewManager } from "./View/viewManager";
|
||||
import { SV } from "./StructV";
|
||||
|
||||
|
||||
export class Engine {
|
||||
private stringifySources: string = null; // 序列化的源数据
|
||||
|
||||
private modelConstructor: ModelConstructor = null;
|
||||
private viewManager: ViewManager
|
||||
private behavior: Behavior;
|
||||
private prevStringSourceData: string;
|
||||
|
||||
public initOptions: EngineInitOptions;
|
||||
public elementOptions: { [key: string]: ElementOption } = { };
|
||||
public linkOptions: { [key: string]: LinkOption } = { };
|
||||
public pointerOptions: { [key: string]: PointerOption } = { };
|
||||
public layoutOptions: LayoutOptions = null;
|
||||
public animationOptions: AnimationOptions = null;
|
||||
public interactionOptions: InteractionOptions = null;
|
||||
public engineOptions: EngineOptions;
|
||||
public viewOptions: ViewOptions;
|
||||
public animationOptions: AnimationOptions;
|
||||
public interactionOptions: InteractionOptions;
|
||||
|
||||
constructor(DOMContainer: HTMLElement, initOptions: EngineInitOptions = { }) {
|
||||
const options: Options = this.defineOptions();
|
||||
|
||||
this.initOptions = initOptions;
|
||||
this.elementOptions = options.element;
|
||||
this.linkOptions = options.link || { };
|
||||
this.pointerOptions = options.pointer || { };
|
||||
public optionsTable: { [key: string]: LayoutGroupOptions };
|
||||
|
||||
this.layoutOptions = Object.assign({
|
||||
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions = { }) {
|
||||
this.optionsTable = {};
|
||||
|
||||
this.engineOptions = Object.assign({
|
||||
freedContainer: null,
|
||||
leakContainer: null
|
||||
}, engineOptions);
|
||||
|
||||
this.viewOptions = Object.assign({
|
||||
fitCenter: true,
|
||||
fitView: false
|
||||
}, options.layout);
|
||||
fitView: false,
|
||||
groupPadding: 20
|
||||
}, engineOptions.view);
|
||||
|
||||
this.animationOptions = Object.assign({
|
||||
enable: true,
|
||||
duration: 750,
|
||||
timingFunction: 'easePolyOut'
|
||||
}, options.animation);
|
||||
}, engineOptions.animation);
|
||||
|
||||
this.interactionOptions = Object.assign({
|
||||
drag: true,
|
||||
@ -46,106 +44,67 @@ export class Engine {
|
||||
dragNode: true,
|
||||
selectNode: true,
|
||||
changeHighlight: '#fc5185'
|
||||
}, options.interaction);
|
||||
}, engineOptions.interaction);
|
||||
|
||||
this.initOptions = Object.assign({
|
||||
freedContainer: null,
|
||||
leakContainer: null
|
||||
}, initOptions);
|
||||
// 初始化布局器配置项
|
||||
Object.keys(SV.registeredLayouter).forEach(layouter => {
|
||||
if(this.optionsTable[layouter] === undefined) {
|
||||
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.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) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 若前后数据没有发生变化,什么也不干(将json字符串化后比较)
|
||||
let stringifySources = JSON.stringify(sourceData);
|
||||
if(stringifySources === this.stringifySources) return;
|
||||
this.stringifySources = stringifySources;
|
||||
|
||||
let processedSourcesData = this.sourcesPreprocess(sourceData);
|
||||
if(processedSourcesData) {
|
||||
sourceData = processedSourcesData;
|
||||
let stringSourceData = JSON.stringify(sourceData);
|
||||
if(this.prevStringSourceData === stringSourceData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceList: SourceElement[] = this.sourcesProcess(sourceData);
|
||||
this.prevStringSourceData = stringSourceData;
|
||||
|
||||
// 1 转换模型(data => model)
|
||||
const constructList: ConstructList = this.modelConstructor.construct(sourceList);
|
||||
const layoutGroupTable = this.modelConstructor.construct(sourceData);
|
||||
|
||||
// 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() {
|
||||
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 => {
|
||||
let model = item.G6Item.getModel(),
|
||||
x = item.get('x'),
|
||||
y = item.get('y');
|
||||
layoutGroupTable.forEach(group => {
|
||||
group.modelList.forEach(item => {
|
||||
if(item instanceof Link) return;
|
||||
|
||||
model.x = x;
|
||||
model.y = y;
|
||||
let model = item.G6Item.getModel(),
|
||||
x = item.get('x'),
|
||||
y = item.get('y');
|
||||
|
||||
model.x = x;
|
||||
model.y = y;
|
||||
});
|
||||
});
|
||||
|
||||
this.viewManager.refresh();
|
||||
@ -160,35 +119,69 @@ export class Engine {
|
||||
|
||||
/**
|
||||
* 获取所有 element
|
||||
* @param group
|
||||
*/
|
||||
public getElements(): Element[] {
|
||||
const constructList = this.modelConstructor.getConstructList();
|
||||
return constructList.element;
|
||||
public getElements(group?: string): Element[] {
|
||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
|
||||
if(group && layoutGroupTable.has('group')) {
|
||||
return layoutGroupTable.get('group').element;
|
||||
}
|
||||
|
||||
const elements: Element[] = [];
|
||||
layoutGroupTable.forEach(item => {
|
||||
elements.push(...item.element);
|
||||
})
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 pointer
|
||||
* @param group
|
||||
*/
|
||||
public getPointers(): Pointer[] {
|
||||
const constructList = this.modelConstructor.getConstructList();
|
||||
return constructList.pointer;
|
||||
public getPointers(group?: string): Pointer[] {
|
||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
|
||||
if(group && layoutGroupTable.has('group')) {
|
||||
return layoutGroupTable.get('group').pointer;
|
||||
}
|
||||
|
||||
const pointers: Pointer[] = [];
|
||||
layoutGroupTable.forEach(item => {
|
||||
pointers.push(...item.pointer);
|
||||
})
|
||||
|
||||
return pointers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 link
|
||||
* @param group
|
||||
*/
|
||||
public getLinks() {
|
||||
const constructList = this.modelConstructor.getConstructList();
|
||||
return constructList.link;
|
||||
public getLinks(group?: string): Link[] {
|
||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
|
||||
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 height
|
||||
*/
|
||||
public resize(width: number, height: number) {
|
||||
this.viewManager.resize(width, height);
|
||||
public resize(containerName: string, width: number, height: number) {
|
||||
this.viewManager.resize(containerName, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,7 +190,7 @@ export class Engine {
|
||||
* @param callback
|
||||
*/
|
||||
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() {
|
||||
this.modelConstructor.destroy();
|
||||
this.viewManager.destroy();
|
||||
this.behavior = null;
|
||||
}
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
import { Element } from "./Model/modelData";
|
||||
import { SourceElement } from "./sources";
|
||||
|
||||
|
||||
export interface Style {
|
||||
@ -59,12 +61,38 @@ export interface PointerOption extends ElementOption {
|
||||
|
||||
|
||||
export interface LayoutOptions {
|
||||
fitCenter: boolean;
|
||||
fitView: boolean;
|
||||
[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 {
|
||||
enable: boolean;
|
||||
duration: number;
|
||||
@ -73,29 +101,24 @@ export interface AnimationOptions {
|
||||
|
||||
|
||||
export interface InteractionOptions {
|
||||
changeHighlight: string;
|
||||
drag: boolean;
|
||||
zoom: boolean;
|
||||
dragNode: boolean | string[];
|
||||
selectNode: boolean | string[];
|
||||
changeHighlight: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface Options {
|
||||
element: { [key: string]: ElementOption };
|
||||
link?: { [key: string]: LinkOption }
|
||||
pointer?: { [key: string]: PointerOption };
|
||||
layout?: LayoutOptions;
|
||||
export interface EngineOptions {
|
||||
freedContainer?: HTMLElement;
|
||||
leakContainer?: HTMLElement;
|
||||
view?: ViewOptions;
|
||||
animation?: AnimationOptions;
|
||||
interaction?: InteractionOptions;
|
||||
};
|
||||
|
||||
|
||||
export interface EngineInitOptions {
|
||||
freedContainer?: HTMLElement;
|
||||
leakContainer?: HTMLElement;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export interface Layouter {
|
||||
defineOptions(): LayoutGroupOptions;
|
||||
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