Merge branch 'main' of https://gitlab.com/phenomLi/StructV2 into main
# Conflicts: # demo/Layouter/Force.js # src/Model/SVModel.ts # src/StructV.ts # src/View/renderer.ts # src/View/viewContainer.ts # src/engine.ts
This commit is contained in:
commit
7b9fffe0ab
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
test
|
test
|
||||||
|
dist
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const sourcePath = 'D:\\个人项目\\v\\StructV2\\dist\\sv.js';
|
const sourcePath = 'D:\\个人项目\\v\\StructV2\\dist\\sv.js';
|
||||||
const targetPath = 'D:\\个人项目\\anyview项目\\froend_student\\src\\pages\\student\\assets\\js\\sv.js'
|
const targetPath = 'D:\\个人项目\\froend_student\\src\\pages\\student\\assets\\js\\sv.js'
|
||||||
|
|
||||||
|
|
||||||
function COPY(from, to) {
|
function COPY(from, to) {
|
||||||
|
@ -20,7 +20,6 @@ const isNeighbor = function (itemA, itemB) {
|
|||||||
|
|
||||||
|
|
||||||
SV.registerLayout('AdjoinMatrixGraph', {
|
SV.registerLayout('AdjoinMatrixGraph', {
|
||||||
|
|
||||||
sourcesPreprocess(sources) {
|
sourcesPreprocess(sources) {
|
||||||
let dataLength = sources.length;
|
let dataLength = sources.length;
|
||||||
let matrixNodeLength = dataLength * dataLength;
|
let matrixNodeLength = dataLength * dataLength;
|
155
demo/Layouter/BinaryTree.js
Normal file
155
demo/Layouter/BinaryTree.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
SV.registerLayout('BinaryTree', {
|
||||||
|
defineOptions() {
|
||||||
|
return {
|
||||||
|
node: {
|
||||||
|
default: {
|
||||||
|
type: 'binary-tree-node',
|
||||||
|
size: [60, 30],
|
||||||
|
label: '[data]',
|
||||||
|
style: {
|
||||||
|
fill: '#b83b5e',
|
||||||
|
stroke: '#333',
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
child: {
|
||||||
|
type: 'line',
|
||||||
|
sourceAnchor: index => (index === 0 ? 3 : 1),
|
||||||
|
targetAnchor: 0,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
lineAppendWidth: 6,
|
||||||
|
cursor: 'pointer',
|
||||||
|
endArrow: 'default',
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marker: {
|
||||||
|
external: {
|
||||||
|
type: 'pointer',
|
||||||
|
anchor: 0,
|
||||||
|
offset: 14,
|
||||||
|
style: {
|
||||||
|
fill: '#f08a5d',
|
||||||
|
},
|
||||||
|
labelOptions: {
|
||||||
|
style: {
|
||||||
|
fill: '#000099',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
xInterval: 40,
|
||||||
|
yInterval: 40,
|
||||||
|
},
|
||||||
|
behavior: {
|
||||||
|
// dragNode: false
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对子树进行递归布局
|
||||||
|
*/
|
||||||
|
layoutItem(node, layoutOptions) {
|
||||||
|
// 次双亲不进行布局
|
||||||
|
if (!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bound = node.getBound(),
|
||||||
|
width = bound.width,
|
||||||
|
height = bound.height,
|
||||||
|
group = new Group(node),
|
||||||
|
leftGroup = null,
|
||||||
|
rightGroup = null,
|
||||||
|
leftBound = null,
|
||||||
|
rightBound = null;
|
||||||
|
|
||||||
|
if (node.visited) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.visited = true;
|
||||||
|
|
||||||
|
if (node.child && node.child[0]) {
|
||||||
|
leftGroup = this.layoutItem(node.child[0], layoutOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.child && node.child[1]) {
|
||||||
|
rightGroup = this.layoutItem(node.child[1], layoutOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftGroup) {
|
||||||
|
leftBound = leftGroup.getBound();
|
||||||
|
node.set('y', leftBound.y - layoutOptions.yInterval - height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightGroup) {
|
||||||
|
rightBound = rightGroup.getBound();
|
||||||
|
|
||||||
|
if (leftGroup) {
|
||||||
|
rightGroup.translate(0, leftBound.y - rightBound.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
rightBound = rightGroup.getBound();
|
||||||
|
node.set('y', rightBound.y - layoutOptions.yInterval - height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理左右子树相交问题
|
||||||
|
if (leftGroup && rightGroup) {
|
||||||
|
let move = Math.abs(rightBound.x - layoutOptions.xInterval - leftBound.x - leftBound.width);
|
||||||
|
if (move > 0) {
|
||||||
|
leftGroup.translate(-move / 2, 0);
|
||||||
|
rightGroup.translate(move / 2, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftGroup) {
|
||||||
|
leftBound = leftGroup.getBound();
|
||||||
|
node.set('x', leftBound.x + leftBound.width + layoutOptions.xInterval / 2 - width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightGroup) {
|
||||||
|
rightBound = rightGroup.getBound();
|
||||||
|
node.set('x', rightBound.x - layoutOptions.xInterval / 2 - width);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (leftGroup) {
|
||||||
|
group.add(leftGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightGroup) {
|
||||||
|
group.add(rightGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布局函数
|
||||||
|
* @param {*} elements
|
||||||
|
* @param {*} layoutOptions
|
||||||
|
*/
|
||||||
|
layout(elements, layoutOptions) {
|
||||||
|
let root = elements[0];
|
||||||
|
this.layoutItem(root, layoutOptions);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
[{
|
||||||
|
id: 6385328,
|
||||||
|
data: '',
|
||||||
|
external: ['L'],
|
||||||
|
root: true,
|
||||||
|
after: null,
|
||||||
|
next: null,
|
||||||
|
}, ];
|
@ -13,7 +13,7 @@ SV.registerLayout('ChainHashTable', {
|
|||||||
element: {
|
element: {
|
||||||
head: {
|
head: {
|
||||||
type: 'two-cell-node',
|
type: 'two-cell-node',
|
||||||
label: '[id]',
|
label: '[data]',
|
||||||
size: [70, 40],
|
size: [70, 40],
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
@ -22,7 +22,7 @@ SV.registerLayout('ChainHashTable', {
|
|||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
type: 'link-list-node',
|
type: 'link-list-node',
|
||||||
label: '[id]',
|
label: '[data]',
|
||||||
size: [60, 30],
|
size: [60, 30],
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
@ -64,12 +64,13 @@ SV.registerLayout('ChainHashTable', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pointer: {
|
marker: {
|
||||||
external: {
|
external: {
|
||||||
anchor: 3,
|
type: 'pointer',
|
||||||
|
anchor: 1,
|
||||||
offset: 8,
|
offset: 8,
|
||||||
style: {
|
style: {
|
||||||
fill: '#f08a5d'
|
fill: '#f08a5d'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
@ -63,6 +63,40 @@ SV.registerShape('three-cell-node', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//节点没有原子时
|
||||||
|
if(!cfg.sub){
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width,
|
||||||
|
y: height * ( 8 / 7),
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: '#000',
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
name: 'text',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//节点没有子表时
|
||||||
|
if(!cfg.next){
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (4 / 3),
|
||||||
|
y: height * ( 8 / 7),
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: '#000',
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
name: 'text',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return wrapperRect;
|
return wrapperRect;
|
||||||
},
|
},
|
||||||
|
|
@ -1,17 +1,14 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
SV.registerLayout('HashTable', {
|
SV.registerLayout('HashTable', {
|
||||||
|
|
||||||
sourcesPreprocess(sources) {
|
sourcesPreprocess(sources) {
|
||||||
const firstElement = sources[0];
|
const firstElement = sources[0];
|
||||||
|
|
||||||
if(firstElement.external) {
|
if (firstElement.external) {
|
||||||
firstElement.headExternal = firstElement.external;
|
firstElement.headExternal = firstElement.external;
|
||||||
delete firstElement.external;
|
delete firstElement.external;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(firstElement.cursor) {
|
if (firstElement.cursor) {
|
||||||
firstElement.headCursor = firstElement.cursor;
|
firstElement.headCursor = firstElement.cursor;
|
||||||
delete firstElement.cursor;
|
delete firstElement.cursor;
|
||||||
}
|
}
|
||||||
@ -21,9 +18,9 @@ SV.registerLayout('HashTable', {
|
|||||||
|
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
default: {
|
default: {
|
||||||
type: 'indexed-node',
|
type: 'array-node',
|
||||||
label: '[data]',
|
label: '[data]',
|
||||||
size: [60, 30],
|
size: [60, 30],
|
||||||
style: {
|
style: {
|
||||||
@ -71,25 +68,29 @@ SV.registerLayout('HashTable', {
|
|||||||
layout(elements) {
|
layout(elements) {
|
||||||
let arr = elements;
|
let arr = elements;
|
||||||
|
|
||||||
for(let i = 0; i < arr.length; i++) {
|
for (let i = 0; i < arr.length; i++) {
|
||||||
let width = arr[i].get('size')[0];
|
let width = arr[i].get('size')[0];
|
||||||
|
|
||||||
if(i > 0) {
|
if (i > 0) {
|
||||||
arr[i].set('x', arr[i - 1].get('x') + width);
|
arr[i].set('x', arr[i - 1].get('x') + width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(arr[i].empty) {
|
if (arr[i].empty) {
|
||||||
arr[i].set('style', {
|
arr[i].set('style', {
|
||||||
fill: null
|
fill: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(arr[i].disable) {
|
if (arr[i].disable) {
|
||||||
arr[i].set('style', {
|
arr[i].set('style', {
|
||||||
fill: '#ccc'
|
fill: null
|
||||||
});
|
});
|
||||||
|
arr[i].set('labelCfg', {
|
||||||
|
style: {
|
||||||
|
opacity: 0.4
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
SV.registerLayout('LinkList', {
|
SV.registerLayout('LinkList', {
|
||||||
|
|
||||||
sourcesPreprocess(sources) {
|
sourcesPreprocess(sources) {
|
||||||
let root = sources[0];
|
let root = sources[0];
|
||||||
|
|
||||||
if(root.external) {
|
if (root.external) {
|
||||||
root.rootExternal = root.external;
|
root.rootExternal = root.external;
|
||||||
delete root.external;
|
delete root.external;
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ SV.registerLayout('LinkList', {
|
|||||||
|
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
node: {
|
node: {
|
||||||
default: {
|
default: {
|
||||||
type: 'link-list-node',
|
type: 'link-list-node',
|
||||||
label: '[data]',
|
label: '[data]',
|
||||||
@ -28,7 +28,7 @@ SV.registerLayout('LinkList', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
link: {
|
link: {
|
||||||
next: {
|
next: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
sourceAnchor: 2,
|
sourceAnchor: 2,
|
||||||
targetAnchor: 6,
|
targetAnchor: 6,
|
||||||
@ -36,7 +36,7 @@ SV.registerLayout('LinkList', {
|
|||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
endArrow: 'default',
|
endArrow: 'default',
|
||||||
startArrow: {
|
startArrow: {
|
||||||
path: G6.Arrow.circle(2, -1),
|
path: G6.Arrow.circle(2, -1),
|
||||||
fill: '#333'
|
fill: '#333'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ SV.registerLayout('LinkList', {
|
|||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
endArrow: 'default',
|
endArrow: 'default',
|
||||||
startArrow: {
|
startArrow: {
|
||||||
path: G6.Arrow.circle(2, -1),
|
path: G6.Arrow.circle(2, -1),
|
||||||
fill: '#333'
|
fill: '#333'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,9 +77,6 @@ SV.registerLayout('LinkList', {
|
|||||||
layout: {
|
layout: {
|
||||||
xInterval: 50,
|
xInterval: 50,
|
||||||
yInterval: 50
|
yInterval: 50
|
||||||
},
|
|
||||||
behavior: {
|
|
||||||
dragNode: false
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -90,22 +87,22 @@ SV.registerLayout('LinkList', {
|
|||||||
* @param node
|
* @param node
|
||||||
* @param parent
|
* @param parent
|
||||||
*/
|
*/
|
||||||
layoutItem(node, prev, layoutOptions) {
|
layoutItem(node, prev, layoutOptions) {
|
||||||
if(!node) {
|
if (!node) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let width = node.get('size')[0];
|
let width = node.get('size')[0];
|
||||||
|
|
||||||
if(prev) {
|
if (prev) {
|
||||||
node.set('y', prev.get('y'));
|
node.set('y', prev.get('y'));
|
||||||
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
|
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(node.next) {
|
if (node.next) {
|
||||||
this.layoutItem(node.next, node, layoutOptions);
|
this.layoutItem(node.next, node, layoutOptions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
layout(elements, layoutOptions) {
|
layout(elements, layoutOptions) {
|
@ -72,6 +72,20 @@ SV.registerLayout('LinkQueue', {
|
|||||||
fill: '#333'
|
fill: '#333'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
loopNext: {
|
||||||
|
type: 'quadratic',
|
||||||
|
curveOffset: -100,
|
||||||
|
sourceAnchor: 2,
|
||||||
|
targetAnchor: 7,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
endArrow: 'default',
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
marker: {
|
marker: {
|
||||||
@ -85,7 +99,7 @@ SV.registerLayout('LinkQueue', {
|
|||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
xInterval: 50,
|
xInterval: 50,
|
||||||
yInterval: 50
|
yInterval: 58
|
||||||
},
|
},
|
||||||
behavior: {
|
behavior: {
|
||||||
dragNode: ['node']
|
dragNode: ['node']
|
@ -20,7 +20,7 @@
|
|||||||
element: {
|
element: {
|
||||||
default: {
|
default: {
|
||||||
type: 'link-list-node',
|
type: 'link-list-node',
|
||||||
label: '[id]',
|
label: '[data]',
|
||||||
size: [60, 30],
|
size: [60, 30],
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
@ -44,6 +44,20 @@
|
|||||||
fill: '#333'
|
fill: '#333'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
loopNext: {
|
||||||
|
type: 'quadratic',
|
||||||
|
curveOffset: -100,
|
||||||
|
sourceAnchor: 2,
|
||||||
|
targetAnchor: 7,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
endArrow: 'default',
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
marker: {
|
marker: {
|
||||||
@ -83,7 +97,7 @@
|
|||||||
|
|
||||||
let height = node.get('size')[1];
|
let height = node.get('size')[1];
|
||||||
|
|
||||||
if(prev) {
|
if(prev && node.isSameGroup(prev)) {
|
||||||
node.set('x', prev.get('x'));
|
node.set('x', prev.get('x'));
|
||||||
node.set('y', prev.get('y') - layoutOptions.yInterval - height);
|
node.set('y', prev.get('y') - layoutOptions.yInterval - height);
|
||||||
}
|
}
|
197
demo/Layouter/PCTree.js
Normal file
197
demo/Layouter/PCTree.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
|
||||||
|
SV.registerLayout('PCTree', {
|
||||||
|
|
||||||
|
sourcesPreprocess(sources) {
|
||||||
|
|
||||||
|
let headNodes = sources.filter(item => item.type === 'PCTreeHead');
|
||||||
|
|
||||||
|
for(let i = 0; i < headNodes.length; i++){
|
||||||
|
|
||||||
|
let dataNode = {
|
||||||
|
type: 'PCTreePreHead',
|
||||||
|
id: headNodes[i].id + '_0',
|
||||||
|
data: headNodes[i].preData,
|
||||||
|
indexLeft: headNodes[i].index,
|
||||||
|
root: headNodes[i].root
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dataNode.root){
|
||||||
|
dataNode.indexTop = 'data';
|
||||||
|
headNodes[i].indexTop = ' parent firstChild'
|
||||||
|
}
|
||||||
|
sources.push(dataNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources;
|
||||||
|
},
|
||||||
|
|
||||||
|
defineOptions() {
|
||||||
|
return {
|
||||||
|
node: {
|
||||||
|
PCTreePreHead: {
|
||||||
|
type: 'rect',
|
||||||
|
label: '[data]',
|
||||||
|
size: [60, 34],
|
||||||
|
labelOptions: {
|
||||||
|
style: { fontSize: 16 }
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#95e1d3',
|
||||||
|
offset: 25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PCTreeHead: {
|
||||||
|
type: 'two-cell-node',
|
||||||
|
label: '[data]',
|
||||||
|
size: [120, 34],
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#95e1d3'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PCTreeNode: {
|
||||||
|
type: 'link-list-node',
|
||||||
|
label: '[data]',
|
||||||
|
size: [60, 27],
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
fill: '#00AF92'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indexLabel: {
|
||||||
|
indexTop: { position: 'top' },
|
||||||
|
indexLeft: { position: 'left' }
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
headNext: {
|
||||||
|
sourceAnchor: 1,
|
||||||
|
targetAnchor: 6,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
endArrow: {
|
||||||
|
path: G6.Arrow.triangle(8, 6, 0),
|
||||||
|
fill: '#333'
|
||||||
|
},
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
sourceAnchor: 2,
|
||||||
|
targetAnchor: 6,
|
||||||
|
style: {
|
||||||
|
stroke: '#333',
|
||||||
|
endArrow: {
|
||||||
|
path: G6.Arrow.triangle(8, 6, 0),
|
||||||
|
fill: '#333'
|
||||||
|
},
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
marker: {
|
||||||
|
external: {
|
||||||
|
type: 'pointer',
|
||||||
|
anchor: 0,
|
||||||
|
offset: 8,
|
||||||
|
style: {
|
||||||
|
fill: '#f08a5d'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
xInterval: 50,
|
||||||
|
yInterval: 86
|
||||||
|
},
|
||||||
|
behavior: {
|
||||||
|
dragNode: ['PCTreeNode']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
//判断node节点是否之前布局过
|
||||||
|
isUnique(value, allNodeIdValue){
|
||||||
|
let re = new RegExp("" + value);
|
||||||
|
return !re.test(allNodeIdValue);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对子树进行递归布局
|
||||||
|
* @param node
|
||||||
|
* @param parent
|
||||||
|
*/
|
||||||
|
layoutItem(node, prev, layoutOptions, allNodeId) {
|
||||||
|
if(!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let width = node.get('size')[0],
|
||||||
|
idValue = node.id.split('(')[1].slice(0, -1);
|
||||||
|
|
||||||
|
//有y型链表的情况不用再布局
|
||||||
|
if(this.isUnique(idValue, allNodeId.value)){
|
||||||
|
if(prev) {
|
||||||
|
node.set('y', prev.get('y'));
|
||||||
|
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
|
||||||
|
}
|
||||||
|
|
||||||
|
allNodeId.value += idValue;
|
||||||
|
|
||||||
|
if(node.next) {
|
||||||
|
this.layoutItem(node.next, node, layoutOptions, allNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
layout(elements, layoutOptions) {
|
||||||
|
let headNode = elements.filter(item => item.type === 'PCTreeHead'),
|
||||||
|
preHeadNode = elements.filter(item => item.type === 'PCTreePreHead'),
|
||||||
|
roots = elements.filter(item => item.type === 'PCTreeNode' && item.root),
|
||||||
|
height = headNode[0].get('size')[1],
|
||||||
|
width = headNode[0].get('size')[0],
|
||||||
|
i,
|
||||||
|
allNodeId = { value: ''}; //引用类型用于传参
|
||||||
|
|
||||||
|
for(i = 0; i < headNode.length; i++) {
|
||||||
|
let node = headNode[i],
|
||||||
|
preNode = preHeadNode[i];
|
||||||
|
|
||||||
|
node.set({
|
||||||
|
x: 0,
|
||||||
|
y: i * height
|
||||||
|
});
|
||||||
|
|
||||||
|
preNode.set({
|
||||||
|
x: width / 4,
|
||||||
|
y: (i + 1) * height
|
||||||
|
})
|
||||||
|
|
||||||
|
if(node.headNext) {
|
||||||
|
let y = node.get('y') + height - node.headNext.get('size')[1],
|
||||||
|
x = width + layoutOptions.xInterval * 2;
|
||||||
|
|
||||||
|
node.headNext.set({ x, y });
|
||||||
|
this.layoutItem(node.headNext, null, layoutOptions, allNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < roots.length; i++) {
|
||||||
|
let nodeWidth = roots[0].get('size')[0],
|
||||||
|
nodeInternalSum = i * (nodeWidth + layoutOptions.xInterval);
|
||||||
|
|
||||||
|
roots[i].set({
|
||||||
|
x: headNode[0].get('x') + width + layoutOptions.xInterval * 2 + nodeInternalSum,
|
||||||
|
y: headNode[0].get('y') - layoutOptions.yInterval
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,8 +35,12 @@ SV.registerLayout('PTree', {
|
|||||||
data: sources[i].parent
|
data: sources[i].parent
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
sources[0].indexLeft = 'data';
|
if(sources[0]){
|
||||||
parentNodes[0].indexLeft = 'parent';
|
sources[0].indexLeft = 'data';
|
||||||
|
}
|
||||||
|
if(parentNodes[0]){
|
||||||
|
parentNodes[0].indexLeft = 'parent';
|
||||||
|
}
|
||||||
|
|
||||||
sources.push(...parentNodes);
|
sources.push(...parentNodes);
|
||||||
|
|
||||||
@ -70,14 +74,17 @@ SV.registerLayout('PTree', {
|
|||||||
|
|
||||||
|
|
||||||
layout(elements) {
|
layout(elements) {
|
||||||
let nodeLength = elements.length,
|
let nodeLength = elements.length;
|
||||||
halfLength = nodeLength / 2,
|
|
||||||
|
if(nodeLength == 0) return;
|
||||||
|
|
||||||
|
let halfLength = nodeLength / 2,
|
||||||
size = elements[0].get('size')[0],
|
size = elements[0].get('size')[0],
|
||||||
i;
|
i;
|
||||||
|
|
||||||
|
|
||||||
for (i = 0; i < nodeLength; i++) {
|
for (i = 0; i < nodeLength; i++) {
|
||||||
let x = (i % halfLength) * size;
|
let x = (i % halfLength) * size,
|
||||||
y = Math.floor(i / halfLength) * size;
|
y = Math.floor(i / halfLength) * size;
|
||||||
|
|
||||||
elements[i].set({ x, y });
|
elements[i].set({ x, y });
|
@ -1,96 +1,115 @@
|
|||||||
/**
|
/**
|
||||||
* 三叉树
|
* 三叉树
|
||||||
*/
|
*/
|
||||||
SV.registerLayout('TriTree', {
|
SV.registerLayout('TriTree', {
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
/**
|
node: {
|
||||||
* 结点
|
|
||||||
*/
|
|
||||||
element: {
|
|
||||||
default: {
|
default: {
|
||||||
type: 'tri-tree-node',
|
type: 'tri-tree-node',
|
||||||
size: [60, 30],
|
size: [60, 30],
|
||||||
label: '[data]',
|
label: '[data]',
|
||||||
style: {
|
style: {
|
||||||
fill: '#ff2e63',
|
fill: '#95e1d3',
|
||||||
stroke: "#333",
|
stroke: '#333',
|
||||||
cursor: 'pointer'
|
cursor: 'pointer',
|
||||||
|
backgroundFill: '#eee'
|
||||||
|
},
|
||||||
|
labelOptions: {
|
||||||
|
style: { fontSize: 16 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 箭头
|
|
||||||
*/
|
|
||||||
link: {
|
link: {
|
||||||
child: {
|
child: {
|
||||||
type: 'line',
|
|
||||||
sourceAnchor: index => index,
|
sourceAnchor: index => index,
|
||||||
targetAnchor: 3,
|
targetAnchor: 3,
|
||||||
|
type: 'line',
|
||||||
style: {
|
style: {
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
//边的响应范围
|
lineAppendWidth: 10,
|
||||||
lineAppendWidth: 6,
|
lineWidth: 1.6,
|
||||||
cursor: 'pointer',
|
|
||||||
endArrow: 'default',
|
endArrow: 'default',
|
||||||
startArrow: {
|
startArrow: {
|
||||||
//参数:半径、偏移量
|
path: G6.Arrow.circle(2, -1),
|
||||||
path: G6.Arrow.circle(2, -1),
|
|
||||||
fill: '#333'
|
fill: '#333'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parent: {
|
parent: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
sourceAnchor: 4,
|
sourceAnchor: 4,
|
||||||
targetAnchor: 2,
|
targetAnchor: 6,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#A9A9A9',
|
stroke: '#999',
|
||||||
//边的响应范围
|
lineAppendWidth: 10,
|
||||||
lineAppendWidth: 6,
|
lineWidth: 1.6,
|
||||||
cursor: 'pointer',
|
|
||||||
endArrow: 'default',
|
endArrow: 'default',
|
||||||
startArrow: {
|
startArrow: {
|
||||||
//参数:半径、偏移量
|
path: G6.Arrow.circle(2, -1),
|
||||||
path: G6.Arrow.circle(2, -1),
|
fill: '#999'
|
||||||
fill: '#333'
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
r_parent: {
|
||||||
|
type: 'quadratic',
|
||||||
|
sourceAnchor: 4,
|
||||||
|
targetAnchor: 5,
|
||||||
|
curveOffset: -20,
|
||||||
|
style: {
|
||||||
|
stroke: '#999',
|
||||||
|
lineAppendWidth: 10,
|
||||||
|
lineWidth: 1.6,
|
||||||
|
endArrow: 'default',
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#999'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
l_parent: {
|
||||||
|
type: 'quadratic',
|
||||||
|
sourceAnchor: 4,
|
||||||
|
targetAnchor: 2,
|
||||||
|
curveOffset: 20,
|
||||||
|
style: {
|
||||||
|
stroke: '#999',
|
||||||
|
lineAppendWidth: 10,
|
||||||
|
lineWidth: 1.6,
|
||||||
|
endArrow: 'default',
|
||||||
|
startArrow: {
|
||||||
|
path: G6.Arrow.circle(2, -1),
|
||||||
|
fill: '#999'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 指针
|
|
||||||
*/
|
|
||||||
marker: {
|
marker: {
|
||||||
external: {
|
external: {
|
||||||
type: "pointer",
|
type: 'pointer',
|
||||||
anchor: 3,
|
anchor: 3,
|
||||||
offset: 14,
|
offset: 14,
|
||||||
|
labelOffset: 2,
|
||||||
style: {
|
style: {
|
||||||
fill: '#f08a5d'
|
fill: '#f08a5d'
|
||||||
},
|
},
|
||||||
labelOptions: {
|
labelOptions: {
|
||||||
style: {
|
style: {
|
||||||
// stroke:
|
fontSize: 15,
|
||||||
|
fill: '#999'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
addressLabel: {
|
||||||
* 布局
|
style: {
|
||||||
*/
|
fill: '#999'
|
||||||
|
}
|
||||||
|
},
|
||||||
layout: {
|
layout: {
|
||||||
xInterval: 40,
|
xInterval: 40,
|
||||||
yInterval: 40,
|
yInterval: 40,
|
||||||
},
|
}
|
||||||
/**
|
|
||||||
* 动画
|
|
||||||
*/
|
|
||||||
//animation: {
|
|
||||||
// enable: true,
|
|
||||||
// duration: 750,
|
|
||||||
// timingFunction: 'easePolyOut'
|
|
||||||
//}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -99,44 +118,44 @@
|
|||||||
*/
|
*/
|
||||||
layoutItem(node, parent, index, layoutOptions) {
|
layoutItem(node, parent, index, layoutOptions) {
|
||||||
// 次双亲不进行布局
|
// 次双亲不进行布局
|
||||||
if(!node) {
|
if (!node) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bound = node.getBound(), //获取包围盒
|
let bound = node.getBound(), //获取包围盒
|
||||||
width = bound.width,
|
width = bound.width,
|
||||||
height = bound.height,
|
height = bound.height,
|
||||||
group = new Group(node); //创建分组
|
group = new Group(node); //创建分组
|
||||||
|
|
||||||
//有双亲,设置结点的位置
|
//有双亲,设置结点的位置
|
||||||
if(parent) {
|
if (parent) {
|
||||||
// 纵坐标
|
// 纵坐标
|
||||||
node.set('y', parent.get('y') + layoutOptions.yInterval + height);
|
node.set('y', parent.get('y') + layoutOptions.yInterval + height);
|
||||||
// 左节点横坐标
|
// 左节点横坐标
|
||||||
if(index === 0) {
|
if (index === 0) {
|
||||||
node.set('x', parent.get('x') - layoutOptions.xInterval / 2 - width / 2);
|
node.set('x', parent.get('x') - layoutOptions.xInterval / 2 - width / 2);
|
||||||
}
|
}
|
||||||
// 右结点横坐标
|
// 右结点横坐标
|
||||||
if(index === 1) {
|
if (index === 1) {
|
||||||
node.set('x', parent.get('x') + layoutOptions.xInterval / 2 + width / 2);
|
node.set('x', parent.get('x') + layoutOptions.xInterval / 2 + width / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//有孩子
|
//有孩子
|
||||||
if(node.child && (node.child[0] || node.child[1])) {
|
if (node.child && (node.child[0] || node.child[1])) {
|
||||||
let leftChild = node.child[0],
|
let leftChild = node.child[0],
|
||||||
rightChild = node.child[1],
|
rightChild = node.child[1],
|
||||||
leftGroup = this.layoutItem(leftChild, node, 0, layoutOptions),
|
leftGroup = this.layoutItem(leftChild, node, 0, layoutOptions),
|
||||||
rightGroup = this.layoutItem(rightChild, node, 1, layoutOptions),
|
rightGroup = this.layoutItem(rightChild, node, 1, layoutOptions),
|
||||||
intersection = null,
|
intersection = null,
|
||||||
move = 0;
|
move = 0;
|
||||||
|
|
||||||
// 处理左子树中子树相交问题
|
// 处理左子树中子树相交问题
|
||||||
if(leftGroup && rightChild) {
|
if (leftGroup && rightChild) {
|
||||||
//求出包围盒相交部分
|
//求出包围盒相交部分
|
||||||
intersection = Bound.intersect(leftGroup.getBound(), rightGroup.getBound());
|
intersection = Bound.intersect(leftGroup.getBound(), rightGroup.getBound());
|
||||||
move = 0;
|
move = 0;
|
||||||
//处理
|
//处理
|
||||||
if(intersection && intersection.width > 0) {
|
if (intersection && intersection.width > 0) {
|
||||||
move = (intersection.width + layoutOptions.xInterval) / 2;
|
move = (intersection.width + layoutOptions.xInterval) / 2;
|
||||||
// console.log(move,intersection.width,layoutOptions.xInterval);
|
// console.log(move,intersection.width,layoutOptions.xInterval);
|
||||||
leftGroup.translate(-move, 0);
|
leftGroup.translate(-move, 0);
|
||||||
@ -145,17 +164,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//加入分组
|
//加入分组
|
||||||
if(leftGroup) {
|
if (leftGroup) {
|
||||||
group.add(leftGroup);
|
group.add(leftGroup);
|
||||||
}
|
}
|
||||||
if(rightGroup) {
|
if (rightGroup) {
|
||||||
group.add(rightGroup)
|
group.add(rightGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//返回分组
|
//返回分组
|
||||||
return group;
|
return group;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 布局函数
|
* 布局函数
|
292
demo/data.js
Normal file
292
demo/data.js
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
const SOURCES_DATA = [{
|
||||||
|
"TriTree0": {
|
||||||
|
"data": [{
|
||||||
|
"external": [
|
||||||
|
"T"
|
||||||
|
],
|
||||||
|
"parent": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"child": [
|
||||||
|
"0xfd9ee0",
|
||||||
|
"0xfd9f10"
|
||||||
|
],
|
||||||
|
"id": "0xfd9eb0",
|
||||||
|
"name": "T",
|
||||||
|
"data": "A",
|
||||||
|
"root": true,
|
||||||
|
"type": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9ee0",
|
||||||
|
"name": "T->lchild",
|
||||||
|
"data": "B",
|
||||||
|
"type": "default",
|
||||||
|
"l_parent": [
|
||||||
|
"0xfd9eb0"
|
||||||
|
],
|
||||||
|
"external": [
|
||||||
|
"T1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f10",
|
||||||
|
"name": "T->rchild",
|
||||||
|
"data": "C",
|
||||||
|
"type": "default",
|
||||||
|
"r_parent": [
|
||||||
|
"0xfd9eb0"
|
||||||
|
],
|
||||||
|
"external": [
|
||||||
|
"T2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layouter": "TriTree"
|
||||||
|
},
|
||||||
|
"TriTree3": {
|
||||||
|
"data": [{
|
||||||
|
"external": [
|
||||||
|
"T3"
|
||||||
|
],
|
||||||
|
"parent": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f40",
|
||||||
|
"name": "T3",
|
||||||
|
"data": "D",
|
||||||
|
"root": true,
|
||||||
|
"type": "default"
|
||||||
|
}],
|
||||||
|
"layouter": "TriTree"
|
||||||
|
},
|
||||||
|
"TriTree4": {
|
||||||
|
"data": [{
|
||||||
|
"external": [
|
||||||
|
"T4"
|
||||||
|
],
|
||||||
|
"parent": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f70",
|
||||||
|
"name": "T4",
|
||||||
|
"data": "E",
|
||||||
|
"root": true,
|
||||||
|
"type": "default"
|
||||||
|
}],
|
||||||
|
"layouter": "TriTree"
|
||||||
|
},
|
||||||
|
"handleUpdate": {
|
||||||
|
"isEnterFunction": true,
|
||||||
|
"isFirstDebug": true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"TriTree0": {
|
||||||
|
"data": [{
|
||||||
|
"external": [
|
||||||
|
"T"
|
||||||
|
],
|
||||||
|
"parent": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"child": [
|
||||||
|
"0xfd9ee0",
|
||||||
|
"0xfd9f10"
|
||||||
|
],
|
||||||
|
"id": "0xfd9eb0",
|
||||||
|
"name": "T",
|
||||||
|
"data": "A",
|
||||||
|
"root": true,
|
||||||
|
"type": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9ee0",
|
||||||
|
"name": "T->lchild",
|
||||||
|
"data": "B",
|
||||||
|
"type": "default",
|
||||||
|
"l_parent": [
|
||||||
|
"0xfd9eb0"
|
||||||
|
],
|
||||||
|
"external": [
|
||||||
|
"T1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f10",
|
||||||
|
"name": "T->rchild",
|
||||||
|
"data": "C",
|
||||||
|
"type": "default",
|
||||||
|
"r_parent": [
|
||||||
|
"0xfd9eb0"
|
||||||
|
],
|
||||||
|
"external": [
|
||||||
|
"T2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layouter": "TriTree"
|
||||||
|
},
|
||||||
|
"TriTree3": {
|
||||||
|
"data": [{
|
||||||
|
"external": [
|
||||||
|
"T3"
|
||||||
|
],
|
||||||
|
"parent": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f40",
|
||||||
|
"name": "T3",
|
||||||
|
"data": "D",
|
||||||
|
"root": true,
|
||||||
|
"type": "default"
|
||||||
|
}],
|
||||||
|
"layouter": "TriTree"
|
||||||
|
},
|
||||||
|
"TriTree4": {
|
||||||
|
"data": [{
|
||||||
|
"external": [
|
||||||
|
"T4"
|
||||||
|
],
|
||||||
|
"parent": [
|
||||||
|
"0xfd9f40"
|
||||||
|
],
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f70",
|
||||||
|
"name": "T4",
|
||||||
|
"data": "E",
|
||||||
|
"root": true,
|
||||||
|
"type": "default"
|
||||||
|
}],
|
||||||
|
"layouter": "TriTree"
|
||||||
|
},
|
||||||
|
"handleUpdate": {
|
||||||
|
"isEnterFunction": false,
|
||||||
|
"isFirstDebug": false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"TriTree0": {
|
||||||
|
"data": [{
|
||||||
|
"external": [
|
||||||
|
"T"
|
||||||
|
],
|
||||||
|
"parent": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"child": [
|
||||||
|
"0xfd9ee0",
|
||||||
|
"0xfd9f10"
|
||||||
|
],
|
||||||
|
"id": "0xfd9eb0",
|
||||||
|
"name": "T",
|
||||||
|
"data": "A",
|
||||||
|
"root": true,
|
||||||
|
"type": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9ee0",
|
||||||
|
"name": "T->lchild",
|
||||||
|
"data": "B",
|
||||||
|
"type": "default",
|
||||||
|
"l_parent": [
|
||||||
|
"0xfd9eb0"
|
||||||
|
],
|
||||||
|
"external": [
|
||||||
|
"T1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f10",
|
||||||
|
"name": "T->rchild",
|
||||||
|
"data": "C",
|
||||||
|
"type": "default",
|
||||||
|
"r_parent": [
|
||||||
|
"0xfd9eb0"
|
||||||
|
],
|
||||||
|
"external": [
|
||||||
|
"T2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layouter": "TriTree"
|
||||||
|
},
|
||||||
|
"TriTree3": {
|
||||||
|
"data": [{
|
||||||
|
"external": [
|
||||||
|
"T3"
|
||||||
|
],
|
||||||
|
"parent": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"child": [
|
||||||
|
"0xfd9f70",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f40",
|
||||||
|
"name": "T3",
|
||||||
|
"data": "D",
|
||||||
|
"root": true,
|
||||||
|
"type": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"id": "0xfd9f70",
|
||||||
|
"name": "T3->lchild",
|
||||||
|
"data": "E",
|
||||||
|
"type": "default",
|
||||||
|
"l_parent": [
|
||||||
|
"0xfd9f40"
|
||||||
|
],
|
||||||
|
"external": [
|
||||||
|
"T4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layouter": "TriTree"
|
||||||
|
},
|
||||||
|
"handleUpdate": {
|
||||||
|
"isEnterFunction": false,
|
||||||
|
"isFirstDebug": false
|
||||||
|
}
|
||||||
|
}];
|
@ -2,8 +2,8 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>DEMO</title>
|
<title>DEMO</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
@ -11,25 +11,25 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.down {
|
.down {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#leak {
|
#leak {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -42,7 +42,7 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.75s ease-in-out;
|
transition: opacity 0.75s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#leak>span {
|
#leak>span {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
@ -50,7 +50,6 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="container" id="container">
|
<div class="container" id="container">
|
||||||
<div id="leak">
|
<div id="leak">
|
||||||
<span>泄漏区</span>
|
<span>泄漏区</span>
|
||||||
@ -67,15 +66,14 @@
|
|||||||
|
|
||||||
<script src="./../dist/sv.js"></script>
|
<script src="./../dist/sv.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const Group = SV.Group,
|
const Group = SV.Group,
|
||||||
Bound = SV.Bound,
|
Bound = SV.Bound,
|
||||||
G6 = SV.G6,
|
G6 = SV.G6,
|
||||||
Vector = SV.Vector;
|
Vector = SV.Vector;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="./Layouter/LinkList.js"></script>
|
<script src="./Layouter/LinkList.js"></script>
|
||||||
<script src="./Layouter/BinaryTree.js"></script>
|
<script src="./Layouter/BinaryTree.js"></script>
|
||||||
|
<script src="./Layouter/TriTree.js"></script>
|
||||||
<script src="./Layouter/Stack.js"></script>
|
<script src="./Layouter/Stack.js"></script>
|
||||||
<script src="./Layouter/LinkQueue.js"></script>
|
<script src="./Layouter/LinkQueue.js"></script>
|
||||||
<script src="./Layouter/GeneralizedList.js"></script>
|
<script src="./Layouter/GeneralizedList.js"></script>
|
||||||
@ -87,9 +85,10 @@
|
|||||||
<script src="./Layouter/AdjoinTableGraph.js"></script>
|
<script src="./Layouter/AdjoinTableGraph.js"></script>
|
||||||
<script src="./Layouter/SqQueue.js"></script>
|
<script src="./Layouter/SqQueue.js"></script>
|
||||||
<script src="./Layouter/PTree.js"></script>
|
<script src="./Layouter/PTree.js"></script>
|
||||||
|
<script src="./Layouter/PCTree.js"></script>
|
||||||
|
<script src="./data.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
let cur = SV(document.getElementById('container'), {
|
let cur = SV(document.getElementById('container'), {
|
||||||
view: {
|
view: {
|
||||||
leakAreaHeight: 130,
|
leakAreaHeight: 130,
|
||||||
@ -97,133 +96,8 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let data = [{
|
|
||||||
"sqStack0": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "0x617eb5",
|
|
||||||
"data": "",
|
|
||||||
"index": 5,
|
|
||||||
"cursor": "top"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb4",
|
|
||||||
"data": "2",
|
|
||||||
"index": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb3",
|
|
||||||
"data": "6",
|
|
||||||
"index": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb2",
|
|
||||||
"data": "7",
|
|
||||||
"index": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb1",
|
|
||||||
"data": "9",
|
|
||||||
"index": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb0",
|
|
||||||
"data": "1",
|
|
||||||
"index": 0,
|
|
||||||
"external": "S"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"layouter": "Stack"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"sqStack0": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "0x617eb4",
|
|
||||||
"data": "2",
|
|
||||||
"index": 4,
|
|
||||||
"cursor": "top",
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb3",
|
|
||||||
"data": "6",
|
|
||||||
"index": 3,
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb2",
|
|
||||||
"data": "7",
|
|
||||||
"index": 2,
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb1",
|
|
||||||
"data": "9",
|
|
||||||
"index": 1,
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb0",
|
|
||||||
"data": "1",
|
|
||||||
"index": 0,
|
|
||||||
"bottomExternal": "S",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"layouter": "Stack"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "0x617eb5",
|
|
||||||
"data": "",
|
|
||||||
"index": 5,
|
|
||||||
"cursor": "top",
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb4",
|
|
||||||
"data": "2",
|
|
||||||
"index": 4,
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb3",
|
|
||||||
"data": "6",
|
|
||||||
"index": 3,
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb2",
|
|
||||||
"data": "7",
|
|
||||||
"index": 2,
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb1",
|
|
||||||
"data": "9",
|
|
||||||
"index": 1,
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617eb0",
|
|
||||||
"data": "1",
|
|
||||||
"index": 0,
|
|
||||||
"bottomExternal": "S",
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"layouter": "Stack"
|
|
||||||
}];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let dataIndex = 0,
|
let dataIndex = 0,
|
||||||
curData = data[dataIndex];
|
curData = SOURCES_DATA[dataIndex];
|
||||||
|
|
||||||
let enableBrushSelect = false;
|
let enableBrushSelect = false;
|
||||||
|
|
||||||
@ -235,12 +109,12 @@
|
|||||||
cur.render(curData);
|
cur.render(curData);
|
||||||
|
|
||||||
document.getElementById('btn-next').addEventListener('click', e => {
|
document.getElementById('btn-next').addEventListener('click', e => {
|
||||||
curData = data[++dataIndex];
|
curData = SOURCES_DATA[++dataIndex];
|
||||||
cur.render(curData);
|
cur.render(curData);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('btn-prev').addEventListener('click', e => {
|
document.getElementById('btn-prev').addEventListener('click', e => {
|
||||||
curData = data[--dataIndex];
|
curData = SOURCES_DATA[--dataIndex];
|
||||||
cur.render(curData);
|
cur.render(curData);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -270,12 +144,10 @@
|
|||||||
// -------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
container.addEventListener('mousemove', e => {
|
container.addEventListener('mousemove', e => {
|
||||||
let x = e.offsetX, y = e.offsetY;
|
let x = e.offsetX,
|
||||||
|
y = e.offsetY;
|
||||||
pos.innerHTML = `${x},${y}`;
|
pos.innerHTML = `${x},${y}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
@ -1,147 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
SV.registerLayout('BinaryTree', {
|
|
||||||
defineOptions() {
|
|
||||||
return {
|
|
||||||
element: {
|
|
||||||
default: {
|
|
||||||
type: 'binary-tree-node',
|
|
||||||
size: [60, 30],
|
|
||||||
label: '[data]',
|
|
||||||
style: {
|
|
||||||
fill: '#b83b5e',
|
|
||||||
stroke: "#333",
|
|
||||||
cursor: 'pointer'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
child: {
|
|
||||||
type: 'line',
|
|
||||||
sourceAnchor: index => index === 0? 3: 1,
|
|
||||||
targetAnchor: 0,
|
|
||||||
style: {
|
|
||||||
stroke: '#333',
|
|
||||||
lineAppendWidth: 6,
|
|
||||||
cursor: 'pointer',
|
|
||||||
endArrow: 'default',
|
|
||||||
startArrow: {
|
|
||||||
path: G6.Arrow.circle(2, -1),
|
|
||||||
fill: '#333'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
marker: {
|
|
||||||
external: {
|
|
||||||
type: 'pointer',
|
|
||||||
anchor: 0,
|
|
||||||
offset: 14,
|
|
||||||
style: {
|
|
||||||
fill: '#f08a5d'
|
|
||||||
},
|
|
||||||
labelOptions: {
|
|
||||||
style: {
|
|
||||||
fill: '#000099'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
xInterval: 40,
|
|
||||||
yInterval: 40
|
|
||||||
},
|
|
||||||
behavior: {
|
|
||||||
// dragNode: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对子树进行递归布局
|
|
||||||
*/
|
|
||||||
layoutItem(node, parent, index, layoutOptions) {
|
|
||||||
// 次双亲不进行布局
|
|
||||||
if(!node) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bound = node.getBound(),
|
|
||||||
width = bound.width,
|
|
||||||
height = bound.height,
|
|
||||||
group = new Group(node);
|
|
||||||
|
|
||||||
if(parent) {
|
|
||||||
node.set('y', parent.get('y') + layoutOptions.yInterval + height);
|
|
||||||
|
|
||||||
// 左节点
|
|
||||||
if(index === 0) {
|
|
||||||
node.set('x', parent.get('x') - layoutOptions.xInterval / 2 - width / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右结点
|
|
||||||
if(index === 1) {
|
|
||||||
node.set('x', parent.get('x') + layoutOptions.xInterval / 2 + width / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(node.child && (node.child[0] || node.child[1])) {
|
|
||||||
let leftChild = node.child[0],
|
|
||||||
rightChild = node.child[1],
|
|
||||||
leftGroup = this.layoutItem(leftChild, node, 0, layoutOptions),
|
|
||||||
rightGroup = this.layoutItem(rightChild, node, 1, layoutOptions),
|
|
||||||
intersection = null,
|
|
||||||
move = 0;
|
|
||||||
|
|
||||||
// 处理左右子树相交问题
|
|
||||||
if(leftGroup && rightGroup) {
|
|
||||||
intersection = Bound.intersect(leftGroup.getBound(), rightGroup.getBound());
|
|
||||||
move = 0;
|
|
||||||
|
|
||||||
if(intersection && intersection.width > 0) {
|
|
||||||
move = (intersection.width + layoutOptions.xInterval) / 2;
|
|
||||||
leftGroup.translate(-move, 0);
|
|
||||||
rightGroup.translate(move, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(leftGroup) {
|
|
||||||
group.add(leftGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(rightGroup) {
|
|
||||||
group.add(rightGroup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return group;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 布局函数
|
|
||||||
* @param {*} elements
|
|
||||||
* @param {*} layoutOptions
|
|
||||||
*/
|
|
||||||
layout(elements, layoutOptions) {
|
|
||||||
let root = elements[0];
|
|
||||||
this.layoutItem(root, null, -1, layoutOptions);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 6385328,
|
|
||||||
"data": "",
|
|
||||||
"external": [
|
|
||||||
"L"
|
|
||||||
],
|
|
||||||
"root": true,
|
|
||||||
"after": null,
|
|
||||||
"next": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
14104
dist/sv.js
vendored
14104
dist/sv.js
vendored
File diff suppressed because one or more lines are too long
4080
package-lock.json
generated
Normal file
4080
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@ -1,19 +1,20 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
|
||||||
"@antv/g6": "^4.4.1",
|
|
||||||
"merge": "^2.1.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"merge": "^2.1.1",
|
|
||||||
"ts-loader": "^5.2.1",
|
|
||||||
"typescript": "^3.2.2",
|
|
||||||
"webpack": "^4.46.0",
|
|
||||||
"webpack-cli": "^3.2.3"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.product.js",
|
"build": "webpack --config webpack.config.product.js",
|
||||||
"dep": "webpack --config webpack.config.product.js && node copyDist2Anyview.js",
|
"dep": "webpack --config webpack.config.product.js && node copyDist2Anyview.js",
|
||||||
"dev": "webpack --w --config webpack.config.develop.js",
|
"dev": "webpack --watch --config webpack.config.develop.js",
|
||||||
"copy": "node copyDist2Anyview.js"
|
"copy": "node copyDist2Anyview.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"acorn": "^8.7.0",
|
||||||
|
"merge": "^2.1.1",
|
||||||
|
"ts-loader": "^9.2.8",
|
||||||
|
"typescript": "^4.6.2",
|
||||||
|
"webpack": "^5.70.0",
|
||||||
|
"webpack-cli": "^4.9.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@antv/g6": "^4.6.4",
|
||||||
|
"merge": "^2.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
import { Graph, INode } from "@antv/g6";
|
import { G6Event, Graph, INode } from '@antv/g6';
|
||||||
import { EventBus } from "../Common/eventBus";
|
import { EventBus } from '../Common/eventBus';
|
||||||
import { LayoutGroupTable } from "../Model/modelConstructor";
|
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||||
import { SVModel } from "../Model/SVModel";
|
import { SVModel } from '../Model/SVModel';
|
||||||
import { SVNode } from "../Model/SVNode";
|
import { SVNode } from '../Model/SVNode';
|
||||||
import { ViewContainer } from "../View/viewContainer";
|
import { ViewContainer } from '../View/viewContainer';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断当前节点是否可以单独拖拽
|
* 判断当前节点是否可以单独拖拽
|
||||||
* @param node
|
* @param node
|
||||||
* @param dragNodeOption
|
* @param dragNodeOption
|
||||||
*/
|
*/
|
||||||
const checkNodeDragAlone = function (node: SVNode, dragNodeOption: boolean | string[]): boolean {
|
const checkNodeDragAlone = function (node: SVNode, dragNodeOption: boolean | string[]): boolean {
|
||||||
const nodeSourceType = node.sourceType;
|
const nodeSourceType = node.sourceType;
|
||||||
|
|
||||||
if (Array.isArray(dragNodeOption)) {
|
if (Array.isArray(dragNodeOption)) {
|
||||||
return dragNodeOption.includes(nodeSourceType);
|
return dragNodeOption.includes(nodeSourceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dragNodeOption === undefined || dragNodeOption === true) {
|
if (dragNodeOption === undefined || dragNodeOption === true) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判定该节点是否可以被拖拽
|
* 判定该节点是否可以被拖拽
|
||||||
@ -32,181 +30,173 @@ const checkNodeDragAlone = function (node: SVNode, dragNodeOption: boolean | str
|
|||||||
* 2. 当 dragNodeOption 声明了某些节点的type时(字符数组),这些节点可以单独拖拽
|
* 2. 当 dragNodeOption 声明了某些节点的type时(字符数组),这些节点可以单独拖拽
|
||||||
* 3. 当 dragNodeOption 为 false 或者不包含在声明的type的节点,只能批量拖拽
|
* 3. 当 dragNodeOption 为 false 或者不包含在声明的type的节点,只能批量拖拽
|
||||||
*/
|
*/
|
||||||
export const DetermineNodeDrag = function (layoutGroupTable: LayoutGroupTable, node: SVNode, brushSelectedModels: SVModel[]) {
|
export const DetermineNodeDrag = function (
|
||||||
const layoutGroup = layoutGroupTable.get(node.group),
|
layoutGroupTable: LayoutGroupTable,
|
||||||
dragNodeOption = layoutGroup.options.behavior?.dragNode,
|
node: SVNode,
|
||||||
canNodeDragAlone = checkNodeDragAlone(node, dragNodeOption);
|
brushSelectedModels: SVModel[]
|
||||||
|
) {
|
||||||
|
const layoutGroup = layoutGroupTable.get(node.group),
|
||||||
|
dragNodeOption = layoutGroup.options.behavior?.dragNode,
|
||||||
|
canNodeDragAlone = checkNodeDragAlone(node, dragNodeOption);
|
||||||
|
|
||||||
if (canNodeDragAlone) {
|
if (canNodeDragAlone) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeSourceType = node.sourceType,
|
const nodeSourceType = node.sourceType,
|
||||||
nodeModelType = node.getModelType(),
|
nodeModelType = node.getModelType(),
|
||||||
modelList = (<SVModel[]>layoutGroup[nodeModelType]).filter(item => item.sourceType === nodeSourceType),
|
modelList = (<SVModel[]>layoutGroup[nodeModelType]).filter(item => item.sourceType === nodeSourceType),
|
||||||
brushSelectedSameTypeModels = brushSelectedModels.filter(item => {
|
brushSelectedSameTypeModels = brushSelectedModels.filter(item => {
|
||||||
return item.group === node.group &&
|
return (
|
||||||
item.getModelType() === nodeModelType &&
|
item.group === node.group && item.getModelType() === nodeModelType && item.sourceType === nodeSourceType
|
||||||
item.sourceType === nodeSourceType
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return modelList.length === brushSelectedSameTypeModels.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return modelList.length === brushSelectedSameTypeModels.length;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在初始化渲染器之后,修正节点拖拽时,外部指针或者其他 appendage 没有跟着动的问题
|
* 在初始化渲染器之后,修正节点拖拽时,外部指针或者其他 appendage 没有跟着动的问题
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
|
export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
|
||||||
const g6Instance: Graph = viewContainer.getG6Instance();
|
const g6Instance: Graph = viewContainer.getG6Instance();
|
||||||
|
|
||||||
g6Instance.on('node:dragstart', event => {
|
g6Instance.on('node:dragstart', event => {
|
||||||
let node: SVNode = event.item['SVModel'];
|
let node: SVNode = event.item['SVModel'];
|
||||||
|
|
||||||
if (node instanceof SVNode === false) {
|
if (node instanceof SVNode === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNodeSelected = viewContainer.brushSelectedModels.find(item => item.id === node.id);
|
const isNodeSelected = viewContainer.brushSelectedModels.find(item => item.id === node.id);
|
||||||
|
|
||||||
// 如果在框选完成之后,拖拽了被框选之外的其他节点,那么取消已框选的节点的选中状态
|
// 如果在框选完成之后,拖拽了被框选之外的其他节点,那么取消已框选的节点的选中状态
|
||||||
if (isNodeSelected === undefined) {
|
if (isNodeSelected === undefined) {
|
||||||
viewContainer.brushSelectedModels.forEach(item => {
|
viewContainer.brushSelectedModels.forEach(item => {
|
||||||
item.setSelectedState(false);
|
item.setSelectedState(false);
|
||||||
|
|
||||||
if (item instanceof SVNode) {
|
if (item instanceof SVNode) {
|
||||||
item.appendages.forEach(appendage => appendage.setSelectedState(false));
|
item.getAppendagesList().forEach(appendage => appendage.setSelectedState(false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
viewContainer.brushSelectedModels.length = 0;
|
viewContainer.brushSelectedModels.length = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
g6Instance.on('node:dragend', event => {
|
g6Instance.on('node:dragend', event => {
|
||||||
let node: SVNode = event.item['SVModel'];
|
let node: SVNode = event.item['SVModel'];
|
||||||
|
|
||||||
if (node instanceof SVNode === false) {
|
if (node instanceof SVNode === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNodeSelected = viewContainer.brushSelectedModels.find(item => item.id === node.id);
|
const isNodeSelected = viewContainer.brushSelectedModels.find(item => item.id === node.id);
|
||||||
|
|
||||||
// 如果当前拖拽的节点是在已框选选中的节点之中,那么不需要取消选中的状态,否则需要取消
|
// 如果当前拖拽的节点是在已框选选中的节点之中,那么不需要取消选中的状态,否则需要取消
|
||||||
if (isNodeSelected === undefined) {
|
if (isNodeSelected === undefined) {
|
||||||
node.setSelectedState(false);
|
node.setSelectedState(false);
|
||||||
node.set({
|
node.set({
|
||||||
x: node.G6Item.getModel().x,
|
x: node.G6Item.getModel().x,
|
||||||
y: node.G6Item.getModel().y
|
y: node.G6Item.getModel().y,
|
||||||
});
|
});
|
||||||
|
|
||||||
node.appendages.forEach(item => {
|
node.getAppendagesList().forEach(item => {
|
||||||
item.setSelectedState(false);
|
item.setSelectedState(false);
|
||||||
item.set({
|
item.set({
|
||||||
x: item.G6Item.getModel().x,
|
x: item.G6Item.getModel().x,
|
||||||
y: item.G6Item.getModel().y
|
y: item.G6Item.getModel().y,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
viewContainer.brushSelectedModels.forEach(item => {
|
viewContainer.brushSelectedModels.forEach(item => {
|
||||||
item.set({
|
item.set({
|
||||||
x: item.G6Item.getModel().x,
|
x: item.G6Item.getModel().x,
|
||||||
y: item.G6Item.getModel().y
|
y: item.G6Item.getModel().y,
|
||||||
});
|
});
|
||||||
|
|
||||||
if(item instanceof SVNode) {
|
if (item instanceof SVNode) {
|
||||||
item.appendages.forEach(appendage => {
|
item.getAppendagesList().forEach(appendage => {
|
||||||
appendage.set({
|
appendage.set({
|
||||||
x: appendage.G6Item.getModel().x,
|
x: appendage.G6Item.getModel().x,
|
||||||
y: appendage.G6Item.getModel().y
|
y: appendage.G6Item.getModel().y,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测框选到的节点是不是都可以被选中
|
* 检测框选到的节点是不是都可以被选中
|
||||||
* @param viewContainer
|
* @param viewContainer
|
||||||
*/
|
*/
|
||||||
export function SolveBrushSelectDrag(viewContainer: ViewContainer) {
|
export function SolveBrushSelectDrag(viewContainer: ViewContainer) {
|
||||||
const g6Instance: Graph = viewContainer.getG6Instance();
|
const g6Instance: Graph = viewContainer.getG6Instance();
|
||||||
|
|
||||||
// 当框选完成后,监听被框选节点的数量变化事件,将被框选的节点添加到 brushSelectedModels 数组里面
|
// 当框选完成后,监听被框选节点的数量变化事件,将被框选的节点添加到 brushSelectedModels 数组里面
|
||||||
g6Instance.on('nodeselectchange', event => {
|
g6Instance.on('nodeselectchange' as G6Event, event => {
|
||||||
const selectedItems = event.selectedItems as { nodes: INode[]; },
|
const selectedItems = event.selectedItems as { nodes: INode[] },
|
||||||
tmpSelectedModelList = [];
|
tmpSelectedModelList = [];
|
||||||
|
|
||||||
// 如果是点击选中,不理会
|
// 如果是点击选中,不理会
|
||||||
if (event.target) {
|
if (event.target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先清空上一次框选保存的内容
|
// 先清空上一次框选保存的内容
|
||||||
viewContainer.brushSelectedModels.length = 0;
|
viewContainer.brushSelectedModels.length = 0;
|
||||||
|
|
||||||
// 首先将已框选中的节点加到一个临时队列
|
// 首先将已框选中的节点加到一个临时队列
|
||||||
selectedItems.nodes.forEach(item => {
|
selectedItems.nodes.forEach(item => {
|
||||||
tmpSelectedModelList.push(item['SVModel']);
|
tmpSelectedModelList.push(item['SVModel']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 之后逐个检测被框选中的节点是否可以拖拽,可以拖拽的才加入到真正的框选队列
|
// 之后逐个检测被框选中的节点是否可以拖拽,可以拖拽的才加入到真正的框选队列
|
||||||
selectedItems.nodes.forEach(item => {
|
selectedItems.nodes.forEach(item => {
|
||||||
const node: SVNode = item['SVModel'];
|
const node: SVNode = item['SVModel'];
|
||||||
|
|
||||||
if (DetermineNodeDrag(viewContainer.getLayoutGroupTable(), node, tmpSelectedModelList)) {
|
if (DetermineNodeDrag(viewContainer.getLayoutGroupTable(), node, tmpSelectedModelList)) {
|
||||||
viewContainer.brushSelectedModels.push(node);
|
viewContainer.brushSelectedModels.push(node);
|
||||||
}
|
} else {
|
||||||
else {
|
node.setSelectedState(false);
|
||||||
node.setSelectedState(false);
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解决泄漏区随着视图拖动的问题
|
* 解决泄漏区随着视图拖动的问题
|
||||||
* @param g6Instance
|
* @param g6Instance
|
||||||
* @param hasLeak
|
* @param hasLeak
|
||||||
*/
|
*/
|
||||||
export function SolveDragCanvasWithLeak(viewContainer: ViewContainer) {
|
export function SolveDragCanvasWithLeak(viewContainer: ViewContainer) {
|
||||||
let g6Instance = viewContainer.getG6Instance(),
|
let g6Instance = viewContainer.getG6Instance();
|
||||||
prevDy = 0;
|
|
||||||
|
|
||||||
g6Instance.on('viewportchange', event => {
|
g6Instance.on('viewportchange', event => {
|
||||||
if (event.action !== 'translate') {
|
if (event.action !== 'translate') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let translateX = event.matrix[7],
|
let translateY = event.matrix[7],
|
||||||
dy = translateX - prevDy;
|
dy = translateY - viewContainer.lastLeakAreaTranslateY;
|
||||||
|
|
||||||
prevDy = translateX;
|
viewContainer.lastLeakAreaTranslateY = translateY;
|
||||||
|
|
||||||
viewContainer.leakAreaY = viewContainer.leakAreaY + dy;
|
viewContainer.leakAreaY = viewContainer.leakAreaY + dy;
|
||||||
if (viewContainer.hasLeak) {
|
if (viewContainer.hasLeak) {
|
||||||
EventBus.emit('onLeakAreaUpdate', {
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
leakAreaY: viewContainer.leakAreaY,
|
leakAreaY: viewContainer.leakAreaY,
|
||||||
hasLeak: viewContainer.hasLeak
|
hasLeak: viewContainer.hasLeak,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解决泄漏区随着视图缩放的问题(这里搞不出来,尽力了)
|
* 解决泄漏区随着视图缩放的问题(这里搞不出来,尽力了)
|
||||||
* @param g6Instance
|
* @param g6Instance
|
||||||
* @param generalModelsGroup
|
* @param generalModelsGroup
|
||||||
*/
|
*/
|
||||||
export function SolveZoomCanvasWithLeak(viewContainer: ViewContainer) {
|
export function SolveZoomCanvasWithLeak(viewContainer: ViewContainer) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -45,7 +45,7 @@ export function InitG6Behaviors(engine: Engine, viewContainer: ViewContainer): M
|
|||||||
// 这里之所以要把节点和其 appendages 的选中状态设置为true,是因为 g6 处理拖拽节点的逻辑是将所以已选中的元素一起拖动,
|
// 这里之所以要把节点和其 appendages 的选中状态设置为true,是因为 g6 处理拖拽节点的逻辑是将所以已选中的元素一起拖动,
|
||||||
// 这样 appendages 就可以很自然地跟着节点动(我是看源码才知道的)
|
// 这样 appendages 就可以很自然地跟着节点动(我是看源码才知道的)
|
||||||
node.setSelectedState(true);
|
node.setSelectedState(true);
|
||||||
node.appendages.forEach(item => {
|
node.getAppendagesList().forEach(item => {
|
||||||
item.setSelectedState(true);
|
item.setSelectedState(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,8 +53,13 @@ export function InitG6Behaviors(engine: Engine, viewContainer: ViewContainer): M
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectNodeFilter = event => {
|
const selectNodeFilter = event => {
|
||||||
let g6Item = event.item,
|
let g6Item = event.item;
|
||||||
node: SVNode = g6Item.SVModel;
|
|
||||||
|
if(g6Item === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let node: SVNode = g6Item.SVModel;
|
||||||
|
|
||||||
if (g6Item === null || node.isNode() === false) {
|
if (g6Item === null || node.isNode() === false) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,152 +1,161 @@
|
|||||||
import { Vector } from "./vector";
|
import { Vector } from './vector';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 包围盒类型
|
// 包围盒类型
|
||||||
export type BoundingRect = {
|
export type BoundingRect = {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 包围盒操作
|
// 包围盒操作
|
||||||
export const Bound = {
|
export const Bound = {
|
||||||
|
/**
|
||||||
|
* 从点集生成包围盒
|
||||||
|
* @param points
|
||||||
|
*/
|
||||||
|
fromPoints(points: Array<[number, number]>): BoundingRect {
|
||||||
|
let maxX = -Infinity,
|
||||||
|
minX = Infinity,
|
||||||
|
maxY = -Infinity,
|
||||||
|
minY = Infinity;
|
||||||
|
|
||||||
/**
|
points.map(item => {
|
||||||
* 从点集生成包围盒
|
if (item[0] > maxX) maxX = item[0];
|
||||||
* @param points
|
if (item[0] < minX) minX = item[0];
|
||||||
*/
|
if (item[1] > maxY) maxY = item[1];
|
||||||
fromPoints(points: Array<[number, number]>): BoundingRect {
|
if (item[1] < minY) minY = item[1];
|
||||||
let maxX = -Infinity,
|
});
|
||||||
minX = Infinity,
|
|
||||||
maxY = -Infinity,
|
|
||||||
minY = Infinity;
|
|
||||||
|
|
||||||
points.map(item => {
|
return {
|
||||||
if(item[0] > maxX) maxX = item[0];
|
x: minX,
|
||||||
if(item[0] < minX) minX = item[0];
|
y: minY,
|
||||||
if(item[1] > maxY) maxY = item[1];
|
width: maxX - minX,
|
||||||
if(item[1] < minY) minY = item[1];
|
height: maxY - minY,
|
||||||
});
|
};
|
||||||
|
},
|
||||||
|
|
||||||
return {
|
/**
|
||||||
x: minX,
|
* 由包围盒转化为四个顶点(顺时针)
|
||||||
y: minY,
|
* @param bound
|
||||||
width: maxX - minX,
|
*/
|
||||||
height: maxY - minY
|
toPoints(bound: BoundingRect): Array<[number, number]> {
|
||||||
};
|
return [
|
||||||
},
|
[bound.x, bound.y],
|
||||||
|
[bound.x + bound.width, bound.y],
|
||||||
|
[bound.x + bound.width, bound.y + bound.height],
|
||||||
|
[bound.x, bound.y + bound.height],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 由包围盒转化为四个顶点(顺时针)
|
* 求包围盒并集
|
||||||
* @param bound
|
* @param arg
|
||||||
*/
|
*/
|
||||||
toPoints(bound: BoundingRect): Array<[number, number]> {
|
union(...arg: BoundingRect[]): BoundingRect {
|
||||||
return [
|
if (arg.length === 0) {
|
||||||
[bound.x, bound.y],
|
return {
|
||||||
[bound.x + bound.width, bound.y],
|
x: 0,
|
||||||
[bound.x + bound.width, bound.y + bound.height],
|
y: 0,
|
||||||
[bound.x, bound.y + bound.height]
|
width: 0,
|
||||||
];
|
height: 0,
|
||||||
},
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return arg.length > 1
|
||||||
* 求包围盒并集
|
? arg.reduce((total, cur) => {
|
||||||
* @param arg
|
let minX = Math.min(total.x, cur.x),
|
||||||
*/
|
maxX = Math.max(total.x + total.width, cur.x + cur.width),
|
||||||
union(...arg: BoundingRect[]): BoundingRect {
|
minY = Math.min(total.y, cur.y),
|
||||||
return arg.length > 1?
|
maxY = Math.max(total.y + total.height, cur.y + cur.height);
|
||||||
arg.reduce((total, cur) => {
|
|
||||||
let minX = total.x < cur.x? total.x: cur.x,
|
|
||||||
maxX = total.x + total.width < cur.x + cur.width? cur.x + cur.width: total.x + total.width,
|
|
||||||
minY = total.y < cur.y? total.y: cur.y,
|
|
||||||
maxY = total.y + total.height < cur.y + cur.height? cur.y + cur.height: total.y + total.height;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: minX,
|
x: minX,
|
||||||
y: minY,
|
y: minY,
|
||||||
width: maxX - minX,
|
width: maxX - minX,
|
||||||
height: maxY - minY
|
height: maxY - minY,
|
||||||
};
|
};
|
||||||
}): arg[0];
|
})
|
||||||
},
|
: arg[0];
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 包围盒求交集
|
* 包围盒求交集
|
||||||
* @param b1
|
* @param b1
|
||||||
* @param b2
|
* @param b2
|
||||||
*/
|
*/
|
||||||
intersect(b1: BoundingRect, b2: BoundingRect): BoundingRect {
|
intersect(b1: BoundingRect, b2: BoundingRect): BoundingRect {
|
||||||
let x, y,
|
let x,
|
||||||
maxX, maxY,
|
y,
|
||||||
overlapsX,
|
maxX,
|
||||||
overlapsY;
|
maxY,
|
||||||
|
overlapsX,
|
||||||
|
overlapsY,
|
||||||
|
b1x = b1.x,
|
||||||
|
b1mx = b1.x + b1.width,
|
||||||
|
b2x = b2.x,
|
||||||
|
b2mx = b2.x + b2.width,
|
||||||
|
b1y = b1.y,
|
||||||
|
b1my = b1.y + b1.height,
|
||||||
|
b2y = b2.y,
|
||||||
|
b2my = b2.y + b2.height;
|
||||||
|
|
||||||
if(b1.x < b2.x + b2.width && b1.x + b1.width > b2.x) {
|
x = Math.max(b1x, b2x);
|
||||||
x = b1.x < b2.x? b2.x: b1.x;
|
maxX = Math.min(b1mx, b2mx);
|
||||||
// maxX = b1.x + b1.width < b2.x + b2.width? b1.x + b1.width: b2.x + b2.width;
|
overlapsX = maxX - x;
|
||||||
maxX = b1.x + b1.width;
|
|
||||||
overlapsX = maxX - x;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(b1.y < b2.y + b2.height && b1.y + b1.height > b2.y) {
|
|
||||||
y = b1.y < b2.y? b2.y: b1.y;
|
|
||||||
maxY = b1.y + b1.height < b2.y + b2.height? b1.y + b1.height: b2.y + b2.height;
|
|
||||||
overlapsY = maxY - y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!overlapsX || !overlapsY) return null;
|
y = Math.max(b1y, b2y);
|
||||||
|
maxY = Math.min(b1my, b2my);
|
||||||
|
overlapsY = maxY - y;
|
||||||
|
|
||||||
return {
|
if (!overlapsX || !overlapsY) return null;
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width: overlapsX,
|
|
||||||
height: overlapsY
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
return {
|
||||||
* 位移包围盒
|
x,
|
||||||
* @param bound
|
y,
|
||||||
* @param dx
|
width: overlapsX,
|
||||||
* @param dy
|
height: overlapsY,
|
||||||
*/
|
};
|
||||||
translate(bound: BoundingRect, dx: number, dy: number) {
|
},
|
||||||
bound.x += dx;
|
|
||||||
bound.y += dy;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 求包围盒旋转后新形成的包围盒
|
* 位移包围盒
|
||||||
* @param bound
|
* @param bound
|
||||||
* @param rot
|
* @param dx
|
||||||
*/
|
* @param dy
|
||||||
rotation(bound: BoundingRect, rot: number): BoundingRect {
|
*/
|
||||||
let cx = bound.x + bound.width / 2,
|
translate(bound: BoundingRect, dx: number, dy: number) {
|
||||||
cy = bound.y + bound.height / 2;
|
bound.x += dx;
|
||||||
|
bound.y += dy;
|
||||||
|
},
|
||||||
|
|
||||||
return Bound.fromPoints(Bound.toPoints(bound).map(item => Vector.rotation(rot, item, [cx, cy])));
|
/**
|
||||||
},
|
* 求包围盒旋转后新形成的包围盒
|
||||||
|
* @param bound
|
||||||
|
* @param rot
|
||||||
|
*/
|
||||||
|
rotation(bound: BoundingRect, rot: number): BoundingRect {
|
||||||
|
let cx = bound.x + bound.width / 2,
|
||||||
|
cy = bound.y + bound.height / 2;
|
||||||
|
|
||||||
/**
|
return Bound.fromPoints(Bound.toPoints(bound).map(item => Vector.rotation(rot, item, [cx, cy])));
|
||||||
* 判断两个包围盒是否相交
|
},
|
||||||
* @param b1
|
|
||||||
* @param b2
|
|
||||||
*/
|
|
||||||
isOverlap(b1: BoundingRect, b2: BoundingRect): boolean {
|
|
||||||
let maxX1 = b1.x + b1.width,
|
|
||||||
maxY1 = b1.y + b1.height,
|
|
||||||
maxX2 = b2.x + b2.width,
|
|
||||||
maxY2 = b2.y + b2.height;
|
|
||||||
|
|
||||||
if (b1.x < maxX2 && b2.x < maxX1 && b1.y < maxY2 && b2.y < maxY1) {
|
/**
|
||||||
return true;
|
* 判断两个包围盒是否相交
|
||||||
}
|
* @param b1
|
||||||
|
* @param b2
|
||||||
return false;
|
*/
|
||||||
}
|
isOverlap(b1: BoundingRect, b2: BoundingRect): boolean {
|
||||||
|
let maxX1 = b1.x + b1.width,
|
||||||
|
maxY1 = b1.y + b1.height,
|
||||||
|
maxX2 = b2.x + b2.width,
|
||||||
|
maxY2 = b2.y + b2.height;
|
||||||
|
|
||||||
|
if (b1.x < maxX2 && b2.x < maxX1 && b1.y < maxY2 && b2.y < maxY1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,21 +122,6 @@ export const Util = {
|
|||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* G6 data 转换器
|
|
||||||
* @param layoutGroup
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
convertG6Data(layoutGroup: LayoutGroup): GraphData {
|
|
||||||
let nodes = [...layoutGroup.node, ...layoutGroup.marker],
|
|
||||||
edges = layoutGroup.link;
|
|
||||||
|
|
||||||
return {
|
|
||||||
nodes: nodes.map(item => item.getG6ModelProps()) as NodeConfig[],
|
|
||||||
edges: edges.map(item => item.getG6ModelProps()) as EdgeConfig[]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 modelList 转换到 G6Data
|
* 将 modelList 转换到 G6Data
|
||||||
* @param modelList
|
* @param modelList
|
||||||
|
@ -1,52 +1,89 @@
|
|||||||
import { EdgeConfig, IEdge } from "@antv/g6-core";
|
import { EdgeConfig, IEdge } from '@antv/g6-core';
|
||||||
import { Util } from "../Common/util";
|
import { Util } from '../Common/util';
|
||||||
import { LinkLabelOption, LinkOption, Style } from "../options";
|
import { LinkLabelOption, LinkOption, Style } from '../options';
|
||||||
import { SVModel } from "./SVModel";
|
import { SVModel } from './SVModel';
|
||||||
import { SVNode } from "./SVNode";
|
import { SVNode } from './SVNode';
|
||||||
|
|
||||||
export class SVLink extends SVModel {
|
export class SVLink extends SVModel {
|
||||||
public node: SVNode;
|
public node: SVNode;
|
||||||
public target: SVNode;
|
public target: SVNode;
|
||||||
public linkIndex: number;
|
public linkIndex: number;
|
||||||
|
|
||||||
public shadowG6Item: IEdge;
|
public shadowG6Item: IEdge;
|
||||||
public G6Item: IEdge;
|
public G6Item: IEdge;
|
||||||
|
|
||||||
constructor(id: string, type: string, group: string, layout: string, node: SVNode, target: SVNode, index: number, options: LinkOption) {
|
public nodeId: string;
|
||||||
super(id, type, group, layout, 'link');
|
public targetId: string;
|
||||||
|
|
||||||
this.node = node;
|
constructor(
|
||||||
this.target = target;
|
id: string,
|
||||||
this.linkIndex = index;
|
type: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
node: SVNode,
|
||||||
|
target: SVNode,
|
||||||
|
index: number,
|
||||||
|
options: LinkOption
|
||||||
|
) {
|
||||||
|
super(id, type, group, layout, 'link');
|
||||||
|
|
||||||
node.links.outDegree.push(this);
|
this.node = node;
|
||||||
target.links.inDegree.push(this);
|
this.target = target;
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
this.nodeId = node.id;
|
||||||
}
|
this.targetId = target.id;
|
||||||
|
this.linkIndex = index;
|
||||||
|
|
||||||
generateG6ModelProps(options: LinkOption): EdgeConfig {
|
node.links.outDegree.push(this);
|
||||||
let sourceAnchor = options.sourceAnchor,
|
target.links.inDegree.push(this);
|
||||||
targetAnchor = options.targetAnchor;
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
|
}
|
||||||
|
|
||||||
if(options.sourceAnchor && typeof options.sourceAnchor === 'function' && this.linkIndex !== null) {
|
generateG6ModelProps(options: LinkOption): EdgeConfig {
|
||||||
sourceAnchor = options.sourceAnchor(this.linkIndex);
|
let sourceAnchor = options.sourceAnchor,
|
||||||
}
|
targetAnchor = options.targetAnchor;
|
||||||
|
|
||||||
if(options.targetAnchor && typeof options.targetAnchor === 'function' && this.linkIndex !== null) {
|
if (options.sourceAnchor && typeof options.sourceAnchor === 'function' && this.linkIndex !== null) {
|
||||||
targetAnchor = options.targetAnchor(this.linkIndex);
|
sourceAnchor = options.sourceAnchor(this.linkIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
if (options.targetAnchor && typeof options.targetAnchor === 'function' && this.linkIndex !== null) {
|
||||||
id: this.id,
|
targetAnchor = options.targetAnchor(this.linkIndex);
|
||||||
type: options.type,
|
}
|
||||||
source: this.node.id,
|
|
||||||
target: this.target.id,
|
let label = this.target.sourceNode.freed ? 'freed' : '',
|
||||||
sourceAnchor,
|
labelCfg = this.target.sourceNode.freed
|
||||||
targetAnchor,
|
? { position: 'start', autoRotate: true, refY: 7, style: { fontSize: 11, opacity: 0.8 } }
|
||||||
label: options.label,
|
: Util.objectClone<LinkLabelOption>(options.labelOptions || ({} as LinkLabelOption));
|
||||||
style: Util.objectClone<Style>(options.style),
|
|
||||||
labelCfg: Util.objectClone<LinkLabelOption>(options.labelOptions),
|
return {
|
||||||
curveOffset: options.curveOffset
|
id: this.id,
|
||||||
};
|
type: options.type,
|
||||||
}
|
source: this.node.id,
|
||||||
};
|
target: this.target.id,
|
||||||
|
sourceAnchor,
|
||||||
|
targetAnchor,
|
||||||
|
label,
|
||||||
|
style: Util.objectClone<Style>(options.style),
|
||||||
|
labelCfg,
|
||||||
|
curveOffset: options.curveOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerHighlight(changeHighlightColor: string) {
|
||||||
|
this.originStyle = Util.objectClone(this.G6ModelProps.style);
|
||||||
|
this.set('style', {
|
||||||
|
stroke: changeHighlightColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isEqual(link: SVLink): boolean {
|
||||||
|
return link.targetId === this.targetId && link.nodeId === this.nodeId && link.linkIndex === this.linkIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeDestroy(): void {
|
||||||
|
Util.removeFromList(this.target.links.inDegree, item => item.id === this.id);
|
||||||
|
Util.removeFromList(this.node.links.outDegree, item => item.id === this.id);
|
||||||
|
this.node = null;
|
||||||
|
this.target = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,220 +1,223 @@
|
|||||||
import { Util } from "../Common/util";
|
import { Util } from '../Common/util';
|
||||||
import { Style } from "../options";
|
import { Style } from '../options';
|
||||||
import { BoundingRect } from "../Common/boundingRect";
|
import { BoundingRect } from '../Common/boundingRect';
|
||||||
import { EdgeConfig, Item, NodeConfig } from "@antv/g6-core";
|
import { EdgeConfig, Item, NodeConfig } from '@antv/g6-core';
|
||||||
import { Graph } from "@antv/g6";
|
import { Graph } from '@antv/g6-pc';
|
||||||
import merge from 'merge';
|
import merge from 'merge';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class SVModel {
|
export class SVModel {
|
||||||
public id: string;
|
public id: string;
|
||||||
public sourceType: string;
|
public sourceType: string;
|
||||||
|
|
||||||
public g6Instance: Graph;
|
public g6Instance: Graph;
|
||||||
public shadowG6Instance: Graph;
|
public shadowG6Instance: Graph;
|
||||||
public group: string;
|
public group: string;
|
||||||
public layout: string;
|
public layout: string;
|
||||||
public G6ModelProps: NodeConfig | EdgeConfig;
|
public G6ModelProps: NodeConfig | EdgeConfig;
|
||||||
public shadowG6Item: Item;
|
public shadowG6Item: Item;
|
||||||
public G6Item: Item;
|
public G6Item: Item;
|
||||||
|
|
||||||
public preLayout: boolean; // 是否进入预备布局阶段
|
public preLayout: boolean; // 是否进入预备布局阶段
|
||||||
public discarded: boolean;
|
public discarded: boolean;
|
||||||
public freed: boolean;
|
public freed: boolean;
|
||||||
public leaked: boolean;
|
public leaked: boolean;
|
||||||
public generalStyle: Partial<Style>;
|
public originStyle: Partial<Style>; // 用作保存修改前的样式
|
||||||
|
|
||||||
private transformMatrix: number[];
|
private transformMatrix: number[];
|
||||||
private modelType: string;
|
private modelType: string;
|
||||||
|
|
||||||
public layoutX: number;
|
public layoutX: number;
|
||||||
public layoutY: number;
|
public layoutY: number;
|
||||||
|
|
||||||
constructor(id: string, type: string, group: string, layout: string, modelType: string) {
|
constructor(id: string, type: string, group: string, layout: string, modelType: string) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.sourceType = type;
|
this.sourceType = type;
|
||||||
this.group = group;
|
this.group = group;
|
||||||
this.layout = layout;
|
this.layout = layout;
|
||||||
this.shadowG6Item = null;
|
this.shadowG6Item = null;
|
||||||
|
this.G6Item = null;
|
||||||
|
this.preLayout = false;
|
||||||
|
this.discarded = false;
|
||||||
|
this.freed = false;
|
||||||
|
this.leaked = false;
|
||||||
|
this.transformMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
||||||
|
this.modelType = modelType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* 定义 G6 model 的属性
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
generateG6ModelProps(options: unknown): NodeConfig | EdgeConfig {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 G6 model 的属性
|
||||||
|
* @param attr
|
||||||
|
*/
|
||||||
|
get(attr: string): any {
|
||||||
|
return this.G6ModelProps[attr];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 G6 model 的属性
|
||||||
|
* @param attr
|
||||||
|
* @param value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
set(attr: string | object, value?: any) {
|
||||||
|
if (this.discarded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof attr === 'object') {
|
||||||
|
Object.keys(attr).map(item => {
|
||||||
|
this.set(item, attr[item]);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.G6ModelProps[attr] === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr === 'style' || attr === 'labelCfg') {
|
||||||
|
this.G6ModelProps[attr] = merge(this.G6ModelProps[attr] || {}, value);
|
||||||
|
} else {
|
||||||
|
this.G6ModelProps[attr] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr === 'rotation') {
|
||||||
|
const matrix = Util.calcRotateMatrix(this.getMatrix(), value);
|
||||||
|
this.setMatrix(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新G6Item
|
||||||
|
if (this.G6Item) {
|
||||||
|
if (this.preLayout) {
|
||||||
|
const G6ItemModel = this.G6Item.getModel();
|
||||||
|
G6ItemModel[attr] = value;
|
||||||
|
} else {
|
||||||
|
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新shadowG6Item
|
||||||
|
if (this.shadowG6Item) {
|
||||||
|
this.shadowG6Instance.updateItem(this.shadowG6Item, this.G6ModelProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param G6ModelProps
|
||||||
|
*/
|
||||||
|
updateG6ModelStyle(G6ModelProps: NodeConfig | EdgeConfig) {
|
||||||
|
const newG6ModelProps = {
|
||||||
|
style: {
|
||||||
|
...G6ModelProps.style,
|
||||||
|
},
|
||||||
|
labelCfg: {
|
||||||
|
...G6ModelProps.labelCfg,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.G6ModelProps = merge.recursive(this.G6ModelProps, newG6ModelProps);
|
||||||
|
|
||||||
|
if (this.G6Item) {
|
||||||
|
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shadowG6Item) {
|
||||||
|
this.shadowG6Instance.updateItem(this.shadowG6Item, this.G6ModelProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取包围盒
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getBound(): BoundingRect {
|
||||||
|
return this.shadowG6Item.getBBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取变换矩阵
|
||||||
|
*/
|
||||||
|
getMatrix(): number[] {
|
||||||
|
return [...this.transformMatrix];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置变换矩阵
|
||||||
|
* @param matrix
|
||||||
|
*/
|
||||||
|
setMatrix(matrix: number[]) {
|
||||||
|
this.transformMatrix = matrix;
|
||||||
|
this.set('style', { matrix });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否被选中的状态
|
||||||
|
* @param isSelected
|
||||||
|
*/
|
||||||
|
setSelectedState(isSelected: boolean) {
|
||||||
|
if (this.G6Item === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.G6Item.setState('selected', isSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getG6ModelProps(): NodeConfig | EdgeConfig {
|
||||||
|
return Util.objectClone(this.G6ModelProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 model 类型
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getModelType(): string {
|
||||||
|
return this.modelType;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerHighlight(changeHighlightColor: string) {
|
||||||
|
this.originStyle = Util.objectClone(this.G6ModelProps.style);
|
||||||
|
this.set('style', {
|
||||||
|
fill: changeHighlightColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreHighlight() {
|
||||||
|
if (this.originStyle) {
|
||||||
|
this.set('style', { ...this.originStyle });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为节点model(SVNode)
|
||||||
|
*/
|
||||||
|
isNode(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEqual(model: SVModel): boolean {
|
||||||
|
return this.id === model.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeRender () {}
|
||||||
|
afterRender() {}
|
||||||
|
|
||||||
|
beforeDestroy () {}
|
||||||
|
afterDestroy() {
|
||||||
this.G6Item = null;
|
this.G6Item = null;
|
||||||
this.preLayout = false;
|
this.shadowG6Instance = null;
|
||||||
this.discarded = false;
|
this.shadowG6Item = null;
|
||||||
this.freed = false;
|
|
||||||
this.leaked = false;
|
|
||||||
this.transformMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
||||||
this.modelType = modelType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* 定义 G6 model 的属性
|
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
generateG6ModelProps(options: unknown): NodeConfig | EdgeConfig {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 G6 model 的属性
|
|
||||||
* @param attr
|
|
||||||
*/
|
|
||||||
get(attr: string): any {
|
|
||||||
return this.G6ModelProps[attr];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 G6 model 的属性
|
|
||||||
* @param attr
|
|
||||||
* @param value
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
set(attr: string | object, value?: any) {
|
|
||||||
if (this.discarded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof attr === 'object') {
|
|
||||||
Object.keys(attr).map(item => {
|
|
||||||
this.set(item, attr[item]);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.G6ModelProps[attr] === value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attr === 'style' || attr === 'labelCfg') {
|
|
||||||
this.G6ModelProps[attr] = merge(this.G6ModelProps[attr] || {}, value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.G6ModelProps[attr] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attr === 'rotation') {
|
|
||||||
const matrix = Util.calcRotateMatrix(this.getMatrix(), value);
|
|
||||||
this.setMatrix(matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新G6Item
|
|
||||||
if (this.G6Item) {
|
|
||||||
if (this.preLayout) {
|
|
||||||
const G6ItemModel = this.G6Item.getModel();
|
|
||||||
G6ItemModel[attr] = value;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新shadowG6Item
|
|
||||||
if (this.shadowG6Item) {
|
|
||||||
this.shadowG6Instance.updateItem(this.shadowG6Item, this.G6ModelProps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param G6ModelProps
|
|
||||||
*/
|
|
||||||
updateG6ModelStyle(G6ModelProps: NodeConfig | EdgeConfig) {
|
|
||||||
const newG6ModelProps = {
|
|
||||||
style: {
|
|
||||||
...G6ModelProps.style
|
|
||||||
},
|
|
||||||
labelCfg: {
|
|
||||||
...G6ModelProps.labelCfg
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.G6ModelProps = merge.recursive(this.G6ModelProps, newG6ModelProps);
|
|
||||||
|
|
||||||
if (this.G6Item) {
|
|
||||||
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.shadowG6Item) {
|
|
||||||
this.shadowG6Instance.updateItem(this.shadowG6Item, this.G6ModelProps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取包围盒
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
getBound(): BoundingRect {
|
|
||||||
return this.shadowG6Item.getBBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取变换矩阵
|
|
||||||
*/
|
|
||||||
getMatrix(): number[] {
|
|
||||||
return [...this.transformMatrix];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置变换矩阵
|
|
||||||
* @param matrix
|
|
||||||
*/
|
|
||||||
setMatrix(matrix: number[]) {
|
|
||||||
this.transformMatrix = matrix;
|
|
||||||
this.set('style', { matrix });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置是否被选中的状态
|
|
||||||
* @param isSelected
|
|
||||||
*/
|
|
||||||
setSelectedState(isSelected: boolean) {
|
|
||||||
if(this.G6Item === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.G6Item.setState('selected', isSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
getG6ModelProps(): NodeConfig | EdgeConfig {
|
|
||||||
return Util.objectClone(this.G6ModelProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 model 类型
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
getModelType(): string {
|
|
||||||
return this.modelType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为节点model(SVNode)
|
|
||||||
*/
|
|
||||||
isNode(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,101 +1,119 @@
|
|||||||
import { INode, NodeConfig } from "@antv/g6-core";
|
import { INode, NodeConfig } from '@antv/g6-core';
|
||||||
import { Util } from "../Common/util";
|
import { Util } from '../Common/util';
|
||||||
import { NodeLabelOption, NodeOption, Style } from "../options";
|
import { NodeLabelOption, NodeOption, Style } from '../options';
|
||||||
import { SourceNode } from "../sources";
|
import { SourceNode } from '../sources';
|
||||||
import { SVLink } from "./SVLink";
|
import { ModelConstructor } from './modelConstructor';
|
||||||
import { SVModel } from "./SVModel";
|
import { SVLink } from './SVLink';
|
||||||
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker, SVNodeAppendage } from "./SVNodeAppendage";
|
import { SVModel } from './SVModel';
|
||||||
|
import { SVNodeAppendage } from './SVNodeAppendage';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class SVNode extends SVModel {
|
export class SVNode extends SVModel {
|
||||||
public sourceId: string;
|
public sourceId: string;
|
||||||
public sourceNode: SourceNode;
|
public sourceNode: SourceNode;
|
||||||
public links: {
|
public links: {
|
||||||
inDegree: SVLink[];
|
inDegree: SVLink[];
|
||||||
outDegree: SVLink[];
|
outDegree: SVLink[];
|
||||||
};
|
};
|
||||||
|
|
||||||
private label: string | string[];
|
private label: string | string[];
|
||||||
private disable: boolean;
|
private disable: boolean;
|
||||||
|
public appendages: { [key: string]: SVNodeAppendage[] };
|
||||||
|
|
||||||
public shadowG6Item: INode;
|
constructor(
|
||||||
public G6Item: INode;
|
id: string,
|
||||||
|
type: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
sourceNode: SourceNode,
|
||||||
|
label: string | string[],
|
||||||
|
options: NodeOption
|
||||||
|
) {
|
||||||
|
super(id, type, group, layout, 'node');
|
||||||
|
|
||||||
public marker: SVMarker;
|
this.group = group;
|
||||||
public freedLabel: SVFreedLabel;
|
this.layout = layout;
|
||||||
public indexLabel: SVIndexLabel;
|
|
||||||
public addressLabel: SVAddressLabel;
|
|
||||||
public appendages: SVNodeAppendage[];
|
|
||||||
|
|
||||||
constructor(id: string, type: string, group: string, layout: string, sourceNode: SourceNode, label: string | string[], options: NodeOption) {
|
Object.keys(sourceNode).map(prop => {
|
||||||
super(id, type, group, layout, 'node');
|
if (prop !== 'id') {
|
||||||
|
this[prop] = sourceNode[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.group = group;
|
this.sourceNode = sourceNode;
|
||||||
this.layout = layout;
|
this.sourceId = sourceNode.id.toString();
|
||||||
|
|
||||||
|
this.links = { inDegree: [], outDegree: [] };
|
||||||
|
this.appendages = {};
|
||||||
|
this.sourceNode = sourceNode;
|
||||||
|
this.label = label;
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
|
}
|
||||||
|
|
||||||
Object.keys(sourceNode).map(prop => {
|
generateG6ModelProps(options: NodeOption): NodeConfig {
|
||||||
if (prop !== 'id') {
|
const style = Util.objectClone<Style>(options.style);
|
||||||
this[prop] = sourceNode[prop];
|
|
||||||
}
|
return {
|
||||||
|
...this.sourceNode,
|
||||||
|
id: this.id,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
rotation: options.rotation || 0,
|
||||||
|
type: options.type,
|
||||||
|
size: options.size || [60, 30],
|
||||||
|
anchorPoints: options.anchorPoints,
|
||||||
|
label: this.label as string,
|
||||||
|
style: {
|
||||||
|
...style,
|
||||||
|
},
|
||||||
|
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isNode(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否被选中的状态
|
||||||
|
* @param isSelected
|
||||||
|
*/
|
||||||
|
setSelectedState(isSelected: boolean) {
|
||||||
|
if (this.G6Item === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.G6Item.setState('selected', isSelected);
|
||||||
|
this.getAppendagesList().forEach(item => {
|
||||||
|
item.setSelectedState(isSelected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSourceId(): string {
|
||||||
|
return this.sourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAppendagesList(): SVNodeAppendage[] {
|
||||||
|
const list = [];
|
||||||
|
|
||||||
|
Object.entries(this.appendages).forEach(item => {
|
||||||
|
list.push(...item[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sourceNode = sourceNode;
|
return list;
|
||||||
this.sourceId = sourceNode.id.toString();
|
|
||||||
|
|
||||||
this.links = { inDegree: [], outDegree: [] };
|
|
||||||
this.appendages = [];
|
|
||||||
this.sourceNode = sourceNode;
|
|
||||||
this.label = label;
|
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generateG6ModelProps(options: NodeOption): NodeConfig {
|
/**
|
||||||
const style = Util.objectClone<Style>(options.style);
|
* 判断这个节点是否来自相同group
|
||||||
|
* @param node
|
||||||
return {
|
*/
|
||||||
...this.sourceNode,
|
isSameGroup(node: SVNode): boolean {
|
||||||
id: this.id,
|
return ModelConstructor.isSameGroup(this, node);
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
rotation: options.rotation || 0,
|
|
||||||
type: options.type,
|
|
||||||
size: options.size || [60, 30],
|
|
||||||
anchorPoints: options.anchorPoints,
|
|
||||||
label: this.label as string,
|
|
||||||
style: {
|
|
||||||
...style,
|
|
||||||
fill: this.disable ? '#ccc' : style.fill
|
|
||||||
},
|
|
||||||
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isNode(): boolean {
|
beforeDestroy(): void {
|
||||||
return true;
|
this.sourceNode = null;
|
||||||
|
this.links.inDegree.length = 0;
|
||||||
|
this.links.outDegree.length = 0;
|
||||||
|
this.appendages = {};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* 设置是否被选中的状态
|
|
||||||
* @param isSelected
|
|
||||||
*/
|
|
||||||
setSelectedState(isSelected: boolean) {
|
|
||||||
if (this.G6Item === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.G6Item.setState('selected', isSelected);
|
|
||||||
this.appendages.forEach(item => {
|
|
||||||
item.setSelectedState(isSelected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getSourceId(): string {
|
|
||||||
return this.sourceId;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,182 +1,206 @@
|
|||||||
import { INode, NodeConfig, EdgeConfig } from "@antv/g6-core";
|
import { INode, NodeConfig, EdgeConfig } from '@antv/g6-core';
|
||||||
import { Util } from "../Common/util";
|
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||||
import { AddressLabelOption, IndexLabelOption, MarkerOption, NodeLabelOption, Style } from "../options";
|
import { Util } from '../Common/util';
|
||||||
import { SVModel } from "./SVModel";
|
import { AddressLabelOption, IndexLabelOption, MarkerOption, NodeLabelOption, Style } from '../options';
|
||||||
import { SVNode } from "./SVNode";
|
import { SVModel } from './SVModel';
|
||||||
|
import { SVNode } from './SVNode';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class SVNodeAppendage extends SVModel {
|
export class SVNodeAppendage extends SVModel {
|
||||||
public target: SVNode;
|
public targetId: string;
|
||||||
|
public target: SVNode;
|
||||||
|
public label: string;
|
||||||
|
|
||||||
constructor(id: string, type: string, group: string, layout: string, modelType: string, target: SVNode) {
|
constructor(id: string, type: string, group: string, layout: string, modelType: string, target: SVNode, label: string | string[]) {
|
||||||
super(id, type, group, layout, modelType);
|
super(id, type, group, layout, modelType);
|
||||||
|
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.target.appendages.push(this);
|
this.targetId = target.id;
|
||||||
}
|
this.label = typeof label === 'string' ? label : label.join(', ');
|
||||||
|
|
||||||
|
if (this.target.appendages[modelType] === undefined) {
|
||||||
|
this.target.appendages[modelType] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.target.appendages[modelType].push(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeDestroy(): void {
|
||||||
|
const targetAppendageList = this.target.appendages[this.getModelType()];
|
||||||
|
|
||||||
|
if (targetAppendageList) {
|
||||||
|
Util.removeFromList(targetAppendageList, item => item.id === this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.target = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEqual(appendage: SVNodeAppendage): boolean {
|
||||||
|
return appendage.targetId === this.targetId && appendage.label === this.label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 已释放节点下面的文字(“已释放‘)
|
* 已释放节点下面的文字(“已释放‘)
|
||||||
*/
|
*/
|
||||||
export class SVFreedLabel extends SVNodeAppendage {
|
export class SVFreedLabel extends SVNodeAppendage {
|
||||||
constructor(id: string, type: string, group: string, layout: string, target: SVNode) {
|
constructor(id: string, type: string, group: string, layout: string, target: SVNode) {
|
||||||
super(id, type, group, layout, 'freedLabel', target);
|
super(id, type, group, layout, 'freedLabel', target, '已释放');
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps();
|
||||||
|
}
|
||||||
|
|
||||||
this.target.freedLabel = this;
|
generateG6ModelProps() {
|
||||||
this.G6ModelProps = this.generateG6ModelProps();
|
return {
|
||||||
}
|
id: this.id,
|
||||||
|
x: 0,
|
||||||
generateG6ModelProps() {
|
y: 0,
|
||||||
return {
|
type: 'rect',
|
||||||
id: this.id,
|
label: this.label,
|
||||||
x: 0,
|
labelCfg: {
|
||||||
y: 0,
|
style: {
|
||||||
type: 'rect',
|
fill: '#b83b5e',
|
||||||
label: '已释放',
|
opacity: 0.6,
|
||||||
labelCfg: {
|
},
|
||||||
style: {
|
},
|
||||||
fill: '#b83b5e',
|
size: [0, 0],
|
||||||
opacity: 0.6
|
style: {
|
||||||
}
|
opacity: 0,
|
||||||
},
|
stroke: null,
|
||||||
size: [0, 0],
|
fill: 'transparent',
|
||||||
style: {
|
},
|
||||||
opacity: 0,
|
};
|
||||||
stroke: null,
|
}
|
||||||
fill: 'transparent'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 被移动到泄漏区的节点上面显示的地址
|
* 被移动到泄漏区的节点上面显示的地址
|
||||||
*/
|
*/
|
||||||
export class SVAddressLabel extends SVNodeAppendage {
|
export class SVAddressLabel extends SVNodeAppendage {
|
||||||
private sourceId: string;
|
constructor(id: string, type: string, group: string, layout: string, target: SVNode, options: AddressLabelOption) {
|
||||||
|
super(id, type, group, layout, 'addressLabel', target, target.sourceId);
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(id: string, type: string, group: string, layout: string, target: SVNode, options: AddressLabelOption) {
|
getBound(): BoundingRect {
|
||||||
super(id, type, group, layout, 'addressLabel', target);
|
const textBound = this.shadowG6Item.getContainer().getChildren()[1].getBBox(),
|
||||||
|
keyBound = this.shadowG6Item.getBBox();
|
||||||
|
return {
|
||||||
|
x: keyBound.x + textBound.x,
|
||||||
|
y: keyBound.y + textBound.y,
|
||||||
|
width: textBound.width,
|
||||||
|
height: textBound.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.sourceId = target.sourceId;
|
generateG6ModelProps(options: AddressLabelOption) {
|
||||||
this.target.addressLabel = this;
|
return {
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
id: this.id,
|
||||||
}
|
x: 0,
|
||||||
|
y: 0,
|
||||||
generateG6ModelProps(options: AddressLabelOption) {
|
type: 'rect',
|
||||||
return {
|
label: this.label,
|
||||||
id: this.id,
|
labelCfg: {
|
||||||
x: 0,
|
style: {
|
||||||
y: 0,
|
fill: '#666',
|
||||||
type: 'rect',
|
fontSize: 16,
|
||||||
label: this.sourceId,
|
...options.style,
|
||||||
labelCfg: {
|
},
|
||||||
style: {
|
},
|
||||||
fill: '#666',
|
size: [0, 0],
|
||||||
fontSize: 16,
|
style: {
|
||||||
...options.style
|
stroke: null,
|
||||||
}
|
fill: 'transparent',
|
||||||
},
|
},
|
||||||
size: [0, 0],
|
};
|
||||||
style: {
|
}
|
||||||
stroke: null,
|
|
||||||
fill: 'transparent'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点的下标文字
|
* 节点的下标文字
|
||||||
*/
|
*/
|
||||||
export class SVIndexLabel extends SVNodeAppendage {
|
export class SVIndexLabel extends SVNodeAppendage {
|
||||||
private value: string;
|
constructor(
|
||||||
|
id: string,
|
||||||
|
indexName: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
value: string,
|
||||||
|
target: SVNode,
|
||||||
|
options: IndexLabelOption
|
||||||
|
) {
|
||||||
|
super(id, indexName, group, layout, 'indexLabel', target, value);
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps(options) as NodeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(id: string, indexName: string, group: string, layout: string, value: string, target: SVNode, options: IndexLabelOption) {
|
generateG6ModelProps(options: IndexLabelOption): NodeConfig | EdgeConfig {
|
||||||
super(id, indexName, group, layout, 'indexLabel', target);
|
return {
|
||||||
|
id: this.id,
|
||||||
this.target.indexLabel = this;
|
x: 0,
|
||||||
this.value = value;
|
y: 0,
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options) as NodeConfig;
|
type: 'rect',
|
||||||
}
|
label: this.label,
|
||||||
|
labelCfg: {
|
||||||
generateG6ModelProps(options: IndexLabelOption): NodeConfig | EdgeConfig {
|
style: {
|
||||||
return {
|
fill: '#bbb',
|
||||||
id: this.id,
|
textAlign: 'center',
|
||||||
x: 0,
|
textBaseline: 'middle',
|
||||||
y: 0,
|
fontSize: 14,
|
||||||
type: 'rect',
|
fontStyle: 'italic',
|
||||||
label: this.value,
|
...options.style,
|
||||||
labelCfg: {
|
},
|
||||||
style: {
|
},
|
||||||
fill: '#bbb',
|
size: [0, 0],
|
||||||
textAlign: 'center',
|
style: {
|
||||||
textBaseline: 'middle',
|
stroke: null,
|
||||||
fontSize: 14,
|
fill: 'transparent',
|
||||||
fontStyle: 'italic',
|
},
|
||||||
...options.style
|
};
|
||||||
}
|
}
|
||||||
},
|
|
||||||
size: [0, 0],
|
|
||||||
style: {
|
|
||||||
stroke: null,
|
|
||||||
fill: 'transparent'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 外部指针
|
* 外部指针
|
||||||
*/
|
*/
|
||||||
export class SVMarker extends SVNodeAppendage {
|
export class SVMarker extends SVNodeAppendage {
|
||||||
public label: string | string[];
|
public anchor: number;
|
||||||
public anchor: number;
|
|
||||||
|
|
||||||
public shadowG6Item: INode;
|
public shadowG6Item: INode;
|
||||||
public G6Item: INode;
|
public G6Item: INode;
|
||||||
|
|
||||||
constructor(id: string, type: string, group: string, layout: string, label: string | string[], target: SVNode, options: MarkerOption) {
|
constructor(
|
||||||
super(id, type, group, layout, 'marker', target);
|
id: string,
|
||||||
|
type: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
label: string | string[],
|
||||||
|
target: SVNode,
|
||||||
|
options: MarkerOption
|
||||||
|
) {
|
||||||
|
super(id, type, group, layout, 'marker', target, label);
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
|
}
|
||||||
|
|
||||||
this.label = label;
|
generateG6ModelProps(options: MarkerOption): NodeConfig {
|
||||||
|
this.anchor = options.anchor;
|
||||||
|
|
||||||
this.target.marker = this;
|
const type = options.type,
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
defaultSize: [number, number] = type === 'pointer' ? [8, 30] : [12, 12];
|
||||||
}
|
|
||||||
|
|
||||||
generateG6ModelProps(options: MarkerOption): NodeConfig {
|
return {
|
||||||
this.anchor = options.anchor;
|
id: this.id,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
rotation: 0,
|
||||||
|
type: options.type || 'marker',
|
||||||
|
size: options.size || defaultSize,
|
||||||
|
anchorPoints: null,
|
||||||
|
label: this.label,
|
||||||
|
style: Util.objectClone<Style>(options.style),
|
||||||
|
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const type = options.type,
|
public getLabelSizeRadius(): number {
|
||||||
defaultSize: [number, number] = type === 'pointer' ? [8, 30] : [12, 12];
|
const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox();
|
||||||
|
return Math.max(width, height);
|
||||||
return {
|
}
|
||||||
id: this.id,
|
}
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
rotation: 0,
|
|
||||||
type: options.type || 'marker',
|
|
||||||
size: options.size || defaultSize,
|
|
||||||
anchorPoints: null,
|
|
||||||
label: typeof this.label === 'string' ? this.label : this.label.join(', '),
|
|
||||||
style: Util.objectClone<Style>(options.style),
|
|
||||||
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getLabelSizeRadius(): number {
|
|
||||||
const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox();
|
|
||||||
return Math.max(width, height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -1,440 +1,590 @@
|
|||||||
import { Util } from "../Common/util";
|
import { Group } from '../Common/group';
|
||||||
import { Engine } from "../engine";
|
import { Util } from '../Common/util';
|
||||||
import { AddressLabelOption, IndexLabelOption, LayoutCreator, LayoutGroupOptions, LinkOption, MarkerOption, NodeOption } from "../options";
|
import { Engine } from '../engine';
|
||||||
import { sourceLinkData, LinkTarget, Sources, SourceNode } from "../sources";
|
import {
|
||||||
import { SV } from "../StructV";
|
AddressLabelOption,
|
||||||
import { SVLink } from "./SVLink";
|
IndexLabelOption,
|
||||||
import { SVModel } from "./SVModel";
|
LayoutCreator,
|
||||||
import { SVNode } from "./SVNode";
|
LayoutGroupOptions,
|
||||||
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker } from "./SVNodeAppendage";
|
LinkOption,
|
||||||
|
MarkerOption,
|
||||||
|
NodeOption,
|
||||||
|
} from '../options';
|
||||||
|
import { sourceLinkData, LinkTarget, Sources, SourceNode } from '../sources';
|
||||||
|
import { SV } from '../StructV';
|
||||||
|
import { SVLink } from './SVLink';
|
||||||
|
import { SVModel } from './SVModel';
|
||||||
|
import { SVNode } from './SVNode';
|
||||||
|
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker, SVNodeAppendage } from './SVNodeAppendage';
|
||||||
|
|
||||||
export type LayoutGroup = {
|
export type LayoutGroup = {
|
||||||
name: string;
|
name: string;
|
||||||
node: SVNode[];
|
node: SVNode[];
|
||||||
indexLabel: SVIndexLabel[];
|
appendage: SVNodeAppendage[];
|
||||||
freedLabel: SVFreedLabel[];
|
link: SVLink[];
|
||||||
addressLabel: SVAddressLabel[];
|
layoutCreator: LayoutCreator;
|
||||||
link: SVLink[];
|
layout: string;
|
||||||
marker: SVMarker[];
|
options: LayoutGroupOptions;
|
||||||
layoutCreator: LayoutCreator;
|
modelList: SVModel[];
|
||||||
layout: string;
|
isHide: boolean;
|
||||||
options: LayoutGroupOptions;
|
|
||||||
modelList: SVModel[];
|
|
||||||
isHide: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type LayoutGroupTable = Map<string, LayoutGroup>;
|
export type LayoutGroupTable = Map<string, LayoutGroup>;
|
||||||
|
|
||||||
|
|
||||||
export class ModelConstructor {
|
export class ModelConstructor {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private layoutGroupTable: LayoutGroupTable;
|
private layoutGroupTable: LayoutGroupTable;
|
||||||
private prevSourcesStringMap: { [key: string]: string }; // 保存上一次源数据转换为字符串之后的值,用作比较该次源数据和上一次源数据是否有差异,若相同,则可跳过重复构建过程
|
private prevSourcesStringMap: { [key: string]: string }; // 保存上一次源数据转换为字符串之后的值,用作比较该次源数据和上一次源数据是否有差异,若相同,则可跳过重复构建过程
|
||||||
|
|
||||||
constructor(engine: Engine) {
|
constructor(engine: Engine) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.prevSourcesStringMap = {};
|
this.prevSourcesStringMap = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建SVNode,SVLink, SVMarker, SVAddressLabel, SVIndexLabel等
|
* 构建SVNode,SVLink, SVMarker, SVAddressLabel, SVIndexLabel等
|
||||||
* @param sourceList
|
* @param sourceList
|
||||||
*/
|
*/
|
||||||
public construct(sources: Sources): LayoutGroupTable {
|
public construct(sources: Sources): LayoutGroupTable {
|
||||||
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
||||||
layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout;
|
layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout;
|
||||||
|
|
||||||
|
|
||||||
Object.keys(sources).forEach(group => {
|
Object.keys(sources).forEach(group => {
|
||||||
let sourceGroup = sources[group],
|
let sourceGroup = sources[group],
|
||||||
layout = sourceGroup.layouter,
|
layout = sourceGroup.layouter,
|
||||||
layoutCreator: LayoutCreator = layoutMap[layout];
|
layoutCreator: LayoutCreator = layoutMap[layout];
|
||||||
|
|
||||||
if (!layout || !layoutCreator) {
|
if (!layout || !layoutCreator) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sourceDataString: string = JSON.stringify(sourceGroup.data),
|
let sourceDataString: string = JSON.stringify(sourceGroup.data),
|
||||||
prevString: string = this.prevSourcesStringMap[group],
|
prevString: string = this.prevSourcesStringMap[group],
|
||||||
nodeList: SVNode[] = [],
|
nodeList: SVNode[] = [],
|
||||||
freedLabelList: SVFreedLabel[] = [],
|
appendageList: SVNodeAppendage[] = [];
|
||||||
addressLabelList: SVAddressLabel[] = [],
|
|
||||||
indexLabelList: SVIndexLabel[] = [],
|
|
||||||
markerList: SVMarker[] = [];
|
|
||||||
|
|
||||||
if (prevString === sourceDataString) {
|
if (prevString === sourceDataString) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: LayoutGroupOptions = layoutCreator.defineOptions(sourceGroup.data),
|
const options: LayoutGroupOptions = layoutCreator.defineOptions(sourceGroup.data),
|
||||||
sourceData = layoutCreator.sourcesPreprocess(sourceGroup.data, options),
|
sourceData = layoutCreator.sourcesPreprocess(sourceGroup.data, options),
|
||||||
nodeOptions = options.node || options['element'] || {},
|
nodeOptions = options.node || options['element'] || {},
|
||||||
markerOptions = options.marker || {},
|
markerOptions = options.marker || {},
|
||||||
indexLabelOptions = options.indexLabel || {},
|
indexLabelOptions = options.indexLabel || {},
|
||||||
addressLabelOption = options.addressLabel || {};
|
addressLabelOption = options.addressLabel || {};
|
||||||
|
|
||||||
nodeList = this.constructNodes(group, layout, nodeOptions, sourceData);
|
nodeList = this.constructNodes(group, layout, nodeOptions, sourceData);
|
||||||
markerList = this.constructMarkers(group, layout, markerOptions, nodeList);
|
appendageList.push(...this.constructMarkers(group, layout, markerOptions, nodeList));
|
||||||
indexLabelList = this.constructIndexLabel(group, layout, indexLabelOptions, nodeList);
|
appendageList.push(...this.constructIndexLabel(group, layout, indexLabelOptions, nodeList));
|
||||||
addressLabelList = this.constructAddressLabel(group, layout, addressLabelOption, nodeList);
|
appendageList.push(...this.constructAddressLabel(group, layout, addressLabelOption, nodeList));
|
||||||
nodeList.forEach(item => {
|
nodeList.forEach(item => {
|
||||||
if(item.freedLabel) {
|
if (item.appendages.freedLabel) {
|
||||||
freedLabelList.push(item.freedLabel);
|
appendageList.push(...item.appendages.freedLabel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
layoutGroupTable.set(group, {
|
layoutGroupTable.set(group, {
|
||||||
name: group,
|
name: group,
|
||||||
node: nodeList,
|
node: nodeList,
|
||||||
freedLabel: freedLabelList,
|
appendage: appendageList,
|
||||||
addressLabel: addressLabelList,
|
link: [],
|
||||||
indexLabel: indexLabelList,
|
options,
|
||||||
link: [],
|
layoutCreator,
|
||||||
marker: markerList,
|
modelList: [...nodeList, ...appendageList],
|
||||||
options,
|
layout,
|
||||||
layoutCreator,
|
isHide: false,
|
||||||
modelList: [
|
});
|
||||||
...nodeList,
|
});
|
||||||
...markerList,
|
|
||||||
...freedLabelList,
|
|
||||||
...addressLabelList,
|
|
||||||
...indexLabelList
|
|
||||||
],
|
|
||||||
layout,
|
|
||||||
isHide: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
layoutGroupTable.forEach((layoutGroup: LayoutGroup, group: string) => {
|
layoutGroupTable.forEach((layoutGroup: LayoutGroup, group: string) => {
|
||||||
const linkOptions = layoutGroup.options.link || {},
|
const linkOptions = layoutGroup.options.link || {},
|
||||||
linkList: SVLink[] = this.constructLinks(group, layoutGroup.layout, linkOptions, layoutGroup.node, layoutGroupTable);
|
linkList: SVLink[] = this.constructLinks(
|
||||||
|
group,
|
||||||
|
layoutGroup.layout,
|
||||||
|
linkOptions,
|
||||||
|
layoutGroup.node,
|
||||||
|
layoutGroupTable
|
||||||
|
);
|
||||||
|
|
||||||
layoutGroup.link = linkList;
|
layoutGroup.link = linkList;
|
||||||
layoutGroup.modelList.push(...linkList);
|
layoutGroup.modelList.push(...linkList);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.layoutGroupTable = layoutGroupTable;
|
this.layoutGroupTable = layoutGroupTable;
|
||||||
|
|
||||||
return this.layoutGroupTable;
|
return this.layoutGroupTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从源数据构建 node 集
|
||||||
|
* @param nodeOptions
|
||||||
|
* @param group
|
||||||
|
* @param sourceList
|
||||||
|
* @param layout
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private constructNodes(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
nodeOptions: { [key: string]: NodeOption },
|
||||||
|
sourceList: SourceNode[]
|
||||||
|
): SVNode[] {
|
||||||
|
let defaultSourceNodeType: string = 'default',
|
||||||
|
nodeList: SVNode[] = [];
|
||||||
|
|
||||||
/**
|
sourceList.forEach(item => {
|
||||||
* 从源数据构建 node 集
|
if (item === null) {
|
||||||
* @param nodeOptions
|
return;
|
||||||
* @param group
|
}
|
||||||
* @param sourceList
|
|
||||||
* @param layout
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
private constructNodes(group: string, layout: string, nodeOptions: { [key: string]: NodeOption }, sourceList: SourceNode[]): SVNode[] {
|
|
||||||
let defaultSourceNodeType: string = 'default',
|
|
||||||
nodeList: SVNode[] = [];
|
|
||||||
|
|
||||||
sourceList.forEach(item => {
|
if (item.type === undefined || item.type === null) {
|
||||||
if (item === null) {
|
item.type = defaultSourceNodeType;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type === undefined || item.type === null) {
|
nodeList.push(this.createNode(item, item.type, group, layout, nodeOptions[item.type]));
|
||||||
item.type = defaultSourceNodeType;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
nodeList.push(this.createNode(item, item.type, group, layout, nodeOptions[item.type]));
|
return nodeList;
|
||||||
});
|
}
|
||||||
|
|
||||||
return nodeList;
|
/**
|
||||||
}
|
* 从配置和 node 集构建 link 集
|
||||||
|
* @param linkOptions
|
||||||
|
* @param nodes
|
||||||
|
* @param layoutGroupTable
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private constructLinks(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
linkOptions: { [key: string]: LinkOption },
|
||||||
|
nodes: SVNode[],
|
||||||
|
layoutGroupTable: LayoutGroupTable
|
||||||
|
): SVLink[] {
|
||||||
|
let linkList: SVLink[] = [],
|
||||||
|
linkNames = Object.keys(linkOptions);
|
||||||
|
|
||||||
/**
|
linkNames.forEach(name => {
|
||||||
* 从配置和 node 集构建 link 集
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
* @param linkOptions
|
let node: SVNode = nodes[i],
|
||||||
* @param nodes
|
sourceLinkData: sourceLinkData = node.sourceNode[name],
|
||||||
* @param layoutGroupTable
|
targetNode: SVNode | SVNode[] = null,
|
||||||
* @returns
|
link: SVLink = null;
|
||||||
*/
|
|
||||||
private constructLinks(group: string, layout: string, linkOptions: { [key: string]: LinkOption }, nodes: SVNode[], layoutGroupTable: LayoutGroupTable): SVLink[] {
|
|
||||||
let linkList: SVLink[] = [],
|
|
||||||
linkNames = Object.keys(linkOptions);
|
|
||||||
|
|
||||||
linkNames.forEach(name => {
|
if (sourceLinkData === undefined || sourceLinkData === null) {
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
node[name] = null;
|
||||||
let node: SVNode = nodes[i],
|
continue;
|
||||||
sourceLinkData: sourceLinkData = node.sourceNode[name],
|
}
|
||||||
targetNode: SVNode | SVNode[] = null,
|
|
||||||
link: SVLink = null;
|
|
||||||
|
|
||||||
if (sourceLinkData === undefined || sourceLinkData === null) {
|
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 SVNode -------------------
|
||||||
node[name] = null;
|
if (Array.isArray(sourceLinkData)) {
|
||||||
continue;
|
node[name] = sourceLinkData.map((item, index) => {
|
||||||
}
|
targetNode = this.fetchTargetNodes(layoutGroupTable, node, item);
|
||||||
|
let isGeneralLink = ModelConstructor.isGeneralLink(sourceLinkData.toString());
|
||||||
|
|
||||||
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 SVNode -------------------
|
if (targetNode) {
|
||||||
if (Array.isArray(sourceLinkData)) {
|
link = this.createLink(name, group, layout, node, targetNode, index, linkOptions[name]);
|
||||||
node[name] = sourceLinkData.map((item, index) => {
|
linkList.push(link);
|
||||||
targetNode = this.fetchTargetNodes(layoutGroupTable, node, item);
|
}
|
||||||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
|
||||||
|
|
||||||
if (targetNode) {
|
return isGeneralLink ? targetNode : null;
|
||||||
link = this.createLink(name, group, layout, node, targetNode, index, linkOptions[name]);
|
});
|
||||||
linkList.push(link);
|
} else {
|
||||||
}
|
targetNode = this.fetchTargetNodes(layoutGroupTable, node, sourceLinkData);
|
||||||
|
let isGeneralLink = ModelConstructor.isGeneralLink(sourceLinkData.toString());
|
||||||
|
|
||||||
return isGeneralLink ? targetNode : null;
|
if (targetNode) {
|
||||||
});
|
link = this.createLink(name, group, layout, node, targetNode, 0, linkOptions[name]);
|
||||||
}
|
linkList.push(link);
|
||||||
else {
|
}
|
||||||
targetNode = this.fetchTargetNodes(layoutGroupTable, node, sourceLinkData);
|
|
||||||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
|
||||||
|
|
||||||
if (targetNode) {
|
node[name] = isGeneralLink ? targetNode : null;
|
||||||
link = this.createLink(name, group, layout, node, targetNode, null, linkOptions[name]);
|
}
|
||||||
linkList.push(link);
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
node[name] = isGeneralLink ? targetNode : null;
|
return linkList;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return linkList;
|
/**
|
||||||
}
|
* 从配置项构建 indexLabel 集
|
||||||
|
* @param group
|
||||||
|
* @param layout
|
||||||
|
* @param indexLabelOptions
|
||||||
|
*/
|
||||||
|
private constructIndexLabel(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
indexLabelOptions: { [key: string]: IndexLabelOption },
|
||||||
|
nodes: SVNode[]
|
||||||
|
): SVIndexLabel[] {
|
||||||
|
let indexLabelList: SVIndexLabel[] = [],
|
||||||
|
indexNames = Object.keys(indexLabelOptions);
|
||||||
|
|
||||||
/**
|
indexNames.forEach(name => {
|
||||||
* 从配置项构建 indexLabel 集
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
* @param group
|
let node = nodes[i],
|
||||||
* @param layout
|
value = node[name];
|
||||||
* @param indexLabelOptions
|
|
||||||
*/
|
|
||||||
private constructIndexLabel(group: string, layout: string, indexLabelOptions: { [key: string]: IndexLabelOption }, nodes: SVNode[]): SVIndexLabel[] {
|
|
||||||
let indexLabelList: SVIndexLabel[] = [],
|
|
||||||
indexNames = Object.keys(indexLabelOptions);
|
|
||||||
|
|
||||||
indexNames.forEach(name => {
|
// 若没有指针字段的结点则跳过
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
if (value === undefined || value === null) continue;
|
||||||
let node = nodes[i],
|
|
||||||
value = node[name];
|
|
||||||
|
|
||||||
// 若没有指针字段的结点则跳过
|
let id = `${group}[${name}(${value})]`,
|
||||||
if (value === undefined || value === null) continue;
|
indexLabel = new SVIndexLabel(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
group,
|
||||||
|
layout,
|
||||||
|
value.toString(),
|
||||||
|
node,
|
||||||
|
indexLabelOptions[name]
|
||||||
|
);
|
||||||
|
|
||||||
let id = `${group}.${name}#${value}`,
|
indexLabelList.push(indexLabel);
|
||||||
indexLabel = new SVIndexLabel(id, name, group, layout, value.toString(), node, indexLabelOptions[name]);
|
}
|
||||||
|
});
|
||||||
|
|
||||||
indexLabelList.push(indexLabel);
|
return indexLabelList;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return indexLabelList;
|
/**
|
||||||
}
|
*
|
||||||
|
* @param group
|
||||||
|
* @param layout
|
||||||
|
* @param addressLabelOption
|
||||||
|
* @param nodes
|
||||||
|
*/
|
||||||
|
private constructAddressLabel(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
addressLabelOption: AddressLabelOption,
|
||||||
|
nodes: SVNode[]
|
||||||
|
): SVAddressLabel[] {
|
||||||
|
let addressLabelList: SVAddressLabel[] = [];
|
||||||
|
|
||||||
/**
|
nodes.forEach(item => {
|
||||||
*
|
const addressLabel = new SVAddressLabel(
|
||||||
* @param group
|
`address-label(${item.id})`,
|
||||||
* @param layout
|
item.sourceType,
|
||||||
* @param addressLabelOption
|
group,
|
||||||
* @param nodes
|
layout,
|
||||||
*/
|
item,
|
||||||
private constructAddressLabel(group: string, layout: string, addressLabelOption: AddressLabelOption, nodes: SVNode[]): SVAddressLabel[] {
|
addressLabelOption
|
||||||
let addressLabelList: SVAddressLabel[] = [];
|
);
|
||||||
|
addressLabelList.push(addressLabel);
|
||||||
|
});
|
||||||
|
|
||||||
nodes.forEach(item => {
|
return addressLabelList;
|
||||||
const addressLabel = new SVAddressLabel(`${item.id}-address-label`, item.sourceType, group, layout, item, addressLabelOption);
|
}
|
||||||
addressLabelList.push(addressLabel);
|
|
||||||
});
|
/**
|
||||||
|
* 从配置和 node 集构建 marker 集
|
||||||
|
* @param markerOptions
|
||||||
|
* @param nodes
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private constructMarkers(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
markerOptions: { [key: string]: MarkerOption },
|
||||||
|
nodes: SVNode[]
|
||||||
|
): SVMarker[] {
|
||||||
|
let markerList: SVMarker[] = [],
|
||||||
|
markerNames = Object.keys(markerOptions);
|
||||||
|
|
||||||
|
markerNames.forEach(name => {
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
let node = nodes[i],
|
||||||
|
markerData = node[name];
|
||||||
|
|
||||||
|
// 若没有指针字段的结点则跳过
|
||||||
|
if (!markerData) continue;
|
||||||
|
|
||||||
|
let id = `[${name}(${Array.isArray(markerData) ? markerData.join('-') : markerData})]`,
|
||||||
|
|
||||||
return addressLabelList;
|
marker = new SVMarker(id, name, group, layout, markerData, node, markerOptions[name]);
|
||||||
|
|
||||||
|
markerList.push(marker);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return markerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 求解label文本
|
||||||
|
* @param label
|
||||||
|
* @param sourceNode
|
||||||
|
*/
|
||||||
|
private resolveNodeLabel(label: string | string[], sourceNode: SourceNode): string {
|
||||||
|
let targetLabel: any = '';
|
||||||
|
|
||||||
|
if (Array.isArray(label)) {
|
||||||
|
targetLabel = label.map(item => this.parserNodeContent(sourceNode, item) ?? '');
|
||||||
|
} else {
|
||||||
|
targetLabel = this.parserNodeContent(sourceNode, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetLabel === 'undefined') {
|
||||||
|
targetLabel = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetLabel ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元素工厂,创建 Node
|
||||||
|
* @param sourceNode
|
||||||
|
* @param sourceNodeType
|
||||||
|
* @param group
|
||||||
|
* @param layout
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
private createNode(
|
||||||
|
sourceNode: SourceNode,
|
||||||
|
sourceNodeType: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
options: NodeOption
|
||||||
|
): SVNode {
|
||||||
|
let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode),
|
||||||
|
id = `${sourceNodeType}(${sourceNode.id.toString()})`,
|
||||||
|
|
||||||
|
node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options);
|
||||||
|
|
||||||
|
if (node.freed) {
|
||||||
|
new SVFreedLabel(`freed-label(${id})`, sourceNodeType, group, layout, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连线工厂,创建Link
|
||||||
|
* @param linkName
|
||||||
|
* @param group
|
||||||
|
* @param layout
|
||||||
|
* @param node
|
||||||
|
* @param target
|
||||||
|
* @param index
|
||||||
|
* @param options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private createLink(
|
||||||
|
linkName: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
node: SVNode,
|
||||||
|
target: SVNode,
|
||||||
|
index: number,
|
||||||
|
options: LinkOption
|
||||||
|
): SVLink {
|
||||||
|
let id = `${linkName}{${node.id}-${target.id}}#${index}`;
|
||||||
|
return new SVLink(id, linkName, group, layout, node, target, index, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析元素文本内容
|
||||||
|
* @param sourceNode
|
||||||
|
* @param formatLabel
|
||||||
|
*/
|
||||||
|
private parserNodeContent(sourceNode: SourceNode, formatLabel: string): string {
|
||||||
|
let fields = Util.textParser(formatLabel);
|
||||||
|
|
||||||
|
if (Array.isArray(fields)) {
|
||||||
|
let values = fields.map(item => sourceNode[item]);
|
||||||
|
|
||||||
|
values.map((item, index) => {
|
||||||
|
formatLabel = formatLabel.replace('[' + fields[index] + ']', item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由source中的连接字段获取真实的连接目标元素
|
||||||
|
* @param nodeContainer
|
||||||
|
* @param node
|
||||||
|
* @param linkTarget
|
||||||
|
*/
|
||||||
|
private fetchTargetNodes(layoutGroupTable: LayoutGroupTable, node: SVNode, linkTarget: LinkTarget): SVNode {
|
||||||
|
let group: string = node.group,
|
||||||
|
sourceNodeType = node.sourceType,
|
||||||
|
nodeList: SVNode[],
|
||||||
|
targetId = linkTarget,
|
||||||
|
targetGroupName = group,
|
||||||
|
targetNode = null;
|
||||||
|
|
||||||
|
if (linkTarget === null || linkTarget === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof linkTarget === 'number' || (typeof linkTarget === 'string' && !linkTarget.includes('#'))) {
|
||||||
|
linkTarget = 'default#' + linkTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = linkTarget.split('#');
|
||||||
|
|
||||||
|
targetId = info.pop();
|
||||||
|
|
||||||
|
if (info.length > 1) {
|
||||||
|
sourceNodeType = info.pop();
|
||||||
|
targetGroupName = info.pop();
|
||||||
|
} else {
|
||||||
|
let field = info.pop();
|
||||||
|
if (layoutGroupTable.get(targetGroupName).node.find(item => item.sourceType === field)) {
|
||||||
|
sourceNodeType = field;
|
||||||
|
} else if (layoutGroupTable.has(field)) {
|
||||||
|
targetGroupName = field;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为了可以连接到不同group的结点
|
||||||
|
for (let layoutGroup of layoutGroupTable.values()) {
|
||||||
|
nodeList = layoutGroup.node.filter(item => item.sourceType === sourceNodeType);
|
||||||
|
if (nodeList === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
targetNode = nodeList.find(item => item.sourceId === targetId);
|
||||||
|
if (targetNode) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// nodeList = layoutGroupTable.get(targetGroupName).node.filter(item => item.sourceType === sourceNodeType);
|
||||||
|
|
||||||
|
// // 若目标node不存在,返回null
|
||||||
|
// if (nodeList === undefined) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// targetNode = nodeList.find(item => item.sourceId === targetId);
|
||||||
|
return targetNode || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getLayoutGroupTable(): LayoutGroupTable {
|
||||||
|
return this.layoutGroupTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.layoutGroupTable = null;
|
||||||
|
this.prevSourcesStringMap = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测改指针是否为常规指针(指向另一个group)
|
||||||
|
* @param linkId
|
||||||
|
*/
|
||||||
|
static isGeneralLink(linkId: string): boolean {
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < linkId.length; i++) {
|
||||||
|
if (linkId[i] === '#') {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return counter <= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断这个节点是否来自相同group
|
||||||
|
* @param node1
|
||||||
|
* @param node2
|
||||||
|
*/
|
||||||
|
static isSameGroup(node1: SVNode, node2: SVNode): boolean {
|
||||||
|
return node1.group === node2.group;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从配置和 node 集构建 marker 集
|
* 获取簇
|
||||||
* @param markerOptions
|
* - 什么为一个簇?有边相连的一堆节点,再加上这些节点各自的appendages,共同组成了一个簇
|
||||||
* @param nodes
|
* @param models
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructMarkers(group: string, layout: string, markerOptions: { [key: string]: MarkerOption }, nodes: SVNode[]): SVMarker[] {
|
static getClusters(models: SVModel[]): Group[] {
|
||||||
let markerList: SVMarker[] = [],
|
const clusterGroupList = [],
|
||||||
markerNames = Object.keys(markerOptions);
|
idMap = {},
|
||||||
|
idName = '__clusterId';
|
||||||
|
|
||||||
markerNames.forEach(name => {
|
models.forEach(item => {
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
idMap[item.id] = item;
|
||||||
let node = nodes[i],
|
});
|
||||||
markerData = node[name];
|
|
||||||
|
|
||||||
// 若没有指针字段的结点则跳过
|
const DFS = (model: SVModel, clusterId: number, idMap): SVModel[] => {
|
||||||
if (!markerData) continue;
|
if(model === null) {
|
||||||
|
return [];
|
||||||
let id = `${group}.${name}.${Array.isArray(markerData) ? markerData.join('-') : markerData}`,
|
|
||||||
marker = new SVMarker(id, name, group, layout, markerData, node, markerOptions[name]);
|
|
||||||
|
|
||||||
markerList.push(marker);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return markerList;
|
if (idMap[model.id] === undefined) {
|
||||||
}
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if (model[idName] !== undefined) {
|
||||||
* 求解label文本
|
return [];
|
||||||
* @param label
|
}
|
||||||
* @param sourceNode
|
|
||||||
*/
|
|
||||||
private resolveNodeLabel(label: string | string[], sourceNode: SourceNode): string {
|
|
||||||
let targetLabel: any = '';
|
|
||||||
|
|
||||||
if (Array.isArray(label)) {
|
const list = [model];
|
||||||
targetLabel = label.map(item => this.parserNodeContent(sourceNode, item) ?? '');
|
model[idName] = clusterId;
|
||||||
}
|
|
||||||
else {
|
|
||||||
targetLabel = this.parserNodeContent(sourceNode, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetLabel === 'undefined') {
|
if (model instanceof SVNode) {
|
||||||
targetLabel = '';
|
model.getAppendagesList().forEach(item => {
|
||||||
}
|
list.push(...DFS(item, clusterId, idMap));
|
||||||
|
});
|
||||||
|
|
||||||
return targetLabel ?? '';
|
model.links.inDegree.forEach(item => {
|
||||||
}
|
list.push(...DFS(item, clusterId, idMap));
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
model.links.outDegree.forEach(item => {
|
||||||
* 元素工厂,创建 Node
|
list.push(...DFS(item, clusterId, idMap));
|
||||||
* @param sourceNode
|
});
|
||||||
* @param sourceNodeType
|
}
|
||||||
* @param group
|
|
||||||
* @param layout
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
private createNode(sourceNode: SourceNode, sourceNodeType: string, group: string, layout: string, options: NodeOption): SVNode {
|
|
||||||
let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode),
|
|
||||||
id = sourceNodeType + '.' + sourceNode.id.toString(),
|
|
||||||
node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options);
|
|
||||||
|
|
||||||
if(node.freed) {
|
if (model instanceof SVLink) {
|
||||||
node.freedLabel = new SVFreedLabel(`${id}-freed-label`, sourceNodeType, group, layout, node);
|
list.push(...DFS(model.node, clusterId, idMap));
|
||||||
}
|
list.push(...DFS(model.target, clusterId, idMap));
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
if (model instanceof SVNodeAppendage) {
|
||||||
}
|
list.push(...DFS(model.target, clusterId, idMap));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return list;
|
||||||
* 连线工厂,创建Link
|
};
|
||||||
* @param linkName
|
|
||||||
* @param group
|
|
||||||
* @param layout
|
|
||||||
* @param node
|
|
||||||
* @param target
|
|
||||||
* @param index
|
|
||||||
* @param options
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
private createLink(linkName: string, group: string, layout: string, node: SVNode, target: SVNode, index: number, options: LinkOption): SVLink {
|
|
||||||
let id = `${linkName}(${node.id}-${target.id})`;
|
|
||||||
return new SVLink(id, linkName, group, layout, node, target, index, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
for (let i = 0; i < models.length; i++) {
|
||||||
* 解析元素文本内容
|
const model = models[i];
|
||||||
* @param sourceNode
|
|
||||||
* @param formatLabel
|
|
||||||
*/
|
|
||||||
private parserNodeContent(sourceNode: SourceNode, formatLabel: string): string {
|
|
||||||
let fields = Util.textParser(formatLabel);
|
|
||||||
|
|
||||||
if (Array.isArray(fields)) {
|
if (model[idName] !== undefined) {
|
||||||
let values = fields.map(item => sourceNode[item]);
|
delete model[idName];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
values.map((item, index) => {
|
const group = new Group(),
|
||||||
formatLabel = formatLabel.replace('[' + fields[index] + ']', item);
|
clusterList = DFS(model, i, idMap);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatLabel;
|
clusterList.forEach(item => {
|
||||||
}
|
group.add(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
clusterGroupList.push(group);
|
||||||
|
delete model[idName];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return clusterGroupList;
|
||||||
* 由source中的连接字段获取真实的连接目标元素
|
}
|
||||||
* @param nodeContainer
|
}
|
||||||
* @param node
|
|
||||||
* @param linkTarget
|
|
||||||
*/
|
|
||||||
private fetchTargetNodes(layoutGroupTable: LayoutGroupTable, node: SVNode, linkTarget: LinkTarget): SVNode {
|
|
||||||
let group: string = node.group,
|
|
||||||
sourceNodeType = node.sourceType,
|
|
||||||
nodeList: SVNode[],
|
|
||||||
targetId = linkTarget,
|
|
||||||
targetGroupName = group,
|
|
||||||
targetNode = null;
|
|
||||||
|
|
||||||
if (linkTarget === null || linkTarget === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof linkTarget === 'number' || (typeof linkTarget === 'string' && !linkTarget.includes('#'))) {
|
|
||||||
linkTarget = 'default#' + linkTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
let info = linkTarget.split('#');
|
|
||||||
|
|
||||||
targetId = info.pop();
|
|
||||||
|
|
||||||
if (info.length > 1) {
|
|
||||||
sourceNodeType = info.pop();
|
|
||||||
targetGroupName = info.pop();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let field = info.pop();
|
|
||||||
if (layoutGroupTable.get(targetGroupName).node.find(item => item.sourceType === field)) {
|
|
||||||
sourceNodeType = field;
|
|
||||||
}
|
|
||||||
else if (layoutGroupTable.has(field)) {
|
|
||||||
targetGroupName = field;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeList = layoutGroupTable.get(targetGroupName).node.filter(item => item.sourceType === sourceNodeType);
|
|
||||||
|
|
||||||
// 若目标node不存在,返回null
|
|
||||||
if (nodeList === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
targetNode = nodeList.find(item => item.sourceId === targetId);
|
|
||||||
return targetNode || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测改指针是否为常规指针(指向另一个group)
|
|
||||||
* @param linkId
|
|
||||||
*/
|
|
||||||
private isGeneralLink(linkId: string): boolean {
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < linkId.length; i++) {
|
|
||||||
if (linkId[i] === '#') {
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return counter <= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public getLayoutGroupTable(): LayoutGroupTable {
|
|
||||||
return this.layoutGroupTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 销毁
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
this.layoutGroupTable = null;
|
|
||||||
this.prevSourcesStringMap = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -1,67 +1,111 @@
|
|||||||
import { Util } from '../Common/util';
|
import { Util } from '../Common/util';
|
||||||
|
|
||||||
|
export default Util.registerShape(
|
||||||
|
'binary-tree-node',
|
||||||
|
{
|
||||||
|
draw(cfg, group) {
|
||||||
|
cfg.size = cfg.size;
|
||||||
|
|
||||||
export default Util.registerShape('binary-tree-node', {
|
const width = cfg.size[0],
|
||||||
draw(cfg, group) {
|
height = cfg.size[1];
|
||||||
cfg.size = cfg.size;
|
|
||||||
|
|
||||||
const width = cfg.size[0],
|
const wrapperRect = group.addShape('rect', {
|
||||||
height = cfg.size[1];
|
attrs: {
|
||||||
|
x: width / 2,
|
||||||
|
y: height / 2,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
stroke: cfg.style.stroke || '#333',
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
fill: cfg.style.backgroundFill || '#eee',
|
||||||
|
},
|
||||||
|
name: 'wrapper',
|
||||||
|
});
|
||||||
|
|
||||||
const wrapperRect = group.addShape('rect', {
|
group.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: width / 2,
|
x: width / 4 + width / 2,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
width: width,
|
width: width / 2,
|
||||||
height: height,
|
height: height,
|
||||||
stroke: cfg.style.stroke || '#333',
|
fill: cfg.style.fill,
|
||||||
cursor: cfg.style.cursor,
|
stroke: cfg.style.stroke || '#333',
|
||||||
fill: cfg.style.backgroundFill || '#eee'
|
cursor: cfg.style.cursor,
|
||||||
},
|
},
|
||||||
name: 'wrapper'
|
name: 'mid',
|
||||||
});
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
group.addShape('rect', {
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
attrs: {
|
|
||||||
x: width / 4 + width / 2,
|
|
||||||
y: height / 2,
|
|
||||||
width: width / 2,
|
|
||||||
height: height,
|
|
||||||
fill: cfg.style.fill,
|
|
||||||
stroke: cfg.style.stroke || '#333',
|
|
||||||
cursor: cfg.style.cursor
|
|
||||||
},
|
|
||||||
name: 'mid',
|
|
||||||
draggable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cfg.label) {
|
if (cfg.label) {
|
||||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
group.addShape('text', {
|
||||||
group.addShape('text', {
|
attrs: {
|
||||||
attrs: {
|
x: width, // 居中
|
||||||
x: width, // 居中
|
y: height,
|
||||||
y: height,
|
textAlign: 'center',
|
||||||
textAlign: 'center',
|
textBaseline: 'middle',
|
||||||
textBaseline: 'middle',
|
text: cfg.label,
|
||||||
text: cfg.label,
|
fill: style.fill || '#000',
|
||||||
fill: style.fill || '#000',
|
fontSize: style.fontSize || 16,
|
||||||
fontSize: style.fontSize || 16,
|
cursor: cfg.style.cursor,
|
||||||
cursor: cfg.style.cursor
|
},
|
||||||
},
|
name: 'label',
|
||||||
name: 'text',
|
draggable: true,
|
||||||
draggable: true
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return wrapperRect;
|
const isLeftEmpty =
|
||||||
},
|
!cfg.child || cfg.child[0] === undefined || cfg.child[0] === undefined || cfg.child[0] == '0x0',
|
||||||
|
isRightEmpty =
|
||||||
|
!cfg.child || cfg.child[1] === undefined || cfg.child[1] === undefined || cfg.child[1] == '0x0';
|
||||||
|
|
||||||
getAnchorPoints() {
|
//节点没有左孩子节点时
|
||||||
return [
|
if (isLeftEmpty) {
|
||||||
[0.5, 0],
|
group.addShape('text', {
|
||||||
[0.875, 0.5],
|
attrs: {
|
||||||
[0.5, 1],
|
x: width * (5 / 8),
|
||||||
[0.125, 0.5]
|
y: height * (8 / 7),
|
||||||
];
|
textAlign: 'center',
|
||||||
},
|
textBaseline: 'middle',
|
||||||
}, 'rect');
|
text: '^',
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'null-left',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//节点没有右孩子节点时
|
||||||
|
if (isRightEmpty) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (11 / 8),
|
||||||
|
y: height * (8 / 7),
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'null-right',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapperRect;
|
||||||
|
},
|
||||||
|
|
||||||
|
getAnchorPoints() {
|
||||||
|
return [
|
||||||
|
[0.5, 0],
|
||||||
|
[0.875, 0.5],
|
||||||
|
[0.5, 1],
|
||||||
|
[0.125, 0.5],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'rect'
|
||||||
|
);
|
||||||
|
74
src/RegisteredShape/chainHashTableNode.ts
Normal file
74
src/RegisteredShape/chainHashTableNode.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Util } from '../Common/util';
|
||||||
|
export default Util.registerShape(
|
||||||
|
'chain-hashtable-node',
|
||||||
|
{
|
||||||
|
draw(cfg, group) {
|
||||||
|
cfg.size = cfg.size || [30, 30];
|
||||||
|
|
||||||
|
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: cfg.style.backgroundFill || '#eee'
|
||||||
|
},
|
||||||
|
name: 'wrapper',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
|
|
||||||
|
if (cfg.label) {
|
||||||
|
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',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//节点没有后续指针时
|
||||||
|
if (!cfg.next && !cfg.loopNext) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width,
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'text',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapperRect;
|
||||||
|
},
|
||||||
|
|
||||||
|
getAnchorPoints() {
|
||||||
|
return [
|
||||||
|
[1 / 6, 0],
|
||||||
|
[0.5, 0],
|
||||||
|
[0.5, 0.5],
|
||||||
|
[5 / 6, 0.5],
|
||||||
|
[0.5, 1],
|
||||||
|
[0, 0.5]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
@ -1,120 +1,106 @@
|
|||||||
import G6 from "@antv/g6";
|
import G6 from '@antv/g6';
|
||||||
import { Util } from "../Common/util";
|
import { Util } from '../Common/util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function rotate(shape, angle, transform) {
|
export default function rotate(shape, angle, transform) {
|
||||||
const matrix1 = shape.getMatrix();
|
const matrix1 = shape.getMatrix();
|
||||||
const newMatrix1 = transform(matrix1, [
|
const newMatrix1 = transform(matrix1, [['r', angle]]);
|
||||||
['r', angle],
|
shape.setMatrix(newMatrix1);
|
||||||
]);
|
|
||||||
shape.setMatrix(newMatrix1);
|
|
||||||
}
|
}
|
||||||
function translate(shape, x, y, transform) {
|
function translate(shape, x, y, transform) {
|
||||||
const matrix1 = shape.getMatrix();
|
const matrix1 = shape.getMatrix();
|
||||||
const newMatrix1 = transform(matrix1, [
|
const newMatrix1 = transform(matrix1, [['t', x, y]]);
|
||||||
['t', x, y],
|
shape.setMatrix(newMatrix1);
|
||||||
]);
|
|
||||||
shape.setMatrix(newMatrix1);
|
|
||||||
}
|
}
|
||||||
function culcuRotate(angle, R) {
|
function culcuRotate(angle, R) {
|
||||||
let offsetX = Math.cos(angle) * R;
|
let offsetX = Math.cos(angle) * R;
|
||||||
let offsetY = -Math.sin(angle) * R;
|
let offsetY = -Math.sin(angle) * R;
|
||||||
console.log(offsetX, offsetY, R);
|
console.log(offsetX, offsetY, R);
|
||||||
return {
|
return {
|
||||||
offsetX,
|
offsetX,
|
||||||
offsetY,
|
offsetY,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Util.registerShape('clen-queue-pointer', {
|
Util.registerShape('clen-queue-pointer', {
|
||||||
draw(cfg, group) {
|
draw(cfg, group) {
|
||||||
let id = cfg.id as string;
|
let id = cfg.id as string;
|
||||||
|
|
||||||
const index = parseInt(id.split('-')[1]);
|
const index = parseInt(id.split('-')[1]);
|
||||||
const len = parseInt(id.split('-')[2]);
|
const len = parseInt(id.split('-')[2]);
|
||||||
const keyShape = group.addShape('path', {
|
const keyShape = group.addShape('path', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
path: this.getPath(cfg),
|
path: this.getPath(cfg),
|
||||||
fill: cfg.style.fill,
|
fill: cfg.style.fill,
|
||||||
// matrix: cfg.style.matrix
|
// matrix: cfg.style.matrix
|
||||||
},
|
},
|
||||||
name: 'pointer-path'
|
name: 'pointer-path',
|
||||||
});
|
});
|
||||||
|
|
||||||
const angle = index * Math.PI * 2 / len;
|
const angle = (index * Math.PI * 2) / len;
|
||||||
if (cfg.label) {
|
if (cfg.label) {
|
||||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
|
|
||||||
const bgRect = group.addShape('rect', {
|
|
||||||
attrs: {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
text: cfg.label,
|
|
||||||
fill: null,
|
|
||||||
radius: 2
|
|
||||||
},
|
|
||||||
name: 'bgRect'
|
|
||||||
});
|
|
||||||
|
|
||||||
let label = cfg.label as string;
|
const bgRect = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
text: cfg.label,
|
||||||
|
fill: null,
|
||||||
|
radius: 2,
|
||||||
|
},
|
||||||
|
name: 'bgRect',
|
||||||
|
});
|
||||||
|
|
||||||
let pointerText = label.split('-')[0];
|
let label = cfg.label as string;
|
||||||
let y = pointerText=="front"?30:15;
|
|
||||||
const text = group.addShape('text', {
|
|
||||||
attrs: {
|
|
||||||
x: culcuRotate(Math.PI/2 - angle, y).offsetX,
|
|
||||||
y: culcuRotate(Math.PI/2 - angle, y).offsetY,
|
|
||||||
// x: 0,
|
|
||||||
// y: 0,
|
|
||||||
textAlign: 'center',
|
|
||||||
textBaseline: 'middle',
|
|
||||||
text: pointerText,
|
|
||||||
fill: style.fill || '#999',
|
|
||||||
fontSize: style.fontSize || 16
|
|
||||||
},
|
|
||||||
name: 'pointer-text-shape'
|
|
||||||
});
|
|
||||||
|
|
||||||
// rotate(text, angle, G6.Util.transform);
|
let pointerText = label.split('-')[0];
|
||||||
translate(text, 0, -75, G6.Util.transform);
|
let y = pointerText == 'front' ? 30 : 15;
|
||||||
}
|
const text = group.addShape('text', {
|
||||||
rotate(keyShape, angle, G6.Util.transform);
|
attrs: {
|
||||||
translate(keyShape, 0, -75, G6.Util.transform);
|
x: culcuRotate(Math.PI / 2 - angle, y).offsetX,
|
||||||
|
y: culcuRotate(Math.PI / 2 - angle, y).offsetY,
|
||||||
|
// x: 0,
|
||||||
|
// y: 0,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: pointerText,
|
||||||
|
fill: style.fill || '#999',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
},
|
||||||
|
name: 'pointer-text-shape',
|
||||||
|
});
|
||||||
|
|
||||||
return keyShape;
|
// rotate(text, angle, G6.Util.transform);
|
||||||
|
translate(text, 0, -75, G6.Util.transform);
|
||||||
|
}
|
||||||
|
rotate(keyShape, angle, G6.Util.transform);
|
||||||
|
translate(keyShape, 0, -75, G6.Util.transform);
|
||||||
|
|
||||||
|
return keyShape;
|
||||||
},
|
},
|
||||||
|
|
||||||
getPath(cfg) {
|
|
||||||
let width = 1,
|
|
||||||
height = 38,
|
|
||||||
arrowWidth = width + 4,
|
|
||||||
arrowHeight = height * 0.3;
|
|
||||||
|
|
||||||
const path = [
|
getPath(cfg) {
|
||||||
['M', 0, 0],
|
let width = 1,
|
||||||
['L', -width / 2, 0],
|
height = 38,
|
||||||
['L', -width / 2, -height],
|
arrowWidth = width + 4,
|
||||||
['L', -width / 2 - (arrowWidth / 2), -height],
|
arrowHeight = height * 0.3;
|
||||||
['L', 0, -height-arrowHeight],
|
|
||||||
['L', width / 2 + (arrowWidth / 2), -height],
|
|
||||||
['L', width / 2, -height],
|
|
||||||
['L', width / 2, 0],
|
|
||||||
['Z'],
|
|
||||||
];
|
|
||||||
|
|
||||||
return path;
|
const path = [
|
||||||
},
|
['M', 0, 0],
|
||||||
|
['L', -width / 2, 0],
|
||||||
|
['L', -width / 2, -height],
|
||||||
|
['L', -width / 2 - arrowWidth / 2, -height],
|
||||||
|
['L', 0, -height - arrowHeight],
|
||||||
|
['L', width / 2 + arrowWidth / 2, -height],
|
||||||
|
['L', width / 2, -height],
|
||||||
|
['L', width / 2, 0],
|
||||||
|
['Z'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return path;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,71 +1,100 @@
|
|||||||
import { Util } from "../Common/util";
|
/*
|
||||||
|
* @Author: your name
|
||||||
|
* @Date: 2022-01-26 01:58:25
|
||||||
|
* @LastEditTime: 2022-02-18 18:51:28
|
||||||
|
* @LastEditors: Please set LastEditors
|
||||||
|
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||||
|
* @FilePath: \水功能c:\Users\13127\Desktop\最近的前端文件\可视化0126\StructV2\src\RegisteredShape\linkListNode.ts
|
||||||
|
*/
|
||||||
|
import { Util } from '../Common/util';
|
||||||
|
|
||||||
|
export default Util.registerShape(
|
||||||
|
'link-list-node',
|
||||||
|
{
|
||||||
|
draw(cfg, group) {
|
||||||
|
cfg.size = cfg.size || [30, 10];
|
||||||
|
|
||||||
|
const width = cfg.size[0],
|
||||||
|
height = cfg.size[1];
|
||||||
|
|
||||||
export default Util.registerShape('link-list-node', {
|
const wrapperRect = group.addShape('rect', {
|
||||||
draw(cfg, group) {
|
attrs: {
|
||||||
cfg.size = cfg.size || [30, 10];
|
x: width / 2,
|
||||||
|
y: height / 2,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
stroke: cfg.style.stroke || '#333',
|
||||||
|
fill: cfg.style.backgroundFill || '#eee',
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'wrapper',
|
||||||
|
});
|
||||||
|
|
||||||
const width = cfg.size[0],
|
group.addShape('rect', {
|
||||||
height = cfg.size[1];
|
attrs: {
|
||||||
|
x: width / 2,
|
||||||
const wrapperRect = group.addShape('rect', {
|
y: height / 2,
|
||||||
attrs: {
|
width: width * (2 / 3),
|
||||||
x: width / 2,
|
height: height,
|
||||||
y: height / 2,
|
fill: cfg.style.fill,
|
||||||
width: width,
|
stroke: cfg.style.stroke || '#333',
|
||||||
height: height,
|
cursor: cfg.style.cursor,
|
||||||
stroke: cfg.style.stroke || '#333',
|
},
|
||||||
fill: cfg.style.backgroundFill || '#eee',
|
name: 'main-rect',
|
||||||
cursor: cfg.style.cursor
|
draggable: true,
|
||||||
},
|
});
|
||||||
name: 'wrapper'
|
|
||||||
});
|
|
||||||
|
|
||||||
group.addShape('rect', {
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
attrs: {
|
|
||||||
x: width / 2,
|
|
||||||
y: height / 2,
|
|
||||||
width: width * (2 / 3),
|
|
||||||
height: height,
|
|
||||||
fill: cfg.style.fill,
|
|
||||||
stroke: cfg.style.stroke || '#333',
|
|
||||||
cursor: cfg.style.cursor
|
|
||||||
},
|
|
||||||
name: 'main-rect',
|
|
||||||
draggable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cfg.label) {
|
if (cfg.label) {
|
||||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
group.addShape('text', {
|
||||||
group.addShape('text', {
|
attrs: {
|
||||||
attrs: {
|
x: width * (5 / 6),
|
||||||
x: width * (5 / 6),
|
y: height,
|
||||||
y: height,
|
textAlign: 'center',
|
||||||
textAlign: 'center',
|
textBaseline: 'middle',
|
||||||
textBaseline: 'middle',
|
text: cfg.label,
|
||||||
text: cfg.label,
|
fill: style.fill || '#000',
|
||||||
fill: style.fill || '#000',
|
fontSize: style.fontSize || 16,
|
||||||
fontSize: style.fontSize || 16
|
},
|
||||||
},
|
name: 'label',
|
||||||
name: 'text',
|
draggable: true,
|
||||||
draggable: true
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return wrapperRect;
|
//节点没有后续指针时
|
||||||
},
|
if (!cfg.next && !cfg.loopNext) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (4 / 3),
|
||||||
|
y: height * (6 / 5),
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'null',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getAnchorPoints() {
|
return wrapperRect;
|
||||||
return [
|
},
|
||||||
[0.5, 0],
|
|
||||||
[5 / 6, 0],
|
getAnchorPoints() {
|
||||||
[5 / 6, 0.5],
|
return [
|
||||||
[1, 0.5],
|
[0.5, 0],
|
||||||
[5 / 6, 1],
|
[5 / 6, 0],
|
||||||
[0.5, 1],
|
[5 / 6, 0.5],
|
||||||
[0, 0.5],
|
[1, 0.5],
|
||||||
[1 / 3, 1]
|
[5 / 6, 1],
|
||||||
];
|
[0.5, 1],
|
||||||
}
|
[0, 0.5],
|
||||||
}, 'rect');
|
[1 / 3, 1],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'rect'
|
||||||
|
);
|
||||||
|
@ -1,88 +1,89 @@
|
|||||||
import { Util } from '../Common/util';
|
import { Util } from '../Common/util';
|
||||||
|
|
||||||
|
|
||||||
export default Util.registerShape('pointer', {
|
export default Util.registerShape('pointer', {
|
||||||
draw(cfg, group) {
|
draw(cfg, group) {
|
||||||
const keyShape = group.addShape('path', {
|
const keyShape = group.addShape('path', {
|
||||||
attrs: {
|
attrs: {
|
||||||
path: this.getPath(cfg),
|
path: this.getPath(cfg),
|
||||||
fill: cfg.style.fill,
|
fill: cfg.style.fill,
|
||||||
matrix: cfg.style.matrix
|
matrix: cfg.style.matrix,
|
||||||
},
|
},
|
||||||
name: 'pointer-path'
|
name: 'pointer-path',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cfg.label) {
|
if (cfg.label) {
|
||||||
const labelStyle = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
const labelStyle = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
|
|
||||||
const bgRect = group.addShape('rect', {
|
const bgRect = group.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
text: cfg.label,
|
text: cfg.label,
|
||||||
fill: null,
|
fill: null,
|
||||||
radius: 2
|
radius: 2,
|
||||||
},
|
},
|
||||||
name: 'bgRect'
|
name: 'bgRect',
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = group.addShape('text', {
|
const text = group.addShape('text', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
textBaseline: 'middle',
|
textBaseline: 'middle',
|
||||||
text: cfg.label,
|
text: cfg.label,
|
||||||
fill: labelStyle.fill || '#999',
|
fill: labelStyle.fill || '#999',
|
||||||
fontSize: labelStyle.fontSize || 16
|
fontSize: labelStyle.fontSize || 16,
|
||||||
},
|
},
|
||||||
name: 'pointer-text-shape'
|
name: 'pointer-text-shape',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { width: textWidth, height: textHeight } = text.getBBox();
|
const { width: textWidth, height: textHeight } = text.getBBox();
|
||||||
bgRect.attr({
|
const { width: pointerWidth, height: pointerHeight } = keyShape.getBBox();
|
||||||
width: textWidth + 6,
|
bgRect.attr({
|
||||||
height: textHeight + 6
|
width: textWidth + pointerWidth + 6,
|
||||||
});
|
height: textHeight + pointerHeight + 6,
|
||||||
|
});
|
||||||
|
|
||||||
// 旋转文字
|
// 旋转文字
|
||||||
const markerEndPosition = cfg.markerEndPosition;
|
const markerEndPosition = cfg.markerEndPosition;
|
||||||
if(markerEndPosition) {
|
if (markerEndPosition) {
|
||||||
let textX = markerEndPosition[0],
|
let textX = markerEndPosition[0],
|
||||||
textY = markerEndPosition[1];
|
textY = markerEndPosition[1];
|
||||||
|
|
||||||
text.attr({
|
text.attr({
|
||||||
x: textX,
|
x: textX,
|
||||||
y: textY
|
y: textY,
|
||||||
});
|
});
|
||||||
|
|
||||||
bgRect.attr({
|
bgRect.attr({
|
||||||
x: textX - textWidth / 2 - 3,
|
x: textX - textWidth / 2 - 3,
|
||||||
y: textY - textHeight / 2 - 3
|
y: textY - textHeight / 2 - 3,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
return bgRect;
|
||||||
|
}
|
||||||
|
|
||||||
return keyShape;
|
return keyShape;
|
||||||
},
|
},
|
||||||
|
|
||||||
getPath(cfg) {
|
|
||||||
let width = cfg.size[0],
|
|
||||||
height = cfg.size[1],
|
|
||||||
arrowWidth = width + 4,
|
|
||||||
arrowHeight = height * 0.3;
|
|
||||||
|
|
||||||
const path = [
|
getPath(cfg) {
|
||||||
['M', 0, 0],
|
let width = cfg.size[0],
|
||||||
['L', -width / 2 - (arrowWidth / 2), -arrowHeight],
|
height = cfg.size[1],
|
||||||
['L', -width / 2, -arrowHeight],
|
arrowWidth = width + 4,
|
||||||
['L', -width / 2, -height],
|
arrowHeight = height * 0.3;
|
||||||
['L', width / 2, -height],
|
|
||||||
['L', width / 2, -arrowHeight],
|
|
||||||
['L', width / 2 + (arrowWidth / 2), -arrowHeight],
|
|
||||||
['Z'],
|
|
||||||
];
|
|
||||||
|
|
||||||
return path;
|
const path = [
|
||||||
}
|
['M', 0, 0],
|
||||||
});
|
['L', -width / 2 - arrowWidth / 2, -arrowHeight],
|
||||||
|
['L', -width / 2, -arrowHeight],
|
||||||
|
['L', -width / 2, -height],
|
||||||
|
['L', width / 2, -height],
|
||||||
|
['L', width / 2, -arrowHeight],
|
||||||
|
['L', width / 2 + arrowWidth / 2, -arrowHeight],
|
||||||
|
['Z'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
189
src/RegisteredShape/threeCellNode.ts
Normal file
189
src/RegisteredShape/threeCellNode.ts
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* @Author: your name
|
||||||
|
* @Date: 2022-01-18 20:42:31
|
||||||
|
* @LastEditTime: 2022-02-17 21:57:59
|
||||||
|
* @LastEditors: Please set LastEditors
|
||||||
|
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||||
|
* @FilePath: \测试数据c:\Users\13127\Desktop\最近的前端文件\可视化源码-new\StructV2\src\RegisteredShape\treeCellNode.ts
|
||||||
|
*/
|
||||||
|
import { registerNode } from '@antv/g6';
|
||||||
|
|
||||||
|
export default registerNode('three-cell', {
|
||||||
|
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: cfg.style.backgroundFill || '#eee',
|
||||||
|
},
|
||||||
|
name: 'wrapper',
|
||||||
|
});
|
||||||
|
|
||||||
|
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: cfg.style.fill,
|
||||||
|
stroke: cfg.style.stroke,
|
||||||
|
},
|
||||||
|
name: 'middle-rect',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
|
|
||||||
|
//节点上方文字
|
||||||
|
if (cfg.root && cfg.rootLabel) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (2 / 3),
|
||||||
|
y: 0,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.rootLabel[0],
|
||||||
|
fill: style.fill || '#bbb',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
fontStyle: 'italic',
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'rootLabel0',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width,
|
||||||
|
y: 0,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.rootLabel[1],
|
||||||
|
fill: style.fill || '#bbb',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
fontStyle: 'italic',
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'rootLabel1',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (4 / 3),
|
||||||
|
y: 0,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.rootLabel[2],
|
||||||
|
fill: style.fill || '#bbb',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
fontStyle: 'italic',
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'rootLabel2',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//节点左边文字
|
||||||
|
if (cfg.index !== null) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (2 / 5),
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.index,
|
||||||
|
fill: style.fill || '#bbb',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
fontStyle: 'italic',
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'index',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//节点文字(数组形式)
|
||||||
|
if (cfg.label) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (2 / 3),
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.label[0],
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'label1',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width,
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.label[1],
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'label2',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//节点没有后续指针时
|
||||||
|
if (!cfg.headNext) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (4 / 3),
|
||||||
|
y: height * (6 / 5),
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: 22,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'null',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapperRect;
|
||||||
|
},
|
||||||
|
|
||||||
|
getAnchorPoints() {
|
||||||
|
return [
|
||||||
|
[0.5, 0],
|
||||||
|
[5 / 6, 0.5],
|
||||||
|
[0.5, 1],
|
||||||
|
[0, 0.5],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
@ -1,100 +1,143 @@
|
|||||||
import { Util } from '../Common/util';
|
import { Util } from '../Common/util';
|
||||||
|
|
||||||
|
|
||||||
export default Util.registerShape('tri-tree-node', {
|
export default Util.registerShape('tri-tree-node', {
|
||||||
draw(cfg, group) {
|
draw(cfg, group) {
|
||||||
cfg.size = cfg.size;
|
cfg.size = cfg.size;
|
||||||
|
|
||||||
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 || '#333',
|
stroke: cfg.style.stroke || '#333',
|
||||||
cursor: cfg.style.cursor,
|
cursor: cfg.style.cursor,
|
||||||
fill: cfg.style.backgroundFill || '#eee'
|
fill: '#eee',
|
||||||
},
|
},
|
||||||
name: 'wrapper'
|
name: 'wrapper',
|
||||||
});
|
});
|
||||||
|
|
||||||
group.addShape('rect', {
|
group.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: width / 4 + width / 2,
|
x: width / 4 + width / 2,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
width: width / 2,
|
width: width / 2,
|
||||||
height: height / 4,
|
height: height / 4,
|
||||||
fill: '#eee',
|
fill: '#eee',
|
||||||
stroke: cfg.style.stroke || '#333',
|
stroke: cfg.style.stroke || '#333',
|
||||||
cursor: cfg.style.cursor
|
cursor: cfg.style.cursor,
|
||||||
},
|
},
|
||||||
name: 'top',
|
name: 'top',
|
||||||
draggable: true
|
draggable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
group.addShape('rect', {
|
group.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: width / 4 + width / 2,
|
x: width / 4 + width / 2,
|
||||||
y: height / 2 + height / 4,
|
y: height / 2 + height / 4,
|
||||||
width: width / 2,
|
width: width / 2,
|
||||||
height: height / 4 * 3,
|
height: (height / 4) * 3,
|
||||||
fill: cfg.color || cfg.style.fill,
|
fill: cfg.color || cfg.style.fill,
|
||||||
stroke: cfg.style.stroke || '#333',
|
stroke: cfg.style.stroke || '#333',
|
||||||
cursor: cfg.style.cursor
|
cursor: cfg.style.cursor,
|
||||||
},
|
},
|
||||||
name: 'bottom',
|
name: 'bottom',
|
||||||
draggable: true
|
draggable: true,
|
||||||
});
|
});
|
||||||
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
|
if (cfg.label) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width, // 居中
|
||||||
|
y: height + height / 8,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.label,
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'label',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let parent = cfg.parent || cfg.l_parent || cfg.r_parent;
|
||||||
|
const isLeftEmpty =
|
||||||
|
!cfg.child || cfg.child[0] === undefined || cfg.child[0] === undefined || cfg.child[0] == '0x0',
|
||||||
|
isRightEmpty =
|
||||||
|
!cfg.child || cfg.child[1] === undefined || cfg.child[1] === undefined || cfg.child[1] == '0x0',
|
||||||
|
isParentEmpty = parent[0] == '0x0';
|
||||||
|
|
||||||
if (cfg.label) {
|
|
||||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
|
||||||
group.addShape('text', {
|
|
||||||
attrs: {
|
|
||||||
x: width, // 居中
|
|
||||||
y: height + height / 8,
|
|
||||||
textAlign: 'center',
|
|
||||||
textBaseline: 'middle',
|
|
||||||
text: cfg.label,
|
|
||||||
fill: style.fill || '#000',
|
|
||||||
fontSize: style.fontSize || 16,
|
|
||||||
cursor: cfg.style.cursor
|
|
||||||
},
|
|
||||||
name: 'text',
|
|
||||||
draggable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cfg.root) {
|
if (isParentEmpty) {
|
||||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
{
|
||||||
group.addShape('text', {
|
group.addShape('text', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: width, // 居中
|
x: width, // 居中
|
||||||
y: height / 4 * 3,
|
y: (height / 4) * 3,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
textBaseline: 'middle',
|
textBaseline: 'middle',
|
||||||
text: "^",
|
text: '^',
|
||||||
fill: style.fill || '#000',
|
fill: style.fill || '#000',
|
||||||
fontSize: style.fontSize || 14,
|
fontSize: style.fontSize || 14,
|
||||||
cursor: cfg.style.cursor
|
cursor: cfg.style.cursor,
|
||||||
},
|
},
|
||||||
name: 'parent',
|
name: 'null-parent',
|
||||||
draggable: true
|
draggable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return wrapperRect;
|
//节点没有左孩子节点时
|
||||||
},
|
if (isLeftEmpty) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (5 / 8),
|
||||||
|
y: height * (8 / 7),
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'null-left',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//节点没有右孩子节点时
|
||||||
|
if (isRightEmpty) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (11 / 8),
|
||||||
|
y: height * (8 / 7),
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'null-right',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getAnchorPoints() {
|
return wrapperRect;
|
||||||
return [
|
},
|
||||||
[0.125, 0.5],
|
|
||||||
[0.875, 0.5],
|
getAnchorPoints() {
|
||||||
[0.5, 1],
|
return [
|
||||||
[0.5, 0],
|
[0.125, 0.5],
|
||||||
[0.5, 0.125]
|
[0.875, 0.5],
|
||||||
];
|
[0.4, 1],
|
||||||
},
|
[0.5, 0],
|
||||||
}, 'rect');
|
[0.5, 0.125],
|
||||||
|
[0.6, 1],
|
||||||
|
[0.5, 1],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -1,102 +1,157 @@
|
|||||||
import { Util } from '../Common/util';
|
import { Util } from '../Common/util';
|
||||||
|
|
||||||
|
export default Util.registerShape(
|
||||||
|
'two-cell-node',
|
||||||
|
{
|
||||||
|
draw(cfg, group) {
|
||||||
|
cfg.size = cfg.size || [30, 10];
|
||||||
|
|
||||||
|
const width = cfg.size[0],
|
||||||
|
height = cfg.size[1];
|
||||||
|
|
||||||
export default Util.registerShape('two-cell-node', {
|
const wrapperRect = group.addShape('rect', {
|
||||||
draw(cfg, group) {
|
attrs: {
|
||||||
cfg.size = cfg.size || [30, 10];
|
x: width / 2,
|
||||||
|
y: height / 2,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
stroke: cfg.style.stroke,
|
||||||
|
fill: cfg.style.backgroundFill || '#eee',
|
||||||
|
},
|
||||||
|
name: 'wrapper',
|
||||||
|
});
|
||||||
|
|
||||||
const width = cfg.size[0],
|
group.addShape('rect', {
|
||||||
height = cfg.size[1];
|
attrs: {
|
||||||
|
x: width / 2,
|
||||||
|
y: height / 2,
|
||||||
|
width: width / 2,
|
||||||
|
height: height,
|
||||||
|
fill: cfg.style.fill,
|
||||||
|
stroke: cfg.style.stroke,
|
||||||
|
},
|
||||||
|
name: 'left-rect',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
const wrapperRect = group.addShape('rect', {
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
attrs: {
|
|
||||||
x: width / 2,
|
|
||||||
y: height / 2,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
stroke: cfg.style.stroke,
|
|
||||||
fill: cfg.style.backgroundFill || '#eee'
|
|
||||||
},
|
|
||||||
name: 'wrapper'
|
|
||||||
});
|
|
||||||
|
|
||||||
group.addShape('rect', {
|
if (cfg.label) {
|
||||||
attrs: {
|
if (Array.isArray(cfg.label)) {
|
||||||
x: width / 2,
|
let tag = cfg.label[0],
|
||||||
y: height / 2,
|
data = cfg.label[1];
|
||||||
width: width / 2,
|
|
||||||
height: height,
|
|
||||||
fill: cfg.style.fill,
|
|
||||||
stroke: cfg.style.stroke
|
|
||||||
},
|
|
||||||
name: 'left-rect',
|
|
||||||
draggable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (3 / 4),
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: tag,
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'tag',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (cfg.label) {
|
group.addShape('text', {
|
||||||
if(Array.isArray(cfg.label)) {
|
attrs: {
|
||||||
let tag = cfg.label[0], data = cfg.label[1];
|
x: width * (5 / 4),
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: data,
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'data',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (3 / 4),
|
||||||
|
y: height,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: cfg.label,
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: style.fontSize || 16,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'label',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
group.addShape('text', {
|
//图 数据结构中没有后续指针
|
||||||
attrs: {
|
if (cfg.id.includes('tableHeadNode') && !cfg.headNext) {
|
||||||
x: width * (3 / 4),
|
group.addShape('text', {
|
||||||
y: height,
|
attrs: {
|
||||||
textAlign: 'center',
|
x: width * (5 / 4),
|
||||||
textBaseline: 'middle',
|
y: height * (8 / 7),
|
||||||
text: tag,
|
textAlign: 'center',
|
||||||
fill: style.fill || '#000',
|
textBaseline: 'middle',
|
||||||
fontSize: style.fontSize || 16,
|
text: '^',
|
||||||
cursor: cfg.style.cursor,
|
fill: style.fill || '#000',
|
||||||
},
|
fontSize: 20,
|
||||||
name: 'text',
|
cursor: cfg.style.cursor,
|
||||||
draggable: true
|
},
|
||||||
});
|
name: 'null-headNext',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
group.addShape('text', {
|
//哈希表 数据结构中没有后续指针
|
||||||
attrs: {
|
if (cfg.id.includes('head') && !cfg.start) {
|
||||||
x: width * (5 / 4),
|
group.addShape('text', {
|
||||||
y: height,
|
attrs: {
|
||||||
textAlign: 'center',
|
x: width * (5 / 4),
|
||||||
textBaseline: 'middle',
|
y: height * (8 / 7),
|
||||||
text: data,
|
textAlign: 'center',
|
||||||
fill: style.fill || '#000',
|
textBaseline: 'middle',
|
||||||
fontSize: style.fontSize || 16,
|
text: '^',
|
||||||
cursor: cfg.style.cursor,
|
fill: style.fill || '#000',
|
||||||
},
|
fontSize: 20,
|
||||||
name: 'text',
|
cursor: cfg.style.cursor,
|
||||||
draggable: true
|
},
|
||||||
});
|
name: 'null-start',
|
||||||
}
|
draggable: true,
|
||||||
else {
|
});
|
||||||
group.addShape('text', {
|
}
|
||||||
attrs: {
|
|
||||||
x: width * (3 / 4),
|
|
||||||
y: height,
|
|
||||||
textAlign: 'center',
|
|
||||||
textBaseline: 'middle',
|
|
||||||
text: cfg.label,
|
|
||||||
fill: style.fill || '#000',
|
|
||||||
fontSize: style.fontSize || 16,
|
|
||||||
cursor: cfg.style.cursor,
|
|
||||||
},
|
|
||||||
name: 'text',
|
|
||||||
draggable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapperRect;
|
//pctree 数据结构中没有后续指针
|
||||||
},
|
if (cfg.id.includes('PCTreeHead') && !cfg.headNext) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: width * (5 / 4),
|
||||||
|
y: height * (8 / 7),
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: '^',
|
||||||
|
fill: style.fill || '#000',
|
||||||
|
fontSize: 20,
|
||||||
|
cursor: cfg.style.cursor,
|
||||||
|
},
|
||||||
|
name: 'null-headNext2',
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return wrapperRect;
|
||||||
|
},
|
||||||
|
|
||||||
getAnchorPoints() {
|
getAnchorPoints() {
|
||||||
return [
|
return [
|
||||||
[0.5, 0],
|
[0.5, 0],
|
||||||
[3 / 4, 0.5],
|
[3 / 4, 0.5],
|
||||||
[0.5, 1],
|
[0.5, 1],
|
||||||
[0, 0.5]
|
[0, 0.5],
|
||||||
];
|
];
|
||||||
}
|
},
|
||||||
}, 'rect');
|
},
|
||||||
|
'rect'
|
||||||
|
);
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* @Author: your name
|
||||||
|
* @Date: 2022-01-26 01:58:25
|
||||||
|
* @LastEditTime: 2022-01-26 02:06:16
|
||||||
|
* @LastEditors: your name
|
||||||
|
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||||
|
* @FilePath: \测试数据c:\Users\13127\Desktop\最近的前端文件\可视化0126\StructV2\src\StructV.ts
|
||||||
|
*/
|
||||||
import { Engine } from "./engine";
|
import { Engine } from "./engine";
|
||||||
import { Bound } from "./Common/boundingRect";
|
import { Bound } from "./Common/boundingRect";
|
||||||
import { Group } from "./Common/group";
|
import { Group } from "./Common/group";
|
||||||
@ -6,15 +14,16 @@ import Pointer from "./RegisteredShape/pointer";
|
|||||||
import LinkListNode from "./RegisteredShape/linkListNode";
|
import LinkListNode from "./RegisteredShape/linkListNode";
|
||||||
import BinaryTreeNode from "./RegisteredShape/binaryTreeNode";
|
import BinaryTreeNode from "./RegisteredShape/binaryTreeNode";
|
||||||
import ForceNode from "./RegisteredShape/force";
|
import ForceNode from "./RegisteredShape/force";
|
||||||
|
import TriTreeNode from "./RegisteredShape/triTreeNode";
|
||||||
import CLenQueuePointer from "./RegisteredShape/clenQueuePointer";
|
import CLenQueuePointer from "./RegisteredShape/clenQueuePointer";
|
||||||
import TwoCellNode from "./RegisteredShape/twoCellNode";
|
import TwoCellNode from "./RegisteredShape/twoCellNode";
|
||||||
|
import ThreeCellNode from "./RegisteredShape/threeCellNode";
|
||||||
import ArrayNode from "./RegisteredShape/arrayNode";
|
import ArrayNode from "./RegisteredShape/arrayNode";
|
||||||
import Cursor from "./RegisteredShape/cursor";
|
import Cursor from "./RegisteredShape/cursor";
|
||||||
import { Vector } from "./Common/vector";
|
import { Vector } from "./Common/vector";
|
||||||
import { EngineOptions, LayoutCreator } from "./options";
|
import { EngineOptions, LayoutCreator } from "./options";
|
||||||
import { SourceNode } from "./sources";
|
import { SourceNode } from "./sources";
|
||||||
import { Util } from "./Common/util";
|
import { Util } from "./Common/util";
|
||||||
import { SVModel } from "./Model/SVModel";
|
|
||||||
import { SVNode } from "./Model/SVNode";
|
import { SVNode } from "./Model/SVNode";
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +67,9 @@ SV.registeredShape = [
|
|||||||
Pointer,
|
Pointer,
|
||||||
LinkListNode,
|
LinkListNode,
|
||||||
BinaryTreeNode,
|
BinaryTreeNode,
|
||||||
|
TriTreeNode,
|
||||||
TwoCellNode,
|
TwoCellNode,
|
||||||
|
ThreeCellNode,
|
||||||
Cursor,
|
Cursor,
|
||||||
ArrayNode,
|
ArrayNode,
|
||||||
CLenQueuePointer,
|
CLenQueuePointer,
|
||||||
@ -85,5 +96,6 @@ SV.registerLayout = function(name: string, layoutCreator: LayoutCreator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SV.registeredLayout[name] = layoutCreator;
|
SV.registeredLayout[name] = layoutCreator;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,345 +1,347 @@
|
|||||||
import { IPoint } from '@antv/g6-core';
|
import { IPoint, INode } from '@antv/g6-core';
|
||||||
import { Bound, BoundingRect } from '../Common/boundingRect';
|
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||||
import { Group } from '../Common/group';
|
import { Group } from '../Common/group';
|
||||||
import { Util } from '../Common/util';
|
import { Util } from '../Common/util';
|
||||||
import { Vector } from '../Common/vector';
|
import { Vector } from '../Common/vector';
|
||||||
import { Engine } from '../engine';
|
import { Engine } from '../engine';
|
||||||
import { LayoutGroupTable } from '../Model/modelConstructor';
|
import { LayoutGroupTable, ModelConstructor } from '../Model/modelConstructor';
|
||||||
import { SVModel } from '../Model/SVModel';
|
import { SVModel } from '../Model/SVModel';
|
||||||
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker } from '../Model/SVNodeAppendage';
|
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker } from '../Model/SVNodeAppendage';
|
||||||
import { AddressLabelOption, IndexLabelOption, LayoutOptions, MarkerOption, ViewOptions } from '../options';
|
import { AddressLabelOption, IndexLabelOption, LayoutOptions, MarkerOption, ViewOptions } from '../options';
|
||||||
import { ViewContainer } from './viewContainer';
|
import { ViewContainer } from './viewContainer';
|
||||||
|
|
||||||
|
|
||||||
export class LayoutProvider {
|
export class LayoutProvider {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private viewOptions: ViewOptions;
|
private viewOptions: ViewOptions;
|
||||||
private viewContainer: ViewContainer;
|
private viewContainer: ViewContainer;
|
||||||
|
private leakAreaXoffset: number = 20;
|
||||||
|
private leakClusterXInterval = 25;
|
||||||
|
|
||||||
constructor(engine: Engine, viewContainer: ViewContainer) {
|
constructor(engine: Engine, viewContainer: ViewContainer) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.viewOptions = this.engine.viewOptions;
|
this.viewOptions = this.engine.viewOptions;
|
||||||
this.viewContainer = viewContainer;
|
this.viewContainer = viewContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布局前处理
|
||||||
|
* @param layoutGroupTable
|
||||||
|
*/
|
||||||
|
private preLayoutProcess(layoutGroupTable: LayoutGroupTable) {
|
||||||
|
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable);
|
||||||
|
|
||||||
/**
|
modelList.forEach(item => {
|
||||||
* 布局前处理
|
item.preLayout = true;
|
||||||
* @param layoutGroupTable
|
|
||||||
*/
|
|
||||||
private preLayoutProcess(layoutGroupTable: LayoutGroupTable) {
|
|
||||||
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable);
|
|
||||||
|
|
||||||
modelList.forEach(item => {
|
item.set('rotation', item.get('rotation'));
|
||||||
item.preLayout = true;
|
item.set({ x: 0, y: 0 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
item.set('rotation', item.get('rotation'));
|
/**
|
||||||
item.set({ x: 0, y: 0 });
|
* 布局后处理
|
||||||
});
|
* @param layoutGroupTable
|
||||||
}
|
*/
|
||||||
|
private postLayoutProcess(layoutGroupTable: LayoutGroupTable) {
|
||||||
|
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable);
|
||||||
|
|
||||||
/**
|
modelList.forEach(item => {
|
||||||
* 布局后处理
|
item.preLayout = false;
|
||||||
* @param layoutGroupTable
|
|
||||||
*/
|
|
||||||
private postLayoutProcess(layoutGroupTable: LayoutGroupTable) {
|
|
||||||
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable);
|
|
||||||
|
|
||||||
modelList.forEach(item => {
|
// 用两个变量保存节点布局完成后的坐标,因为拖拽节点会改变节点的x,y坐标
|
||||||
item.preLayout = false;
|
// 然后当节点移动到泄漏区的时候,不应该保持节点被拖拽后的状态,应该恢复到布局完成后的状态,不然就会很奇怪
|
||||||
|
item.layoutX = item.get('x');
|
||||||
|
item.layoutY = item.get('y');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 用两个变量保存节点布局完成后的坐标,因为拖拽节点会改变节点的x,y坐标
|
/**
|
||||||
// 然后当节点移动到泄漏区的时候,不应该保持节点被拖拽后的状态,应该恢复到布局完成后的状态,不然就会很奇怪
|
* 布局外部指针
|
||||||
item.layoutX = item.get('x');
|
* @param marker
|
||||||
item.layoutY = item.get('y');
|
* @param markerOptions
|
||||||
});
|
*/
|
||||||
}
|
private layoutMarker(markers: SVMarker[], markerOptions: { [key: string]: MarkerOption }) {
|
||||||
|
markers.forEach(item => {
|
||||||
|
const options: MarkerOption = markerOptions[item.sourceType],
|
||||||
|
offset = options.offset ?? 8,
|
||||||
|
anchor = item.anchor ?? 0,
|
||||||
|
labelOffset = options.labelOffset ?? 2;
|
||||||
|
|
||||||
/**
|
let target = item.target,
|
||||||
* 布局外部指针
|
targetBound: BoundingRect = target.getBound(),
|
||||||
* @param marker
|
g6AnchorPosition = (<INode>item.target.shadowG6Item).getAnchorPoints()[anchor] as IPoint,
|
||||||
* @param markerOptions
|
center: [number, number] = [
|
||||||
*/
|
targetBound.x + targetBound.width / 2,
|
||||||
private layoutMarker(markers: SVMarker[], markerOptions: { [key: string]: MarkerOption }) {
|
targetBound.y + targetBound.height / 2,
|
||||||
markers.forEach(item => {
|
],
|
||||||
const options: MarkerOption = markerOptions[item.sourceType],
|
markerPosition: [number, number],
|
||||||
offset = options.offset ?? 8,
|
markerEndPosition: [number, number];
|
||||||
anchor = item.anchor ?? 0,
|
|
||||||
labelOffset = options.labelOffset ?? 2;
|
|
||||||
|
|
||||||
let target = item.target,
|
let anchorPosition: [number, number] = [g6AnchorPosition.x, g6AnchorPosition.y];
|
||||||
targetBound: BoundingRect = target.getBound(),
|
|
||||||
g6AnchorPosition = item.target.shadowG6Item.getAnchorPoints()[anchor] as IPoint,
|
|
||||||
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
|
|
||||||
markerPosition: [number, number],
|
|
||||||
markerEndPosition: [number, number];
|
|
||||||
|
|
||||||
let anchorPosition: [number, number] = [g6AnchorPosition.x, g6AnchorPosition.y];
|
let anchorVector = Vector.subtract(anchorPosition, center),
|
||||||
|
angle = 0,
|
||||||
|
len = Vector.length(anchorVector) + offset;
|
||||||
|
|
||||||
let anchorVector = Vector.subtract(anchorPosition, center),
|
if (anchorVector[0] === 0) {
|
||||||
angle = 0, len = Vector.length(anchorVector) + offset;
|
angle = anchorVector[1] > 0 ? -Math.PI : 0;
|
||||||
|
} else {
|
||||||
|
angle = Math.sign(anchorVector[0]) * (Math.PI / 2 - Math.atan(anchorVector[1] / anchorVector[0]));
|
||||||
|
}
|
||||||
|
|
||||||
if (anchorVector[0] === 0) {
|
const markerHeight = item.get('size')[1],
|
||||||
angle = anchorVector[1] > 0 ? -Math.PI : 0;
|
labelRadius = item.getLabelSizeRadius() / 2;
|
||||||
}
|
|
||||||
else {
|
|
||||||
angle = Math.sign(anchorVector[0]) * (Math.PI / 2 - Math.atan(anchorVector[1] / anchorVector[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
const markerHeight = item.get('size')[1],
|
anchorVector = Vector.normalize(anchorVector);
|
||||||
labelRadius = item.getLabelSizeRadius() / 2;
|
markerPosition = Vector.location(center, anchorVector, len);
|
||||||
|
markerEndPosition = Vector.location(center, anchorVector, markerHeight + len + labelRadius + labelOffset);
|
||||||
|
markerEndPosition = Vector.subtract(markerEndPosition, markerPosition);
|
||||||
|
|
||||||
anchorVector = Vector.normalize(anchorVector);
|
item.set({
|
||||||
markerPosition = Vector.location(center, anchorVector, len);
|
x: markerPosition[0],
|
||||||
markerEndPosition = Vector.location(center, anchorVector, markerHeight + len + labelRadius + labelOffset);
|
y: markerPosition[1],
|
||||||
markerEndPosition = Vector.subtract(markerEndPosition, markerPosition);
|
rotation: angle,
|
||||||
|
markerEndPosition,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
item.set({
|
/**
|
||||||
x: markerPosition[0],
|
* 布局节点的‘已释放’文本
|
||||||
y: markerPosition[1],
|
* @param freedLabels
|
||||||
rotation: angle,
|
*/
|
||||||
markerEndPosition
|
private layoutFreedLabel(freedLabels: SVFreedLabel[]) {
|
||||||
});
|
freedLabels.forEach(item => {
|
||||||
});
|
const freedNodeBound = item.target.getBound();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
item.set({
|
||||||
* 布局节点的‘已释放’文本
|
x: freedNodeBound.x + freedNodeBound.width / 2,
|
||||||
* @param freedLabels
|
y: freedNodeBound.y + freedNodeBound.height * 1.5,
|
||||||
*/
|
size: [freedNodeBound.width, 0],
|
||||||
private layoutFreedLabel(freedLabels: SVFreedLabel[]) {
|
});
|
||||||
freedLabels.forEach(item => {
|
});
|
||||||
const freedNodeBound = item.target.getBound();
|
}
|
||||||
|
|
||||||
item.set({
|
/**
|
||||||
x: freedNodeBound.x + freedNodeBound.width / 2,
|
*
|
||||||
y: freedNodeBound.y + freedNodeBound.height * 1.5,
|
* @param indexLabels
|
||||||
size: [freedNodeBound.width, 0]
|
* @param indexLabelOptions
|
||||||
});
|
*/
|
||||||
});
|
private layoutIndexLabel(indexLabels: SVIndexLabel[], indexLabelOptions: { [key: string]: IndexLabelOption }) {
|
||||||
}
|
const indexLabelPositionMap: {
|
||||||
|
[key: string]: (
|
||||||
|
nodeBound: BoundingRect,
|
||||||
|
labelBound: BoundingRect,
|
||||||
|
offset: number
|
||||||
|
) => { x: number; y: number };
|
||||||
|
} = {
|
||||||
|
top: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
|
||||||
|
return {
|
||||||
|
x: nodeBound.x + nodeBound.width / 2,
|
||||||
|
y: nodeBound.y - offset,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
right: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
|
||||||
|
return {
|
||||||
|
x: nodeBound.x + nodeBound.width + offset,
|
||||||
|
y: nodeBound.y + nodeBound.height / 2,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
bottom: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
|
||||||
|
return {
|
||||||
|
x: nodeBound.x + nodeBound.width / 2,
|
||||||
|
y: nodeBound.y + nodeBound.height + offset,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
left: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
|
||||||
|
return {
|
||||||
|
x: nodeBound.x - labelBound.width / 2- offset,
|
||||||
|
y: nodeBound.y + nodeBound.height / 2,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
indexLabels.forEach(item => {
|
||||||
*
|
const options: IndexLabelOption = indexLabelOptions[item.sourceType],
|
||||||
* @param indexLabels
|
nodeBound = item.target.getBound(),
|
||||||
* @param indexLabelOptions
|
// labelBound = item.getBound(),
|
||||||
*/
|
labelBound = item.shadowG6Item.getContainer().getChildren()[1].getBBox(),
|
||||||
private layoutIndexLabel(indexLabels: SVIndexLabel[], indexLabelOptions: { [key: string]: IndexLabelOption }) {
|
offset = options.offset ?? 20,
|
||||||
const indexLabelPositionMap: { [key: string]: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => { x: number, y: number } } = {
|
position = options.position ?? 'bottom';
|
||||||
top: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
|
|
||||||
return {
|
|
||||||
x: nodeBound.x + nodeBound.width / 2,
|
|
||||||
y: nodeBound.y - offset
|
|
||||||
};
|
|
||||||
},
|
|
||||||
right: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
|
|
||||||
return {
|
|
||||||
x: nodeBound.x + nodeBound.width + offset,
|
|
||||||
y: nodeBound.y + nodeBound.height / 2
|
|
||||||
};
|
|
||||||
},
|
|
||||||
bottom: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
|
|
||||||
return {
|
|
||||||
x: nodeBound.x + nodeBound.width / 2,
|
|
||||||
y: nodeBound.y + nodeBound.height + offset
|
|
||||||
};
|
|
||||||
},
|
|
||||||
left: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
|
|
||||||
return {
|
|
||||||
x: nodeBound.x - labelBound.width - offset,
|
|
||||||
y: nodeBound.y + nodeBound.height / 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
indexLabels.forEach(item => {
|
const pos = indexLabelPositionMap[position](nodeBound, labelBound, offset);
|
||||||
const options: IndexLabelOption = indexLabelOptions[item.sourceType],
|
item.set(pos);
|
||||||
nodeBound = item.target.getBound(),
|
});
|
||||||
labelBound = item.getBound(),
|
}
|
||||||
offset = options.offset ?? 20,
|
|
||||||
position = options.position ?? 'bottom';
|
|
||||||
|
|
||||||
const pos = indexLabelPositionMap[position](nodeBound, labelBound, offset);
|
/**
|
||||||
item.set(pos);
|
* 布局泄漏区节点上面的address label
|
||||||
});
|
* @param leakAddress
|
||||||
}
|
*/
|
||||||
|
private layoutAddressLabel(leakAddress: SVAddressLabel[], addressLabelOption: AddressLabelOption) {
|
||||||
|
const offset = addressLabelOption.offset || 16;
|
||||||
|
|
||||||
/**
|
leakAddress.forEach(item => {
|
||||||
* 布局泄漏区节点上面的address label
|
const nodeBound = item.target.getBound();
|
||||||
* @param leakAddress
|
|
||||||
*/
|
|
||||||
private layoutAddressLabel(leakAddress: SVAddressLabel[], addressLabelOption: AddressLabelOption) {
|
|
||||||
const offset = addressLabelOption.offset || 16;
|
|
||||||
|
|
||||||
leakAddress.forEach(item => {
|
item.set({
|
||||||
const nodeBound = item.target.getBound();
|
x: nodeBound.x + nodeBound.width / 2,
|
||||||
|
y: nodeBound.y - offset,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
item.set({
|
/**
|
||||||
x: nodeBound.x + nodeBound.width / 2,
|
* 对每个组内部的model进行布局
|
||||||
y: nodeBound.y - offset
|
* @param layoutGroupTable
|
||||||
});
|
*/
|
||||||
});
|
private layoutModels(layoutGroupTable: LayoutGroupTable): Group[] {
|
||||||
}
|
const modelGroupList: Group[] = [];
|
||||||
|
|
||||||
|
layoutGroupTable.forEach(group => {
|
||||||
|
const modelList: SVModel[] = group.modelList,
|
||||||
|
modelGroup: Group = new Group();
|
||||||
|
|
||||||
/**
|
const layoutOptions: LayoutOptions = group.options.layout;
|
||||||
* 对每个组内部的model进行布局
|
|
||||||
* @param layoutGroupTable
|
|
||||||
*/
|
|
||||||
private layoutModels(layoutGroupTable: LayoutGroupTable): Group[] {
|
|
||||||
const modelGroupList: Group[] = [];
|
|
||||||
|
|
||||||
layoutGroupTable.forEach(group => {
|
modelList.forEach(item => {
|
||||||
const modelList: SVModel[] = group.modelList,
|
modelGroup.add(item);
|
||||||
modelGroup: Group = new Group();
|
});
|
||||||
|
|
||||||
const layoutOptions: LayoutOptions = group.options.layout;
|
group.layoutCreator.layout(group.node, layoutOptions); // 布局节点
|
||||||
|
modelGroupList.push(modelGroup);
|
||||||
|
});
|
||||||
|
|
||||||
modelList.forEach(item => {
|
layoutGroupTable.forEach(group => {
|
||||||
modelGroup.add(item);
|
const markerOptions = group.options.marker || {},
|
||||||
});
|
indexLabelOptions = group.options.indexLabel || {},
|
||||||
|
addressLabelOption = group.options.addressLabel || {};
|
||||||
|
|
||||||
group.layoutCreator.layout(group.node, layoutOptions); // 布局节点
|
const indexLabel = group.appendage.filter(item => item instanceof SVIndexLabel) as SVIndexLabel[],
|
||||||
modelGroupList.push(modelGroup);
|
freedLabel = group.appendage.filter(item => item instanceof SVFreedLabel) as SVFreedLabel[],
|
||||||
});
|
addressLabel = group.appendage.filter(item => item instanceof SVAddressLabel) as SVAddressLabel[],
|
||||||
|
marker = group.appendage.filter(item => item instanceof SVMarker) as SVMarker[];
|
||||||
|
|
||||||
layoutGroupTable.forEach(group => {
|
this.layoutIndexLabel(indexLabel, indexLabelOptions);
|
||||||
const markerOptions = group.options.marker || {},
|
this.layoutFreedLabel(freedLabel);
|
||||||
indexLabelOptions = group.options.indexLabel || {},
|
this.layoutAddressLabel(addressLabel, addressLabelOption);
|
||||||
addressLabelOption = group.options.addressLabel || {};
|
this.layoutMarker(marker, markerOptions); // 布局外部指针
|
||||||
|
});
|
||||||
|
|
||||||
this.layoutIndexLabel(group.indexLabel, indexLabelOptions);
|
return modelGroupList;
|
||||||
this.layoutFreedLabel(group.freedLabel);
|
}
|
||||||
this.layoutAddressLabel(group.addressLabel, addressLabelOption);
|
|
||||||
this.layoutMarker(group.marker, markerOptions); // 布局外部指针
|
|
||||||
});
|
|
||||||
|
|
||||||
return modelGroupList;
|
/**
|
||||||
}
|
* 对泄漏区进行布局
|
||||||
|
* @param accumulateLeakModels
|
||||||
|
* // todo: 部分元素被抽离后的泄漏区
|
||||||
|
*/
|
||||||
|
private layoutLeakArea(accumulateLeakModels: SVModel[]) {
|
||||||
|
const containerHeight = this.viewContainer.getG6Instance().getHeight(),
|
||||||
|
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||||
|
leakAreaY = containerHeight - leakAreaHeight;
|
||||||
|
|
||||||
/**
|
let prevBound: BoundingRect;
|
||||||
* 对泄漏区进行布局
|
|
||||||
* @param leakModels
|
|
||||||
* @param accumulateLeakModels
|
|
||||||
*/
|
|
||||||
private layoutLeakModels(leakModels: SVModel[], accumulateLeakModels: SVModel[]) {
|
|
||||||
const group: Group = new Group(),
|
|
||||||
containerHeight = this.viewContainer.getG6Instance().getHeight(),
|
|
||||||
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
|
||||||
leakAreaY = containerHeight - leakAreaHeight,
|
|
||||||
xOffset = 60;
|
|
||||||
|
|
||||||
let prevBound: BoundingRect;
|
// 避免在泄漏前拖拽节点导致的位置变化,先把节点位置重置为布局后的标准位置
|
||||||
|
accumulateLeakModels.forEach(item => {
|
||||||
|
item.set({
|
||||||
|
x: item.layoutX,
|
||||||
|
y: item.layoutY,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 避免在泄漏前拖拽节点导致的位置变化,先把节点位置重置为布局后的标准位置
|
const clusters = ModelConstructor.getClusters(accumulateLeakModels);
|
||||||
leakModels.forEach(item => {
|
|
||||||
item.set({
|
|
||||||
x: item.layoutX,
|
|
||||||
y: item.layoutY
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const globalLeakGroupBound: BoundingRect = accumulateLeakModels.length ?
|
// 每一个簇从左往右布局就完事了,比之前的方法简单稳定很多
|
||||||
Bound.union(...accumulateLeakModels.map(item => item.getBound())) :
|
clusters.forEach(item => {
|
||||||
{ x: 0, y: leakAreaY, width: 0, height: 0 };
|
const bound = item.getBound(),
|
||||||
|
x = prevBound ? prevBound.x + prevBound.width + this.leakClusterXInterval : this.leakAreaXoffset,
|
||||||
|
dx = x - bound.x,
|
||||||
|
dy = leakAreaY - bound.y;
|
||||||
|
|
||||||
const layoutGroups = Util.groupBy(leakModels, 'group');
|
item.translate(dx, dy);
|
||||||
Object.keys(layoutGroups).forEach(key => {
|
Bound.translate(bound, dx, dy);
|
||||||
group.add(...layoutGroups[key]);
|
prevBound = bound;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const currentBound: BoundingRect = group.getBound(),
|
/**
|
||||||
prevBoundEnd = prevBound? prevBound.x + prevBound.width: 0,
|
* 对所有组进行相互布局
|
||||||
{ x: groupX, y: groupY } = currentBound,
|
* @param modelGroupTable
|
||||||
dx = globalLeakGroupBound.x + globalLeakGroupBound.width + prevBoundEnd + xOffset - groupX,
|
*/
|
||||||
dy = globalLeakGroupBound.y - groupY;
|
private layoutGroups(modelGroupList: Group[]): Group {
|
||||||
|
let wrapperGroup: Group = new Group(),
|
||||||
|
group: Group,
|
||||||
|
prevBound: BoundingRect,
|
||||||
|
bound: BoundingRect,
|
||||||
|
boundList: BoundingRect[] = [],
|
||||||
|
dx = 0;
|
||||||
|
|
||||||
group.translate(dx, dy);
|
// 左往右布局
|
||||||
group.clear();
|
for (let i = 0; i < modelGroupList.length; i++) {
|
||||||
Bound.translate(currentBound, dx, dy);
|
group = modelGroupList[i];
|
||||||
|
bound = group.getPaddingBound(this.viewOptions.groupPadding);
|
||||||
|
|
||||||
prevBound = currentBound;
|
if (prevBound) {
|
||||||
});
|
dx = prevBound.x + prevBound.width - bound.x;
|
||||||
}
|
} else {
|
||||||
|
dx = bound.x;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
group.translate(dx, 0);
|
||||||
* 对所有组进行相互布局
|
Bound.translate(bound, dx, 0);
|
||||||
* @param modelGroupTable
|
boundList.push(bound);
|
||||||
*/
|
wrapperGroup.add(group);
|
||||||
private layoutGroups(modelGroupList: Group[]): Group {
|
prevBound = bound;
|
||||||
let wrapperGroup: Group = new Group(),
|
}
|
||||||
group: Group,
|
|
||||||
prevBound: BoundingRect,
|
|
||||||
bound: BoundingRect,
|
|
||||||
boundList: BoundingRect[] = [],
|
|
||||||
dx = 0;
|
|
||||||
|
|
||||||
// 左往右布局
|
return wrapperGroup;
|
||||||
for (let i = 0; i < modelGroupList.length; i++) {
|
}
|
||||||
group = modelGroupList[i];
|
|
||||||
bound = group.getPaddingBound(this.viewOptions.groupPadding);
|
|
||||||
|
|
||||||
if (prevBound) {
|
/**
|
||||||
dx = prevBound.x + prevBound.width - bound.x;
|
* 将视图调整至画布中心
|
||||||
}
|
* @param models
|
||||||
else {
|
* @param leakAreaHeight
|
||||||
dx = bound.x;
|
*/
|
||||||
}
|
private fitCenter(group: Group) {
|
||||||
|
let width = this.viewContainer.getG6Instance().getWidth(),
|
||||||
|
height = this.viewContainer.getG6Instance().getHeight(),
|
||||||
|
viewBound: BoundingRect = group.getBound(),
|
||||||
|
leakAreaHeight = this.engine.viewOptions.leakAreaHeight;
|
||||||
|
|
||||||
group.translate(dx, 0);
|
const centerX = width / 2,
|
||||||
Bound.translate(bound, dx, 0);
|
centerY = height / 2,
|
||||||
boundList.push(bound);
|
boundCenterX = viewBound.x + viewBound.width / 2;
|
||||||
wrapperGroup.add(group);
|
|
||||||
prevBound = bound;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapperGroup;
|
let dx = centerX - boundCenterX,
|
||||||
}
|
dy = 0;
|
||||||
|
|
||||||
|
if (this.viewContainer.hasLeak) {
|
||||||
|
const boundBottomY = viewBound.y + viewBound.height;
|
||||||
|
dy = height - leakAreaHeight - 130 - boundBottomY;
|
||||||
|
} else {
|
||||||
|
const boundCenterY = viewBound.y + viewBound.height / 2;
|
||||||
|
dy = centerY - boundCenterY;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
group.translate(dx, dy);
|
||||||
* 将视图调整至画布中心
|
}
|
||||||
* @param models
|
|
||||||
* @param leakAreaHeight
|
|
||||||
*/
|
|
||||||
private fitCenter(group: Group) {
|
|
||||||
let width = this.viewContainer.getG6Instance().getWidth(),
|
|
||||||
height = this.viewContainer.getG6Instance().getHeight(),
|
|
||||||
leakAreaHeight = this.engine.viewOptions.leakAreaHeight;
|
|
||||||
|
|
||||||
if (this.viewContainer.hasLeak) {
|
/**
|
||||||
height = height - leakAreaHeight;
|
* 布局
|
||||||
}
|
* @param layoutGroupTable
|
||||||
|
* @param accumulateLeakModels
|
||||||
|
*/
|
||||||
|
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[]) {
|
||||||
|
this.preLayoutProcess(layoutGroupTable);
|
||||||
|
|
||||||
const viewBound: BoundingRect = group.getBound(),
|
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
|
||||||
centerX = width / 2, centerY = height / 2,
|
const generalGroup: Group = this.layoutGroups(modelGroupList);
|
||||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
|
||||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
|
||||||
dx = centerX - boundCenterX,
|
|
||||||
dy = centerY - boundCenterY;
|
|
||||||
|
|
||||||
group.translate(dx, dy);
|
this.layoutLeakArea(accumulateLeakModels);
|
||||||
}
|
|
||||||
|
|
||||||
|
this.fitCenter(generalGroup);
|
||||||
|
this.postLayoutProcess(layoutGroupTable);
|
||||||
/**
|
}
|
||||||
* 布局
|
}
|
||||||
* @param layoutGroupTable
|
|
||||||
* @param leakModels
|
|
||||||
* @param hasLeak
|
|
||||||
* @param needFitCenter
|
|
||||||
*/
|
|
||||||
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], leakModels: SVModel[]) {
|
|
||||||
this.preLayoutProcess(layoutGroupTable);
|
|
||||||
|
|
||||||
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
|
|
||||||
const generalGroup: Group = this.layoutGroups(modelGroupList);
|
|
||||||
|
|
||||||
if (leakModels.length) {
|
|
||||||
this.layoutLeakModels(leakModels, accumulateLeakModels);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fitCenter(generalGroup);
|
|
||||||
this.postLayoutProcess(layoutGroupTable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,430 +1,442 @@
|
|||||||
import { EventBus } from "../Common/eventBus";
|
import { EventBus } from '../Common/eventBus';
|
||||||
import { Util } from "../Common/util";
|
import { Util } from '../Common/util';
|
||||||
import { Engine } from "../engine";
|
import { Engine } from '../engine';
|
||||||
import { LayoutGroupTable } from "../Model/modelConstructor";
|
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||||
import { SVLink } from "../Model/SVLink";
|
import { SVLink } from '../Model/SVLink';
|
||||||
import { SVModel } from "../Model/SVModel";
|
import { SVModel } from '../Model/SVModel';
|
||||||
import { SVNode } from "../Model/SVNode";
|
import { SVNode } from '../Model/SVNode';
|
||||||
import { SVAddressLabel, SVMarker, SVNodeAppendage } from "../Model/SVNodeAppendage";
|
import { SVAddressLabel, SVMarker, SVNodeAppendage } from '../Model/SVNodeAppendage';
|
||||||
import { Animations } from "./animation";
|
import { handleUpdate } from '../sources';
|
||||||
import { Renderer } from "./renderer";
|
import { Animations } from './animation';
|
||||||
|
import { Renderer } from './renderer';
|
||||||
|
|
||||||
export interface DiffResult {
|
export interface DiffResult {
|
||||||
CONTINUOUS: SVModel[];
|
CONTINUOUS: SVModel[];
|
||||||
APPEND: SVModel[];
|
APPEND: SVModel[];
|
||||||
REMOVE: SVModel[];
|
REMOVE: SVModel[];
|
||||||
FREED: SVNode[];
|
FREED: SVNode[];
|
||||||
LEAKED: SVModel[];
|
LEAKED: SVModel[];
|
||||||
UPDATE: SVModel[];
|
UPDATE: SVModel[];
|
||||||
ACCUMULATE_LEAK: SVModel[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class Reconcile {
|
export class Reconcile {
|
||||||
|
private engine: Engine;
|
||||||
private engine: Engine;
|
private renderer: Renderer;
|
||||||
private renderer: Renderer;
|
private isFirstPatch: boolean;
|
||||||
private isFirstPatch: boolean;
|
|
||||||
|
constructor(engine: Engine, renderer: Renderer) {
|
||||||
constructor(engine: Engine, renderer: Renderer) {
|
this.engine = engine;
|
||||||
this.engine = engine;
|
this.renderer = renderer;
|
||||||
this.renderer = renderer;
|
this.isFirstPatch = true;
|
||||||
this.isFirstPatch = true;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* 获取上一次渲染存在的,这一次渲染也存在的models
|
||||||
* 获取上一次渲染存在的,这一次渲染也存在的models
|
* @param prevModelList
|
||||||
* @param prevModelList
|
* @param modelList
|
||||||
* @param modelList
|
*/
|
||||||
*/
|
private getContinuousModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||||
private getContinuousModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
const continuousModels = modelList.filter(item => prevModelList.find(prevModel => item.id === prevModel.id));
|
||||||
const continuousModels = modelList.filter(item => prevModelList.find(prevModel => item.id === prevModel.id));
|
return continuousModels;
|
||||||
return continuousModels;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* 获取新增的节点,这些节点有可能来自泄漏区(上一步的情况)
|
||||||
* 获取新增的节点,这些节点有可能来自泄漏区(上一步的情况)
|
* @param prevModelList
|
||||||
* @param prevModelList
|
* @param modelList
|
||||||
* @param modelList
|
* @param accumulateLeakModels
|
||||||
* @param accumulateLeakModels
|
* @returns
|
||||||
* @returns
|
*/
|
||||||
*/
|
private getAppendModels(
|
||||||
private getAppendModels(prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): SVModel[] {
|
prevModelList: SVModel[],
|
||||||
const appendModels = modelList.filter(item => !prevModelList.find(model => model.id === item.id));
|
modelList: SVModel[],
|
||||||
|
accumulateLeakModels: SVModel[]
|
||||||
appendModels.forEach(item => {
|
): SVModel[] {
|
||||||
let removeIndex = accumulateLeakModels.findIndex(leakModel => item.id === leakModel.id);
|
const appendModels = modelList.filter(item => !prevModelList.find(model => model.id === item.id));
|
||||||
|
|
||||||
if (removeIndex > -1) {
|
// 看看新增的节点是不是从泄漏区来的
|
||||||
accumulateLeakModels.splice(removeIndex, 1);
|
// 目前的判断方式比较傻:看看泄漏区有没有相同id的节点,但是发现这个方法可能不可靠,不知道还有没有更好的办法
|
||||||
}
|
appendModels.forEach(item => {
|
||||||
});
|
const removeIndex = accumulateLeakModels.findIndex(leakModel => item.id === leakModel.id),
|
||||||
|
svModel = accumulateLeakModels[removeIndex];
|
||||||
return appendModels;
|
|
||||||
}
|
if (removeIndex > -1) {
|
||||||
|
svModel.leaked = false;
|
||||||
/**
|
accumulateLeakModels.splice(removeIndex, 1);
|
||||||
* 获取被泄露的节点
|
}
|
||||||
* @param layoutGroupTable
|
});
|
||||||
* @param prevModelList
|
|
||||||
* @param modelList
|
return appendModels;
|
||||||
* @returns
|
}
|
||||||
*/
|
|
||||||
private getLeakModels(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
/**
|
||||||
let potentialLeakModels: SVModel[] = prevModelList.filter(item =>
|
* 获取被泄露的节点
|
||||||
!modelList.find(model => model.id === item.id) && !item.freed
|
* @param layoutGroupTable
|
||||||
);
|
* @param prevModelList
|
||||||
const leakModels: SVModel[] = [];
|
* @param modelList
|
||||||
|
* @returns
|
||||||
// 先把节点拿出来
|
*/
|
||||||
const potentialLeakNodes = potentialLeakModels.filter(item => item.isNode()) as SVNode[],
|
private getLeakModels(
|
||||||
groups = Util.groupBy<SVNode>(potentialLeakNodes, 'group');
|
layoutGroupTable: LayoutGroupTable,
|
||||||
|
prevModelList: SVModel[],
|
||||||
// 再把非节点的model拿出来
|
modelList: SVModel[]
|
||||||
potentialLeakModels = potentialLeakModels.filter(item => item.isNode() === false);
|
): SVModel[] {
|
||||||
|
let potentialLeakModels: SVModel[] = prevModelList.filter(
|
||||||
Object.keys(groups).forEach(key => {
|
item => !modelList.find(model => model.id === item.id) && !item.freed
|
||||||
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
|
);
|
||||||
if(leakRule && typeof leakRule === 'function') {
|
const leakModels: SVModel[] = [];
|
||||||
potentialLeakModels.push(...leakRule(groups[key]));
|
|
||||||
}
|
// 先把节点拿出来
|
||||||
});
|
const potentialLeakNodes = potentialLeakModels.filter(item => item.isNode()) as SVNode[],
|
||||||
|
groups = Util.groupBy<SVNode>(potentialLeakNodes, 'group');
|
||||||
potentialLeakModels.forEach(item => {
|
|
||||||
if (item instanceof SVNode) {
|
// 再把非节点的model拿出来
|
||||||
item.leaked = true;
|
potentialLeakModels = potentialLeakModels.filter(item => item.isNode() === false);
|
||||||
leakModels.push(item);
|
|
||||||
|
Object.keys(groups).forEach(key => {
|
||||||
item.appendages.forEach(appendage => {
|
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
|
||||||
appendage.leaked = true;
|
if (leakRule && typeof leakRule === 'function') {
|
||||||
leakModels.push(appendage);
|
potentialLeakModels.push(...leakRule(groups[key]));
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
potentialLeakModels.forEach(item => {
|
||||||
potentialLeakModels.forEach(item => {
|
if (item instanceof SVNode) {
|
||||||
if (item instanceof SVLink && item.node.leaked !== false && item.target.leaked !== false) {
|
item.leaked = true;
|
||||||
item.leaked = true;
|
leakModels.push(item);
|
||||||
leakModels.push(item);
|
|
||||||
}
|
item.getAppendagesList().forEach(appendage => {
|
||||||
});
|
// 外部指针先不加入泄漏区(这个需要讨论一下,我觉得不应该)
|
||||||
|
if (appendage instanceof SVMarker) {
|
||||||
leakModels.forEach(item => {
|
return;
|
||||||
// 不能用上次的G6item了,不然布局的时候会没有动画
|
}
|
||||||
item.G6Item = null;
|
|
||||||
});
|
appendage.leaked = true;
|
||||||
|
leakModels.push(appendage);
|
||||||
return leakModels;
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
/**
|
|
||||||
* 找出需要移除的节点
|
potentialLeakModels.forEach(item => {
|
||||||
* @param prevModelList
|
if (item instanceof SVLink && item.node.leaked !== false && item.target.leaked !== false) {
|
||||||
* @param modelList
|
item.leaked = true;
|
||||||
*/
|
leakModels.push(item);
|
||||||
private getRemoveModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
}
|
||||||
let removedModels: SVModel[] = [];
|
});
|
||||||
|
|
||||||
for (let i = 0; i < prevModelList.length; i++) {
|
return leakModels;
|
||||||
let prevModel = prevModelList[i],
|
}
|
||||||
target = modelList.find(item => item.id === prevModel.id);
|
|
||||||
|
/**
|
||||||
if (target === undefined && !prevModel.leaked) {
|
* 找出需要移除的节点
|
||||||
removedModels.push(prevModel);
|
* @param prevModelList
|
||||||
}
|
* @param modelList
|
||||||
}
|
*/
|
||||||
|
private getRemoveModels(
|
||||||
return removedModels;
|
prevModelList: SVModel[],
|
||||||
}
|
modelList: SVModel[],
|
||||||
|
accumulateLeakModels: SVModel[]
|
||||||
|
): SVModel[] {
|
||||||
/**
|
let removedModels: SVModel[] = [];
|
||||||
* 找出重新指向的外部指针
|
|
||||||
* @param prevModelList
|
for (let i = 0; i < prevModelList.length; i++) {
|
||||||
* @param modelList
|
let prevModel = prevModelList[i],
|
||||||
* @returns
|
target = modelList.find(item => item.id === prevModel.id);
|
||||||
*/
|
|
||||||
private getReTargetMarkers(prevModelList: SVModel[], modelList: SVModel[]): SVMarker[] {
|
if (target === undefined && !prevModel.leaked) {
|
||||||
const prevMarkers: SVMarker[] = prevModelList.filter(item => item instanceof SVMarker) as SVMarker[],
|
removedModels.push(prevModel);
|
||||||
markers: SVMarker[] = modelList.filter(item => item instanceof SVMarker) as SVMarker[];
|
}
|
||||||
|
}
|
||||||
return markers.filter(item => prevMarkers.find(prevItem => {
|
|
||||||
return prevItem.id === item.id && prevItem.target.id !== item.target.id
|
// 假如某个节点从泄漏区移回可视化区域,那么与原来泄漏结构的连线应该消失掉
|
||||||
}));
|
for (let i = 0; i < accumulateLeakModels.length; i++) {
|
||||||
}
|
let leakModel = accumulateLeakModels[i];
|
||||||
|
if (leakModel instanceof SVLink) {
|
||||||
/**
|
if (leakModel.node.leaked === false || leakModel.target.leaked === false) {
|
||||||
* 找出前后 label 发生变化的 model
|
accumulateLeakModels.splice(i, 1);
|
||||||
* @param prevModelList
|
i--;
|
||||||
* @param modelList
|
removedModels.push(leakModel);
|
||||||
* @returns
|
}
|
||||||
*/
|
}
|
||||||
private getLabelChangeModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
}
|
||||||
let labelChangeModels: SVModel[] = [];
|
|
||||||
|
removedModels.forEach(item => {
|
||||||
modelList.forEach(item => {
|
item.beforeDestroy();
|
||||||
const prevItem = prevModelList.find(prevItem => prevItem.id === item.id);
|
});
|
||||||
|
|
||||||
if (prevItem === undefined) {
|
return removedModels;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
const prevLabel = prevItem.get('label'),
|
* 找出重新指向的外部指针
|
||||||
label = item.get('label');
|
* @param prevModelList
|
||||||
|
* @param modelList
|
||||||
if (prevLabel !== label) {
|
* @returns
|
||||||
labelChangeModels.push(item);
|
*/
|
||||||
}
|
private getReTargetMarkers(prevModelList: SVModel[], modelList: SVModel[]): SVMarker[] {
|
||||||
});
|
const prevMarkers: SVMarker[] = prevModelList.filter(item => item instanceof SVMarker) as SVMarker[],
|
||||||
|
markers: SVMarker[] = modelList.filter(item => item instanceof SVMarker) as SVMarker[];
|
||||||
return labelChangeModels;
|
|
||||||
}
|
return markers.filter(item =>
|
||||||
|
prevMarkers.find(prevItem => {
|
||||||
/**
|
return prevItem.id === item.id && prevItem.target.id !== item.target.id;
|
||||||
* 获取被 free 的节点
|
})
|
||||||
* @param prevModelList
|
);
|
||||||
* @param modelList
|
}
|
||||||
* @returns
|
|
||||||
*/
|
/**
|
||||||
private getFreedModels(prevModelList: SVModel[], modelList: SVModel[]): SVNode[] {
|
* 找出前后 label 发生变化的 model
|
||||||
const freedNodes = modelList.filter(item => item instanceof SVNode && item.freed) as SVNode[];
|
* @param prevModelList
|
||||||
|
* @param modelList
|
||||||
freedNodes.forEach(item => {
|
* @returns
|
||||||
const prev = prevModelList.find(prevModel => item.id === prevModel.id);
|
*/
|
||||||
|
private getLabelChangeModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||||
if (prev) {
|
let labelChangeModels: SVModel[] = [];
|
||||||
item.set('label', prev.get('label'));
|
|
||||||
}
|
modelList.forEach(item => {
|
||||||
});
|
const prevItem = prevModelList.find(prevItem => prevItem.id === item.id);
|
||||||
|
|
||||||
return freedNodes;
|
if (prevItem === undefined) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
|
const prevLabel = prevItem.get('label'),
|
||||||
/**
|
label = item.get('label');
|
||||||
* 处理不变的models
|
|
||||||
* @param continuousModels
|
if (String(prevLabel) !== String(label)) {
|
||||||
*/
|
labelChangeModels.push(item);
|
||||||
private handleContinuousModels(continuousModels: SVModel[]) {
|
}
|
||||||
for (let i = 0; i < continuousModels.length; i++) {
|
});
|
||||||
let model = continuousModels[i];
|
|
||||||
|
return labelChangeModels;
|
||||||
if (model instanceof SVNode) {
|
}
|
||||||
const group = model.G6Item.getContainer();
|
|
||||||
group.attr({ opacity: 1 });
|
/**
|
||||||
}
|
* 获取被 free 的节点
|
||||||
}
|
* @param prevModelList
|
||||||
}
|
* @param modelList
|
||||||
|
* @returns
|
||||||
/**
|
*/
|
||||||
* 处理新增的 models
|
private getFreedModels(prevModelList: SVModel[], modelList: SVModel[]): SVNode[] {
|
||||||
* @param appendData
|
const freedNodes = modelList.filter(item => item instanceof SVNode && item.freed) as SVNode[];
|
||||||
*/
|
|
||||||
private handleAppendModels(appendModels: SVModel[]) {
|
freedNodes.forEach(item => {
|
||||||
let { duration, timingFunction } = this.engine.animationOptions;
|
const prev = prevModelList.find(prevModel => item.id === prevModel.id);
|
||||||
|
|
||||||
appendModels.forEach(item => {
|
if (prev) {
|
||||||
if (item instanceof SVNodeAppendage) {
|
item.set('label', prev.get('label'));
|
||||||
// 先不显示泄漏区节点上面的地址文本
|
}
|
||||||
if (item instanceof SVAddressLabel) {
|
});
|
||||||
// 先将透明度改为0,隐藏掉
|
|
||||||
const AddressLabelG6Group = item.G6Item.getContainer();
|
return freedNodes;
|
||||||
AddressLabelG6Group.attr({ opacity: 0 });
|
}
|
||||||
}
|
|
||||||
else {
|
// ------------------------------------------------------------------------------------------------
|
||||||
Animations.FADE_IN(item.G6Item, {
|
|
||||||
duration,
|
/**
|
||||||
timingFunction
|
* 处理不变的models
|
||||||
});
|
* @param continuousModels
|
||||||
}
|
*/
|
||||||
}
|
private handleContinuousModels(continuousModels: SVModel[]) {
|
||||||
else {
|
for (let i = 0; i < continuousModels.length; i++) {
|
||||||
Animations.APPEND(item.G6Item, {
|
let model = continuousModels[i];
|
||||||
duration,
|
|
||||||
timingFunction
|
if (model instanceof SVNode) {
|
||||||
});
|
const group = model.G6Item.getContainer();
|
||||||
}
|
group.attr({ opacity: 1 });
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* 处理被移除 models
|
/**
|
||||||
* @param removeData
|
* 处理新增的 models
|
||||||
*/
|
* @param appendData
|
||||||
private handleRemoveModels(removeModels: SVModel[]) {
|
*/
|
||||||
let { duration, timingFunction } = this.engine.animationOptions;
|
private handleAppendModels(appendModels: SVModel[]) {
|
||||||
|
let { duration, timingFunction } = this.engine.animationOptions;
|
||||||
removeModels.forEach(item => {
|
|
||||||
Animations.REMOVE(item.G6Item, {
|
appendModels.forEach(item => {
|
||||||
duration,
|
if (item instanceof SVNodeAppendage) {
|
||||||
timingFunction,
|
// 先不显示泄漏区节点上面的地址文本
|
||||||
callback: () => {
|
if (item instanceof SVAddressLabel) {
|
||||||
this.renderer.removeModel(item);
|
// 先将透明度改为0,隐藏掉
|
||||||
}
|
const AddressLabelG6Group = item.G6Item.getContainer();
|
||||||
});
|
AddressLabelG6Group.attr({ opacity: 0 });
|
||||||
});
|
} else {
|
||||||
}
|
Animations.FADE_IN(item.G6Item, {
|
||||||
|
duration,
|
||||||
/**
|
timingFunction,
|
||||||
* 处理泄漏区 models
|
});
|
||||||
* @param leakModels
|
}
|
||||||
*/
|
} else {
|
||||||
private handleLeakModels(leakModels: SVModel[]) {
|
Animations.APPEND(item.G6Item, {
|
||||||
let { duration, timingFunction } = this.engine.animationOptions;
|
duration,
|
||||||
|
timingFunction,
|
||||||
leakModels.forEach(item => {
|
callback: () => item.afterRender(),
|
||||||
if (item instanceof SVAddressLabel) {
|
});
|
||||||
Animations.FADE_IN(item.G6Item, {
|
}
|
||||||
duration,
|
});
|
||||||
timingFunction
|
}
|
||||||
});
|
|
||||||
}
|
/**
|
||||||
|
* 处理被移除 models
|
||||||
item.G6Item.enableCapture(false);
|
* @param removeData
|
||||||
});
|
*/
|
||||||
|
private handleRemoveModels(removeModels: SVModel[]) {
|
||||||
EventBus.emit('onLeak', leakModels);
|
let { duration, timingFunction } = this.engine.animationOptions;
|
||||||
}
|
|
||||||
|
removeModels.forEach(item => {
|
||||||
/**
|
Animations.REMOVE(item.G6Item, {
|
||||||
* 处理已经堆积的泄漏区 models
|
duration,
|
||||||
* @param accumulateModels
|
timingFunction,
|
||||||
*/
|
callback: () => {
|
||||||
private handleAccumulateLeakModels(accumulateModels: SVModel[]) {
|
this.renderer.removeModel(item);
|
||||||
accumulateModels.forEach(item => {
|
item.afterDestroy();
|
||||||
if (item.generalStyle) {
|
},
|
||||||
item.set('style', { ...item.generalStyle });
|
});
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 处理泄漏区 models
|
||||||
/**
|
* @param leakModels
|
||||||
* 处理被释放的节点 models
|
*/
|
||||||
* @param freedModes
|
private handleLeakModels(leakModels: SVModel[]) {
|
||||||
*/
|
let { duration, timingFunction } = this.engine.animationOptions;
|
||||||
private handleFreedModels(freedModes: SVNode[]) {
|
|
||||||
const { duration, timingFunction } = this.engine.animationOptions,
|
leakModels.forEach(item => {
|
||||||
alpha = 0.4;
|
if (item instanceof SVAddressLabel) {
|
||||||
|
Animations.FADE_IN(item.G6Item, {
|
||||||
freedModes.forEach(item => {
|
duration,
|
||||||
const nodeGroup = item.G6Item.getContainer();
|
timingFunction,
|
||||||
|
});
|
||||||
item.set('style', { fill: '#ccc' });
|
}
|
||||||
nodeGroup.attr({ opacity: alpha });
|
|
||||||
|
item.G6Item.enableCapture(false);
|
||||||
if (item.marker) {
|
});
|
||||||
const markerGroup = item.marker.G6Item.getContainer();
|
|
||||||
item.marker.set('style', { fill: '#ccc' });
|
EventBus.emit('onLeak', leakModels);
|
||||||
markerGroup.attr({ opacity: alpha + 0.5 });
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
item.freedLabel.G6Item.toFront();
|
* 处理被释放的节点 models
|
||||||
Animations.FADE_IN(item.freedLabel.G6Item, { duration, timingFunction });
|
* @param freedModes
|
||||||
});
|
*/
|
||||||
|
private handleFreedModels(freedModes: SVNode[]) {
|
||||||
EventBus.emit('onFreed', freedModes);
|
const { duration, timingFunction } = this.engine.animationOptions,
|
||||||
}
|
alpha = 0.4;
|
||||||
|
|
||||||
/**
|
freedModes.forEach(item => {
|
||||||
* 处理发生变化的 models
|
const nodeGroup = item.G6Item.getContainer();
|
||||||
* @param models
|
|
||||||
*/
|
item.set('style', { fill: '#ccc' });
|
||||||
private handleChangeModels(models: SVModel[]) {
|
nodeGroup.attr({ opacity: alpha });
|
||||||
const changeHighlightColor: string = this.engine.viewOptions.updateHighlight;
|
|
||||||
|
if (item.appendages.marker) {
|
||||||
if (!changeHighlightColor || typeof changeHighlightColor !== 'string') {
|
item.appendages.marker.forEach(marker => {
|
||||||
return;
|
const markerGroup = marker.G6Item.getContainer();
|
||||||
}
|
marker.set('style', { fill: '#ccc' });
|
||||||
|
markerGroup.attr({ opacity: alpha + 0.5 });
|
||||||
models.forEach(item => {
|
});
|
||||||
if (item.generalStyle === undefined) {
|
}
|
||||||
item.generalStyle = Util.objectClone(item.G6ModelProps.style);
|
|
||||||
}
|
if (item.appendages.freedLabel) {
|
||||||
|
item.appendages.freedLabel.forEach(freedLabel => {
|
||||||
if (item instanceof SVLink) {
|
freedLabel.G6Item.toFront();
|
||||||
item.set('style', {
|
Animations.FADE_IN(freedLabel.G6Item, { duration, timingFunction });
|
||||||
stroke: changeHighlightColor
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
else {
|
|
||||||
item.set('style', {
|
EventBus.emit('onFreed', freedModes);
|
||||||
fill: changeHighlightColor
|
}
|
||||||
});
|
|
||||||
}
|
/**
|
||||||
});
|
* 处理发生变化的 models
|
||||||
}
|
* @param models
|
||||||
|
*/
|
||||||
|
private handleChangeModels(models: SVModel[]) {
|
||||||
/**
|
const changeHighlightColor: string = this.engine.viewOptions.updateHighlight;
|
||||||
* 进行diff
|
|
||||||
* @param layoutGroupTable
|
if (!changeHighlightColor || typeof changeHighlightColor !== 'string') {
|
||||||
* @param prevModelList
|
return;
|
||||||
* @param modelList
|
}
|
||||||
* @param accumulateLeakModels
|
|
||||||
* @returns
|
models.forEach(item => {
|
||||||
*/
|
item.triggerHighlight(changeHighlightColor);
|
||||||
public diff(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): DiffResult {
|
});
|
||||||
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
|
}
|
||||||
const leakModels: SVModel[] = this.getLeakModels(layoutGroupTable, prevModelList, modelList);
|
|
||||||
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels);
|
/**
|
||||||
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList);
|
* 过滤新增model中那些不需要高亮的model(比如target和node都一样的link,marker等)
|
||||||
const updateModels: SVModel[] = [
|
* @param appendModels
|
||||||
...this.getReTargetMarkers(prevModelList, modelList),
|
*/
|
||||||
...this.getLabelChangeModels(prevModelList, modelList),
|
private filterUnChangeModelsOfAppend(appendModels: SVModel[], prevModelList: SVModel[]): SVModel[] {
|
||||||
...appendModels,
|
return appendModels.filter(item => !prevModelList.some(prev => item.isEqual(prev)));
|
||||||
...leakModels
|
}
|
||||||
];
|
|
||||||
const freedModels: SVNode[] = this.getFreedModels(prevModelList, modelList);
|
/**
|
||||||
|
* 进行diff
|
||||||
return {
|
* @param layoutGroupTable
|
||||||
CONTINUOUS: continuousModels,
|
* @param prevModelList
|
||||||
APPEND: appendModels,
|
* @param modelList
|
||||||
REMOVE: removeModels,
|
* @param accumulateLeakModels
|
||||||
FREED: freedModels,
|
* @returns
|
||||||
LEAKED: leakModels,
|
*/
|
||||||
UPDATE: updateModels,
|
public diff(
|
||||||
ACCUMULATE_LEAK: [...accumulateLeakModels]
|
layoutGroupTable: LayoutGroupTable,
|
||||||
};
|
prevModelList: SVModel[],
|
||||||
}
|
modelList: SVModel[],
|
||||||
|
accumulateLeakModels: SVModel[],
|
||||||
|
isEnterFunction: boolean
|
||||||
/**
|
): DiffResult {
|
||||||
* 执行调和操作
|
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
|
||||||
* @param diffResult
|
const leakModels: SVModel[] = isEnterFunction
|
||||||
* @param isFirstRender
|
? []
|
||||||
*/
|
: this.getLeakModels(layoutGroupTable, prevModelList, modelList);
|
||||||
public patch(diffResult: DiffResult) {
|
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels);
|
||||||
const {
|
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList, accumulateLeakModels);
|
||||||
APPEND,
|
const updateModels: SVModel[] = [
|
||||||
REMOVE,
|
...this.getReTargetMarkers(prevModelList, modelList),
|
||||||
FREED,
|
...this.getLabelChangeModels(prevModelList, modelList),
|
||||||
LEAKED,
|
...this.filterUnChangeModelsOfAppend(appendModels, prevModelList),
|
||||||
UPDATE,
|
...leakModels,
|
||||||
CONTINUOUS,
|
];
|
||||||
ACCUMULATE_LEAK
|
const freedModels: SVNode[] = this.getFreedModels(prevModelList, modelList);
|
||||||
} = diffResult;
|
|
||||||
|
return {
|
||||||
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
|
CONTINUOUS: continuousModels,
|
||||||
|
APPEND: appendModels,
|
||||||
// 第一次渲染的时候不高亮变化的元素
|
REMOVE: removeModels,
|
||||||
if (this.isFirstPatch === false) {
|
FREED: freedModels,
|
||||||
this.handleChangeModels(UPDATE);
|
LEAKED: leakModels,
|
||||||
}
|
UPDATE: updateModels,
|
||||||
|
};
|
||||||
this.handleContinuousModels(CONTINUOUS);
|
}
|
||||||
this.handleFreedModels(FREED);
|
|
||||||
this.handleAppendModels(APPEND);
|
/**
|
||||||
this.handleLeakModels(LEAKED);
|
* 执行调和操作
|
||||||
this.handleRemoveModels(REMOVE);
|
* @param diffResult
|
||||||
|
* @param isFirstRender
|
||||||
if (this.isFirstPatch) {
|
*/
|
||||||
this.isFirstPatch = false;
|
public patch(diffResult: DiffResult, handleUpdate: handleUpdate) {
|
||||||
}
|
const { APPEND, REMOVE, FREED, LEAKED, UPDATE, CONTINUOUS } = diffResult;
|
||||||
}
|
|
||||||
|
// 第一次渲染和进入函数的时候不高亮变化的元素
|
||||||
public destroy() { }
|
if (this.isFirstPatch === false && !handleUpdate?.isEnterFunction && !handleUpdate?.isFirstDebug) {
|
||||||
}
|
this.handleChangeModels(UPDATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleContinuousModels(CONTINUOUS);
|
||||||
|
this.handleFreedModels(FREED);
|
||||||
|
this.handleAppendModels(APPEND);
|
||||||
|
this.handleLeakModels(LEAKED);
|
||||||
|
this.handleRemoveModels(REMOVE);
|
||||||
|
|
||||||
|
if (this.isFirstPatch) {
|
||||||
|
this.isFirstPatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {}
|
||||||
|
}
|
||||||
|
@ -4,16 +4,14 @@ import { Util } from '../Common/util';
|
|||||||
import { Tooltip, Graph, GraphData, Modes } from '@antv/g6';
|
import { Tooltip, Graph, GraphData, Modes } from '@antv/g6';
|
||||||
import { InitG6Behaviors } from '../BehaviorHelper/initG6Behaviors';
|
import { InitG6Behaviors } from '../BehaviorHelper/initG6Behaviors';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface RenderModelPack {
|
export interface RenderModelPack {
|
||||||
leaKModels: SVModel[];
|
leaKModels: SVModel[];
|
||||||
generalModel: SVModel[];
|
generalModel: SVModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type g6Behavior =
|
||||||
export type g6Behavior = string | { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function; };
|
| string
|
||||||
|
| { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function };
|
||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
@ -194,4 +192,4 @@ export class Renderer {
|
|||||||
this.shadowG6Instance.destroy();
|
this.shadowG6Instance.destroy();
|
||||||
this.g6Instance.destroy();
|
this.g6Instance.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
import { Engine } from "../engine";
|
import { Engine } from '../engine';
|
||||||
import { LayoutProvider } from "./layoutProvider";
|
import { LayoutProvider } from './layoutProvider';
|
||||||
import { LayoutGroupTable } from "../Model/modelConstructor";
|
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||||
import { Util } from "../Common/util";
|
import { Util } from '../Common/util';
|
||||||
import { SVModel } from "../Model/SVModel";
|
import { SVModel } from '../Model/SVModel';
|
||||||
import { Renderer } from "./renderer";
|
import { Renderer } from './renderer';
|
||||||
import { Reconcile } from "./reconcile";
|
import { Reconcile } from './reconcile';
|
||||||
import { EventBus } from "../Common/eventBus";
|
import { EventBus } from '../Common/eventBus';
|
||||||
import { Group } from "../Common/group";
|
import { Group } from '../Common/group';
|
||||||
import { Graph, Modes } from "@antv/g6-pc";
|
import { Graph, Modes } from '@antv/g6-pc';
|
||||||
import { InitG6Behaviors } from "../BehaviorHelper/initG6Behaviors";
|
import { InitG6Behaviors } from '../BehaviorHelper/initG6Behaviors';
|
||||||
import { SVNode } from "../Model/SVNode";
|
import { SVNode } from '../Model/SVNode';
|
||||||
import { SolveBrushSelectDrag, SolveDragCanvasWithLeak, SolveNodeAppendagesDrag, SolveZoomCanvasWithLeak } from "../BehaviorHelper/behaviorIssueHelper";
|
import {
|
||||||
|
SolveBrushSelectDrag,
|
||||||
|
SolveDragCanvasWithLeak,
|
||||||
|
SolveNodeAppendagesDrag,
|
||||||
|
SolveZoomCanvasWithLeak,
|
||||||
|
} from '../BehaviorHelper/behaviorIssueHelper';
|
||||||
|
import { handleUpdate } from '../sources';
|
||||||
|
|
||||||
export class ViewContainer {
|
export class ViewContainer {
|
||||||
|
<<<<<<< HEAD
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private layoutProvider: LayoutProvider;
|
private layoutProvider: LayoutProvider;
|
||||||
private reconcile: Reconcile;
|
private reconcile: Reconcile;
|
||||||
@ -129,98 +134,239 @@ export class ViewContainer {
|
|||||||
this.renderer.refresh();
|
this.renderer.refresh();
|
||||||
|
|
||||||
this.leakAreaY += dy;
|
this.leakAreaY += dy;
|
||||||
|
=======
|
||||||
|
private engine: Engine;
|
||||||
|
private layoutProvider: LayoutProvider;
|
||||||
|
private reconcile: Reconcile;
|
||||||
|
public renderer: Renderer;
|
||||||
|
|
||||||
|
private layoutGroupTable: LayoutGroupTable;
|
||||||
|
private prevModelList: SVModel[];
|
||||||
|
private accumulateLeakModels: SVModel[];
|
||||||
|
|
||||||
|
public hasLeak: boolean;
|
||||||
|
public leakAreaY: number;
|
||||||
|
public lastLeakAreaTranslateY: number;
|
||||||
|
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
|
||||||
|
public clickSelectNode: SVNode; // 点击选中的节点
|
||||||
|
|
||||||
|
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||||
|
const behaviorsModes: Modes = InitG6Behaviors(engine, this);
|
||||||
|
|
||||||
|
this.engine = engine;
|
||||||
|
this.layoutProvider = new LayoutProvider(engine, this);
|
||||||
|
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes);
|
||||||
|
this.reconcile = new Reconcile(engine, this.renderer);
|
||||||
|
this.layoutGroupTable = new Map();
|
||||||
|
this.prevModelList = [];
|
||||||
|
this.accumulateLeakModels = [];
|
||||||
|
this.hasLeak = false; // 判断是否已经发生过泄漏
|
||||||
|
this.brushSelectedModels = [];
|
||||||
|
this.clickSelectNode = null;
|
||||||
|
|
||||||
|
const g6Instance = this.renderer.getG6Instance(),
|
||||||
|
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||||
|
height = g6Instance.getHeight(),
|
||||||
|
{ drag, zoom } = this.engine.behaviorOptions;
|
||||||
|
|
||||||
|
this.leakAreaY = height - leakAreaHeight;
|
||||||
|
this.lastLeakAreaTranslateY = 0;
|
||||||
|
|
||||||
|
SolveNodeAppendagesDrag(this);
|
||||||
|
SolveBrushSelectDrag(this);
|
||||||
|
drag && SolveDragCanvasWithLeak(this);
|
||||||
|
zoom && SolveZoomCanvasWithLeak(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对主视图进行重新布局
|
||||||
|
*/
|
||||||
|
reLayout() {
|
||||||
|
const g6Instance = this.getG6Instance(),
|
||||||
|
group = g6Instance.getGroup(),
|
||||||
|
matrix = group.getMatrix(),
|
||||||
|
bound = group.getCanvasBBox();
|
||||||
|
|
||||||
|
const { duration, enable, timingFunction } = this.engine.animationOptions;
|
||||||
|
|
||||||
|
if (matrix) {
|
||||||
|
let dx = matrix[6],
|
||||||
|
dy = matrix[7];
|
||||||
|
|
||||||
|
g6Instance.moveTo(bound.minX - dx, bound.minY - dy, enable, {
|
||||||
|
duration,
|
||||||
|
easing: timingFunction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||||
|
height = g6Instance.getHeight();
|
||||||
|
|
||||||
|
this.leakAreaY = height - leakAreaHeight;
|
||||||
|
this.lastLeakAreaTranslateY = 0;
|
||||||
|
this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels);
|
||||||
|
g6Instance.refresh();
|
||||||
|
|
||||||
|
>>>>>>> eac9d007bcc1b52fe573ddf6cb97030c9b2d3a6d
|
||||||
EventBus.emit('onLeakAreaUpdate', {
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
leakAreaY: this.leakAreaY,
|
leakAreaY: this.leakAreaY,
|
||||||
hasLeak: this.hasLeak
|
hasLeak: this.hasLeak,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染所有视图
|
* 获取 g6 实例
|
||||||
* @param models
|
*/
|
||||||
* @param layoutFn
|
getG6Instance(): Graph {
|
||||||
*/
|
return this.renderer.getG6Instance();
|
||||||
render(layoutGroupTable: LayoutGroupTable) {
|
}
|
||||||
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
|
|
||||||
diffResult = this.reconcile.diff(this.layoutGroupTable, this.prevModelList, modelList, this.accumulateLeakModels),
|
|
||||||
renderModelList = [
|
|
||||||
...modelList,
|
|
||||||
...diffResult.REMOVE,
|
|
||||||
...diffResult.LEAKED,
|
|
||||||
...diffResult.ACCUMULATE_LEAK
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
|
/**
|
||||||
this.hasLeak = false;
|
* 获取泄漏区里面的元素
|
||||||
EventBus.emit('onLeakAreaUpdate', {
|
* @returns
|
||||||
leakAreaY: this.leakAreaY,
|
*/
|
||||||
hasLeak: this.hasLeak
|
getAccumulateLeakModels(): SVModel[] {
|
||||||
});
|
return this.accumulateLeakModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diffResult.LEAKED.length) {
|
/**
|
||||||
this.hasLeak = true;
|
*
|
||||||
EventBus.emit('onLeakAreaUpdate', {
|
*/
|
||||||
leakAreaY: this.leakAreaY,
|
getLayoutGroupTable(): LayoutGroupTable {
|
||||||
hasLeak: this.hasLeak
|
return this.layoutGroupTable;
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
|
/**
|
||||||
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels, diffResult.LEAKED); // 进行布局(设置model的x,y,样式等)
|
* 刷新视图
|
||||||
|
*/
|
||||||
|
refresh() {
|
||||||
|
this.renderer.getG6Instance().refresh();
|
||||||
|
}
|
||||||
|
|
||||||
this.beforeRender();
|
/**
|
||||||
this.renderer.render(renderModelList); // 渲染视图
|
* 重新调整容器尺寸
|
||||||
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
|
* @param width
|
||||||
this.afterRender();
|
* @param height
|
||||||
|
*/
|
||||||
|
resize(width: number, height: number) {
|
||||||
|
const g6Instance = this.getG6Instance(),
|
||||||
|
prevContainerHeight = g6Instance.getHeight(),
|
||||||
|
globalGroup: Group = new Group();
|
||||||
|
|
||||||
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行累积
|
globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels);
|
||||||
|
this.renderer.changeSize(width, height);
|
||||||
|
|
||||||
this.layoutGroupTable = layoutGroupTable;
|
const containerHeight = g6Instance.getHeight(),
|
||||||
this.prevModelList = modelList;
|
dy = containerHeight - prevContainerHeight;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
globalGroup.translate(0, dy);
|
||||||
* 销毁
|
this.renderer.refresh();
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
this.renderer.destroy();
|
|
||||||
this.reconcile.destroy();
|
|
||||||
this.layoutProvider = null;
|
|
||||||
this.layoutGroupTable = null;
|
|
||||||
this.prevModelList.length = 0;
|
|
||||||
this.accumulateLeakModels.length = 0;
|
|
||||||
this.brushSelectedModels.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.leakAreaY += dy;
|
||||||
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
|
leakAreaY: this.leakAreaY,
|
||||||
|
hasLeak: this.hasLeak,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
/**
|
||||||
|
*
|
||||||
|
* @param models
|
||||||
|
*/
|
||||||
|
private restoreHighlight(models: SVModel[]) {
|
||||||
|
models.forEach(item => {
|
||||||
|
// 不是free节点才进行还原
|
||||||
|
if (!item.freed) {
|
||||||
|
item.restoreHighlight();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染所有视图
|
||||||
|
* @param models
|
||||||
|
* @param layoutFn
|
||||||
|
*/
|
||||||
|
render(layoutGroupTable: LayoutGroupTable, isSameSources: boolean, handleUpdate: handleUpdate) {
|
||||||
|
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable);
|
||||||
|
|
||||||
/**
|
this.restoreHighlight([...modelList, ...this.accumulateLeakModels]);
|
||||||
* 把渲染后要触发的逻辑放在这里
|
|
||||||
*/
|
|
||||||
private afterRender() {
|
|
||||||
this.prevModelList.forEach(item => {
|
|
||||||
if (item.leaked === false) {
|
|
||||||
item.discarded = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// 如果数据没变的话,直接退出
|
||||||
* 把渲染前要触发的逻辑放在这里
|
if (isSameSources) {
|
||||||
*/
|
return;
|
||||||
private beforeRender() { }
|
}
|
||||||
|
|
||||||
|
const diffResult = this.reconcile.diff(
|
||||||
|
this.layoutGroupTable,
|
||||||
|
this.prevModelList,
|
||||||
|
modelList,
|
||||||
|
this.accumulateLeakModels,
|
||||||
|
handleUpdate?.isEnterFunction
|
||||||
|
),
|
||||||
|
renderModelList = [...modelList, ...diffResult.REMOVE, ...diffResult.LEAKED, ...this.accumulateLeakModels];
|
||||||
|
|
||||||
|
// 从有泄漏区变成无泄漏区
|
||||||
|
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
|
||||||
|
this.hasLeak = false;
|
||||||
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
|
leakAreaY: this.leakAreaY,
|
||||||
|
hasLeak: this.hasLeak,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从无泄漏区变成有泄漏区
|
||||||
|
if (diffResult.LEAKED.length) {
|
||||||
|
this.hasLeak = true;
|
||||||
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
|
leakAreaY: this.leakAreaY,
|
||||||
|
hasLeak: this.hasLeak,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行向后累积
|
||||||
|
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
|
||||||
|
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels); // 进行布局(设置model的x,y,样式等)
|
||||||
|
|
||||||
|
this.beforeRender();
|
||||||
|
this.renderer.render(renderModelList); // 渲染视图
|
||||||
|
this.reconcile.patch(diffResult, handleUpdate); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
|
||||||
|
this.afterRender();
|
||||||
|
|
||||||
|
this.layoutGroupTable = layoutGroupTable;
|
||||||
|
this.prevModelList = modelList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
this.renderer.destroy();
|
||||||
|
this.reconcile.destroy();
|
||||||
|
this.layoutProvider = null;
|
||||||
|
this.layoutGroupTable = null;
|
||||||
|
this.prevModelList.length = 0;
|
||||||
|
this.accumulateLeakModels.length = 0;
|
||||||
|
this.brushSelectedModels.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把渲染后要触发的逻辑放在这里
|
||||||
|
*/
|
||||||
|
private afterRender() {
|
||||||
|
this.prevModelList.forEach(item => {
|
||||||
|
if (item.leaked === false) {
|
||||||
|
item.discarded = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把渲染前要触发的逻辑放在这里
|
||||||
|
*/
|
||||||
|
private beforeRender() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Sources } from "./sources";
|
import { handleUpdate, Sources } from "./sources";
|
||||||
import { LayoutGroupTable, ModelConstructor } from "./Model/modelConstructor";
|
import { LayoutGroupTable, ModelConstructor } from "./Model/modelConstructor";
|
||||||
import { AnimationOptions, BehaviorOptions, EngineOptions, LayoutGroupOptions, ViewOptions } from "./options";
|
import { AnimationOptions, BehaviorOptions, EngineOptions, LayoutGroupOptions, ViewOptions } from "./options";
|
||||||
import { EventBus } from "./Common/eventBus";
|
import { EventBus } from "./Common/eventBus";
|
||||||
@ -6,6 +6,7 @@ import { ViewContainer } from "./View/viewContainer";
|
|||||||
import { SVNode } from "./Model/SVNode";
|
import { SVNode } from "./Model/SVNode";
|
||||||
import { Util } from "./Common/util";
|
import { Util } from "./Common/util";
|
||||||
import { SVModel } from "./Model/SVModel";
|
import { SVModel } from "./Model/SVModel";
|
||||||
|
import { G6Event } from "@antv/g6";
|
||||||
|
|
||||||
|
|
||||||
export class Engine {
|
export class Engine {
|
||||||
@ -49,9 +50,10 @@ export class Engine {
|
|||||||
/**
|
/**
|
||||||
* 输入数据进行渲染
|
* 输入数据进行渲染
|
||||||
* @param sources
|
* @param sources
|
||||||
* @param force
|
* @param prevStep
|
||||||
*/
|
*/
|
||||||
public render(source: Sources) {
|
public render(source: Sources) {
|
||||||
|
<<<<<<< HEAD
|
||||||
if (source === undefined || source === null) {
|
if (source === undefined || source === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -59,15 +61,36 @@ export class Engine {
|
|||||||
let stringSource = JSON.stringify(source);
|
let stringSource = JSON.stringify(source);
|
||||||
if (this.prevStringSource === stringSource) {
|
if (this.prevStringSource === stringSource) {
|
||||||
return;
|
return;
|
||||||
|
=======
|
||||||
|
let isSameSources: boolean = false,
|
||||||
|
layoutGroupTable: LayoutGroupTable;
|
||||||
|
|
||||||
|
if (source === undefined || source === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let handleUpdate: handleUpdate = source.handleUpdate,
|
||||||
|
stringSource = JSON.stringify(source);
|
||||||
|
|
||||||
|
if (this.prevStringSource === stringSource) {
|
||||||
|
isSameSources = true;
|
||||||
|
>>>>>>> eac9d007bcc1b52fe573ddf6cb97030c9b2d3a6d
|
||||||
}
|
}
|
||||||
|
|
||||||
this.prevStringSource = stringSource;
|
this.prevStringSource = stringSource;
|
||||||
|
|
||||||
// 1 转换模型(data => model)
|
|
||||||
const layoutGroupTable = this.modelConstructor.construct(source);
|
if(isSameSources) {
|
||||||
|
// 若源数据两次一样的,用回上一次的layoutGroupTable
|
||||||
|
layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 1 转换模型(data => model)
|
||||||
|
layoutGroupTable = this.modelConstructor.construct(source);
|
||||||
|
}
|
||||||
|
|
||||||
// 2 渲染(使用g6进行渲染)
|
// 2 渲染(使用g6进行渲染)
|
||||||
this.viewContainer.render(layoutGroupTable);
|
this.viewContainer.render(layoutGroupTable, isSameSources, handleUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -192,7 +215,7 @@ export class Engine {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.viewContainer.getG6Instance().on(eventName, event => {
|
this.viewContainer.getG6Instance().on(eventName as G6Event, event => {
|
||||||
callback(event.item['SVModel']);
|
callback(event.item['SVModel']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
// 连接目标信息
|
// 连接目标信息
|
||||||
export type LinkTarget = number | string;
|
export type LinkTarget = number | string;
|
||||||
|
|
||||||
@ -10,17 +9,19 @@ export type sourceMarkerData = string | string[];
|
|||||||
|
|
||||||
// 源数据单元
|
// 源数据单元
|
||||||
export interface SourceNode {
|
export interface SourceNode {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
[key: string]: any | sourceLinkData | sourceMarkerData;
|
[key: string]: any | sourceLinkData | sourceMarkerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface handleUpdate {
|
||||||
|
isEnterFunction: boolean;
|
||||||
|
isFirstDebug: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type Sources = {
|
export type Sources = {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
data: SourceNode[];
|
data: SourceNode[];
|
||||||
layouter: string;
|
layouter: string;
|
||||||
}
|
};
|
||||||
|
handleUpdate?: handleUpdate | any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2015",
|
"target": "ES2015",
|
||||||
"module": "commonJS",
|
"module": "commonJS",
|
||||||
"removeComments": true
|
"removeComments": true,
|
||||||
|
"lib": ["DOM", "ES2015", "ES2016", "ES2017"]
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
mode: 'production',
|
||||||
entry: './src/StructV.ts',
|
entry: './src/StructV.ts',
|
||||||
output: {
|
output: {
|
||||||
filename: './sv.js',
|
filename: './sv.js',
|
||||||
@ -17,6 +17,5 @@ module.exports = {
|
|||||||
loader: 'ts-loader'
|
loader: 'ts-loader'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
// devtool: 'eval-source-map'
|
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user