feature:添加泄漏区功能部分
This commit is contained in:
parent
7e6789c8de
commit
4c1510da1f
@ -8,7 +8,7 @@ class Arrays extends Engine {
|
|||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
default: {
|
default: {
|
||||||
type: 'rect',
|
type: 'indexed-node',
|
||||||
label: '[id]',
|
label: '[id]',
|
||||||
size: [60, 30],
|
size: [60, 30],
|
||||||
style: {
|
style: {
|
||||||
@ -31,27 +31,26 @@ class Arrays extends Engine {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(elements, layoutOptions) {
|
layout(elements) {
|
||||||
let arr = elements.default;
|
let arr = elements,
|
||||||
|
width = arr[0].get('size')[0];
|
||||||
|
|
||||||
for(let i = 0; i < arr.length; i++) {
|
for(let i = 0; i < arr.length; i++) {
|
||||||
let width = arr[i].get('size')[0];
|
arr[i].set('x', i * width);
|
||||||
|
|
||||||
if(i > 0) {
|
|
||||||
arr[i].set('x', arr[i - 1].get('x') + width);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const A = function(container) {
|
const A = function(container) {
|
||||||
return{
|
return{
|
||||||
engine: new Arrays(container),
|
engine: new Arrays(container),
|
||||||
data: [[
|
data: [[
|
||||||
{ id: 1, external: 'A' },
|
{ id: 1, index: 0 },
|
||||||
{ id: 2 },
|
{ id: 2, index: 1 },
|
||||||
{ id: 3 },
|
{ id: 3, index: 2 },
|
||||||
{ id: 4 },
|
{ id: 4 },
|
||||||
{ id: 5 },
|
{ id: 5 },
|
||||||
{ id: 6 },
|
{ id: 6 },
|
||||||
@ -66,7 +65,8 @@ const A = function(container) {
|
|||||||
{ id: 3 },
|
{ id: 3 },
|
||||||
{ id: 6, external: 'A' },
|
{ id: 6, external: 'A' },
|
||||||
{ id: 7 },
|
{ id: 7 },
|
||||||
{ id: 8 }
|
{ id: 8 },
|
||||||
|
{ id: 12 }
|
||||||
]]
|
]]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -27,10 +27,7 @@ class BinaryTree extends Engine {
|
|||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
lineAppendWidth: 6,
|
lineAppendWidth: 6,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
endArrow: {
|
endArrow: 'default',
|
||||||
path: G6.Arrow.triangle(8, 6, 0),
|
|
||||||
fill: '#333'
|
|
||||||
},
|
|
||||||
startArrow: {
|
startArrow: {
|
||||||
path: G6.Arrow.circle(2, -1),
|
path: G6.Arrow.circle(2, -1),
|
||||||
fill: '#333'
|
fill: '#333'
|
||||||
@ -130,7 +127,7 @@ class BinaryTree extends Engine {
|
|||||||
* @param {*} layoutOptions
|
* @param {*} layoutOptions
|
||||||
*/
|
*/
|
||||||
layout(elements, layoutOptions) {
|
layout(elements, layoutOptions) {
|
||||||
let nodes = elements.default,
|
let nodes = elements,
|
||||||
rootNodes = [],
|
rootNodes = [],
|
||||||
node,
|
node,
|
||||||
root,
|
root,
|
||||||
|
|||||||
@ -107,7 +107,7 @@
|
|||||||
|
|
||||||
|
|
||||||
layout(elements, layoutOptions) {
|
layout(elements, layoutOptions) {
|
||||||
let headNode = elements.head;
|
let headNode = elements.filter(item => item.type === 'head');
|
||||||
|
|
||||||
for(let i = 0; i < headNode.length; i++) {
|
for(let i = 0; i < headNode.length; i++) {
|
||||||
let node = headNode[i],
|
let node = headNode[i],
|
||||||
@ -128,9 +128,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const CHT = function(container) {
|
const CHT = function(container, options) {
|
||||||
return{
|
return{
|
||||||
engine: new ChainHashTable(container),
|
engine: new ChainHashTable(container, options),
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
head: [{
|
head: [{
|
||||||
@ -153,7 +153,26 @@ const CHT = function(container) {
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
head: [{
|
||||||
|
id: 0,
|
||||||
|
start: 'node#0'
|
||||||
|
}, {
|
||||||
|
id: 2,
|
||||||
|
start: 'node#2'
|
||||||
|
}, {
|
||||||
|
id: 3,
|
||||||
|
start: 'node#4'
|
||||||
|
}],
|
||||||
|
node: [{
|
||||||
|
id: 0,
|
||||||
|
next: 1
|
||||||
|
}, {
|
||||||
|
id: 1
|
||||||
|
},{
|
||||||
|
id: 2
|
||||||
|
},{
|
||||||
|
id: 4
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
215
demo/dataStruct/GeneralizedList.js
Normal file
215
demo/dataStruct/GeneralizedList.js
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
|
||||||
|
|
||||||
|
SV.registerShape('three-cell-node', {
|
||||||
|
draw(cfg, group) {
|
||||||
|
cfg.size = cfg.size || [30, 10];
|
||||||
|
|
||||||
|
const width = cfg.size[0],
|
||||||
|
height = cfg.size[1];
|
||||||
|
|
||||||
|
const wrapperRect = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: width / 2,
|
||||||
|
y: height / 2,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
stroke: cfg.style.stroke,
|
||||||
|
fill: '#eee'
|
||||||
|
},
|
||||||
|
name: 'wrapper',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: width / 2,
|
||||||
|
y: height / 2,
|
||||||
|
width: width / 3,
|
||||||
|
height: height,
|
||||||
|
fill: cfg.style.fill,
|
||||||
|
stroke: cfg.style.stroke
|
||||||
|
},
|
||||||
|
name: 'left-rect',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (5 / 6),
|
||||||
|
y: height / 2,
|
||||||
|
width: width / 3,
|
||||||
|
height: height,
|
||||||
|
fill: '#eee',
|
||||||
|
stroke: cfg.style.stroke
|
||||||
|
},
|
||||||
|
name: 'mid-rect',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cfg.label) {
|
||||||
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (2 / 3),
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.label,
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: style.fontSize || 16
|
||||||
|
},
|
||||||
|
name: 'text',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapperRect;
|
||||||
|
},
|
||||||
|
|
||||||
|
getAnchorPoints() {
|
||||||
|
return [
|
||||||
|
[0, 0.5],
|
||||||
|
[0.5, 0.5],
|
||||||
|
[0.5, 0],
|
||||||
|
[5 / 6, 0.5]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class GeneralizedList extends Engine {
|
||||||
|
|
||||||
|
defineOptions() {
|
||||||
|
return {
|
||||||
|
element: {
|
||||||
|
tableNode: {
|
||||||
|
type: 'three-cell-node',
|
||||||
|
label: '[tag]',
|
||||||
|
size: [90, 30],
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#b83b5e'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
atomNode: {
|
||||||
|
type: 'two-cell-node',
|
||||||
|
label: '[tag]',
|
||||||
|
size: [60, 30],
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#b83b5e'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
sub: {
|
||||||
|
type: 'line',
|
||||||
|
sourceAnchor: 1,
|
||||||
|
targetAnchor: 2,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
endArrow: {
|
||||||
|
path: G6.Arrow.triangle(8, 6, 0),
|
||||||
|
fill: '#333'
|
||||||
|
},
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
type: 'line',
|
||||||
|
sourceAnchor: 3,
|
||||||
|
targetAnchor: 0,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
endArrow: {
|
||||||
|
path: G6.Arrow.triangle(8, 6, 0),
|
||||||
|
fill: '#333'
|
||||||
|
},
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
xInterval: 40,
|
||||||
|
yInterval: 20,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对子树进行递归布局
|
||||||
|
* @param node
|
||||||
|
* @param parent
|
||||||
|
*/
|
||||||
|
layoutItem(node, prev, layoutOptions) {
|
||||||
|
let [width, height] = node.get('size');
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存在子节点
|
||||||
|
if(node.sub) {
|
||||||
|
node.sub.set('y', node.get('y') + layoutOptions.yInterval + height);
|
||||||
|
|
||||||
|
// 子结点还是广义表
|
||||||
|
if(node.sub.tag === 1) {
|
||||||
|
node.sub.set('x', node.get('x'));
|
||||||
|
this.layoutItem(node.sub, null, layoutOptions);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let subWidth = node.sub.get('size')[0];
|
||||||
|
node.sub.set('x', node.get('x') + width - subWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout(elements, layoutOptions) {
|
||||||
|
let tableNodes = elements.tableNode,
|
||||||
|
tableRootNode = null;
|
||||||
|
|
||||||
|
for(let i = 0; i < tableNodes.length; i++) {
|
||||||
|
if(tableNodes[i].root) {
|
||||||
|
tableRootNode = tableNodes[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tableRootNode) {
|
||||||
|
this.layoutItem(tableRootNode, null, layoutOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const GL = function(container) {
|
||||||
|
return{
|
||||||
|
engine: new GeneralizedList(container),
|
||||||
|
data: [{
|
||||||
|
"atomNode": [],
|
||||||
|
"tableNode": [
|
||||||
|
{
|
||||||
|
"id": 6385328,
|
||||||
|
"tag": 1,
|
||||||
|
"root": true,
|
||||||
|
"external": [
|
||||||
|
"gl"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,94 +1,95 @@
|
|||||||
|
|
||||||
|
|
||||||
G6.registerNode('link-Queue-head', {
|
// G6.registerNode('link-Queue-head', {
|
||||||
draw(cfg, group) {
|
// draw(cfg, group) {
|
||||||
cfg.size = cfg.size || [30, 10];
|
// cfg.size = cfg.size || [30, 10];
|
||||||
|
|
||||||
const width = cfg.size[0],
|
// const width = cfg.size[0],
|
||||||
height = cfg.size[1];
|
// height = cfg.size[1];
|
||||||
|
|
||||||
const wrapperRect = group.addShape('rect', {
|
// const wrapperRect = group.addShape('rect', {
|
||||||
attrs: {
|
// attrs: {
|
||||||
x: width / 2,
|
// x: width / 2,
|
||||||
y: height / 2,
|
// y: height / 2,
|
||||||
width: width,
|
// width: width,
|
||||||
height: height,
|
// height: height,
|
||||||
stroke: cfg.style.stroke,
|
// stroke: cfg.style.stroke,
|
||||||
fill: 'transparent'
|
// fill: 'transparent'
|
||||||
},
|
// },
|
||||||
name: 'wrapper'
|
// name: 'wrapper'
|
||||||
});
|
// });
|
||||||
|
|
||||||
group.addShape('rect', {
|
// group.addShape('rect', {
|
||||||
attrs: {
|
// attrs: {
|
||||||
x: width / 2,
|
// x: width / 2,
|
||||||
y: height / 2,
|
// y: height / 2,
|
||||||
width: width,
|
// width: width,
|
||||||
height: height / 2,
|
// height: height / 2,
|
||||||
fill: cfg.style.fill,
|
// fill: cfg.style.fill,
|
||||||
stroke: cfg.style.stroke
|
// stroke: cfg.style.stroke
|
||||||
},
|
// },
|
||||||
name: 'top-rect'
|
// name: 'top-rect'
|
||||||
});
|
// });
|
||||||
|
|
||||||
group.addShape('rect', {
|
// group.addShape('rect', {
|
||||||
attrs: {
|
// attrs: {
|
||||||
x: width / 2,
|
// x: width / 2,
|
||||||
y: height,
|
// y: height,
|
||||||
width: width,
|
// width: width,
|
||||||
height: height / 2,
|
// height: height / 2,
|
||||||
fill: cfg.style.fill,
|
// fill: cfg.style.fill,
|
||||||
stroke: cfg.style.stroke
|
// stroke: cfg.style.stroke
|
||||||
},
|
// },
|
||||||
name: 'bottom-rect'
|
// name: 'bottom-rect'
|
||||||
});
|
// });
|
||||||
|
|
||||||
group.addShape('text', {
|
// group.addShape('text', {
|
||||||
attrs: {
|
// attrs: {
|
||||||
x: width,
|
// x: width,
|
||||||
y: height * (3 / 4),
|
// y: height * (3 / 4),
|
||||||
textAlign: 'center',
|
// textAlign: 'center',
|
||||||
textBaseline: 'middle',
|
// textBaseline: 'middle',
|
||||||
text: 'front',
|
// text: 'front',
|
||||||
fill: '#000',
|
// fill: '#000',
|
||||||
fontSize: 16
|
// fontSize: 16
|
||||||
},
|
// },
|
||||||
name: 'front'
|
// name: 'front'
|
||||||
});
|
// });
|
||||||
|
|
||||||
group.addShape('text', {
|
// group.addShape('text', {
|
||||||
attrs: {
|
// attrs: {
|
||||||
x: width,
|
// x: width,
|
||||||
y: height * (5 / 4),
|
// y: height * (5 / 4),
|
||||||
textAlign: 'center',
|
// textAlign: 'center',
|
||||||
textBaseline: 'middle',
|
// textBaseline: 'middle',
|
||||||
text: 'rear',
|
// text: 'rear',
|
||||||
fill: '#000',
|
// fill: '#000',
|
||||||
fontSize: 16
|
// fontSize: 16
|
||||||
},
|
// },
|
||||||
name: 'rear'
|
// name: 'rear'
|
||||||
});
|
// });
|
||||||
|
|
||||||
return wrapperRect;
|
// return wrapperRect;
|
||||||
},
|
// },
|
||||||
|
|
||||||
getAnchorPoints() {
|
// getAnchorPoints() {
|
||||||
return [
|
// return [
|
||||||
[1, 0.25],
|
// [1, 0.25],
|
||||||
[1, 0.75]
|
// [1, 0.75]
|
||||||
];
|
// ];
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
|
||||||
class LinkQueue extends Engine {
|
class LinkQueue extends Engine {
|
||||||
|
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
head: {
|
head: {
|
||||||
type: 'link-Queue-head',
|
type: 'rect',
|
||||||
label: '[id]',
|
label: '[label]',
|
||||||
size: [60, 80],
|
size: [60, 40],
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
fill: '#b83b5e'
|
fill: '#b83b5e'
|
||||||
@ -106,9 +107,9 @@ G6.registerNode('link-Queue-head', {
|
|||||||
},
|
},
|
||||||
link: {
|
link: {
|
||||||
front: {
|
front: {
|
||||||
type: 'line',
|
type: 'polyline',
|
||||||
sourceAnchor: 0,
|
sourceAnchor: 1,
|
||||||
targetAnchor: 0,
|
targetAnchor: 5,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
endArrow: {
|
endArrow: {
|
||||||
@ -120,7 +121,7 @@ G6.registerNode('link-Queue-head', {
|
|||||||
rear: {
|
rear: {
|
||||||
type: 'polyline',
|
type: 'polyline',
|
||||||
sourceAnchor: 1,
|
sourceAnchor: 1,
|
||||||
targetAnchor: 3,
|
targetAnchor: 5,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
endArrow: {
|
endArrow: {
|
||||||
@ -157,10 +158,16 @@ G6.registerNode('link-Queue-head', {
|
|||||||
layout: {
|
layout: {
|
||||||
xInterval: 50,
|
xInterval: 50,
|
||||||
yInterval: 50
|
yInterval: 50
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
dragNode: ['node']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourcesPreprocess(sources) {
|
||||||
|
sources.head[1].external = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对子树进行递归布局
|
* 对子树进行递归布局
|
||||||
@ -186,51 +193,56 @@ G6.registerNode('link-Queue-head', {
|
|||||||
|
|
||||||
|
|
||||||
layout(elements, layoutOptions) {
|
layout(elements, layoutOptions) {
|
||||||
let head = elements.head[0];
|
let head1 = elements.head[0],
|
||||||
|
head2 = elements.head[1],
|
||||||
|
nodes = elements.node,
|
||||||
|
headHeight = head1.get('size')[1];
|
||||||
|
|
||||||
if(head.front) {
|
let roots = nodes.filter(item => item.root).reverse();
|
||||||
let d = head.get('size')[1] / 2 - head.front.get('size')[1],
|
|
||||||
x = layoutOptions.xInterval * 2.5,
|
|
||||||
y = head.front.get('size')[1] / 2 + 1.5 * d;
|
|
||||||
|
|
||||||
head.front.set({ x, y });
|
for(let i = 0; i < roots.length; i++) {
|
||||||
|
let root = roots[i],
|
||||||
|
height = root.get('size')[1];
|
||||||
|
|
||||||
|
root.set('y', root.get('y') + i * (layoutOptions.yInterval + height));
|
||||||
|
this.layoutItem(root, null, layoutOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(head.front.next) {
|
let x = -50, y = roots.length? roots[roots.length - 1].get('y'): 0,
|
||||||
this.layoutItem(head.front.next, head.front, layoutOptions);
|
nodeHeight = roots.length? roots[roots.length - 1].get('size')[1]: 0;
|
||||||
}
|
|
||||||
|
head1.set({ x, y: y + nodeHeight * 3 });
|
||||||
|
head2.set({ x, y: head1.get('y') + headHeight });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
head: [
|
||||||
|
{
|
||||||
|
"type": "QPtr",
|
||||||
|
"id": 140737338526359,
|
||||||
|
"label": "front",
|
||||||
|
"front": "node#8358681150976310000",
|
||||||
|
"external": [
|
||||||
|
"lq"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "QPtr",
|
||||||
|
"id": 140737338526360,
|
||||||
|
"label": "rear",
|
||||||
|
"rear": "node#15844482482171916",
|
||||||
|
"external": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
node: []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const LQueue = function(container) {
|
const LQueue = function(container) {
|
||||||
return{
|
return{
|
||||||
engine: new LinkQueue(container),
|
engine: new LinkQueue(container),
|
||||||
data: [{
|
data: [data]
|
||||||
head: [{
|
|
||||||
type: "QPtr",
|
|
||||||
id: 44,
|
|
||||||
front: 'node#1',
|
|
||||||
rear: 'node#13'
|
|
||||||
}],
|
|
||||||
node: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
next: 12,
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 12,
|
|
||||||
next: 13
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 13,
|
|
||||||
next: null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class LinkList extends Engine {
|
|||||||
size: [60, 30],
|
size: [60, 30],
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
fill: '#b83b5e'
|
fill: '#eaffd0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -24,10 +24,7 @@ class LinkList extends Engine {
|
|||||||
targetAnchor: 0,
|
targetAnchor: 0,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
endArrow: {
|
endArrow: 'default',
|
||||||
path: G6.Arrow.triangle(8, 6, 0),
|
|
||||||
fill: '#333'
|
|
||||||
},
|
|
||||||
startArrow: {
|
startArrow: {
|
||||||
path: G6.Arrow.circle(2, -1),
|
path: G6.Arrow.circle(2, -1),
|
||||||
fill: '#333'
|
fill: '#333'
|
||||||
@ -41,10 +38,7 @@ class LinkList extends Engine {
|
|||||||
targetAnchor: 3,
|
targetAnchor: 3,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
endArrow: {
|
endArrow: 'default',
|
||||||
path: G6.Arrow.triangle(6, 6, -2),
|
|
||||||
fill: '#333'
|
|
||||||
},
|
|
||||||
startArrow: {
|
startArrow: {
|
||||||
path: G6.Arrow.circle(2, -1),
|
path: G6.Arrow.circle(2, -1),
|
||||||
fill: '#333'
|
fill: '#333'
|
||||||
@ -92,7 +86,7 @@ class LinkList extends Engine {
|
|||||||
|
|
||||||
|
|
||||||
layout(elements, layoutOptions) {
|
layout(elements, layoutOptions) {
|
||||||
let nodes = elements.default,
|
let nodes = elements,
|
||||||
rootNodes = [],
|
rootNodes = [],
|
||||||
node,
|
node,
|
||||||
i;
|
i;
|
||||||
@ -116,9 +110,9 @@ class LinkList extends Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const LList = function(container) {
|
const LList = function(container, options) {
|
||||||
return{
|
return{
|
||||||
engine: new LinkList(container),
|
engine: new LinkList(container, options),
|
||||||
data: [[
|
data: [[
|
||||||
{ id: 1, root: true, next: 2, external: ['gg'] },
|
{ id: 1, root: true, next: 2, external: ['gg'] },
|
||||||
{ id: 2, next: 3 },
|
{ id: 2, next: 3 },
|
||||||
@ -129,15 +123,19 @@ const LList = function(container) {
|
|||||||
{ id: 7, next: 8 },
|
{ id: 7, next: 8 },
|
||||||
{ id: 8, next: 4 },
|
{ id: 8, next: 4 },
|
||||||
{ id: 9, root: true, next: 10 },
|
{ id: 9, root: true, next: 10 },
|
||||||
{ id: 10 }
|
{ id: 10, free: true }
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ id: 1, root: true, next: 2, external: ['gg'] },
|
{ id: 1, root: true, next: 2 },
|
||||||
{ id: 2, next: 3 },
|
{ id: 2, next: 3 },
|
||||||
{ id: 3, next: 6 },
|
{ id: 3, next: 8, external: ['gg'] },
|
||||||
{ id: 6, next: 7 },
|
{ id: 8, next: 12 },
|
||||||
{ id: 7, next: 8 },
|
{ id: 12, next: 13 },
|
||||||
{ id: 8 }
|
{ id: 13 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ id: 1, root: true, next: 2 },
|
||||||
|
{ id: 2 }
|
||||||
]]
|
]]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,7 +14,28 @@
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 600px;
|
height: 400px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.down {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#freed {
|
||||||
|
width: 200px;
|
||||||
|
height: 300px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fafafa;
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leak {
|
||||||
|
width: 400px;
|
||||||
|
height: 300px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,12 +43,19 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<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-next">reLayout</button>
|
||||||
<button id="btn-set">set</button>
|
<button id="btn-set">set</button>
|
||||||
<span id="pos"></span>
|
<span id="pos"></span>
|
||||||
|
|
||||||
|
<div class="down">
|
||||||
|
<div id="freed"></div>
|
||||||
|
<div id="leak"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script src="./../dist/sv.js"></script>
|
<script src="./../dist/sv.js"></script>
|
||||||
<script>
|
<script>
|
||||||
@ -40,7 +68,9 @@ const Engine = SV.Engine,
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="./dataStruct/BinaryTree.js"></script>
|
<script src="./dataStruct/BinaryTree.js"></script>
|
||||||
|
|
||||||
<script src="./dataStruct/LinkList.js"></script>
|
<script src="./dataStruct/LinkList.js"></script>
|
||||||
|
|
||||||
<script src="./dataStruct/Array.js"></script>
|
<script src="./dataStruct/Array.js"></script>
|
||||||
<script src="./dataStruct/ChainHashTable.js"></script>
|
<script src="./dataStruct/ChainHashTable.js"></script>
|
||||||
<script src="./dataStruct/Stack.js"></script>
|
<script src="./dataStruct/Stack.js"></script>
|
||||||
@ -49,16 +79,34 @@ const Engine = SV.Engine,
|
|||||||
<script src="./dataStruct/Graph.js"></script>
|
<script src="./dataStruct/Graph.js"></script>
|
||||||
<script src="./dataStruct/DirectedGraph.js"></script>
|
<script src="./dataStruct/DirectedGraph.js"></script>
|
||||||
<script src="./dataStruct/RingArray.js"></script>
|
<script src="./dataStruct/RingArray.js"></script>
|
||||||
|
<script src="./dataStruct/GeneralizedList.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const engines = [BTree, LList, A, CHT, St, LStack, LQueue, G, DG, RA];
|
const engines = {
|
||||||
|
0: BTree,
|
||||||
|
1: LList,
|
||||||
|
2: A,
|
||||||
|
3: CHT,
|
||||||
|
4: St,
|
||||||
|
5: LStack,
|
||||||
|
6: LQueue,
|
||||||
|
7: G,
|
||||||
|
8: DG,
|
||||||
|
9: RA,
|
||||||
|
10: GL
|
||||||
|
};
|
||||||
|
|
||||||
let cur = engines[9](document.getElementById('container'));
|
let dataCounter = 0;
|
||||||
|
|
||||||
cur.engine.render(cur.data[0]);
|
let cur = engines[3](document.getElementById('container'), {
|
||||||
|
freedContainer: document.getElementById('freed'),
|
||||||
|
leakContainer: document.getElementById('leak')
|
||||||
|
});
|
||||||
|
|
||||||
|
cur.engine.render(cur.data[dataCounter]);
|
||||||
|
|
||||||
document.getElementById('btn').addEventListener('click', e => {
|
document.getElementById('btn').addEventListener('click', e => {
|
||||||
cur.engine.render(cur.data[1]);
|
cur.engine.render(cur.data[++dataCounter]);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('btn-next').addEventListener('click', e => {
|
document.getElementById('btn-next').addEventListener('click', e => {
|
||||||
@ -82,6 +130,9 @@ container.addEventListener('mousemove', e => {
|
|||||||
pos.innerHTML = `${x},${y}`;
|
pos.innerHTML = `${x},${y}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
2
dist/sv.js
vendored
2
dist/sv.js
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
import { Engine } from "../engine";
|
import { Engine } from "../engine";
|
||||||
import { Util } from "./../Common/util";
|
import { Util } from "../Common/util";
|
||||||
|
|
||||||
export class Behavior {
|
export class Behavior {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
@ -10,10 +10,11 @@ export class Behavior {
|
|||||||
this.graphInstance = graphInstance;
|
this.graphInstance = graphInstance;
|
||||||
|
|
||||||
const interactionOptions = this.engine.interactionOptions,
|
const interactionOptions = this.engine.interactionOptions,
|
||||||
selectNode: boolean | string[] = interactionOptions.selectNode;
|
selectNode: boolean | string[] = interactionOptions.selectNode,
|
||||||
|
dragNode: boolean | string[] = interactionOptions.dragNode;
|
||||||
|
|
||||||
if(interactionOptions.dragNode) {
|
if(interactionOptions.dragNode) {
|
||||||
this.initDragNode();
|
this.initDragNode(dragNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(interactionOptions.selectNode) {
|
if(interactionOptions.selectNode) {
|
||||||
@ -24,7 +25,7 @@ export class Behavior {
|
|||||||
/**
|
/**
|
||||||
* 初始化节点拖拽事件
|
* 初始化节点拖拽事件
|
||||||
*/
|
*/
|
||||||
private initDragNode() {
|
private initDragNode(dragNode: boolean | string[]) {
|
||||||
let pointer = null,
|
let pointer = null,
|
||||||
pointerX = null,
|
pointerX = null,
|
||||||
pointerY = null,
|
pointerY = null,
|
||||||
@ -32,7 +33,17 @@ export class Behavior {
|
|||||||
dragStartY = null;
|
dragStartY = null;
|
||||||
|
|
||||||
this.graphInstance.on('node:dragstart', ev => {
|
this.graphInstance.on('node:dragstart', ev => {
|
||||||
pointer = this.graphInstance.findById(ev.item.getModel().externalPointerId);
|
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) {
|
if(pointer) {
|
||||||
pointerX = pointer.getModel().x,
|
pointerX = pointer.getModel().x,
|
||||||
@ -90,6 +101,10 @@ export class Behavior {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(model.isDynamic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(curSelectItem && curSelectItem !== item) {
|
if(curSelectItem && curSelectItem !== item) {
|
||||||
curSelectItem.update({
|
curSelectItem.update({
|
||||||
style: curSelectItemStyle
|
style: curSelectItemStyle
|
||||||
@ -128,8 +143,12 @@ export class Behavior {
|
|||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
public on(eventName: string, callback: Function) {
|
public on(eventName: string, callback: Function) {
|
||||||
if(this.graphInstance) {
|
if(this.graphInstance === null) {
|
||||||
this.graphInstance.on(eventName, callback)
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.graphInstance.on(eventName, evt => {
|
||||||
|
callback(evt.item);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Vector } from "../Common/vector";
|
import { Vector } from "./vector";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Util } from "../Common/util";
|
import { Util } from "./util";
|
||||||
import { BoundingRect, Bound } from "../View/boundingRect";
|
import { BoundingRect, Bound } from "./boundingRect";
|
||||||
import { Vector } from "../Common/vector";
|
import { Vector } from "./vector";
|
||||||
import { Element } from "../Model/modelData";
|
import { Element } from "../Model/modelData";
|
||||||
|
|
||||||
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { ConstructedData } from "../Model/modelConstructor";
|
import { ConstructList } from "../Model/modelConstructor";
|
||||||
|
import { G6EdgeModel, G6NodeModel, Link, Model } from "../Model/modelData";
|
||||||
import { SV } from "../StructV";
|
import { SV } from "../StructV";
|
||||||
import { G6Data } from "../View/renderer";
|
import { G6Data } from "../View/renderer";
|
||||||
|
|
||||||
@ -8,7 +9,6 @@ import { G6Data } from "../View/renderer";
|
|||||||
*/
|
*/
|
||||||
export const Util = {
|
export const Util = {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成唯一id
|
* 生成唯一id
|
||||||
*/
|
*/
|
||||||
@ -78,28 +78,39 @@ export const Util = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param constructedDataType
|
* @param constructListType
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
converterList(constructedDataType: ConstructedData[keyof ConstructedData]) {
|
converterList(modelContainer: { [key: string]: ConstructList[keyof ConstructList]}) {
|
||||||
return [].concat(...Object.keys(constructedDataType).map(item => constructedDataType[item]));
|
return [].concat(...Object.keys(modelContainer).map(item => modelContainer[item]));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* G6 data 转换器
|
* G6 data 转换器
|
||||||
* @param constructedData
|
* @param constructList
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
convertG6Data(constructedData: ConstructedData): G6Data {
|
convertG6Data(constructList: ConstructList): G6Data {
|
||||||
let nodes = [...Util.converterList(constructedData.element), ...Util.converterList(constructedData.pointer)],
|
let nodes = [...constructList.element, ...constructList.pointer],
|
||||||
edges = Util.converterList(constructedData.link);
|
edges = constructList.link;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: nodes.map(item => item.cloneProps()),
|
nodes: nodes.map(item => item.cloneProps()) as G6NodeModel[],
|
||||||
edges: edges.map(item => item.cloneProps())
|
edges: edges.map(item => item.cloneProps()) as G6EdgeModel[]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 modelList 转换到 G6Data
|
||||||
|
* @param modelList
|
||||||
|
*/
|
||||||
|
convertModelList2G6Data(modelList: Model[]): G6Data {
|
||||||
|
return {
|
||||||
|
nodes: <G6NodeModel[]>(modelList.filter(item => !(item instanceof Link)).map(item => item.cloneProps())),
|
||||||
|
edges: <G6EdgeModel[]>(modelList.filter(item => item instanceof Link).map(item => item.cloneProps()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算旋转矩阵
|
* 计算旋转矩阵
|
||||||
* @param matrix
|
* @param matrix
|
||||||
|
|||||||
@ -1,72 +1,74 @@
|
|||||||
import { Util } from "../Common/util";
|
import { Util } from "../Common/util";
|
||||||
import { Engine } from "../engine";
|
import { Engine } from "../engine";
|
||||||
import { LinkOption, PointerOption } from "../options";
|
import { LinkOption, PointerOption } from "../options";
|
||||||
import { sourceLinkData, SourceElement, Sources, LinkTarget } from "../sources";
|
import { sourceLinkData, SourceElement, LinkTarget } from "../sources";
|
||||||
import { Element, Link, Pointer } from "./modelData";
|
import { Element, Link, Pointer } from "./modelData";
|
||||||
|
|
||||||
|
|
||||||
export interface ConstructedData {
|
export interface ConstructList {
|
||||||
element: { [key: string]: Element[] };
|
element: Element[];
|
||||||
link: { [key: string]: Link[] };
|
link: Link[];
|
||||||
pointer: { [key: string]: Pointer[] };
|
pointer: Pointer[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export class ModelConstructor {
|
export class ModelConstructor {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private constructedData: ConstructedData;
|
private constructList: ConstructList;
|
||||||
|
|
||||||
constructor(engine: Engine) {
|
constructor(engine: Engine) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.constructedData = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建element,link和pointer
|
* 构建element,link和pointer
|
||||||
* @param sourceData
|
* @param sourceList
|
||||||
*/
|
*/
|
||||||
public construct(sourceData: Sources): ConstructedData {
|
public construct(sourceList: SourceElement[]): ConstructList {
|
||||||
let elementContainer = this.constructElements(sourceData),
|
let elementContainer = this.constructElements(sourceList),
|
||||||
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);
|
||||||
|
|
||||||
this.constructedData = {
|
this.constructList = {
|
||||||
element: elementContainer,
|
element: Util.converterList(elementContainer),
|
||||||
link: linkContainer,
|
link: Util.converterList(linkContainer),
|
||||||
pointer: pointerContainer
|
pointer: Util.converterList(pointerContainer)
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.constructedData;
|
return this.constructList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getConstructList(): ConstructList {
|
||||||
|
return this.constructList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从源数据构建 element 集
|
* 从源数据构建 element 集
|
||||||
* @param sourceData
|
* @param sourceList
|
||||||
*/
|
*/
|
||||||
private constructElements(sourceData: Sources): { [key: string]: Element[] } {
|
private constructElements(sourceList: SourceElement[]): { [key: string]: Element[] } {
|
||||||
let defaultElementName: string = 'default',
|
let defaultElementType: string = 'default',
|
||||||
elementContainer: { [key: string]: Element[] } = { };
|
elementContainer: { [key: string]: Element[] } = { };
|
||||||
|
|
||||||
if(Array.isArray(sourceData)) {
|
sourceList.forEach(item => {
|
||||||
elementContainer[defaultElementName] = [];
|
if(item === null) {
|
||||||
sourceData.forEach(item => {
|
return;
|
||||||
if(item) {
|
}
|
||||||
let ele = this.createElement(item, defaultElementName);
|
|
||||||
elementContainer[defaultElementName].push(ele);
|
if(item.type === undefined || item.type === null) {
|
||||||
}
|
item.type = defaultElementType;
|
||||||
});
|
}
|
||||||
}
|
|
||||||
else {
|
if(elementContainer[item.type] === undefined) {
|
||||||
Object.keys(sourceData).forEach(prop => {
|
elementContainer[item.type] = [];
|
||||||
elementContainer[prop] = [];
|
}
|
||||||
sourceData[prop].forEach(item => {
|
|
||||||
if(item) {
|
elementContainer[item.type].push(this.createElement(item, item.type));
|
||||||
let element = this.createElement(item, prop);
|
});
|
||||||
elementContainer[prop].push(element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return elementContainer;
|
return elementContainer;
|
||||||
}
|
}
|
||||||
@ -179,7 +181,7 @@ export class ModelConstructor {
|
|||||||
label = elementOption.label? this.parserElementContent(sourceElement, elementOption.label): '',
|
label = elementOption.label? this.parserElementContent(sourceElement, elementOption.label): '',
|
||||||
id = elementName + '.' + sourceElement.id.toString();
|
id = elementName + '.' + sourceElement.id.toString();
|
||||||
|
|
||||||
if(label === null || label === undefined) {
|
if(label === null || label === 'undefined') {
|
||||||
label = '';
|
label = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +258,7 @@ export class ModelConstructor {
|
|||||||
element: Element,
|
element: Element,
|
||||||
linkTarget: LinkTarget
|
linkTarget: LinkTarget
|
||||||
): Element {
|
): Element {
|
||||||
let elementName = element.modelName,
|
let elementName = element.getType(),
|
||||||
elementList: Element[],
|
elementList: Element[],
|
||||||
targetId = linkTarget,
|
targetId = linkTarget,
|
||||||
targetElement = null;
|
targetElement = null;
|
||||||
@ -285,4 +287,11 @@ export class ModelConstructor {
|
|||||||
targetElement = elementList.find(item => item.sourceId === targetId);
|
targetElement = elementList.find(item => item.sourceId === targetId);
|
||||||
return targetElement || null;
|
return targetElement || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
this.constructList = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Util } from "../Common/util";
|
import { Util } from "../Common/util";
|
||||||
import { ElementLabelOption, ElementOption, LinkLabelOption, LinkOption, PointerOption, Style } from "../options";
|
import { ElementLabelOption, ElementOption, LinkLabelOption, LinkOption, PointerOption, Style } from "../options";
|
||||||
import { SourceElement } from "../sources";
|
import { SourceElement } from "../sources";
|
||||||
import { BoundingRect } from "../View/boundingRect";
|
import { BoundingRect } from "../Common/boundingRect";
|
||||||
import { SV } from './../StructV';
|
import { SV } from './../StructV';
|
||||||
|
|
||||||
|
|
||||||
@ -17,8 +17,8 @@ export interface G6NodeModel {
|
|||||||
style: Style;
|
style: Style;
|
||||||
labelCfg: ElementLabelOption;
|
labelCfg: ElementLabelOption;
|
||||||
externalPointerId: string;
|
externalPointerId: string;
|
||||||
modelType: string;
|
SVModelType: string;
|
||||||
modelName: string;
|
SVModelName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -27,29 +27,29 @@ export interface G6EdgeModel {
|
|||||||
source: string | number;
|
source: string | number;
|
||||||
target: string | number;
|
target: string | number;
|
||||||
type: string;
|
type: string;
|
||||||
controlPoints: { x: number, y: number }[];
|
|
||||||
curveOffset: number;
|
curveOffset: number;
|
||||||
sourceAnchor: number | ((index: number) => number);
|
sourceAnchor: number | ((index: number) => number);
|
||||||
targetAnchor: number | ((index: number) => number);
|
targetAnchor: number | ((index: number) => number);
|
||||||
label: string;
|
label: string;
|
||||||
style: Style;
|
style: Style;
|
||||||
labelCfg: LinkLabelOption;
|
labelCfg: LinkLabelOption;
|
||||||
|
SVModelType: string;
|
||||||
|
SVModelName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export class Model {
|
export class Model {
|
||||||
id: string;
|
id: string;
|
||||||
modelName: string;
|
type: string;
|
||||||
modelType: string;
|
|
||||||
|
|
||||||
props: G6NodeModel | G6EdgeModel;
|
props: G6NodeModel | G6EdgeModel;
|
||||||
shadowG6Item;
|
shadowG6Item;
|
||||||
renderG6Item;
|
renderG6Item;
|
||||||
G6Item;
|
G6Item;
|
||||||
|
|
||||||
constructor(id: string, name: string) {
|
constructor(id: string, type: string) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.modelName = name;
|
this.type = type;
|
||||||
this.shadowG6Item = null;
|
this.shadowG6Item = null;
|
||||||
this.renderG6Item = null;
|
this.renderG6Item = null;
|
||||||
this.G6Item = null;
|
this.G6Item = null;
|
||||||
@ -61,7 +61,7 @@ export class Model {
|
|||||||
* 定义 G6 model 的属性
|
* 定义 G6 model 的属性
|
||||||
* @param option
|
* @param option
|
||||||
*/
|
*/
|
||||||
protected defineProps(option: ElementOption | LinkOption | PointerOption): G6NodeModel | G6EdgeModel {
|
protected defineProps(option: ElementOption | LinkOption | PointerOption) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,9 +77,8 @@ export class Model {
|
|||||||
* 克隆 G6 model 的属性
|
* 克隆 G6 model 的属性
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
cloneProps(): G6NodeModel | G6EdgeModel {
|
cloneProps() {
|
||||||
return Util.objectClone(this.props);
|
return Util.objectClone(this.props);
|
||||||
// return this.props;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -150,15 +149,17 @@ export class Model {
|
|||||||
*/
|
*/
|
||||||
getMatrix(): number[] {
|
getMatrix(): number[] {
|
||||||
if(this.G6Item === null) return null;
|
if(this.G6Item === null) return null;
|
||||||
// return this.G6Item.getContainer().getMatrix();
|
|
||||||
const Mat3 = SV.G6.Util.mat3;
|
const Mat3 = SV.G6.Util.mat3;
|
||||||
return Mat3.create();
|
return Mat3.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getType(): string {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Element extends Model {
|
export class Element extends Model {
|
||||||
modelType = 'element';
|
|
||||||
sourceElement: SourceElement;
|
sourceElement: SourceElement;
|
||||||
sourceId: string;
|
sourceId: string;
|
||||||
free: boolean;
|
free: boolean;
|
||||||
@ -166,6 +167,12 @@ export class Element extends Model {
|
|||||||
constructor(id: string, type: string, sourceElement: SourceElement) {
|
constructor(id: string, type: string, sourceElement: SourceElement) {
|
||||||
super(id, type);
|
super(id, type);
|
||||||
|
|
||||||
|
if(type === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.free = false;
|
||||||
|
|
||||||
Object.keys(sourceElement).map(prop => {
|
Object.keys(sourceElement).map(prop => {
|
||||||
if(prop !== 'id') {
|
if(prop !== 'id') {
|
||||||
this[prop] = sourceElement[prop];
|
this[prop] = sourceElement[prop];
|
||||||
@ -173,11 +180,12 @@ export class Element extends Model {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.sourceId = this.id.split('.')[1];
|
this.sourceId = this.id.split('.')[1];
|
||||||
this.free = false;
|
this.sourceElement = sourceElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected defineProps(option: ElementOption) {
|
protected defineProps(option: ElementOption): G6NodeModel {
|
||||||
return {
|
return {
|
||||||
|
...this.sourceElement,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
@ -185,12 +193,12 @@ export class Element extends Model {
|
|||||||
type: option.type,
|
type: option.type,
|
||||||
size: option.size,
|
size: option.size,
|
||||||
anchorPoints: option.anchorPoint,
|
anchorPoints: option.anchorPoint,
|
||||||
label: null,
|
label: option.label,
|
||||||
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.modelType,
|
SVModelType: 'element',
|
||||||
modelName: this.modelName
|
SVModelName: this.type
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -198,7 +206,6 @@ export class Element extends Model {
|
|||||||
|
|
||||||
|
|
||||||
export class Link extends Model {
|
export class Link extends Model {
|
||||||
modelType = 'link';
|
|
||||||
element: Element;
|
element: Element;
|
||||||
target: Element;
|
target: Element;
|
||||||
index: number;
|
index: number;
|
||||||
@ -211,7 +218,7 @@ export class Link extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected defineProps(option: LinkOption) {
|
protected defineProps(option: LinkOption): G6EdgeModel {
|
||||||
let sourceAnchor = option.sourceAnchor,
|
let sourceAnchor = option.sourceAnchor,
|
||||||
targetAnchor = option.targetAnchor;
|
targetAnchor = option.targetAnchor;
|
||||||
|
|
||||||
@ -233,11 +240,9 @@ export class Link extends Model {
|
|||||||
label: option.label,
|
label: option.label,
|
||||||
style: Util.objectClone<Style>(option.style),
|
style: Util.objectClone<Style>(option.style),
|
||||||
labelCfg: Util.objectClone<LinkLabelOption>(option.labelOptions),
|
labelCfg: Util.objectClone<LinkLabelOption>(option.labelOptions),
|
||||||
controlPoints: option.controlPoints,
|
|
||||||
curveOffset: option.curveOffset,
|
curveOffset: option.curveOffset,
|
||||||
modelType: this.modelType,
|
SVModelType: 'link',
|
||||||
modelName: this.modelName,
|
SVModelName: this.type
|
||||||
zIndex: 20
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -245,7 +250,6 @@ export class Link extends Model {
|
|||||||
|
|
||||||
|
|
||||||
export class Pointer extends Model {
|
export class Pointer extends Model {
|
||||||
modelType = 'pointer';
|
|
||||||
target: Element;
|
target: Element;
|
||||||
label: string | string[];
|
label: string | string[];
|
||||||
|
|
||||||
@ -254,11 +258,10 @@ export class Pointer extends Model {
|
|||||||
this.target = target;
|
this.target = target;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
|
|
||||||
this.target.set('externalPointerId',
|
this.target.set('externalPointerId', id);
|
||||||
id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected defineProps(option: ElementOption) {
|
protected defineProps(option: ElementOption): G6NodeModel {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
x: 0,
|
x: 0,
|
||||||
@ -271,8 +274,8 @@ export class Pointer extends Model {
|
|||||||
style: Util.objectClone<Style>(option.style),
|
style: Util.objectClone<Style>(option.style),
|
||||||
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
||||||
externalPointerId: null,
|
externalPointerId: null,
|
||||||
modelType: this.modelType,
|
SVModelType: 'pointer',
|
||||||
modelName: this.modelName
|
SVModelName: this.type
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -14,7 +14,7 @@ export default G6.registerNode('binary-tree-node', {
|
|||||||
y: height / 2,
|
y: height / 2,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
stroke: cfg.style.stroke,
|
stroke: cfg.style.stroke || '#333',
|
||||||
cursor: cfg.style.cursor,
|
cursor: cfg.style.cursor,
|
||||||
fill: '#eee'
|
fill: '#eee'
|
||||||
},
|
},
|
||||||
@ -28,7 +28,7 @@ export default G6.registerNode('binary-tree-node', {
|
|||||||
width: width / 2,
|
width: width / 2,
|
||||||
height: height,
|
height: height,
|
||||||
fill: cfg.style.fill,
|
fill: cfg.style.fill,
|
||||||
stroke: cfg.style.stroke,
|
stroke: cfg.style.stroke || '#333',
|
||||||
cursor: cfg.style.cursor
|
cursor: cfg.style.cursor
|
||||||
},
|
},
|
||||||
name: 'mid',
|
name: 'mid',
|
||||||
|
|||||||
58
src/RegisteredShape/indexedNode.ts
Normal file
58
src/RegisteredShape/indexedNode.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import * as G6 from "./../Lib/g6.js";
|
||||||
|
|
||||||
|
|
||||||
|
export default G6.registerNode('indexed-node', {
|
||||||
|
draw(cfg, group) {
|
||||||
|
cfg.size = cfg.size || [30, 10];
|
||||||
|
|
||||||
|
const width = cfg.size[0],
|
||||||
|
height = cfg.size[1],
|
||||||
|
disable = cfg.disable === undefined? false: cfg.disable;
|
||||||
|
|
||||||
|
const rect = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: width / 2,
|
||||||
|
y: height / 2,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
stroke: cfg.style.stroke || '#333',
|
||||||
|
fill: disable? '#ccc': cfg.style.fill
|
||||||
|
},
|
||||||
|
name: 'wrapper'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cfg.label) {
|
||||||
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width,
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.label,
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: style.fontSize || 16
|
||||||
|
},
|
||||||
|
name: 'text'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cfg.index !== undefined) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width,
|
||||||
|
y: height + 30,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.index.toString(),
|
||||||
|
fill: '#bbb',
|
||||||
|
fontSize: 14,
|
||||||
|
fontStyle: 'italic'
|
||||||
|
},
|
||||||
|
name: 'index-text'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -14,7 +14,7 @@ export default G6.registerNode('link-list-node', {
|
|||||||
y: height / 2,
|
y: height / 2,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
stroke: cfg.style.stroke,
|
stroke: cfg.style.stroke || '#333',
|
||||||
fill: '#eee'
|
fill: '#eee'
|
||||||
},
|
},
|
||||||
name: 'wrapper'
|
name: 'wrapper'
|
||||||
@ -27,7 +27,7 @@ export default G6.registerNode('link-list-node', {
|
|||||||
width: width * (2 / 3),
|
width: width * (2 / 3),
|
||||||
height: height,
|
height: height,
|
||||||
fill: cfg.style.fill,
|
fill: cfg.style.fill,
|
||||||
stroke: cfg.style.stroke
|
stroke: cfg.style.stroke || '#333'
|
||||||
},
|
},
|
||||||
name: 'main-rect',
|
name: 'main-rect',
|
||||||
draggable: true
|
draggable: true
|
||||||
@ -58,7 +58,9 @@ export default G6.registerNode('link-list-node', {
|
|||||||
[0, 0.5],
|
[0, 0.5],
|
||||||
[5 / 6, 0.5],
|
[5 / 6, 0.5],
|
||||||
[5 / 6, 0],
|
[5 / 6, 0],
|
||||||
[5 / 6, 1]
|
[5 / 6, 1],
|
||||||
|
[0.5, 0],
|
||||||
|
[0.5, 1]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -57,7 +57,8 @@ export default G6.registerNode('two-cell-node', {
|
|||||||
getAnchorPoints() {
|
getAnchorPoints() {
|
||||||
return [
|
return [
|
||||||
[0, 0.5],
|
[0, 0.5],
|
||||||
[3 / 4, 0.5]
|
[3 / 4, 0.5],
|
||||||
|
[0.5, 0]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1,12 +1,13 @@
|
|||||||
import { Engine } from "./engine";
|
import { Engine } from "./engine";
|
||||||
import { Bound } from "./View/boundingRect";
|
import { Bound } from "./Common/boundingRect";
|
||||||
import { Group } from "./View/group";
|
import { Group } from "./Common/group";
|
||||||
import externalPointer from "./RegisteredShape/externalPointer";
|
import externalPointer from "./RegisteredShape/externalPointer";
|
||||||
import * as G6 from "./Lib/g6.js";
|
import * as G6 from "./Lib/g6.js";
|
||||||
import linkListNode from "./RegisteredShape/linkListNode";
|
import linkListNode from "./RegisteredShape/linkListNode";
|
||||||
import binaryTreeNode from "./RegisteredShape/binaryTreeNode";
|
import binaryTreeNode from "./RegisteredShape/binaryTreeNode";
|
||||||
import twoCellNode from "./RegisteredShape/twoCellNode";
|
import twoCellNode from "./RegisteredShape/twoCellNode";
|
||||||
import { Vector } from "./Common/vector";
|
import { Vector } from "./Common/vector";
|
||||||
|
import indexedNode from "./RegisteredShape/indexedNode";
|
||||||
|
|
||||||
|
|
||||||
export const SV = {
|
export const SV = {
|
||||||
@ -14,9 +15,15 @@ export const SV = {
|
|||||||
Group: Group,
|
Group: Group,
|
||||||
Bound: Bound,
|
Bound: Bound,
|
||||||
Vector: Vector,
|
Vector: Vector,
|
||||||
|
Mat3: G6.Util.mat3,
|
||||||
G6,
|
G6,
|
||||||
registeredShape: [
|
registeredShape: [
|
||||||
externalPointer, linkListNode, binaryTreeNode, twoCellNode
|
externalPointer,
|
||||||
]
|
linkListNode,
|
||||||
|
binaryTreeNode,
|
||||||
|
twoCellNode,
|
||||||
|
indexedNode
|
||||||
|
],
|
||||||
|
registerShape: G6.registerNode
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { Model } from "../Model/modelData";
|
||||||
import { SV } from "../StructV";
|
import { SV } from "../StructV";
|
||||||
|
|
||||||
|
|
||||||
@ -5,37 +6,32 @@ import { SV } from "../StructV";
|
|||||||
/**
|
/**
|
||||||
* 动画表
|
* 动画表
|
||||||
*/
|
*/
|
||||||
export class Animations {
|
export const Animations = {
|
||||||
private duration: number;
|
|
||||||
private timingFunction: string;
|
|
||||||
private mat3 = SV.G6.Util.mat3;
|
|
||||||
|
|
||||||
constructor(duration: number, timingFunction: string) {
|
|
||||||
this.duration = duration;
|
|
||||||
this.timingFunction = timingFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加节点 / 边时的动画效果
|
* 添加节点 / 边时的动画效果
|
||||||
* @param G6Item
|
* @param model
|
||||||
|
* @param duration
|
||||||
|
* @param timingFunction
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
append(G6Item, callback: Function = null) {
|
animate_append(model: Model, duration: number, timingFunction: string, callback: Function = null) {
|
||||||
const type = G6Item.getType(),
|
const G6Item = model.G6Item,
|
||||||
|
type = G6Item.getType(),
|
||||||
group = G6Item.getContainer(),
|
group = G6Item.getContainer(),
|
||||||
|
Mat3 = SV.Mat3,
|
||||||
animateCfg = {
|
animateCfg = {
|
||||||
duration: this.duration,
|
duration: duration,
|
||||||
easing: this.timingFunction,
|
easing: timingFunction,
|
||||||
callback
|
callback
|
||||||
};
|
};
|
||||||
|
|
||||||
if(type === 'node') {
|
if(type === 'node') {
|
||||||
let mat3 = this.mat3,
|
let matrix = group.getMatrix(),
|
||||||
matrix = group.getMatrix(),
|
targetMatrix = Mat3.clone(matrix);
|
||||||
targetMatrix = mat3.clone(matrix);
|
|
||||||
|
|
||||||
mat3.scale(matrix, matrix, [0, 0]);
|
Mat3.scale(matrix, matrix, [0, 0]);
|
||||||
mat3.scale(targetMatrix, targetMatrix, [1, 1]);
|
Mat3.scale(targetMatrix, targetMatrix, [1, 1]);
|
||||||
|
|
||||||
group.attr({ opacity: 0, matrix });
|
group.attr({ opacity: 0, matrix });
|
||||||
group.animate({ opacity: 1, matrix: targetMatrix }, animateCfg);
|
group.animate({ opacity: 1, matrix: targetMatrix }, animateCfg);
|
||||||
@ -48,27 +44,30 @@ export class Animations {
|
|||||||
line.attr({ lineDash: [0, length], opacity: 0 });
|
line.attr({ lineDash: [0, length], opacity: 0 });
|
||||||
line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg);
|
line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除节点 / 边时的动画效果
|
* 移除节点 / 边时的动画效果
|
||||||
* @param G6Item
|
* @param model
|
||||||
|
* @param duration
|
||||||
|
* @param timingFunction
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
remove(G6Item, callback: Function = null) {
|
animate_remove(model: Model, duration: number, timingFunction: string, callback: Function = null) {
|
||||||
const type = G6Item.getType(),
|
const G6Item = model.G6Item,
|
||||||
|
type = G6Item.getType(),
|
||||||
group = G6Item.getContainer(),
|
group = G6Item.getContainer(),
|
||||||
|
Mat3 = SV.Mat3,
|
||||||
animateCfg = {
|
animateCfg = {
|
||||||
duration: this.duration,
|
duration: duration,
|
||||||
easing: this.timingFunction,
|
easing: timingFunction,
|
||||||
callback
|
callback
|
||||||
};
|
};
|
||||||
|
|
||||||
if(type === 'node') {
|
if(type === 'node') {
|
||||||
let mat3 = this.mat3,
|
let matrix = Mat3.clone(group.getMatrix());
|
||||||
matrix = mat3.clone(group.getMatrix());
|
|
||||||
|
|
||||||
mat3.scale(matrix, matrix, [0, 0]);
|
Mat3.scale(matrix, matrix, [0, 0]);
|
||||||
group.animate({ opacity: 0, matrix }, animateCfg);
|
group.animate({ opacity: 0, matrix }, animateCfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
188
src/View/container/container.ts
Normal file
188
src/View/container/container.ts
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
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 { Animations } from "../animation";
|
||||||
|
import { g6Behavior, Renderer } from "../renderer";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class Container {
|
||||||
|
protected engine: Engine;
|
||||||
|
protected DOMContainer: HTMLElement; // 可视化视图容器
|
||||||
|
protected renderer: Renderer; // 渲染器
|
||||||
|
protected prevModelList: Model[]; // 上一次渲染的模型列表
|
||||||
|
|
||||||
|
protected animationsOptions: AnimationOptions;
|
||||||
|
protected interactionOptions: InteractionOptions;
|
||||||
|
|
||||||
|
protected afterAppendModelsCallbacks: ((models: Model[]) => void)[] = [];
|
||||||
|
protected afterRemoveModelsCallbacks: ((models: Model[]) => void)[] = [];
|
||||||
|
|
||||||
|
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any } = { }) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.DOMContainer = DOMContainer;
|
||||||
|
this.animationsOptions = engine.animationOptions;
|
||||||
|
this.interactionOptions = engine.interactionOptions;
|
||||||
|
this.renderer = new Renderer(engine, DOMContainer, {
|
||||||
|
...g6Options,
|
||||||
|
modes: {
|
||||||
|
default: this.initBehaviors()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.prevModelList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化交互行为
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected initBehaviors(): g6Behavior[] {
|
||||||
|
return ['drag-canvas', 'zoom-canvas'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对比上一次和该次 modelList 找出新添加的节点和边
|
||||||
|
* @param prevList
|
||||||
|
* @param list
|
||||||
|
*/
|
||||||
|
protected getAppendModels(prevList: Model[], list: Model[]): Model[] {
|
||||||
|
return list.filter(item => !prevList.find(n => n.id === item.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对比上一次和该次 modelList 找出被删除的节点和边
|
||||||
|
* @param prevList
|
||||||
|
* @param list
|
||||||
|
*/
|
||||||
|
protected getRemoveModels(prevList: Model[], list: Model[]): Model[] {
|
||||||
|
return prevList.filter(item => !list.find(n => n.id === item.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 找出重新指向的外部指针
|
||||||
|
* @param list
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected findReTargetPointer(list: Model[]): Pointer[] {
|
||||||
|
let prevPointers = this.prevModelList.filter(item => item instanceof Pointer),
|
||||||
|
pointers = list.filter(item => item instanceof Pointer);
|
||||||
|
|
||||||
|
return <Pointer[]>pointers.filter(item => prevPointers.find(prevItem => {
|
||||||
|
return prevItem.id === item.id && (<Pointer>prevItem).target.id !== (<Pointer>item).target.id
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理新增的 G6Item(主要是动画)
|
||||||
|
* @param appendData
|
||||||
|
*/
|
||||||
|
protected handleAppendModels(appendModels: Model[]) {
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
appendModels.forEach(item => {
|
||||||
|
Animations.animate_append(item, this.animationsOptions.duration, this.animationsOptions.timingFunction, () => {
|
||||||
|
counter++;
|
||||||
|
|
||||||
|
if(counter === appendModels.length) {
|
||||||
|
this.afterAppendModelsCallbacks.map(item => item(appendModels));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理被移除(也就是泄露)的 G6Item(主要是动画)
|
||||||
|
* @param removeData
|
||||||
|
*/
|
||||||
|
protected handleRemoveModels(removeModels: Model[]) {
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
removeModels.forEach(item => {
|
||||||
|
Animations.animate_remove(item, this.animationsOptions.duration, this.animationsOptions.timingFunction, () => {
|
||||||
|
this.renderer.removeModel(item);
|
||||||
|
item.renderG6Item = item.G6Item = null;
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
|
||||||
|
if(counter === removeModels.length) {
|
||||||
|
this.afterRemoveModelsCallbacks.map(item => item(removeModels));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理发生变化的 models
|
||||||
|
* @param models
|
||||||
|
*/
|
||||||
|
protected handleChangeModels(models: Model[]) { }
|
||||||
|
|
||||||
|
// ------------------------------------------ hook ---------------------------------------------
|
||||||
|
|
||||||
|
afterAppendModels(callback: (models: Model[]) => void) {
|
||||||
|
this.afterAppendModelsCallbacks.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
afterRemoveModels(callback: (models: Model[]) => void) {
|
||||||
|
this.afterRemoveModelsCallbacks.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染函数
|
||||||
|
* @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),
|
||||||
|
removeModels: Model[] = this.getRemoveModels(this.prevModelList, modelList),
|
||||||
|
changeModels: Model[] = [...appendModels, ...this.findReTargetPointer(modelList)];
|
||||||
|
|
||||||
|
// 渲染视图
|
||||||
|
this.renderer.render(modelList, removeModels);
|
||||||
|
|
||||||
|
// 处理副作用
|
||||||
|
this.handleAppendModels(appendModels);
|
||||||
|
this.handleRemoveModels(removeModels);
|
||||||
|
this.handleChangeModels(changeModels);
|
||||||
|
|
||||||
|
if(this.renderer.getIsFirstRender()) {
|
||||||
|
this.renderer.setIsFirstRender(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prevModelList = modelList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 g6 实例
|
||||||
|
*/
|
||||||
|
public getG6Instance() {
|
||||||
|
return this.renderer.getG6Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.renderer.destroy();
|
||||||
|
this.DOMContainer = null;
|
||||||
|
this.prevModelList = [];
|
||||||
|
this.animationsOptions = this.interactionOptions = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
6
src/View/container/freed.ts
Normal file
6
src/View/container/freed.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Container } from "./container";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放区可视化视图
|
||||||
|
*/
|
||||||
|
export class FreedContainer extends Container { };
|
||||||
6
src/View/container/leak.ts
Normal file
6
src/View/container/leak.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Container } from "./container";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 泄漏区可视化视图
|
||||||
|
*/
|
||||||
|
export class LeakContainer extends Container { };
|
||||||
80
src/View/container/main.ts
Normal file
80
src/View/container/main.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { Link, Model } from "../../Model/modelData";
|
||||||
|
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();
|
||||||
|
|
||||||
|
if(node.item === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(model.modelType === 'pointer') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof dragNode === 'boolean') {
|
||||||
|
return dragNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Array.isArray(dragNode) && dragNode.indexOf(model.modelName) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modeMap = {
|
||||||
|
drag: 'drag-canvas',
|
||||||
|
zoom: 'zoom-canvas',
|
||||||
|
dragNode: {
|
||||||
|
type: 'drag-node',
|
||||||
|
shouldBegin: node => dragNodeFilter(node)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultModes = [];
|
||||||
|
|
||||||
|
Object.keys(interactionOptions).forEach(item => {
|
||||||
|
if(interactionOptions[item] && modeMap[item] !== undefined) {
|
||||||
|
defaultModes.push(modeMap[item]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return defaultModes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleChangeModels(models: Model[]) {
|
||||||
|
const changeHighlightColor: string = this.interactionOptions.changeHighlight;
|
||||||
|
|
||||||
|
// 第一次渲染的时候不高亮变化的元素
|
||||||
|
if(this.renderer.getIsFirstRender()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!changeHighlightColor || typeof changeHighlightColor !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
models.forEach(item => {
|
||||||
|
if(item instanceof Link) {
|
||||||
|
item.set('style', {
|
||||||
|
stroke: changeHighlightColor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item.set('style', {
|
||||||
|
fill: changeHighlightColor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,10 +1,9 @@
|
|||||||
import { Engine } from "../engine";
|
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||||
import { ConstructedData } from "../Model/modelConstructor";
|
import { Engine } from '../engine';
|
||||||
import { Element, Model, Pointer } from "../Model/modelData";
|
import { ConstructList } from '../Model/modelConstructor';
|
||||||
import { LayoutOptions, PointerOption } from "../options";
|
import { Element, Model, Pointer } from '../Model/modelData';
|
||||||
import { Bound, BoundingRect } from "./boundingRect";
|
import { LayoutOptions, PointerOption } from '../options';
|
||||||
|
import { Container } from './container/container';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class Layouter {
|
export class Layouter {
|
||||||
@ -14,15 +13,54 @@ export class Layouter {
|
|||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化布局参数
|
||||||
|
* @param elements
|
||||||
|
* @param pointers
|
||||||
|
*/
|
||||||
|
private initLayoutValue(elements: Element[], pointers: Pointer[]) {
|
||||||
|
[...elements, ...pointers].forEach(item => {
|
||||||
|
item.set('rotation', item.get('rotation'));
|
||||||
|
item.set({ x: 0, y: 0 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布局外部指针
|
||||||
|
* @param pointer
|
||||||
|
*/
|
||||||
|
private layoutPointer(pointers: Pointer[]) {
|
||||||
|
pointers.forEach(item => {
|
||||||
|
const options: PointerOption = this.engine.pointerOptions[item.getType()],
|
||||||
|
offset = options.offset || 8,
|
||||||
|
anchor = options.anchor || 0;
|
||||||
|
|
||||||
|
let target = item.target,
|
||||||
|
targetBound: BoundingRect = item.target.getBound(),
|
||||||
|
anchorPosition = item.target.G6Item.getAnchorPoints()[anchor];
|
||||||
|
|
||||||
|
item.set({
|
||||||
|
x: targetBound.x + targetBound.width / 2,
|
||||||
|
y: targetBound.y - offset
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将视图调整至画布中心
|
* 将视图调整至画布中心
|
||||||
* @param nodes
|
* @param container
|
||||||
|
* @param models
|
||||||
*/
|
*/
|
||||||
private fitCenter(models: Model[]) {
|
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));
|
const viewBound: BoundingRect = models.map(item => item.getBound()).reduce((prev, cur) => Bound.union(prev, cur));
|
||||||
|
|
||||||
let width = this.engine.getGraphInstance().getWidth(),
|
let width = container.getG6Instance().getWidth(),
|
||||||
height = this.engine.getGraphInstance().getHeight(),
|
height = container.getG6Instance().getHeight(),
|
||||||
centerX = width / 2, centerY = height / 2,
|
centerX = width / 2, centerY = height / 2,
|
||||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
boundCenterX = viewBound.x + viewBound.width / 2,
|
||||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
boundCenterY = viewBound.y + viewBound.height / 2,
|
||||||
@ -38,55 +76,33 @@ export class Layouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 布局外部指针
|
* 进行布局
|
||||||
* @param pointer
|
* @param container
|
||||||
*/
|
* @param constructList
|
||||||
private layoutPointer(pointer: { [key: string]: Pointer[] }) {
|
|
||||||
Object.keys(pointer).map(name => {
|
|
||||||
const options: PointerOption = this.engine.pointerOptions[name],
|
|
||||||
pointerList: Pointer[] = pointer[name],
|
|
||||||
offset = options.offset || 8;
|
|
||||||
|
|
||||||
pointerList.forEach(item => {
|
|
||||||
let targetBound: BoundingRect = item.target.getBound();
|
|
||||||
item.set({
|
|
||||||
x: targetBound.x + targetBound.width / 2,
|
|
||||||
y: targetBound.y - offset
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主布局函数
|
|
||||||
* @param constructedData
|
|
||||||
* @param modelList
|
|
||||||
* @param layoutFn
|
* @param layoutFn
|
||||||
*/
|
*/
|
||||||
public layout(constructedData: ConstructedData, modelList: Model[], layoutFn: (element: { [ket: string]: Element[] }, layoutOptions: LayoutOptions) => void) {
|
public layout(container: Container, constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||||
const options: LayoutOptions = this.engine.layoutOptions;
|
const options: LayoutOptions = this.engine.layoutOptions,
|
||||||
|
modelList: Model[] = [...constructList.element, ...constructList.pointer, ...constructList.link];
|
||||||
|
|
||||||
// 首先初始化所有节点的坐标为0,且设定旋转
|
// 首先初始化所有节点的坐标为0,且设定旋转
|
||||||
modelList.forEach(item => {
|
modelList.forEach(item => {
|
||||||
item.G6Item = item.shadowG6Item;
|
item.G6Item = item.shadowG6Item;
|
||||||
|
|
||||||
if(item.modelType === 'element' || item.modelType === 'pointer') {
|
|
||||||
item.set('rotation', item.get('rotation'));
|
|
||||||
item.set({ x: 0, y: 0 });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 初始化布局参数
|
||||||
|
this.initLayoutValue(constructList.element, constructList.pointer);
|
||||||
// 布局节点
|
// 布局节点
|
||||||
layoutFn.call(this.engine, constructedData.element, options);
|
layoutFn(constructList.element, options);
|
||||||
|
|
||||||
// 布局外部指针
|
// 布局外部指针
|
||||||
this.layoutPointer(constructedData.pointer);
|
this.layoutPointer(constructList.pointer);
|
||||||
|
|
||||||
// 将视图调整到画布中心
|
// 将视图调整到画布中心
|
||||||
options.fitCenter && this.fitCenter(modelList);
|
options.fitCenter && this.fitCenter(container, modelList);
|
||||||
|
|
||||||
modelList.forEach(item => {
|
modelList.forEach(item => {
|
||||||
item.G6Item = item.renderG6Item;
|
item.G6Item = item.renderG6Item;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,8 +1,6 @@
|
|||||||
import { Engine } from '../engine';
|
import { Engine } from '../engine';
|
||||||
import { Element, G6EdgeModel, G6NodeModel, Link, Pointer } from '../Model/modelData';
|
import { G6EdgeModel, G6NodeModel } from '../Model/modelData';
|
||||||
import { ConstructedData } from '../Model/modelConstructor';
|
|
||||||
import { Util } from '../Common/util';
|
import { Util } from '../Common/util';
|
||||||
import { Animations } from './animation';
|
|
||||||
import { SV } from '../StructV';
|
import { SV } from '../StructV';
|
||||||
import { Model } from './../Model/modelData';
|
import { Model } from './../Model/modelData';
|
||||||
|
|
||||||
@ -14,32 +12,28 @@ export interface G6Data {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type g6Behavior = string | { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function; };
|
||||||
|
|
||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private DOMContainer: HTMLElement;
|
|
||||||
private animations: Animations;
|
|
||||||
private isFirstRender: boolean;
|
|
||||||
private prevRenderData: G6Data;
|
|
||||||
private graphInstance;
|
|
||||||
private shadowGraphInstance;
|
|
||||||
private modelList: Model[];
|
|
||||||
|
|
||||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
private DOMContainer: HTMLElement; // 主可视化视图容器
|
||||||
|
private g6Instance; // g6 实例
|
||||||
|
|
||||||
|
private isFirstRender: boolean; // 是否为第一次渲染
|
||||||
|
|
||||||
|
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any }) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.DOMContainer = DOMContainer;
|
this.DOMContainer = DOMContainer;
|
||||||
this.isFirstRender = true;
|
this.isFirstRender = true;
|
||||||
this.modelList = [];
|
|
||||||
this.prevRenderData = {
|
|
||||||
nodes: [],
|
|
||||||
edges: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const enable: boolean = this.engine.animationOptions.enable === undefined? true: this.engine.animationOptions.enable,
|
const enable: boolean = 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;
|
||||||
|
|
||||||
this.graphInstance = new SV.G6.Graph({
|
// 初始化g6实例
|
||||||
|
this.g6Instance = new SV.G6.Graph({
|
||||||
container: DOMContainer,
|
container: DOMContainer,
|
||||||
width: DOMContainer.offsetWidth,
|
width: DOMContainer.offsetWidth,
|
||||||
height: DOMContainer.offsetHeight,
|
height: DOMContainer.offsetHeight,
|
||||||
@ -49,223 +43,77 @@ export class Renderer {
|
|||||||
duration: duration,
|
duration: duration,
|
||||||
easing: timingFunction
|
easing: timingFunction
|
||||||
},
|
},
|
||||||
fitView: this.engine.layoutOptions.fitView,
|
fitView: false,
|
||||||
modes: {
|
modes: {
|
||||||
default: this.initBehaviors()
|
default: []
|
||||||
}
|
},
|
||||||
});
|
...g6Options
|
||||||
|
|
||||||
this.shadowGraphInstance = new SV.G6.Graph({
|
|
||||||
container: DOMContainer.cloneNode()
|
|
||||||
});
|
|
||||||
|
|
||||||
this.animations = new Animations(duration, timingFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化交互行为
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
private initBehaviors() {
|
|
||||||
const interactionOptions = this.engine.interactionOptions,
|
|
||||||
dragNode: boolean | string[] = interactionOptions.dragNode,
|
|
||||||
dragNodeFilter = node => {
|
|
||||||
let model = node.item.getModel();
|
|
||||||
|
|
||||||
if(node.item === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(model.modelType === 'pointer') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof dragNode === 'boolean') {
|
|
||||||
return dragNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Array.isArray(dragNode) && dragNode.indexOf(model.modelName) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modeMap = {
|
|
||||||
drag: 'drag-canvas',
|
|
||||||
zoom: 'zoom-canvas',
|
|
||||||
dragNode: {
|
|
||||||
type: 'drag-node',
|
|
||||||
shouldBegin: node => dragNodeFilter(node)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultModes = [];
|
|
||||||
|
|
||||||
Object.keys(interactionOptions).forEach(item => {
|
|
||||||
if(interactionOptions[item] && modeMap[item] !== undefined) {
|
|
||||||
defaultModes.push(modeMap[item]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return defaultModes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对比上一次和该次 G6Data 找出新添加的节点和边
|
|
||||||
* @param prevData
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
private diffAppendItems(prevData: G6Data, data: G6Data): G6Data {
|
|
||||||
return {
|
|
||||||
nodes: data.nodes.filter(item => !prevData.nodes.find(n => n.id === item.id)),
|
|
||||||
edges: data.edges.filter(item => !prevData.edges.find(e => e.id === item.id))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对比上一次和该次 G6Data 找出被删除的节点和边
|
|
||||||
* @param prevData
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
private diffRemoveItems(prevData: G6Data, data: G6Data): G6Data {
|
|
||||||
return {
|
|
||||||
nodes: prevData.nodes.filter(item => !data.nodes.find(n => n.id === item.id)),
|
|
||||||
edges: prevData.edges.filter(item => !data.edges.find(e => e.id === item.id))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查找被释放的节点
|
|
||||||
* @param constructedData
|
|
||||||
*/
|
|
||||||
private findFreedItems(constructedData: ConstructedData): G6NodeModel[] {
|
|
||||||
return Util.converterList(constructedData.element).filter(item => item.free).map(item => item.G6Item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理新增的 G6Item(主要是动画)
|
|
||||||
* @param appendData
|
|
||||||
*/
|
|
||||||
private handleAppendItems(appendData: G6Data) {
|
|
||||||
const appendItems = [
|
|
||||||
...appendData.nodes.map(item => this.graphInstance.findById(item.id)),
|
|
||||||
...appendData.edges.map(item => this.graphInstance.findById(item.id))
|
|
||||||
];
|
|
||||||
|
|
||||||
appendItems.forEach(item => {
|
|
||||||
this.animations.append(item);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public getIsFirstRender(): boolean {
|
||||||
* 处理被移除的 G6Item(主要是动画)
|
return this.isFirstRender;
|
||||||
* @param removeData
|
}
|
||||||
*/
|
|
||||||
private handleRemoveItems(removeData: G6Data) {
|
|
||||||
const removeItems = [
|
|
||||||
...removeData.nodes.map(item => this.graphInstance.findById(item.id)),
|
|
||||||
...removeData.edges.map(item => this.graphInstance.findById(item.id))
|
|
||||||
];
|
|
||||||
|
|
||||||
removeItems.forEach(item => {
|
public setIsFirstRender(value: boolean) {
|
||||||
this.animations.remove(item, () => {
|
this.isFirstRender = value;
|
||||||
this.graphInstance.removeItem(item);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理被 free 的 G6Item
|
* 从视图中移除一个 Model
|
||||||
* @param freedItems
|
* @param model
|
||||||
*/
|
*/
|
||||||
private handleFreedItems(freedItems: G6NodeModel[]) { }
|
public removeModel(model: Model) {
|
||||||
|
this.g6Instance.removeItem(model.renderG6Item);
|
||||||
/**
|
|
||||||
* 构建 G6 元素
|
|
||||||
* @param constructedData
|
|
||||||
*/
|
|
||||||
public build(constructedData: ConstructedData) {
|
|
||||||
let elementList: Element[] = Util.converterList(constructedData.element),
|
|
||||||
linkList: Link[] = Util.converterList(constructedData.link),
|
|
||||||
pointerList: Pointer[] = Util.converterList(constructedData.pointer),
|
|
||||||
nodeList = [...elementList.map(item => item.cloneProps()), ...pointerList.map(item => item.cloneProps())],
|
|
||||||
edgeList = linkList.map(item => item.cloneProps());
|
|
||||||
|
|
||||||
this.modelList = [...elementList, ...linkList, ...pointerList];
|
|
||||||
|
|
||||||
const data: G6Data = {
|
|
||||||
nodes: <G6NodeModel[]>nodeList,
|
|
||||||
edges: <G6EdgeModel[]>edgeList
|
|
||||||
};
|
|
||||||
|
|
||||||
this.shadowGraphInstance.clear();
|
|
||||||
this.shadowGraphInstance.read(data);
|
|
||||||
|
|
||||||
this.modelList.forEach(item => {
|
|
||||||
item.shadowG6Item = this.shadowGraphInstance.findById(item.id);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染函数
|
* 渲染函数
|
||||||
* @param constructedData
|
* @param modelList
|
||||||
*/
|
*/
|
||||||
public render(constructedData: ConstructedData) {
|
public render(modelList: Model[], removeModels: Model[]) {
|
||||||
let data: G6Data = Util.convertG6Data(constructedData),
|
let data: G6Data = Util.convertModelList2G6Data(modelList),
|
||||||
freedItems = this.findFreedItems(constructedData),
|
removeData: G6Data = Util.convertModelList2G6Data(removeModels),
|
||||||
renderData: G6Data = null,
|
renderData: G6Data = {
|
||||||
appendData: G6Data = null,
|
nodes: [...data.nodes, ...removeData.nodes],
|
||||||
removeData: G6Data = null;
|
edges: [...data.edges, ...removeData.edges]
|
||||||
|
};
|
||||||
appendData = this.diffAppendItems(this.prevRenderData, data);
|
|
||||||
removeData = this.diffRemoveItems(this.prevRenderData, data);
|
|
||||||
renderData = {
|
|
||||||
nodes: [...data.nodes, ...removeData.nodes],
|
|
||||||
edges: [...data.edges, ...removeData.edges]
|
|
||||||
};
|
|
||||||
|
|
||||||
this.prevRenderData = data;
|
|
||||||
|
|
||||||
if(this.isFirstRender) {
|
if(this.isFirstRender) {
|
||||||
this.graphInstance.read(renderData);
|
this.g6Instance.read(renderData);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.graphInstance.changeData(renderData);
|
this.g6Instance.changeData(renderData);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleAppendItems(appendData);
|
|
||||||
this.handleRemoveItems(removeData);
|
|
||||||
|
|
||||||
if(this.engine.layoutOptions.fitView) {
|
if(this.engine.layoutOptions.fitView) {
|
||||||
this.graphInstance.fitView();
|
this.g6Instance.fitView();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modelList.forEach(item => {
|
modelList.forEach(item => {
|
||||||
item.renderG6Item = this.graphInstance.findById(item.id);
|
item.renderG6Item = this.g6Instance.findById(item.id);
|
||||||
item.G6Item = item.renderG6Item;
|
item.G6Item = item.renderG6Item;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 把所有连线置顶
|
||||||
if(this.isFirstRender) {
|
if(this.isFirstRender) {
|
||||||
this.graphInstance.getEdges().forEach(item => item.toFront());
|
this.g6Instance.getEdges().forEach(item => item.toFront());
|
||||||
this.graphInstance.paint();
|
this.g6Instance.paint();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.isFirstRender) {
|
|
||||||
this.isFirstRender = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 model 队列
|
|
||||||
*/
|
|
||||||
getModelList(): Model[] {
|
|
||||||
return this.modelList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 G6 实例
|
* 获取 G6 实例
|
||||||
*/
|
*/
|
||||||
public getGraphInstance() {
|
public getG6Instance() {
|
||||||
return this.graphInstance;
|
return this.g6Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.g6Instance.destroy();
|
||||||
|
this.DOMContainer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
212
src/View/viewManager.ts
Normal file
212
src/View/viewManager.ts
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import { Engine } from "../engine";
|
||||||
|
import { Element, Link } from "../Model/modelData";
|
||||||
|
import { EngineInitOptions, LayoutOptions } 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";
|
||||||
|
|
||||||
|
|
||||||
|
export class ViewManager {
|
||||||
|
private engine: Engine;
|
||||||
|
private layouter: Layouter;
|
||||||
|
private mainContainer: Container;
|
||||||
|
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 shadowG6Instance;
|
||||||
|
|
||||||
|
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.layouter = new Layouter(engine);
|
||||||
|
this.mainContainer = new MainContainer(engine, DOMContainer);
|
||||||
|
|
||||||
|
const options: EngineInitOptions = this.engine.initOptions;
|
||||||
|
|
||||||
|
if(options.freedContainer) {
|
||||||
|
this.freedContainer = new FreedContainer(engine, options.freedContainer, { fitCenter: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.leakContainer) {
|
||||||
|
this.leakContainer = new LeakContainer(engine, options.leakContainer, { fitCenter: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shadowG6Instance = new SV.G6.Graph({
|
||||||
|
container: DOMContainer.cloneNode()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对每一个 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取被 free 的节点
|
||||||
|
* @param constructList
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getFreedConstructList(constructList: ConstructList): ConstructList {
|
||||||
|
const freedList: ConstructList = {
|
||||||
|
element: constructList.element.filter(item => item.free),
|
||||||
|
pointer: [],
|
||||||
|
link: []
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
|
||||||
|
return freedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取被泄露的节点
|
||||||
|
* @param constructList
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
element: elements,
|
||||||
|
link: links,
|
||||||
|
pointer: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对主视图进行重新布局
|
||||||
|
* @param constructList
|
||||||
|
* @param layoutFn
|
||||||
|
*/
|
||||||
|
reLayout(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||||
|
this.layouter.layout(this.mainContainer, constructList, layoutFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 g6 实例
|
||||||
|
*/
|
||||||
|
getG6Instance() {
|
||||||
|
return this.mainContainer.getG6Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新视图
|
||||||
|
*/
|
||||||
|
refresh() {
|
||||||
|
this.mainContainer.getG6Instance().refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新调整容器尺寸
|
||||||
|
* @param width
|
||||||
|
* @param height
|
||||||
|
*/
|
||||||
|
resize(width: number, height: number) {
|
||||||
|
this.mainContainer.getG6Instance().changeSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染所有视图
|
||||||
|
* @param models
|
||||||
|
* @param layoutFn
|
||||||
|
*/
|
||||||
|
renderAll(constructList: ConstructList, layoutFn: (elements: Element[], layoutOptions: LayoutOptions) => void) {
|
||||||
|
this.shadowG6Instance.clear();
|
||||||
|
|
||||||
|
this.build(constructList);
|
||||||
|
|
||||||
|
this.freedConstructList = this.getFreedConstructList(constructList);
|
||||||
|
this.leakConstructList = this.getLeakConstructList(this.prevConstructList, constructList);
|
||||||
|
|
||||||
|
this.build(this.leakConstructList);
|
||||||
|
|
||||||
|
if(this.freedContainer) {
|
||||||
|
this.freedContainer.render(this.freedConstructList, layoutFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进行布局(设置model的x,y)
|
||||||
|
this.layouter.layout(this.mainContainer, constructList, layoutFn);
|
||||||
|
this.mainContainer.render(constructList, layoutFn);
|
||||||
|
|
||||||
|
if(this.leakContainer) {
|
||||||
|
this.mainContainer.afterRemoveModels(() => {
|
||||||
|
this.leakContainer.render(this.leakConstructList, layoutFn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prevConstructList = constructList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
this.shadowG6Instance.destroy();
|
||||||
|
this.mainContainer.destroy();
|
||||||
|
this.freedContainer && this.freedContainer.destroy();
|
||||||
|
this.leakContainer && this.leakContainer.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
114
src/engine.ts
114
src/engine.ts
@ -1,23 +1,19 @@
|
|||||||
import { Element, Pointer } from "./Model/modelData";
|
import { Element, Pointer } from "./Model/modelData";
|
||||||
import { Sources } from "./sources";
|
import { SourceElement } from "./sources";
|
||||||
import { ConstructedData, ModelConstructor } from "./Model/modelConstructor";
|
import { ModelConstructor, ConstructList } from "./Model/modelConstructor";
|
||||||
import { Renderer } from "./View/renderer";
|
import { AnimationOptions, ElementOption, EngineInitOptions, InteractionOptions, LayoutOptions, LinkOption, Options, PointerOption } from "./options";
|
||||||
import { AnimationOptions, ElementOption, InteractionOptions, LayoutOptions, LinkOption, Options, PointerOption } from "./options";
|
import { Behavior } from "./Behavior.ts/behavior";
|
||||||
import { Layouter } from "./View/layouter";
|
import { ViewManager } from "./View/viewManager";
|
||||||
import { Behavior } from "./View/behavior";
|
|
||||||
import { Util } from "./Common/util";
|
|
||||||
|
|
||||||
|
|
||||||
export class Engine {
|
export class Engine {
|
||||||
private stringifySources: string = null; // 序列化的源数据
|
private stringifySources: string = null; // 序列化的源数据
|
||||||
|
|
||||||
private modelConstructor: ModelConstructor = null;
|
private modelConstructor: ModelConstructor = null;
|
||||||
private layouter: Layouter = null;
|
private viewManager: ViewManager
|
||||||
private renderer: Renderer = null;
|
|
||||||
private behavior: Behavior;
|
private behavior: Behavior;
|
||||||
private graphInstance;
|
|
||||||
private constructedData: ConstructedData;
|
|
||||||
|
|
||||||
|
public initOptions: EngineInitOptions;
|
||||||
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 } = { };
|
||||||
@ -25,9 +21,10 @@ export class Engine {
|
|||||||
public animationOptions: AnimationOptions = null;
|
public animationOptions: AnimationOptions = null;
|
||||||
public interactionOptions: InteractionOptions = null;
|
public interactionOptions: InteractionOptions = null;
|
||||||
|
|
||||||
constructor(DOMContainer: HTMLElement) {
|
constructor(DOMContainer: HTMLElement, initOptions: EngineInitOptions = { }) {
|
||||||
const options: Options = this.defineOptions();
|
const options: Options = this.defineOptions();
|
||||||
|
|
||||||
|
this.initOptions = initOptions;
|
||||||
this.elementOptions = options.element;
|
this.elementOptions = options.element;
|
||||||
this.linkOptions = options.link || { };
|
this.linkOptions = options.link || { };
|
||||||
this.pointerOptions = options.pointer || { };
|
this.pointerOptions = options.pointer || { };
|
||||||
@ -47,21 +44,26 @@ export class Engine {
|
|||||||
drag: true,
|
drag: true,
|
||||||
zoom: true,
|
zoom: true,
|
||||||
dragNode: true,
|
dragNode: true,
|
||||||
selectNode: true
|
selectNode: true,
|
||||||
|
changeHighlight: '#fc5185'
|
||||||
}, options.interaction);
|
}, options.interaction);
|
||||||
|
|
||||||
|
this.initOptions = Object.assign({
|
||||||
|
freedContainer: null,
|
||||||
|
leakContainer: null
|
||||||
|
}, initOptions);
|
||||||
|
|
||||||
this.modelConstructor = new ModelConstructor(this);
|
this.modelConstructor = new ModelConstructor(this);
|
||||||
this.layouter = new Layouter(this);
|
|
||||||
this.renderer = new Renderer(this, DOMContainer);
|
this.viewManager = new ViewManager(this, DOMContainer);
|
||||||
this.graphInstance = this.renderer.getGraphInstance();
|
this.behavior = new Behavior(this, this.viewManager.getG6Instance());
|
||||||
this.behavior = new Behavior(this, this.renderer.getGraphInstance());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入数据进行渲染
|
* 输入数据进行渲染
|
||||||
* @param sourceData
|
* @param sourceData
|
||||||
*/
|
*/
|
||||||
public render(sourceData: Sources) {
|
public render(sourceData: SourceElement[] | { [key: string]: SourceElement[] }) {
|
||||||
if(sourceData === undefined || sourceData === null) {
|
if(sourceData === undefined || sourceData === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -71,13 +73,40 @@ export class Engine {
|
|||||||
if(stringifySources === this.stringifySources) return;
|
if(stringifySources === this.stringifySources) return;
|
||||||
this.stringifySources = stringifySources;
|
this.stringifySources = stringifySources;
|
||||||
|
|
||||||
this.constructedData = this.modelConstructor.construct(sourceData);
|
let processedSourcesData = this.sourcesPreprocess(sourceData);
|
||||||
|
if(processedSourcesData) {
|
||||||
|
sourceData = processedSourcesData;
|
||||||
|
}
|
||||||
|
|
||||||
this.renderer.build(this.constructedData);
|
const sourceList: SourceElement[] = this.sourcesProcess(sourceData);
|
||||||
|
|
||||||
this.layouter.layout(this.constructedData, this.renderer.getModelList(), this.layout);
|
// 1 转换模型(data => model)
|
||||||
|
const constructList: ConstructList = this.modelConstructor.construct(sourceList);
|
||||||
|
|
||||||
this.renderer.render(this.constructedData);
|
// 2 渲染(使用g6进行渲染)
|
||||||
|
this.viewManager.renderAll(constructList, this.layout.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源数据处理
|
||||||
|
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,22 +117,29 @@ export class Engine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对源数据进行预处理
|
||||||
|
* @param sourceData
|
||||||
|
*/
|
||||||
|
protected sourcesPreprocess(sourceData: SourceElement[] | { [key: string]: SourceElement[] }): SourceElement[] | { [key: string]: SourceElement[] } | void {
|
||||||
|
return sourceData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置布局函数
|
* 设置布局函数
|
||||||
* @overwrite
|
* @overwrite
|
||||||
*/
|
*/
|
||||||
protected layout(elementContainer: { [ket: string]: Element[] }, layoutOptions: LayoutOptions) { }
|
protected layout(elements: Element[], layoutOptions: LayoutOptions) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新布局
|
* 重新布局
|
||||||
*/
|
*/
|
||||||
public reLayout() {
|
public reLayout() {
|
||||||
const modelList = this.renderer.getModelList();
|
const constructList: ConstructList = this.modelConstructor.getConstructList();
|
||||||
|
|
||||||
this.layouter.layout(this.constructedData, modelList, this.layout);
|
this.viewManager.reLayout(constructList, this.layout.bind(this));
|
||||||
modelList.forEach(item => {
|
|
||||||
if(item.modelType === 'link') return;
|
|
||||||
|
|
||||||
|
[...constructList.element, ...constructList.pointer].forEach(item => {
|
||||||
let model = item.G6Item.getModel(),
|
let model = item.G6Item.getModel(),
|
||||||
x = item.get('x'),
|
x = item.get('x'),
|
||||||
y = item.get('y');
|
y = item.get('y');
|
||||||
@ -112,35 +148,38 @@ export class Engine {
|
|||||||
model.y = y;
|
model.y = y;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.graphInstance.refresh();
|
this.viewManager.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 G6 实例
|
* 获取 G6 实例
|
||||||
*/
|
*/
|
||||||
public getGraphInstance() {
|
public getGraphInstance() {
|
||||||
return this.graphInstance;
|
return this.viewManager.getG6Instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有 element
|
* 获取所有 element
|
||||||
*/
|
*/
|
||||||
public getElements(): Element[] {
|
public getElements(): Element[] {
|
||||||
return Util.converterList(this.constructedData.element);
|
const constructList = this.modelConstructor.getConstructList();
|
||||||
|
return constructList.element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有 pointer
|
* 获取所有 pointer
|
||||||
*/
|
*/
|
||||||
public getPointers(): Pointer[] {
|
public getPointers(): Pointer[] {
|
||||||
return Util.converterList(this.constructedData.pointer);
|
const constructList = this.modelConstructor.getConstructList();
|
||||||
|
return constructList.pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有 link
|
* 获取所有 link
|
||||||
*/
|
*/
|
||||||
public getLinks() {
|
public getLinks() {
|
||||||
return Util.converterList(this.constructedData.link);
|
const constructList = this.modelConstructor.getConstructList();
|
||||||
|
return constructList.link;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,7 +188,7 @@ export class Engine {
|
|||||||
* @param height
|
* @param height
|
||||||
*/
|
*/
|
||||||
public resize(width: number, height: number) {
|
public resize(width: number, height: number) {
|
||||||
this.graphInstance.changeSize(width, height);
|
this.viewManager.resize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,11 +204,8 @@ export class Engine {
|
|||||||
* 销毁引擎
|
* 销毁引擎
|
||||||
*/
|
*/
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.graphInstance.destroy();
|
this.modelConstructor.destroy();
|
||||||
this.modelConstructor = null;
|
this.viewManager.destroy();
|
||||||
this.layouter = null;
|
|
||||||
this.renderer = null;
|
|
||||||
this.behavior = null;
|
this.behavior = null;
|
||||||
this.graphInstance = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -46,7 +46,6 @@ export interface LinkOption {
|
|||||||
sourceAnchor: number | ((index: number) => number);
|
sourceAnchor: number | ((index: number) => number);
|
||||||
targetAnchor: number | ((index: number) => number);
|
targetAnchor: number | ((index: number) => number);
|
||||||
label: string;
|
label: string;
|
||||||
controlPoints: { x: number, y: number }[];
|
|
||||||
curveOffset: number;
|
curveOffset: number;
|
||||||
labelOptions: LinkLabelOption;
|
labelOptions: LinkLabelOption;
|
||||||
style: Style;
|
style: Style;
|
||||||
@ -54,7 +53,7 @@ export interface LinkOption {
|
|||||||
|
|
||||||
|
|
||||||
export interface PointerOption extends ElementOption {
|
export interface PointerOption extends ElementOption {
|
||||||
position: 'top' | 'left' | 'bottom' | 'right';
|
anchor: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -78,6 +77,7 @@ export interface InteractionOptions {
|
|||||||
zoom: boolean;
|
zoom: boolean;
|
||||||
dragNode: boolean | string[];
|
dragNode: boolean | string[];
|
||||||
selectNode: boolean | string[];
|
selectNode: boolean | string[];
|
||||||
|
changeHighlight: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -91,5 +91,11 @@ export interface Options {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export interface EngineInitOptions {
|
||||||
|
freedContainer?: HTMLElement;
|
||||||
|
leakContainer?: HTMLElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,5 @@ export interface SourceElement {
|
|||||||
[key: string]: any | sourceLinkData | sourcePointerData;
|
[key: string]: any | sourceLinkData | sourcePointerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 源数据格式
|
|
||||||
export type Sources = { } | SourceElement[];
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user