fix:修复少量bug
This commit is contained in:
parent
72fcf5394a
commit
0dcad0f117
64
demo/dataStruct/Array.js
Normal file
64
demo/dataStruct/Array.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Arrays extends Engine {
|
||||||
|
defineOptions() {
|
||||||
|
return {
|
||||||
|
element: {
|
||||||
|
default: {
|
||||||
|
type: 'rect',
|
||||||
|
label: '[id]',
|
||||||
|
size: [60, 30],
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#95e1d3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
dragNode: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
layout(elements, layoutOptions) {
|
||||||
|
let arr = elements.default;
|
||||||
|
|
||||||
|
for(let i = 0; i < arr.length; i++) {
|
||||||
|
let width = arr[i].get('size')[0];
|
||||||
|
|
||||||
|
if(i > 0) {
|
||||||
|
arr[i].set('x', arr[i - 1].get('x') + width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const A = function(container) {
|
||||||
|
return{
|
||||||
|
engine: new Arrays(container),
|
||||||
|
data: [[
|
||||||
|
{ id: 1 },
|
||||||
|
{ id: 2 },
|
||||||
|
{ id: 3 },
|
||||||
|
{ id: 4 },
|
||||||
|
{ id: 5 },
|
||||||
|
{ id: 6 },
|
||||||
|
{ id: 7 },
|
||||||
|
{ id: 8 },
|
||||||
|
{ id: 9 },
|
||||||
|
{ id: 10 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ id: 1 },
|
||||||
|
{ id: 2 },
|
||||||
|
{ id: 3 },
|
||||||
|
{ id: 6 },
|
||||||
|
{ id: 7 },
|
||||||
|
{ id: 8 }
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,9 +1,8 @@
|
|||||||
const Engine = SV.Engine,
|
|
||||||
Group = SV.Group,
|
|
||||||
Bound = SV.Bound,
|
|
||||||
G6 = SV.G6;
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二叉树
|
||||||
|
*/
|
||||||
class BinaryTree extends Engine {
|
class BinaryTree extends Engine {
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
@ -13,7 +12,9 @@ class BinaryTree extends Engine {
|
|||||||
size: [60, 30],
|
size: [60, 30],
|
||||||
label: '[id]',
|
label: '[id]',
|
||||||
style: {
|
style: {
|
||||||
fill: '#b83b5e'
|
fill: '#b83b5e',
|
||||||
|
stroke: "#333",
|
||||||
|
cursor: 'pointer'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -24,6 +25,8 @@ class BinaryTree extends Engine {
|
|||||||
targetAnchor: 0,
|
targetAnchor: 0,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
|
lineAppendWidth: 6,
|
||||||
|
cursor: 'pointer',
|
||||||
endArrow: {
|
endArrow: {
|
||||||
path: G6.Arrow.triangle(8, 6, 0),
|
path: G6.Arrow.triangle(8, 6, 0),
|
||||||
fill: '#333'
|
fill: '#333'
|
||||||
|
|||||||
141
demo/dataStruct/HashLinkList.js
Normal file
141
demo/dataStruct/HashLinkList.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单链表
|
||||||
|
*/
|
||||||
|
class HashLinkList extends Engine {
|
||||||
|
|
||||||
|
defineOptions() {
|
||||||
|
return {
|
||||||
|
element: {
|
||||||
|
headNode: {
|
||||||
|
type: 'link-list-node',
|
||||||
|
label: '[id]',
|
||||||
|
size: [60, 30],
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#b83b5e'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
type: 'link-list-node',
|
||||||
|
label: '[id]',
|
||||||
|
size: [60, 30],
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#b83b5e'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
next: {
|
||||||
|
type: 'line',
|
||||||
|
sourceAnchor: 1,
|
||||||
|
targetAnchor: 0,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
endArrow: {
|
||||||
|
path: G6.Arrow.triangle(8, 6, 0),
|
||||||
|
fill: '#333'
|
||||||
|
},
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
external: {
|
||||||
|
offset: 14,
|
||||||
|
style: {
|
||||||
|
fill: '#f08a5d'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
xInterval: 50,
|
||||||
|
yInterval: 50
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对子树进行递归布局
|
||||||
|
* @param node
|
||||||
|
* @param parent
|
||||||
|
*/
|
||||||
|
layoutItem(node, prev, layoutOptions) {
|
||||||
|
if(!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = node.get('size')[0];
|
||||||
|
|
||||||
|
if(prev) {
|
||||||
|
node.set('y', prev.get('y'));
|
||||||
|
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node.next) {
|
||||||
|
this.layoutItem(node.next, node, layoutOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
layout(elements, layoutOptions) {
|
||||||
|
let nodes = elements.default,
|
||||||
|
rootNodes = [],
|
||||||
|
node,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for(i = 0; i < nodes.length; i++) {
|
||||||
|
node = nodes[i];
|
||||||
|
|
||||||
|
if(node.root) {
|
||||||
|
rootNodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < rootNodes.length; i++) {
|
||||||
|
let root = rootNodes[i],
|
||||||
|
height = root.get('size')[1];
|
||||||
|
|
||||||
|
root.set('y', root.get('y') + i * (layoutOptions.yInterval + height));
|
||||||
|
this.layoutItem(root, null, layoutOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const LList = function(container) {
|
||||||
|
return{
|
||||||
|
engine: new LinkList(container),
|
||||||
|
data: [[
|
||||||
|
{ id: 1, root: true, next: 2, external: ['gg'] },
|
||||||
|
{ id: 2, next: 3 },
|
||||||
|
{ id: 3, next: 4 },
|
||||||
|
{ id: 4, next: 5 },
|
||||||
|
{ id: 5 },
|
||||||
|
{ id: 6, root: true, next: 7 },
|
||||||
|
{ id: 7, next: 8 },
|
||||||
|
{ id: 8, next: 4 },
|
||||||
|
{ id: 9, root: true, next: 10 },
|
||||||
|
{ id: 10 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ id: 1, root: true, next: 2, external: ['gg'] },
|
||||||
|
{ id: 2, next: 3 },
|
||||||
|
{ id: 3, next: 6 },
|
||||||
|
{ id: 6, next: 7 },
|
||||||
|
{ id: 7, next: 8 },
|
||||||
|
{ id: 8 }
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
128
demo/dataStruct/linkList.js
Normal file
128
demo/dataStruct/linkList.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单链表
|
||||||
|
*/
|
||||||
|
class LinkList extends Engine {
|
||||||
|
defineOptions() {
|
||||||
|
return {
|
||||||
|
element: {
|
||||||
|
default: {
|
||||||
|
type: 'link-list-node',
|
||||||
|
label: '[id]',
|
||||||
|
size: [60, 30],
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#b83b5e'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
next: {
|
||||||
|
type: 'line',
|
||||||
|
sourceAnchor: 1,
|
||||||
|
targetAnchor: 0,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
endArrow: {
|
||||||
|
path: G6.Arrow.triangle(8, 6, 0),
|
||||||
|
fill: '#333'
|
||||||
|
},
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
external: {
|
||||||
|
offset: 14,
|
||||||
|
style: {
|
||||||
|
fill: '#f08a5d'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
xInterval: 50,
|
||||||
|
yInterval: 50
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对子树进行递归布局
|
||||||
|
* @param node
|
||||||
|
* @param parent
|
||||||
|
*/
|
||||||
|
layoutItem(node, prev, layoutOptions) {
|
||||||
|
if(!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = node.get('size')[0];
|
||||||
|
|
||||||
|
if(prev) {
|
||||||
|
node.set('y', prev.get('y'));
|
||||||
|
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node.next) {
|
||||||
|
this.layoutItem(node.next, node, layoutOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
layout(elements, layoutOptions) {
|
||||||
|
let nodes = elements.default,
|
||||||
|
rootNodes = [],
|
||||||
|
node,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for(i = 0; i < nodes.length; i++) {
|
||||||
|
node = nodes[i];
|
||||||
|
|
||||||
|
if(node.root) {
|
||||||
|
rootNodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < rootNodes.length; i++) {
|
||||||
|
let root = rootNodes[i],
|
||||||
|
height = root.get('size')[1];
|
||||||
|
|
||||||
|
root.set('y', root.get('y') + i * (layoutOptions.yInterval + height));
|
||||||
|
this.layoutItem(root, null, layoutOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const LList = function(container) {
|
||||||
|
return{
|
||||||
|
engine: new LinkList(container),
|
||||||
|
data: [[
|
||||||
|
{ id: 1, root: true, next: 2, external: ['gg'] },
|
||||||
|
{ id: 2, next: 3 },
|
||||||
|
{ id: 3, next: 4 },
|
||||||
|
{ id: 4, next: 5 },
|
||||||
|
{ id: 5 },
|
||||||
|
{ id: 6, root: true, next: 7 },
|
||||||
|
{ id: 7, next: 8 },
|
||||||
|
{ id: 8, next: 4 },
|
||||||
|
{ id: 9, root: true, next: 10 },
|
||||||
|
{ id: 10 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ id: 1, root: true, next: 2, external: ['gg'] },
|
||||||
|
{ id: 2, next: 3 },
|
||||||
|
{ id: 3, next: 6 },
|
||||||
|
{ id: 6, next: 7 },
|
||||||
|
{ id: 7, next: 8 },
|
||||||
|
{ id: 8 }
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -24,14 +24,48 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container" id="container"></div>
|
<div class="container" id="container"></div>
|
||||||
<button id="btn">change</button>
|
<button id="btn">change</button>
|
||||||
|
<button id="btn-next">reLayout</button>
|
||||||
|
<button id="btn-set">set</button>
|
||||||
<span id="pos"></span>
|
<span id="pos"></span>
|
||||||
|
|
||||||
|
|
||||||
<script src="./../dist/sv.js"></script>
|
<script src="./../dist/sv.js"></script>
|
||||||
<script src="./dataStruct/BinaryTree.js"></script>
|
|
||||||
<script src="./demo.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
const Engine = SV.Engine,
|
||||||
|
Group = SV.Group,
|
||||||
|
Bound = SV.Bound,
|
||||||
|
G6 = SV.G6;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script src="./dataStruct/BinaryTree.js"></script>
|
||||||
|
<script src="./dataStruct/linkList.js"></script>
|
||||||
|
<script src="./dataStruct/Array.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const engines = [BTree, LList, A];
|
||||||
|
|
||||||
|
let cur = engines[2](document.getElementById('container'));
|
||||||
|
|
||||||
|
cur.engine.render(cur.data[0]);
|
||||||
|
|
||||||
|
document.getElementById('btn').addEventListener('click', e => {
|
||||||
|
cur.engine.render(cur.data[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
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' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const container = document.getElementById('container'),
|
const container = document.getElementById('container'),
|
||||||
pos = document.getElementById('pos');
|
pos = document.getElementById('pos');
|
||||||
|
|
||||||
|
|||||||
10
demo/demo.js
10
demo/demo.js
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
let cur = BTree(document.getElementById('container'));
|
|
||||||
|
|
||||||
cur.engine.render(cur.data[0]);
|
|
||||||
|
|
||||||
document.getElementById('btn').addEventListener('click', e => {
|
|
||||||
cur.engine.render(cur.data[1]);
|
|
||||||
});
|
|
||||||
2
dist/sv.js
vendored
2
dist/sv.js
vendored
File diff suppressed because one or more lines are too long
@ -96,8 +96,8 @@ export const Util = {
|
|||||||
edges = Util.converterList(constructedData.link);
|
edges = Util.converterList(constructedData.link);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: nodes.map(item => item.props),
|
nodes: nodes.map(item => item.cloneProps()),
|
||||||
edges: edges.map(item => item.props)
|
edges: edges.map(item => item.cloneProps())
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,11 @@ export interface ConstructedData {
|
|||||||
|
|
||||||
export class ModelConstructor {
|
export class ModelConstructor {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
|
private constructedData: ConstructedData;
|
||||||
|
|
||||||
constructor(engine: Engine) {
|
constructor(engine: Engine) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
|
this.constructedData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,11 +30,13 @@ export class ModelConstructor {
|
|||||||
linkContainer = this.constructLinks(this.engine.linkOptions, elementContainer),
|
linkContainer = this.constructLinks(this.engine.linkOptions, elementContainer),
|
||||||
pointerContainer = this.constructPointers(this.engine.pointerOptions, elementContainer);
|
pointerContainer = this.constructPointers(this.engine.pointerOptions, elementContainer);
|
||||||
|
|
||||||
return {
|
this.constructedData = {
|
||||||
element: elementContainer,
|
element: elementContainer,
|
||||||
link: linkContainer,
|
link: linkContainer,
|
||||||
pointer: pointerContainer
|
pointer: pointerContainer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return this.constructedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,12 +92,14 @@ export class ModelConstructor {
|
|||||||
linkNames.forEach(name => {
|
linkNames.forEach(name => {
|
||||||
for(let i = 0; i < elementList.length; i++) {
|
for(let i = 0; i < elementList.length; i++) {
|
||||||
let element: Element = elementList[i],
|
let element: Element = elementList[i],
|
||||||
sourceLinkData: sourceLinkData = element[name],
|
sourceLinkData: sourceLinkData = element.sourceElement[name],
|
||||||
options: LinkOption = linkOptions[name],
|
|
||||||
targetElement: Element | Element[] = null,
|
targetElement: Element | Element[] = null,
|
||||||
link: Link = null;
|
link: Link = null;
|
||||||
|
|
||||||
if(sourceLinkData === undefined || sourceLinkData === null) continue;
|
if(sourceLinkData === undefined || sourceLinkData === null) {
|
||||||
|
element[name] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 Element -------------------
|
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 Element -------------------
|
||||||
if(Array.isArray(sourceLinkData)) {
|
if(Array.isArray(sourceLinkData)) {
|
||||||
@ -101,9 +107,8 @@ export class ModelConstructor {
|
|||||||
targetElement = this.fetchTargetElements(elementContainer, element, item);
|
targetElement = this.fetchTargetElements(elementContainer, element, item);
|
||||||
|
|
||||||
if(targetElement) {
|
if(targetElement) {
|
||||||
link = new Link(name, element, targetElement, index);
|
link = this.createLink(name, element, targetElement, index);
|
||||||
linkContainer[name].push(link);
|
linkContainer[name].push(link);
|
||||||
link.initProps(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetElement;
|
return targetElement;
|
||||||
@ -113,9 +118,8 @@ export class ModelConstructor {
|
|||||||
targetElement = this.fetchTargetElements(elementContainer, element, sourceLinkData);
|
targetElement = this.fetchTargetElements(elementContainer, element, sourceLinkData);
|
||||||
|
|
||||||
if(targetElement) {
|
if(targetElement) {
|
||||||
link = new Link(name, element, targetElement, null);
|
link = this.createLink(name, element, targetElement, null);
|
||||||
linkContainer[name].push(link);
|
linkContainer[name].push(link);
|
||||||
link.initProps(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
element[name] = targetElement;
|
element[name] = targetElement;
|
||||||
@ -145,7 +149,7 @@ export class ModelConstructor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
pointerNames.forEach(name => {
|
pointerNames.forEach(name => {
|
||||||
let options = pointerOptions[name];
|
|
||||||
|
|
||||||
for(let i = 0; i < elementList.length; i++) {
|
for(let i = 0; i < elementList.length; i++) {
|
||||||
let element = elementList[i],
|
let element = elementList[i],
|
||||||
@ -155,9 +159,8 @@ export class ModelConstructor {
|
|||||||
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 = new Pointer(id, name, pointerData, element);
|
pointer = this.createPointer(id, name, pointerData, element);
|
||||||
|
|
||||||
pointer.initProps(options);
|
|
||||||
pointerContainer[name].push(pointer);
|
pointerContainer[name].push(pointer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -172,15 +175,52 @@ export class ModelConstructor {
|
|||||||
*/
|
*/
|
||||||
private createElement(sourceElement: SourceElement, elementName: string): Element {
|
private createElement(sourceElement: SourceElement, elementName: string): Element {
|
||||||
let elementOption = this.engine.elementOptions[elementName],
|
let elementOption = this.engine.elementOptions[elementName],
|
||||||
element = new Element(elementName, sourceElement),
|
element: Element = undefined,
|
||||||
label = elementOption.label? this.parserElementContent(sourceElement, elementOption.label): '';
|
label = elementOption.label? this.parserElementContent(sourceElement, elementOption.label): '';
|
||||||
|
|
||||||
|
element = new Element(elementName, sourceElement);
|
||||||
element.initProps(elementOption);
|
element.initProps(elementOption);
|
||||||
element.set('label', label);
|
element.set('label', label);
|
||||||
|
element.sourceElement = sourceElement;
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部指针工厂,创建Pointer
|
||||||
|
* @param id
|
||||||
|
* @param pointerName
|
||||||
|
* @param label
|
||||||
|
* @param target
|
||||||
|
*/
|
||||||
|
private createPointer(id: string, pointerName: string, pointerData: string | string[], target: Element): Pointer {
|
||||||
|
let options = this.engine.pointerOptions[pointerName],
|
||||||
|
pointer = undefined;
|
||||||
|
|
||||||
|
pointer = new Pointer(id, pointerName, pointerData, target);
|
||||||
|
pointer.initProps(options);
|
||||||
|
|
||||||
|
return pointer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连线工厂,创建Link
|
||||||
|
* @param linkName
|
||||||
|
* @param element
|
||||||
|
* @param target
|
||||||
|
* @param index
|
||||||
|
*/
|
||||||
|
private createLink(linkName: string, element: Element, target: Element, index: number): Link {
|
||||||
|
let options: LinkOption = this.engine.linkOptions[linkName],
|
||||||
|
link = undefined,
|
||||||
|
id = `${element.id}-${target.id}`;
|
||||||
|
|
||||||
|
link = new Link(id, linkName, element, target, index);
|
||||||
|
link.initProps(options);
|
||||||
|
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析元素文本内容
|
* 解析元素文本内容
|
||||||
* @param sourceElement
|
* @param sourceElement
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export interface G6NodeModel {
|
|||||||
style: Style;
|
style: Style;
|
||||||
labelCfg: ElementLabelOption;
|
labelCfg: ElementLabelOption;
|
||||||
externalPointerId: string;
|
externalPointerId: string;
|
||||||
|
modelType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -32,25 +33,50 @@ export interface G6EdgeModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class Model {
|
export class Model {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
type: string;
|
||||||
|
|
||||||
props: G6NodeModel | G6EdgeModel;
|
props: G6NodeModel | G6EdgeModel;
|
||||||
|
shadowG6Item;
|
||||||
|
renderG6Item;
|
||||||
G6Item;
|
G6Item;
|
||||||
|
|
||||||
constructor(id: string, name: string) {
|
constructor(id: string, name: string) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.shadowG6Item = null;
|
||||||
|
this.renderG6Item = null;
|
||||||
this.G6Item = null;
|
this.G6Item = null;
|
||||||
this.props = null;
|
this.props = <G6NodeModel | G6EdgeModel>{ };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* 定义 G6 model 的属性
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
protected defineProps(option: ElementOption | LinkOption | PointerOption): G6NodeModel | G6EdgeModel {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化 G6 model 的属性
|
* 初始化 G6 model 的属性
|
||||||
* @param option
|
* @param option
|
||||||
*/
|
*/
|
||||||
initProps(option: ElementOption | LinkOption | PointerOption) { }
|
initProps(option: ElementOption | LinkOption | PointerOption) {
|
||||||
|
this.props = this.defineProps(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 克隆 G6 model 的属性
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
cloneProps(): G6NodeModel | G6EdgeModel {
|
||||||
|
return Util.objectClone(this.props);
|
||||||
|
// return this.props;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 G6 model 的属性
|
* 获取 G6 model 的属性
|
||||||
@ -66,7 +92,14 @@ class Model {
|
|||||||
* @param value
|
* @param value
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
set(attr: string, value: any) {
|
set(attr: string | object, value?: any) {
|
||||||
|
if(typeof attr === 'object') {
|
||||||
|
Object.keys(attr).map(item => {
|
||||||
|
this.set(item, attr[item]);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(this.props[attr] === undefined) {
|
if(this.props[attr] === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -111,17 +144,13 @@ class Model {
|
|||||||
if(this.G6Item === null) return null;
|
if(this.G6Item === null) return null;
|
||||||
return this.G6Item.getContainer().getMatrix();
|
return this.G6Item.getContainer().getMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 钩子函数:在获取 G6Item 后执行
|
|
||||||
*/
|
|
||||||
afterInitG6Item() {
|
|
||||||
this.set('rotation', this.get('rotation'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Element extends Model {
|
export class Element extends Model {
|
||||||
|
type = 'element';
|
||||||
|
sourceElement: SourceElement;
|
||||||
|
|
||||||
constructor(type: string, sourceElement: SourceElement) {
|
constructor(type: string, sourceElement: SourceElement) {
|
||||||
super(sourceElement.id.toString(), type);
|
super(sourceElement.id.toString(), type);
|
||||||
|
|
||||||
@ -132,12 +161,8 @@ export class Element extends Model {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected defineProps(option: ElementOption) {
|
||||||
* 初始化 G6 model 的属性
|
return {
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
initProps(option: ElementOption) {
|
|
||||||
this.props = {
|
|
||||||
id: this.id,
|
id: this.id,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
@ -148,7 +173,8 @@ export class Element extends Model {
|
|||||||
label: null,
|
label: null,
|
||||||
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,
|
||||||
|
modelType: this.type
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -156,22 +182,20 @@ export class Element extends Model {
|
|||||||
|
|
||||||
|
|
||||||
export class Link extends Model {
|
export class Link extends Model {
|
||||||
|
type = 'link';
|
||||||
element: Element;
|
element: Element;
|
||||||
target: Element;
|
target: Element;
|
||||||
index: number;
|
index: number;
|
||||||
|
|
||||||
constructor(type: string, element: Element, target: Element, index: number) {
|
constructor(id: string, type: string, element: Element, target: Element, index: number) {
|
||||||
super(`${element.id}-${target.id}`, type);
|
super(id, type);
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化 G6 model 的属性
|
protected defineProps(option: LinkOption) {
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
initProps(option: LinkOption) {
|
|
||||||
let sourceAnchor = option.sourceAnchor,
|
let sourceAnchor = option.sourceAnchor,
|
||||||
targetAnchor = option.targetAnchor;
|
targetAnchor = option.targetAnchor;
|
||||||
|
|
||||||
@ -183,7 +207,7 @@ export class Link extends Model {
|
|||||||
targetAnchor = option.targetAnchor(this.index);
|
targetAnchor = option.targetAnchor(this.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props = {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
type: option.type,
|
type: option.type,
|
||||||
source: this.element.id,
|
source: this.element.id,
|
||||||
@ -198,7 +222,9 @@ export class Link extends Model {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class Pointer extends Model {
|
export class Pointer extends Model {
|
||||||
|
type = 'pointer';
|
||||||
target: Element;
|
target: Element;
|
||||||
label: string | string[];
|
label: string | string[];
|
||||||
|
|
||||||
@ -207,15 +233,12 @@ export class Pointer extends Model {
|
|||||||
this.target = target;
|
this.target = target;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
|
|
||||||
this.target.set('externalPointerId', id);
|
this.target.set('externalPointerId',
|
||||||
|
id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected defineProps(option: ElementOption) {
|
||||||
* 初始化 G6 model 的属性
|
return {
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
initProps(option: ElementOption) {
|
|
||||||
this.props = {
|
|
||||||
id: this.id,
|
id: this.id,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
@ -226,7 +249,8 @@ export class Pointer extends Model {
|
|||||||
label: typeof this.label === 'string'? this.label: this.label.join(', '),
|
label: typeof this.label === 'string'? this.label: this.label.join(', '),
|
||||||
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,
|
||||||
|
modelType: this.type
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -14,7 +14,8 @@ export default G6.registerNode('binary-tree-node', {
|
|||||||
y: height / 2,
|
y: height / 2,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
stroke: '#333',
|
stroke: cfg.style.stroke,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
fill: 'transparent'
|
fill: 'transparent'
|
||||||
},
|
},
|
||||||
name: 'wrapper'
|
name: 'wrapper'
|
||||||
@ -26,7 +27,9 @@ 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.style.fill,
|
||||||
|
stroke: cfg.style.stroke,
|
||||||
|
cursor: cfg.style.cursor
|
||||||
},
|
},
|
||||||
name: 'mid',
|
name: 'mid',
|
||||||
draggable: true
|
draggable: true
|
||||||
@ -42,7 +45,8 @@ export default G6.registerNode('binary-tree-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
|
||||||
|
|||||||
@ -14,50 +14,49 @@ export default G6.registerNode('link-list-node', {
|
|||||||
y: height / 2,
|
y: height / 2,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
stroke: '#ddd'
|
stroke: cfg.style.stroke,
|
||||||
|
fill: 'transparent'
|
||||||
},
|
},
|
||||||
name: 'wrapper'
|
name: 'wrapper'
|
||||||
});
|
});
|
||||||
|
|
||||||
group.addShape('rect', {
|
group.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: width / 3,
|
x: width / 2,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
width: width * (2 / 3),
|
width: width * (2 / 3),
|
||||||
height: height,
|
height: height,
|
||||||
fill: cfg.style.fill
|
fill: cfg.style.fill,
|
||||||
|
stroke: cfg.style.stroke
|
||||||
},
|
},
|
||||||
name: 'main-rect'
|
name: 'main-rect',
|
||||||
|
draggable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cfg.label) {
|
if (cfg.label) {
|
||||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
group.addShape('text', {
|
group.addShape('text', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: cfg.size[0] + 2, // 居中
|
x: width * (5 / 6),
|
||||||
y: -cfg.size[1],
|
y: height,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
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
|
||||||
},
|
},
|
||||||
name: 'pointer-text-shape',
|
name: 'text',
|
||||||
draggable: false,
|
draggable: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapperRect;
|
return wrapperRect;
|
||||||
},
|
},
|
||||||
|
|
||||||
update(cfg, item) {
|
|
||||||
console.log(88);
|
|
||||||
},
|
|
||||||
|
|
||||||
getAnchorPoints() {
|
getAnchorPoints() {
|
||||||
return [
|
return [
|
||||||
[0, 0.5],
|
[0, 0.5],
|
||||||
[2 / 3, 0.5]
|
[5 / 6, 0.5]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
0
src/RegisteredShape/twoCell
Normal file
0
src/RegisteredShape/twoCell
Normal file
123
src/View/behavior.ts
Normal file
123
src/View/behavior.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
if(interactionOptions.dragNode) {
|
||||||
|
this.initDragNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(interactionOptions.selectNode) {
|
||||||
|
this.initSelectNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化节点拖拽事件
|
||||||
|
*/
|
||||||
|
private initDragNode() {
|
||||||
|
let pointer = null,
|
||||||
|
pointerX = null,
|
||||||
|
pointerY = null,
|
||||||
|
dragStartX = null,
|
||||||
|
dragStartY = null;
|
||||||
|
|
||||||
|
this.graphInstance.on('node:dragstart', ev => {
|
||||||
|
pointer = this.graphInstance.findById(ev.item.getModel().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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化节/边选中
|
||||||
|
*/
|
||||||
|
private initSelectNode() {
|
||||||
|
let defaultHighlightColor = '#f08a5d',
|
||||||
|
curSelectItem = null,
|
||||||
|
curSelectItemStyle = null;
|
||||||
|
|
||||||
|
const selectCallback = ev => {
|
||||||
|
const item = ev.item,
|
||||||
|
type = item.getType(),
|
||||||
|
highlightColor = item.getModel().style.selectedColor;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this.graphInstance.on(eventName, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Util } from "../Common/util";
|
import { Util } from "../Common/util";
|
||||||
import { Engine } from "../engine";
|
import { Engine } from "../engine";
|
||||||
import { ConstructedData } from "../Model/modelConstructor";
|
import { ConstructedData } from "../Model/modelConstructor";
|
||||||
import { Element, Pointer } from "../Model/modelData";
|
import { Element, Model, Pointer } from "../Model/modelData";
|
||||||
import { LayoutOptions, PointerOption } from "../options";
|
import { LayoutOptions, PointerOption } from "../options";
|
||||||
import { Bound, BoundingRect } from "./boundingRect";
|
import { Bound, BoundingRect } from "./boundingRect";
|
||||||
|
|
||||||
@ -10,31 +10,31 @@ import { Bound, BoundingRect } from "./boundingRect";
|
|||||||
|
|
||||||
export class Layouter {
|
export class Layouter {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private containerWidth: number;
|
|
||||||
private containerHeight: number;
|
|
||||||
|
|
||||||
constructor(engine: Engine, containerWidth: number, containerHeight: number) {
|
constructor(engine: Engine) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.containerWidth = containerWidth;
|
|
||||||
this.containerHeight = containerHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将视图调整至画布中心
|
* 将视图调整至画布中心
|
||||||
* @param nodes
|
* @param nodes
|
||||||
*/
|
*/
|
||||||
private fiTCenter(nodes: (Element | Pointer)[]) {
|
private fitCenter(models: Model[]) {
|
||||||
const viewBound: BoundingRect = nodes.map(item => item.getBound()).reduce((prev, cur) => Bound.union(prev, cur));
|
const viewBound: BoundingRect = models.map(item => item.getBound()).reduce((prev, cur) => Bound.union(prev, cur));
|
||||||
|
|
||||||
let centerX = this.containerWidth / 2, centerY = this.containerHeight / 2,
|
let width = this.engine.getGraphInstance().getWidth(),
|
||||||
|
height = this.engine.getGraphInstance().getHeight(),
|
||||||
|
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;
|
||||||
|
|
||||||
nodes.forEach(item => {
|
models.forEach(item => {
|
||||||
item.set('x', item.get('x') + dx);
|
item.set({
|
||||||
item.set('y', item.get('y') + dy)
|
x: item.get('x') + dx,
|
||||||
|
y: item.get('y') + dy
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +50,10 @@ export class Layouter {
|
|||||||
|
|
||||||
pointerList.forEach(item => {
|
pointerList.forEach(item => {
|
||||||
let targetBound: BoundingRect = item.target.getBound();
|
let targetBound: BoundingRect = item.target.getBound();
|
||||||
item.set('x', targetBound.x + targetBound.width / 2);
|
item.set({
|
||||||
item.set('y', targetBound.y - offset);
|
x: targetBound.x + targetBound.width / 2,
|
||||||
|
y: targetBound.y - offset
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -59,11 +61,21 @@ export class Layouter {
|
|||||||
/**
|
/**
|
||||||
* 主布局函数
|
* 主布局函数
|
||||||
* @param constructedData
|
* @param constructedData
|
||||||
|
* @param modelList
|
||||||
* @param layoutFn
|
* @param layoutFn
|
||||||
*/
|
*/
|
||||||
public layout(constructedData: ConstructedData, layoutFn: (element: { [ket: string]: Element[] }, layoutOptions: LayoutOptions) => void) {
|
public layout(constructedData: ConstructedData, modelList: Model[], layoutFn: (element: { [ket: string]: Element[] }, layoutOptions: LayoutOptions) => void) {
|
||||||
const options: LayoutOptions = this.engine.layoutOptions,
|
const options: LayoutOptions = this.engine.layoutOptions;
|
||||||
nodes: (Element | Pointer)[] = [...Util.converterList(constructedData.element), ...Util.converterList(constructedData.pointer)]
|
|
||||||
|
// 首先初始化所有节点的坐标为0,且设定旋转
|
||||||
|
modelList.forEach(item => {
|
||||||
|
item.G6Item = item.shadowG6Item;
|
||||||
|
|
||||||
|
if(item.type === 'element' || item.type === 'pointer') {
|
||||||
|
item.set('rotation', item.get('rotation'));
|
||||||
|
item.set({ x: 0, y: 0 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 布局节点
|
// 布局节点
|
||||||
layoutFn.call(this.engine, constructedData.element, options);
|
layoutFn.call(this.engine, constructedData.element, options);
|
||||||
@ -72,6 +84,10 @@ export class Layouter {
|
|||||||
this.layoutPointer(constructedData.pointer);
|
this.layoutPointer(constructedData.pointer);
|
||||||
|
|
||||||
// 将视图调整到画布中心
|
// 将视图调整到画布中心
|
||||||
options.fitCenter && this.fiTCenter(nodes);
|
options.fitCenter && this.fitCenter(modelList);
|
||||||
|
|
||||||
|
modelList.forEach(item => {
|
||||||
|
item.G6Item = item.renderG6Item;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import { ConstructedData } from '../Model/modelConstructor';
|
|||||||
import { Util } from '../Common/util';
|
import { Util } from '../Common/util';
|
||||||
import { Animations } from './animation';
|
import { Animations } from './animation';
|
||||||
import { SV } from '../StructV';
|
import { SV } from '../StructV';
|
||||||
|
import { Model } from './../Model/modelData';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -21,12 +22,14 @@ export class Renderer {
|
|||||||
private isFirstRender: boolean;
|
private isFirstRender: boolean;
|
||||||
private prevRenderData: G6Data;
|
private prevRenderData: G6Data;
|
||||||
private graphInstance;
|
private graphInstance;
|
||||||
private helpGraphInstance;
|
private shadowGraphInstance;
|
||||||
|
private modelList: Model[];
|
||||||
|
|
||||||
constructor(engine: Engine, DOMContainer: HTMLElement, containerWidth: number, containerHeight: number) {
|
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.DOMContainer = DOMContainer;
|
this.DOMContainer = DOMContainer;
|
||||||
this.isFirstRender = true;
|
this.isFirstRender = true;
|
||||||
|
this.modelList = [];
|
||||||
this.prevRenderData = {
|
this.prevRenderData = {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: []
|
edges: []
|
||||||
@ -34,12 +37,33 @@ export class Renderer {
|
|||||||
|
|
||||||
const enable: boolean = this.engine.animationOptions.enable === undefined? true: this.engine.animationOptions.enable,
|
const enable: boolean = this.engine.animationOptions.enable === undefined? true: this.engine.animationOptions.enable,
|
||||||
duration: number = this.engine.animationOptions.duration,
|
duration: number = this.engine.animationOptions.duration,
|
||||||
timingFunction: string = this.engine.animationOptions.timingFunction;
|
timingFunction: string = this.engine.animationOptions.timingFunction,
|
||||||
|
interactionOptions = this.engine.interactionOptions;
|
||||||
|
|
||||||
|
const modeMap = {
|
||||||
|
drag: 'drag-canvas',
|
||||||
|
zoom: 'zoom-canvas',
|
||||||
|
dragNode: {
|
||||||
|
type: 'drag-node',
|
||||||
|
shouldBegin: n => {
|
||||||
|
// 不允许拖拽外部指针
|
||||||
|
if (n.item && n.item.getModel().modelType === 'pointer') return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultModes = [];
|
||||||
|
|
||||||
|
Object.keys(interactionOptions).forEach(item => {
|
||||||
|
if(interactionOptions[item] === true && modeMap[item] !== undefined) {
|
||||||
|
defaultModes.push(modeMap[item]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.graphInstance = new SV.G6.Graph({
|
this.graphInstance = new SV.G6.Graph({
|
||||||
container: DOMContainer,
|
container: DOMContainer,
|
||||||
width: containerWidth,
|
width: DOMContainer.offsetWidth,
|
||||||
height: containerHeight,
|
height: DOMContainer.offsetHeight,
|
||||||
animate: enable,
|
animate: enable,
|
||||||
animateCfg: {
|
animateCfg: {
|
||||||
duration: duration,
|
duration: duration,
|
||||||
@ -47,40 +71,15 @@ export class Renderer {
|
|||||||
},
|
},
|
||||||
fitView: this.engine.layoutOptions.fitView,
|
fitView: this.engine.layoutOptions.fitView,
|
||||||
modes: {
|
modes: {
|
||||||
default: ['drag-canvas', 'zoom-canvas', 'drag-node']
|
default: defaultModes
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.helpGraphInstance = new SV.G6.Graph({
|
this.shadowGraphInstance = new SV.G6.Graph({
|
||||||
container: DOMContainer.cloneNode()
|
container: DOMContainer.cloneNode()
|
||||||
});
|
});
|
||||||
|
|
||||||
this.animations = new Animations(duration, timingFunction);
|
this.animations = new Animations(duration, timingFunction);
|
||||||
this.initBehavior();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化交互
|
|
||||||
*/
|
|
||||||
private initBehavior() {
|
|
||||||
this.graphInstance.on('node:drag', (() => {
|
|
||||||
let pointer = null;
|
|
||||||
|
|
||||||
return ev => {
|
|
||||||
if(pointer === null) {
|
|
||||||
pointer = this.graphInstance.findById(ev.item.getModel().externalPointerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pointer) {
|
|
||||||
pointer.updatePosition({
|
|
||||||
x: ev.canvasX,
|
|
||||||
y: ev.canvasY
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(ev);
|
|
||||||
}
|
|
||||||
})());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,21 +146,21 @@ export class Renderer {
|
|||||||
let elementList: Element[] = Util.converterList(constructedData.element),
|
let elementList: Element[] = Util.converterList(constructedData.element),
|
||||||
linkList: Link[] = Util.converterList(constructedData.link),
|
linkList: Link[] = Util.converterList(constructedData.link),
|
||||||
pointerList: Pointer[] = Util.converterList(constructedData.pointer),
|
pointerList: Pointer[] = Util.converterList(constructedData.pointer),
|
||||||
nodeList = [...elementList.map(item => item.props), ...pointerList.map(item => item.props)],
|
nodeList = [...elementList.map(item => item.cloneProps()), ...pointerList.map(item => item.cloneProps())],
|
||||||
edgeList = linkList.map(item => item.props),
|
edgeList = linkList.map(item => item.cloneProps());
|
||||||
list = [...elementList, ...linkList, ...pointerList];
|
|
||||||
|
this.modelList = [...elementList, ...linkList, ...pointerList];
|
||||||
|
|
||||||
const data: G6Data = {
|
const data: G6Data = {
|
||||||
nodes: <G6NodeModel[]>nodeList,
|
nodes: <G6NodeModel[]>nodeList,
|
||||||
edges: <G6EdgeModel[]>edgeList
|
edges: <G6EdgeModel[]>edgeList
|
||||||
};
|
};
|
||||||
|
|
||||||
this.helpGraphInstance.clear();
|
this.shadowGraphInstance.clear();
|
||||||
this.helpGraphInstance.read(data);
|
this.shadowGraphInstance.read(data);
|
||||||
|
|
||||||
list.forEach(item => {
|
this.modelList.forEach(item => {
|
||||||
item.G6Item = this.helpGraphInstance.findById(item.id);
|
item.shadowG6Item = this.shadowGraphInstance.findById(item.id);
|
||||||
item.afterInitG6Item();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,16 +197,24 @@ export class Renderer {
|
|||||||
if(this.engine.layoutOptions.fitView) {
|
if(this.engine.layoutOptions.fitView) {
|
||||||
this.graphInstance.fitView();
|
this.graphInstance.fitView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.modelList.forEach(item => {
|
||||||
|
item.renderG6Item = this.graphInstance.findById(item.id);
|
||||||
|
item.G6Item = item.renderG6Item;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绑定 G6 事件
|
* 获取 model 队列
|
||||||
* @param eventName
|
|
||||||
* @param callback
|
|
||||||
*/
|
*/
|
||||||
public on(eventName: string, callback: Function) {
|
getModelList(): Model[] {
|
||||||
if(this.graphInstance) {
|
return this.modelList;
|
||||||
this.graphInstance.on(eventName, callback)
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 获取 G6 实例
|
||||||
|
*/
|
||||||
|
public getGraphInstance() {
|
||||||
|
return this.graphInstance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
117
src/engine.ts
117
src/engine.ts
@ -1,9 +1,11 @@
|
|||||||
import { Element } from "./Model/modelData";
|
import { Element, Pointer } from "./Model/modelData";
|
||||||
import { Sources } from "./sources";
|
import { Sources } from "./sources";
|
||||||
import { ConstructedData, ModelConstructor } from "./Model/modelConstructor";
|
import { ConstructedData, ModelConstructor } from "./Model/modelConstructor";
|
||||||
import { Renderer } from "./View/renderer";
|
import { Renderer } from "./View/renderer";
|
||||||
import { AnimationOptions, ElementOption, LayoutOptions, LinkOption, Options, PointerOption } from "./options";
|
import { AnimationOptions, ElementOption, InteractionOptions, LayoutOptions, LinkOption, Options, PointerOption } from "./options";
|
||||||
import { Layouter } from "./View/layouter";
|
import { Layouter } from "./View/layouter";
|
||||||
|
import { Behavior } from "./View/behavior";
|
||||||
|
import { Util } from "./Common/util";
|
||||||
|
|
||||||
|
|
||||||
export class Engine {
|
export class Engine {
|
||||||
@ -12,14 +14,16 @@ export class Engine {
|
|||||||
private modelConstructor: ModelConstructor = null;
|
private modelConstructor: ModelConstructor = null;
|
||||||
private layouter: Layouter = null;
|
private layouter: Layouter = null;
|
||||||
private renderer: Renderer = null;
|
private renderer: Renderer = null;
|
||||||
private containerWidth: number;
|
private behavior: Behavior;
|
||||||
private containerHeight: number;
|
private graphInstance;
|
||||||
|
private constructedData: ConstructedData;
|
||||||
|
|
||||||
public elementOptions: { [key: string]: ElementOption } = { };
|
public elementOptions: { [key: string]: ElementOption } = { };
|
||||||
public linkOptions: { [key: string]: LinkOption } = { };
|
public linkOptions: { [key: string]: LinkOption } = { };
|
||||||
public pointerOptions: { [key: string]: PointerOption } = { };
|
public pointerOptions: { [key: string]: PointerOption } = { };
|
||||||
public layoutOptions: LayoutOptions = null;
|
public layoutOptions: LayoutOptions = null;
|
||||||
public animationOptions: AnimationOptions = null;
|
public animationOptions: AnimationOptions = null;
|
||||||
|
public interactionOptions: InteractionOptions = null;
|
||||||
|
|
||||||
constructor(DOMContainer: HTMLElement) {
|
constructor(DOMContainer: HTMLElement) {
|
||||||
const options: Options = this.defineOptions();
|
const options: Options = this.defineOptions();
|
||||||
@ -36,15 +40,21 @@ export class Engine {
|
|||||||
this.animationOptions = Object.assign({
|
this.animationOptions = Object.assign({
|
||||||
enable: true,
|
enable: true,
|
||||||
duration: 750,
|
duration: 750,
|
||||||
timingFunction: 'linearEasing'
|
timingFunction: 'easePolyOut'
|
||||||
}, options.animation);
|
}, options.animation);
|
||||||
|
|
||||||
this.containerWidth = DOMContainer.offsetWidth,
|
this.interactionOptions = Object.assign({
|
||||||
this.containerHeight = DOMContainer.offsetHeight;
|
drag: true,
|
||||||
|
zoom: true,
|
||||||
|
dragNode: true,
|
||||||
|
selectNode: true
|
||||||
|
}, options.interaction);
|
||||||
|
|
||||||
this.modelConstructor = new ModelConstructor(this);
|
this.modelConstructor = new ModelConstructor(this);
|
||||||
this.layouter = new Layouter(this, this.containerWidth, this.containerHeight);
|
this.layouter = new Layouter(this);
|
||||||
this.renderer = new Renderer(this, DOMContainer, this.containerWidth, this.containerHeight);
|
this.renderer = new Renderer(this, DOMContainer);
|
||||||
|
this.graphInstance = this.renderer.getGraphInstance();
|
||||||
|
this.behavior = new Behavior(this, this.renderer.getGraphInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,20 +71,20 @@ export class Engine {
|
|||||||
if(stringifySources === this.stringifySources) return;
|
if(stringifySources === this.stringifySources) return;
|
||||||
this.stringifySources = stringifySources;
|
this.stringifySources = stringifySources;
|
||||||
|
|
||||||
const constructedData: ConstructedData = this.modelConstructor.construct(sourceData);
|
this.constructedData = this.modelConstructor.construct(sourceData);
|
||||||
|
|
||||||
this.renderer.build(constructedData);
|
this.renderer.build(this.constructedData);
|
||||||
|
|
||||||
this.layouter.layout(constructedData, this.layout);
|
this.layouter.layout(this.constructedData, this.renderer.getModelList(), this.layout);
|
||||||
|
|
||||||
this.renderer.render(constructedData);
|
this.renderer.render(this.constructedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义配置项
|
* 定义配置项
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public defineOptions(): Options {
|
protected defineOptions(): Options {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,17 +92,64 @@ export class Engine {
|
|||||||
* 设置布局函数
|
* 设置布局函数
|
||||||
* @overwrite
|
* @overwrite
|
||||||
*/
|
*/
|
||||||
public layout(elementContainer: { [ket: string]: Element[] }, layoutOptions: LayoutOptions) { }
|
protected layout(elementContainer: { [ket: string]: Element[] }, layoutOptions: LayoutOptions) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取容器尺寸
|
* 重新布局
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
public getContainerSize(): { width: number, height: number } {
|
public reLayout() {
|
||||||
return {
|
const modelList = this.renderer.getModelList();
|
||||||
width: this.containerWidth,
|
|
||||||
height: this.containerHeight
|
this.layouter.layout(this.constructedData, modelList, this.layout);
|
||||||
};
|
modelList.forEach(item => {
|
||||||
|
if(item.type === 'link') return;
|
||||||
|
|
||||||
|
let model = item.G6Item.getModel(),
|
||||||
|
x = item.get('x'),
|
||||||
|
y = item.get('y');
|
||||||
|
|
||||||
|
model.x = x;
|
||||||
|
model.y = y;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.graphInstance.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 G6 实例
|
||||||
|
*/
|
||||||
|
public getGraphInstance() {
|
||||||
|
return this.graphInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有 element
|
||||||
|
*/
|
||||||
|
public getElements(): Element[] {
|
||||||
|
return Util.converterList(this.constructedData.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有 pointer
|
||||||
|
*/
|
||||||
|
public getPointers(): Pointer[] {
|
||||||
|
return Util.converterList(this.constructedData.pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有 link
|
||||||
|
*/
|
||||||
|
public getLinks() {
|
||||||
|
return Util.converterList(this.constructedData.link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调整容器尺寸
|
||||||
|
* @param width
|
||||||
|
* @param height
|
||||||
|
*/
|
||||||
|
public resize(width: number, height: number) {
|
||||||
|
this.graphInstance.changeSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,7 +157,19 @@ export class Engine {
|
|||||||
* @param eventName
|
* @param eventName
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
public on(eventName: string, callback: Function) {
|
public on(eventName: string, callback: Function) {
|
||||||
this.renderer.on(eventName, callback);
|
this.behavior.on(eventName, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁引擎
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.graphInstance.destroy();
|
||||||
|
this.modelConstructor = null;
|
||||||
|
this.layouter = null;
|
||||||
|
this.renderer = null;
|
||||||
|
this.behavior = null;
|
||||||
|
this.graphInstance = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -71,12 +71,21 @@ export interface AnimationOptions {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export interface InteractionOptions {
|
||||||
|
drag: boolean;
|
||||||
|
zoom: boolean;
|
||||||
|
dragNode: boolean;
|
||||||
|
selectNode: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
element: { [key: string]: ElementOption };
|
element: { [key: string]: ElementOption };
|
||||||
link?: { [key: string]: LinkOption }
|
link?: { [key: string]: LinkOption }
|
||||||
pointer?: { [key: string]: PointerOption };
|
pointer?: { [key: string]: PointerOption };
|
||||||
layout?: LayoutOptions;
|
layout?: LayoutOptions;
|
||||||
animation?: AnimationOptions;
|
animation?: AnimationOptions;
|
||||||
|
interaction?: InteractionOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user