feat: 重写泄漏区逻辑
This commit is contained in:
parent
5893ef2938
commit
3170bd77ec
@ -1,11 +1,11 @@
|
|||||||
SV.registerLayout('BinaryTree', {
|
SV.registerLayout('BinaryTree', {
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
element: {
|
node: {
|
||||||
default: {
|
default: {
|
||||||
type: 'binary-tree-node',
|
type: 'binary-tree-node',
|
||||||
size: [60, 30],
|
size: [60, 30],
|
||||||
label: '[data]',
|
label: '[id]',
|
||||||
style: {
|
style: {
|
||||||
fill: '#b83b5e',
|
fill: '#b83b5e',
|
||||||
stroke: '#333',
|
stroke: '#333',
|
||||||
@ -58,7 +58,7 @@ SV.registerLayout('BinaryTree', {
|
|||||||
/**
|
/**
|
||||||
* 对子树进行递归布局
|
* 对子树进行递归布局
|
||||||
*/
|
*/
|
||||||
layoutItem(node, parent, index, layoutOptions) {
|
layoutItem(node, layoutOptions) {
|
||||||
// 次双亲不进行布局
|
// 次双亲不进行布局
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return null;
|
return null;
|
||||||
@ -69,43 +69,54 @@ SV.registerLayout('BinaryTree', {
|
|||||||
height = bound.height,
|
height = bound.height,
|
||||||
group = new Group(node),
|
group = new Group(node),
|
||||||
leftGroup = null,
|
leftGroup = null,
|
||||||
rightGroup = null;
|
rightGroup = null,
|
||||||
|
leftBound = null,
|
||||||
|
rightBound = null;
|
||||||
|
|
||||||
if (node.visited) {
|
if (node.visited) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.child && node.child[0]) {
|
if (node.child && node.child[0]) {
|
||||||
leftGroup = this.layoutItem(node.child[0], node, 0, layoutOptions);
|
leftGroup = this.layoutItem(node.child[0], layoutOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.child && node.child[1]) {
|
if (node.child && node.child[1]) {
|
||||||
rightGroup = this.layoutItem(node.child[1], node, 1, layoutOptions);
|
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) {
|
if (leftGroup && rightGroup) {
|
||||||
let intersection = Bound.intersect(leftGroup.getBound(), rightGroup.getBound()),
|
let move = Math.abs(rightBound.x - layoutOptions.xInterval - leftBound.x - leftBound.width);
|
||||||
move = 0;
|
if (move > 0) {
|
||||||
|
leftGroup.translate(-move / 2, 0);
|
||||||
if (intersection && intersection.width > 0) {
|
rightGroup.translate(move / 2, 0);
|
||||||
move = (intersection.width + layoutOptions.xInterval) / 2;
|
|
||||||
leftGroup.translate(-move, 0);
|
|
||||||
rightGroup.translate(move, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leftGroup) {
|
if (leftGroup) {
|
||||||
let leftBound = leftGroup.getBound();
|
leftBound = leftGroup.getBound();
|
||||||
|
|
||||||
node.set('y', leftBound.y - layoutOptions.yInterval - height);
|
|
||||||
node.set('x', leftBound.x + leftBound.width + layoutOptions.xInterval / 2 - width);
|
node.set('x', leftBound.x + leftBound.width + layoutOptions.xInterval / 2 - width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(rightGroup) {
|
if(rightGroup) {
|
||||||
let rightBound = rightGroup.getBound();
|
rightBound = rightGroup.getBound();
|
||||||
|
|
||||||
node.set('y', rightBound.y - layoutOptions.yInterval - height);
|
|
||||||
node.set('x', rightBound.x - layoutOptions.xInterval / 2 - width);
|
node.set('x', rightBound.x - layoutOptions.xInterval / 2 - width);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +140,7 @@ SV.registerLayout('BinaryTree', {
|
|||||||
*/
|
*/
|
||||||
layout(elements, layoutOptions) {
|
layout(elements, layoutOptions) {
|
||||||
let root = elements[0];
|
let root = elements[0];
|
||||||
this.layoutItem(root, null, -1, layoutOptions);
|
this.layoutItem(root, layoutOptions);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -77,25 +77,37 @@ SV.registerLayout('LinkList', {
|
|||||||
layout: {
|
layout: {
|
||||||
xInterval: 50,
|
xInterval: 50,
|
||||||
yInterval: 50
|
yInterval: 50
|
||||||
},
|
|
||||||
behavior: {
|
|
||||||
dragNode: false
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
layout(elements, layoutOptions) {
|
/**
|
||||||
for (let i = 0; i < elements.length; i++) {
|
* 对子树进行递归布局
|
||||||
let node = elements[i],
|
* @param node
|
||||||
prev = elements[1 - 1],
|
* @param parent
|
||||||
width = node.get('size')[0];
|
*/
|
||||||
|
layoutItem(node, prev, layoutOptions) {
|
||||||
|
if (!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this.layoutItem(node.next, node, layoutOptions);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
layout(elements, layoutOptions) {
|
||||||
|
let root = elements[0];
|
||||||
|
this.layoutItem(root, null, layoutOptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
120
demoV2/data.js
Normal file
120
demoV2/data.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
const SOURCES_DATA = [
|
||||||
|
{
|
||||||
|
LinkList0: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '0x617eb0',
|
||||||
|
data: 'Z',
|
||||||
|
next: '0x617ef0',
|
||||||
|
rootExternal: ['L'],
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617ef0',
|
||||||
|
data: 'A',
|
||||||
|
next: '0x617f10',
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617f10',
|
||||||
|
data: 'B',
|
||||||
|
next: '0x617f30',
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617f30',
|
||||||
|
data: 'C',
|
||||||
|
external: ['r', 't'],
|
||||||
|
next: null,
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
layouter: 'LinkList',
|
||||||
|
},
|
||||||
|
LinkList1: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '0x617ed0',
|
||||||
|
data: 'Y',
|
||||||
|
next: '0x617f50',
|
||||||
|
rootExternal: ['L2'],
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617f50',
|
||||||
|
data: 'a',
|
||||||
|
next: '0x617f70',
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617f70',
|
||||||
|
data: 'b',
|
||||||
|
external: ['r2', 't2'],
|
||||||
|
loopNext: 'LinkList0#0x617f30',
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
layouter: 'LinkList',
|
||||||
|
},
|
||||||
|
isEnterFunction: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
LinkList0: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '0x617eb0',
|
||||||
|
data: 'Z',
|
||||||
|
next: '0x617ef0',
|
||||||
|
rootExternal: ['L'],
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617ef0',
|
||||||
|
data: 'A',
|
||||||
|
next: '0x617f10',
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617f10',
|
||||||
|
data: 'B',
|
||||||
|
next: '0x617f30',
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617f30',
|
||||||
|
data: 'C',
|
||||||
|
external: ['r', 't'],
|
||||||
|
next: null,
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
layouter: 'LinkList',
|
||||||
|
},
|
||||||
|
LinkList1: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '0x617ed0',
|
||||||
|
data: 'Y',
|
||||||
|
next: '0x617f50',
|
||||||
|
rootExternal: ['L2'],
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617f50',
|
||||||
|
data: 'a',
|
||||||
|
next: '0x617f70',
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '0x617f70',
|
||||||
|
data: 'b',
|
||||||
|
external: ['r2', 't2'],
|
||||||
|
next: null,
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
layouter: 'LinkList',
|
||||||
|
},
|
||||||
|
isEnterFunction: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -1,7 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<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>
|
||||||
@ -43,13 +42,13 @@
|
|||||||
transition: opacity 0.75s ease-in-out;
|
transition: opacity 0.75s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#leak>span {
|
#leak > span {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container" id="container">
|
<div class="container" id="container">
|
||||||
<div id="leak">
|
<div id="leak">
|
||||||
<span>泄漏区</span>
|
<span>泄漏区</span>
|
||||||
@ -85,6 +84,7 @@
|
|||||||
<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="./Layouter/PCTree.js"></script>
|
||||||
|
<script src="./data.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let cur = SV(document.getElementById('container'), {
|
let cur = SV(document.getElementById('container'), {
|
||||||
@ -94,93 +94,8 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let data = [{
|
|
||||||
"BinaryTree0": {
|
|
||||||
"data": [{
|
|
||||||
"external": [
|
|
||||||
"T1",
|
|
||||||
"r"
|
|
||||||
],
|
|
||||||
"child": [
|
|
||||||
"0x617ee0",
|
|
||||||
"0x617f10"
|
|
||||||
],
|
|
||||||
"id": "0x617eb0",
|
|
||||||
"name": "T1",
|
|
||||||
"data": "Z",
|
|
||||||
"root": true,
|
|
||||||
"type": "default"
|
|
||||||
}, {
|
|
||||||
"child": [
|
|
||||||
"0x0",
|
|
||||||
"0x0"
|
|
||||||
],
|
|
||||||
"id": "0x617ee0",
|
|
||||||
"name": "T1->lchild",
|
|
||||||
"data": "A",
|
|
||||||
"type": "default"
|
|
||||||
}, {
|
|
||||||
"child": [
|
|
||||||
"0x0",
|
|
||||||
"0x617f40"
|
|
||||||
],
|
|
||||||
"id": "0x617f10",
|
|
||||||
"name": "T1->rchild",
|
|
||||||
"data": "B",
|
|
||||||
"type": "default",
|
|
||||||
"external": [
|
|
||||||
"t"
|
|
||||||
]
|
|
||||||
}, ],
|
|
||||||
"layouter": "BinaryTree"
|
|
||||||
},
|
|
||||||
"isEnterFunction": false
|
|
||||||
}, {
|
|
||||||
"BinaryTree1": {
|
|
||||||
"data": [{
|
|
||||||
"external": [
|
|
||||||
"T1",
|
|
||||||
"r"
|
|
||||||
],
|
|
||||||
"child": [
|
|
||||||
"0x617ee0",
|
|
||||||
"0x617f10"
|
|
||||||
],
|
|
||||||
"id": "0x617eb0",
|
|
||||||
"name": "T1",
|
|
||||||
"data": "Z",
|
|
||||||
"root": true,
|
|
||||||
"type": "default"
|
|
||||||
}, {
|
|
||||||
"child": [
|
|
||||||
"0x0",
|
|
||||||
"0x0"
|
|
||||||
],
|
|
||||||
"id": "0x617ee0",
|
|
||||||
"name": "T1->lchild",
|
|
||||||
"data": "A",
|
|
||||||
freed: 'freed',
|
|
||||||
"type": "default"
|
|
||||||
}, {
|
|
||||||
"child": [
|
|
||||||
"0x0",
|
|
||||||
"0x617f40"
|
|
||||||
],
|
|
||||||
"id": "0x617f10",
|
|
||||||
"name": "T1->rchild",
|
|
||||||
"data": "B",
|
|
||||||
"type": "default",
|
|
||||||
"external": [
|
|
||||||
"t"
|
|
||||||
]
|
|
||||||
}, ],
|
|
||||||
"layouter": "BinaryTree"
|
|
||||||
},
|
|
||||||
"isEnterFunction": false
|
|
||||||
}, ];
|
|
||||||
|
|
||||||
let dataIndex = 0,
|
let dataIndex = 0,
|
||||||
curData = data[dataIndex];
|
curData = SOURCES_DATA[dataIndex];
|
||||||
|
|
||||||
let enableBrushSelect = false;
|
let enableBrushSelect = false;
|
||||||
|
|
||||||
@ -192,12 +107,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -232,6 +147,5 @@
|
|||||||
pos.innerHTML = `${x},${y}`;
|
pos.innerHTML = `${x},${y}`;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -76,7 +76,7 @@ export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
|
|||||||
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;
|
||||||
@ -100,7 +100,7 @@ export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
|
|||||||
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,
|
||||||
@ -116,7 +116,7 @@ export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import { Vector } from "./vector";
|
import { Vector } from './vector';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 包围盒类型
|
// 包围盒类型
|
||||||
export type BoundingRect = {
|
export type BoundingRect = {
|
||||||
@ -10,10 +8,8 @@ export type BoundingRect = {
|
|||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 包围盒操作
|
// 包围盒操作
|
||||||
export const Bound = {
|
export const Bound = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从点集生成包围盒
|
* 从点集生成包围盒
|
||||||
* @param points
|
* @param points
|
||||||
@ -25,17 +21,17 @@ export const Bound = {
|
|||||||
minY = Infinity;
|
minY = Infinity;
|
||||||
|
|
||||||
points.map(item => {
|
points.map(item => {
|
||||||
if(item[0] > maxX) maxX = item[0];
|
if (item[0] > maxX) maxX = item[0];
|
||||||
if(item[0] < minX) minX = item[0];
|
if (item[0] < minX) minX = item[0];
|
||||||
if(item[1] > maxY) maxY = item[1];
|
if (item[1] > maxY) maxY = item[1];
|
||||||
if(item[1] < minY) minY = item[1];
|
if (item[1] < minY) minY = item[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: minX,
|
x: minX,
|
||||||
y: minY,
|
y: minY,
|
||||||
width: maxX - minX,
|
width: maxX - minX,
|
||||||
height: maxY - minY
|
height: maxY - minY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -48,7 +44,7 @@ export const Bound = {
|
|||||||
[bound.x, bound.y],
|
[bound.x, bound.y],
|
||||||
[bound.x + bound.width, bound.y],
|
[bound.x + bound.width, bound.y],
|
||||||
[bound.x + bound.width, bound.y + bound.height],
|
[bound.x + bound.width, bound.y + bound.height],
|
||||||
[bound.x, bound.y + bound.height]
|
[bound.x, bound.y + bound.height],
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -57,20 +53,30 @@ export const Bound = {
|
|||||||
* @param arg
|
* @param arg
|
||||||
*/
|
*/
|
||||||
union(...arg: BoundingRect[]): BoundingRect {
|
union(...arg: BoundingRect[]): BoundingRect {
|
||||||
return arg.length > 1?
|
if (arg.length === 0) {
|
||||||
arg.reduce((total, cur) => {
|
return {
|
||||||
let minX = total.x < cur.x? total.x: cur.x,
|
x: 0,
|
||||||
maxX = total.x + total.width < cur.x + cur.width? cur.x + cur.width: total.x + total.width,
|
y: 0,
|
||||||
minY = total.y < cur.y? total.y: cur.y,
|
width: 0,
|
||||||
maxY = total.y + total.height < cur.y + cur.height? cur.y + cur.height: total.y + total.height;
|
height: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return arg.length > 1
|
||||||
|
? arg.reduce((total, cur) => {
|
||||||
|
let minX = Math.min(total.x, cur.x),
|
||||||
|
maxX = Math.max(total.x + total.width, cur.x + cur.width),
|
||||||
|
minY = Math.min(total.y, cur.y),
|
||||||
|
maxY = Math.max(total.y + total.height, cur.y + cur.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];
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,31 +85,36 @@ export const Bound = {
|
|||||||
* @param b2
|
* @param b2
|
||||||
*/
|
*/
|
||||||
intersect(b1: BoundingRect, b2: BoundingRect): BoundingRect {
|
intersect(b1: BoundingRect, b2: BoundingRect): BoundingRect {
|
||||||
let x, y,
|
let x,
|
||||||
maxX, maxY,
|
y,
|
||||||
|
maxX,
|
||||||
|
maxY,
|
||||||
overlapsX,
|
overlapsX,
|
||||||
overlapsY;
|
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;
|
|
||||||
maxX = b1.x + b1.width;
|
|
||||||
overlapsX = maxX - x;
|
overlapsX = maxX - x;
|
||||||
}
|
|
||||||
|
|
||||||
if(b1.y < b2.y + b2.height && b1.y + b1.height > b2.y) {
|
y = Math.max(b1y, b2y);
|
||||||
y = b1.y < b2.y? b2.y: b1.y;
|
maxY = Math.min(b1my, b2my);
|
||||||
maxY = b1.y + b1.height < b2.y + b2.height? b1.y + b1.height: b2.y + b2.height;
|
|
||||||
overlapsY = maxY - y;
|
overlapsY = maxY - y;
|
||||||
}
|
|
||||||
|
|
||||||
if(!overlapsX || !overlapsY) return null;
|
if (!overlapsX || !overlapsY) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: overlapsX,
|
width: overlapsX,
|
||||||
height: overlapsY
|
height: overlapsY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -146,7 +157,5 @@ export const Bound = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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
|
||||||
|
|||||||
@ -49,4 +49,11 @@ export class SVLink extends SVModel {
|
|||||||
curveOffset: options.curveOffset
|
curveOffset: options.curveOffset
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,13 +1,9 @@
|
|||||||
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-pc";
|
import { Graph } from '@antv/g6-pc';
|
||||||
import merge from 'merge';
|
import merge from 'merge';
|
||||||
import { ModelConstructor } from "./modelConstructor";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class SVModel {
|
export class SVModel {
|
||||||
public id: string;
|
public id: string;
|
||||||
@ -89,8 +85,7 @@ export class SVModel {
|
|||||||
|
|
||||||
if (attr === 'style' || attr === 'labelCfg') {
|
if (attr === 'style' || attr === 'labelCfg') {
|
||||||
this.G6ModelProps[attr] = merge(this.G6ModelProps[attr] || {}, value);
|
this.G6ModelProps[attr] = merge(this.G6ModelProps[attr] || {}, value);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.G6ModelProps[attr] = value;
|
this.G6ModelProps[attr] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,8 +99,7 @@ export class SVModel {
|
|||||||
if (this.preLayout) {
|
if (this.preLayout) {
|
||||||
const G6ItemModel = this.G6Item.getModel();
|
const G6ItemModel = this.G6Item.getModel();
|
||||||
G6ItemModel[attr] = value;
|
G6ItemModel[attr] = value;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,11 +117,11 @@ export class SVModel {
|
|||||||
updateG6ModelStyle(G6ModelProps: NodeConfig | EdgeConfig) {
|
updateG6ModelStyle(G6ModelProps: NodeConfig | EdgeConfig) {
|
||||||
const newG6ModelProps = {
|
const newG6ModelProps = {
|
||||||
style: {
|
style: {
|
||||||
...G6ModelProps.style
|
...G6ModelProps.style,
|
||||||
},
|
},
|
||||||
labelCfg: {
|
labelCfg: {
|
||||||
...G6ModelProps.labelCfg
|
...G6ModelProps.labelCfg,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.G6ModelProps = merge.recursive(this.G6ModelProps, newG6ModelProps);
|
this.G6ModelProps = merge.recursive(this.G6ModelProps, newG6ModelProps);
|
||||||
@ -170,7 +164,7 @@ export class SVModel {
|
|||||||
* @param isSelected
|
* @param isSelected
|
||||||
*/
|
*/
|
||||||
setSelectedState(isSelected: boolean) {
|
setSelectedState(isSelected: boolean) {
|
||||||
if(this.G6Item === null) {
|
if (this.G6Item === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,21 +193,12 @@ export class SVModel {
|
|||||||
isNode(): boolean {
|
isNode(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeDestroy () {}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.G6Item = null;
|
||||||
|
this.shadowG6Instance = null;
|
||||||
|
this.shadowG6Item = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { SourceNode } from '../sources';
|
|||||||
import { ModelConstructor } from './modelConstructor';
|
import { ModelConstructor } from './modelConstructor';
|
||||||
import { SVLink } from './SVLink';
|
import { SVLink } from './SVLink';
|
||||||
import { SVModel } from './SVModel';
|
import { SVModel } from './SVModel';
|
||||||
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker, SVNodeAppendage } from './SVNodeAppendage';
|
import { SVNodeAppendage } from './SVNodeAppendage';
|
||||||
|
|
||||||
export class SVNode extends SVModel {
|
export class SVNode extends SVModel {
|
||||||
public sourceId: string;
|
public sourceId: string;
|
||||||
@ -17,17 +17,7 @@ export class SVNode extends SVModel {
|
|||||||
|
|
||||||
private label: string | string[];
|
private label: string | string[];
|
||||||
private disable: boolean;
|
private disable: boolean;
|
||||||
|
public appendages: { [key: string]: SVNodeAppendage[] };
|
||||||
public shadowG6Item: INode;
|
|
||||||
public G6Item: INode;
|
|
||||||
|
|
||||||
public marker: SVMarker;
|
|
||||||
public freedLabel: SVFreedLabel;
|
|
||||||
public indexLabel: SVIndexLabel;
|
|
||||||
public addressLabel: SVAddressLabel;
|
|
||||||
public appendages: SVNodeAppendage[];
|
|
||||||
|
|
||||||
public modelConstructor: ModelConstructor;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: string,
|
id: string,
|
||||||
@ -53,7 +43,7 @@ export class SVNode extends SVModel {
|
|||||||
this.sourceId = sourceNode.id.toString();
|
this.sourceId = sourceNode.id.toString();
|
||||||
|
|
||||||
this.links = { inDegree: [], outDegree: [] };
|
this.links = { inDegree: [], outDegree: [] };
|
||||||
this.appendages = [];
|
this.appendages = {};
|
||||||
this.sourceNode = sourceNode;
|
this.sourceNode = sourceNode;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
@ -94,7 +84,7 @@ export class SVNode extends SVModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.G6Item.setState('selected', isSelected);
|
this.G6Item.setState('selected', isSelected);
|
||||||
this.appendages.forEach(item => {
|
this.getAppendagesList().forEach(item => {
|
||||||
item.setSelectedState(isSelected);
|
item.setSelectedState(isSelected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -103,11 +93,28 @@ export class SVNode extends SVModel {
|
|||||||
return this.sourceId;
|
return this.sourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAppendagesList(): SVNodeAppendage[] {
|
||||||
|
const list = [];
|
||||||
|
|
||||||
|
Object.entries(this.appendages).forEach(item => {
|
||||||
|
list.push(...item[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断这个节点是否来自相同group
|
* 判断这个节点是否来自相同group
|
||||||
* @param node
|
* @param node
|
||||||
*/
|
*/
|
||||||
isSameGroup(node: SVNode): boolean {
|
isSameGroup(node: SVNode): boolean {
|
||||||
return this.modelConstructor.isSameGroup(this, node);
|
return ModelConstructor.isSameGroup(this, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeDestroy(): void {
|
||||||
|
this.sourceNode = null;
|
||||||
|
this.links.inDegree.length = 0;
|
||||||
|
this.links.outDegree.length = 0;
|
||||||
|
this.appendages = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
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 target: SVNode;
|
||||||
@ -14,21 +12,31 @@ export class SVNodeAppendage extends SVModel {
|
|||||||
super(id, type, group, layout, modelType);
|
super(id, type, group, layout, modelType);
|
||||||
|
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.target.appendages.push(this);
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 已释放节点下面的文字(“已释放‘)
|
* 已释放节点下面的文字(“已释放‘)
|
||||||
*/
|
*/
|
||||||
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.target.freedLabel = this;
|
|
||||||
this.G6ModelProps = this.generateG6ModelProps();
|
this.G6ModelProps = this.generateG6ModelProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,20 +50,19 @@ export class SVFreedLabel extends SVNodeAppendage {
|
|||||||
labelCfg: {
|
labelCfg: {
|
||||||
style: {
|
style: {
|
||||||
fill: '#b83b5e',
|
fill: '#b83b5e',
|
||||||
opacity: 0.6
|
opacity: 0.6,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
size: [0, 0],
|
size: [0, 0],
|
||||||
style: {
|
style: {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
stroke: null,
|
stroke: null,
|
||||||
fill: 'transparent'
|
fill: 'transparent',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 被移动到泄漏区的节点上面显示的地址
|
* 被移动到泄漏区的节点上面显示的地址
|
||||||
*/
|
*/
|
||||||
@ -66,10 +73,20 @@ export class SVAddressLabel extends SVNodeAppendage {
|
|||||||
super(id, type, group, layout, 'addressLabel', target);
|
super(id, type, group, layout, 'addressLabel', target);
|
||||||
|
|
||||||
this.sourceId = target.sourceId;
|
this.sourceId = target.sourceId;
|
||||||
this.target.addressLabel = this;
|
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBound(): BoundingRect {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
generateG6ModelProps(options: AddressLabelOption) {
|
generateG6ModelProps(options: AddressLabelOption) {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@ -81,29 +98,34 @@ export class SVAddressLabel extends SVNodeAppendage {
|
|||||||
style: {
|
style: {
|
||||||
fill: '#666',
|
fill: '#666',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
...options.style
|
...options.style,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
size: [0, 0],
|
size: [0, 0],
|
||||||
style: {
|
style: {
|
||||||
stroke: null,
|
stroke: null,
|
||||||
fill: 'transparent'
|
fill: 'transparent',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点的下标文字
|
* 节点的下标文字
|
||||||
*/
|
*/
|
||||||
export class SVIndexLabel extends SVNodeAppendage {
|
export class SVIndexLabel extends SVNodeAppendage {
|
||||||
private value: string;
|
private value: string;
|
||||||
|
|
||||||
constructor(id: string, indexName: string, group: string, layout: string, value: string, target: SVNode, options: IndexLabelOption) {
|
constructor(
|
||||||
|
id: string,
|
||||||
|
indexName: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
value: string,
|
||||||
|
target: SVNode,
|
||||||
|
options: IndexLabelOption
|
||||||
|
) {
|
||||||
super(id, indexName, group, layout, 'indexLabel', target);
|
super(id, indexName, group, layout, 'indexLabel', target);
|
||||||
|
|
||||||
this.target.indexLabel = this;
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options) as NodeConfig;
|
this.G6ModelProps = this.generateG6ModelProps(options) as NodeConfig;
|
||||||
}
|
}
|
||||||
@ -122,20 +144,18 @@ export class SVIndexLabel extends SVNodeAppendage {
|
|||||||
textBaseline: 'middle',
|
textBaseline: 'middle',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
...options.style
|
...options.style,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
size: [0, 0],
|
size: [0, 0],
|
||||||
style: {
|
style: {
|
||||||
stroke: null,
|
stroke: null,
|
||||||
fill: 'transparent'
|
fill: 'transparent',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 外部指针
|
* 外部指针
|
||||||
*/
|
*/
|
||||||
@ -146,12 +166,18 @@ export class SVMarker extends SVNodeAppendage {
|
|||||||
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(
|
||||||
|
id: string,
|
||||||
|
type: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
label: string | string[],
|
||||||
|
target: SVNode,
|
||||||
|
options: MarkerOption
|
||||||
|
) {
|
||||||
super(id, type, group, layout, 'marker', target);
|
super(id, type, group, layout, 'marker', target);
|
||||||
|
|
||||||
this.label = label;
|
this.label = label;
|
||||||
|
|
||||||
this.target.marker = this;
|
|
||||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +197,7 @@ export class SVMarker extends SVNodeAppendage {
|
|||||||
anchorPoints: null,
|
anchorPoints: null,
|
||||||
label: typeof this.label === 'string' ? this.label : this.label.join(', '),
|
label: typeof this.label === 'string' ? this.label : this.label.join(', '),
|
||||||
style: Util.objectClone<Style>(options.style),
|
style: Util.objectClone<Style>(options.style),
|
||||||
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions)
|
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,4 +205,4 @@ export class SVMarker extends SVNodeAppendage {
|
|||||||
const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox();
|
const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox();
|
||||||
return Math.max(width, height);
|
return Math.max(width, height);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@ -1,22 +1,27 @@
|
|||||||
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[];
|
|
||||||
addressLabel: SVAddressLabel[];
|
|
||||||
link: SVLink[];
|
link: SVLink[];
|
||||||
marker: SVMarker[];
|
|
||||||
layoutCreator: LayoutCreator;
|
layoutCreator: LayoutCreator;
|
||||||
layout: string;
|
layout: string;
|
||||||
options: LayoutGroupOptions;
|
options: LayoutGroupOptions;
|
||||||
@ -24,10 +29,8 @@ export type LayoutGroup = {
|
|||||||
isHide: boolean;
|
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;
|
||||||
@ -58,10 +61,7 @@ export class ModelConstructor {
|
|||||||
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;
|
||||||
@ -75,40 +75,37 @@ export class ModelConstructor {
|
|||||||
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,
|
|
||||||
indexLabel: indexLabelList,
|
|
||||||
link: [],
|
link: [],
|
||||||
marker: markerList,
|
|
||||||
options,
|
options,
|
||||||
layoutCreator,
|
layoutCreator,
|
||||||
modelList: [
|
modelList: [...nodeList, ...appendageList],
|
||||||
...nodeList,
|
|
||||||
...markerList,
|
|
||||||
...freedLabelList,
|
|
||||||
...addressLabelList,
|
|
||||||
...indexLabelList
|
|
||||||
],
|
|
||||||
layout,
|
layout,
|
||||||
isHide: false
|
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);
|
||||||
@ -119,7 +116,6 @@ export class ModelConstructor {
|
|||||||
return this.layoutGroupTable;
|
return this.layoutGroupTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从源数据构建 node 集
|
* 从源数据构建 node 集
|
||||||
* @param nodeOptions
|
* @param nodeOptions
|
||||||
@ -128,7 +124,12 @@ export class ModelConstructor {
|
|||||||
* @param layout
|
* @param layout
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructNodes(group: string, layout: string, nodeOptions: { [key: string]: NodeOption }, sourceList: SourceNode[]): SVNode[] {
|
private constructNodes(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
nodeOptions: { [key: string]: NodeOption },
|
||||||
|
sourceList: SourceNode[]
|
||||||
|
): SVNode[] {
|
||||||
let defaultSourceNodeType: string = 'default',
|
let defaultSourceNodeType: string = 'default',
|
||||||
nodeList: SVNode[] = [];
|
nodeList: SVNode[] = [];
|
||||||
|
|
||||||
@ -154,7 +155,13 @@ export class ModelConstructor {
|
|||||||
* @param layoutGroupTable
|
* @param layoutGroupTable
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructLinks(group: string, layout: string, linkOptions: { [key: string]: LinkOption }, nodes: SVNode[], layoutGroupTable: LayoutGroupTable): SVLink[] {
|
private constructLinks(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
linkOptions: { [key: string]: LinkOption },
|
||||||
|
nodes: SVNode[],
|
||||||
|
layoutGroupTable: LayoutGroupTable
|
||||||
|
): SVLink[] {
|
||||||
let linkList: SVLink[] = [],
|
let linkList: SVLink[] = [],
|
||||||
linkNames = Object.keys(linkOptions);
|
linkNames = Object.keys(linkOptions);
|
||||||
|
|
||||||
@ -174,7 +181,7 @@ export class ModelConstructor {
|
|||||||
if (Array.isArray(sourceLinkData)) {
|
if (Array.isArray(sourceLinkData)) {
|
||||||
node[name] = sourceLinkData.map((item, index) => {
|
node[name] = sourceLinkData.map((item, index) => {
|
||||||
targetNode = this.fetchTargetNodes(layoutGroupTable, node, item);
|
targetNode = this.fetchTargetNodes(layoutGroupTable, node, item);
|
||||||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
let isGeneralLink = ModelConstructor.isGeneralLink(sourceLinkData.toString());
|
||||||
|
|
||||||
if (targetNode) {
|
if (targetNode) {
|
||||||
link = this.createLink(name, group, layout, node, targetNode, index, linkOptions[name]);
|
link = this.createLink(name, group, layout, node, targetNode, index, linkOptions[name]);
|
||||||
@ -183,10 +190,9 @@ export class ModelConstructor {
|
|||||||
|
|
||||||
return isGeneralLink ? targetNode : null;
|
return isGeneralLink ? targetNode : null;
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
targetNode = this.fetchTargetNodes(layoutGroupTable, node, sourceLinkData);
|
targetNode = this.fetchTargetNodes(layoutGroupTable, node, sourceLinkData);
|
||||||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
let isGeneralLink = ModelConstructor.isGeneralLink(sourceLinkData.toString());
|
||||||
|
|
||||||
if (targetNode) {
|
if (targetNode) {
|
||||||
link = this.createLink(name, group, layout, node, targetNode, null, linkOptions[name]);
|
link = this.createLink(name, group, layout, node, targetNode, null, linkOptions[name]);
|
||||||
@ -207,7 +213,12 @@ export class ModelConstructor {
|
|||||||
* @param layout
|
* @param layout
|
||||||
* @param indexLabelOptions
|
* @param indexLabelOptions
|
||||||
*/
|
*/
|
||||||
private constructIndexLabel(group: string, layout: string, indexLabelOptions: { [key: string]: IndexLabelOption }, nodes: SVNode[]): SVIndexLabel[] {
|
private constructIndexLabel(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
indexLabelOptions: { [key: string]: IndexLabelOption },
|
||||||
|
nodes: SVNode[]
|
||||||
|
): SVIndexLabel[] {
|
||||||
let indexLabelList: SVIndexLabel[] = [],
|
let indexLabelList: SVIndexLabel[] = [],
|
||||||
indexNames = Object.keys(indexLabelOptions);
|
indexNames = Object.keys(indexLabelOptions);
|
||||||
|
|
||||||
@ -219,8 +230,16 @@ export class ModelConstructor {
|
|||||||
// 若没有指针字段的结点则跳过
|
// 若没有指针字段的结点则跳过
|
||||||
if (value === undefined || value === null) continue;
|
if (value === undefined || value === null) continue;
|
||||||
|
|
||||||
let id = `${group}.${name}#${value}`,
|
let id = `${group}[${name}(${value})]`,
|
||||||
indexLabel = new SVIndexLabel(id, name, group, layout, value.toString(), node, indexLabelOptions[name]);
|
indexLabel = new SVIndexLabel(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
group,
|
||||||
|
layout,
|
||||||
|
value.toString(),
|
||||||
|
node,
|
||||||
|
indexLabelOptions[name]
|
||||||
|
);
|
||||||
|
|
||||||
indexLabelList.push(indexLabel);
|
indexLabelList.push(indexLabel);
|
||||||
}
|
}
|
||||||
@ -236,11 +255,23 @@ export class ModelConstructor {
|
|||||||
* @param addressLabelOption
|
* @param addressLabelOption
|
||||||
* @param nodes
|
* @param nodes
|
||||||
*/
|
*/
|
||||||
private constructAddressLabel(group: string, layout: string, addressLabelOption: AddressLabelOption, nodes: SVNode[]): SVAddressLabel[] {
|
private constructAddressLabel(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
addressLabelOption: AddressLabelOption,
|
||||||
|
nodes: SVNode[]
|
||||||
|
): SVAddressLabel[] {
|
||||||
let addressLabelList: SVAddressLabel[] = [];
|
let addressLabelList: SVAddressLabel[] = [];
|
||||||
|
|
||||||
nodes.forEach(item => {
|
nodes.forEach(item => {
|
||||||
const addressLabel = new SVAddressLabel(`${item.id}-address-label`, item.sourceType, group, layout, item, addressLabelOption);
|
const addressLabel = new SVAddressLabel(
|
||||||
|
`address-label(${item.id})`,
|
||||||
|
item.sourceType,
|
||||||
|
group,
|
||||||
|
layout,
|
||||||
|
item,
|
||||||
|
addressLabelOption
|
||||||
|
);
|
||||||
addressLabelList.push(addressLabel);
|
addressLabelList.push(addressLabel);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -253,7 +284,12 @@ export class ModelConstructor {
|
|||||||
* @param nodes
|
* @param nodes
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructMarkers(group: string, layout: string, markerOptions: { [key: string]: MarkerOption }, nodes: SVNode[]): SVMarker[] {
|
private constructMarkers(
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
markerOptions: { [key: string]: MarkerOption },
|
||||||
|
nodes: SVNode[]
|
||||||
|
): SVMarker[] {
|
||||||
let markerList: SVMarker[] = [],
|
let markerList: SVMarker[] = [],
|
||||||
markerNames = Object.keys(markerOptions);
|
markerNames = Object.keys(markerOptions);
|
||||||
|
|
||||||
@ -265,7 +301,7 @@ export class ModelConstructor {
|
|||||||
// 若没有指针字段的结点则跳过
|
// 若没有指针字段的结点则跳过
|
||||||
if (!markerData) continue;
|
if (!markerData) continue;
|
||||||
|
|
||||||
let id = `${group}.${name}.${Array.isArray(markerData) ? markerData.join('-') : markerData}`,
|
let id = `${group}[${name}(${Array.isArray(markerData) ? markerData.join('-') : markerData})]`,
|
||||||
marker = new SVMarker(id, name, group, layout, markerData, node, markerOptions[name]);
|
marker = new SVMarker(id, name, group, layout, markerData, node, markerOptions[name]);
|
||||||
|
|
||||||
markerList.push(marker);
|
markerList.push(marker);
|
||||||
@ -285,8 +321,7 @@ export class ModelConstructor {
|
|||||||
|
|
||||||
if (Array.isArray(label)) {
|
if (Array.isArray(label)) {
|
||||||
targetLabel = label.map(item => this.parserNodeContent(sourceNode, item) ?? '');
|
targetLabel = label.map(item => this.parserNodeContent(sourceNode, item) ?? '');
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
targetLabel = this.parserNodeContent(sourceNode, label);
|
targetLabel = this.parserNodeContent(sourceNode, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,17 +340,21 @@ export class ModelConstructor {
|
|||||||
* @param layout
|
* @param layout
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
private createNode(sourceNode: SourceNode, sourceNodeType: string, group: string, layout: string, options: NodeOption): SVNode {
|
private createNode(
|
||||||
|
sourceNode: SourceNode,
|
||||||
|
sourceNodeType: string,
|
||||||
|
group: string,
|
||||||
|
layout: string,
|
||||||
|
options: NodeOption
|
||||||
|
): SVNode {
|
||||||
let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode),
|
let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode),
|
||||||
id = sourceNodeType + '.' + sourceNode.id.toString(),
|
id = `${sourceNodeType}(${sourceNode.id.toString()})`,
|
||||||
node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options);
|
node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options);
|
||||||
|
|
||||||
if(node.freed) {
|
if (node.freed) {
|
||||||
node.freedLabel = new SVFreedLabel(`${id}-freed-label`, sourceNodeType, group, layout, node);
|
new SVFreedLabel(`freed-label(${id})`, sourceNodeType, group, layout, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
node.modelConstructor = this;
|
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,8 +369,16 @@ export class ModelConstructor {
|
|||||||
* @param options
|
* @param options
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private createLink(linkName: string, group: string, layout: string, node: SVNode, target: SVNode, index: number, options: LinkOption): SVLink {
|
private createLink(
|
||||||
let id = `${linkName}(${node.id}-${target.id})`;
|
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);
|
return new SVLink(id, linkName, group, layout, node, target, index, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,16 +430,13 @@ export class ModelConstructor {
|
|||||||
if (info.length > 1) {
|
if (info.length > 1) {
|
||||||
sourceNodeType = info.pop();
|
sourceNodeType = info.pop();
|
||||||
targetGroupName = info.pop();
|
targetGroupName = info.pop();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
let field = info.pop();
|
let field = info.pop();
|
||||||
if (layoutGroupTable.get(targetGroupName).node.find(item => item.sourceType === field)) {
|
if (layoutGroupTable.get(targetGroupName).node.find(item => item.sourceType === field)) {
|
||||||
sourceNodeType = field;
|
sourceNodeType = field;
|
||||||
}
|
} else if (layoutGroupTable.has(field)) {
|
||||||
else if (layoutGroupTable.has(field)) {
|
|
||||||
targetGroupName = field;
|
targetGroupName = field;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,11 +452,29 @@ export class ModelConstructor {
|
|||||||
return targetNode || null;
|
return targetNode || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getLayoutGroupTable(): LayoutGroupTable {
|
||||||
|
return this.layoutGroupTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.layoutGroupTable = null;
|
||||||
|
this.prevSourcesStringMap = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测改指针是否为常规指针(指向另一个group)
|
* 检测改指针是否为常规指针(指向另一个group)
|
||||||
* @param linkId
|
* @param linkId
|
||||||
*/
|
*/
|
||||||
private isGeneralLink(linkId: string): boolean {
|
static isGeneralLink(linkId: string): boolean {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
|
|
||||||
for (let i = 0; i < linkId.length; i++) {
|
for (let i = 0; i < linkId.length; i++) {
|
||||||
@ -424,29 +486,91 @@ export class ModelConstructor {
|
|||||||
return counter <= 2;
|
return counter <= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public getLayoutGroupTable(): LayoutGroupTable {
|
|
||||||
return this.layoutGroupTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断这个节点是否来自相同group
|
* 判断这个节点是否来自相同group
|
||||||
* @param node1
|
* @param node1
|
||||||
* @param node2
|
* @param node2
|
||||||
*/
|
*/
|
||||||
public isSameGroup(node1: SVNode, node2: SVNode): boolean {
|
static isSameGroup(node1: SVNode, node2: SVNode): boolean {
|
||||||
const layoutGroup = this.layoutGroupTable.get(node1.group);
|
return node1.group === node2.group;
|
||||||
return layoutGroup.node.find(item => item.id === node2.id) !== undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 销毁
|
* 获取簇
|
||||||
|
* - 什么为一个簇?有边相连的一堆节点,再加上这些节点各自的appendages,共同组成了一个簇
|
||||||
|
* @param models
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
public destroy() {
|
static getClusters(models: SVModel[]): Group[] {
|
||||||
this.layoutGroupTable = null;
|
const clusterGroupList = [],
|
||||||
this.prevSourcesStringMap = null;
|
idMap = {},
|
||||||
|
idName = '__clusterId';
|
||||||
|
|
||||||
|
models.forEach(item => {
|
||||||
|
idMap[item.id] = item;
|
||||||
|
});
|
||||||
|
|
||||||
|
const DFS = (model: SVModel, clusterId: number, idMap): SVModel[] => {
|
||||||
|
if(model === null) {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
if (idMap[model.id] === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model[idName] !== undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = [model];
|
||||||
|
model[idName] = clusterId;
|
||||||
|
|
||||||
|
if (model instanceof SVNode) {
|
||||||
|
model.getAppendagesList().forEach(item => {
|
||||||
|
list.push(...DFS(item, clusterId, idMap));
|
||||||
|
});
|
||||||
|
|
||||||
|
model.links.inDegree.forEach(item => {
|
||||||
|
list.push(...DFS(item, clusterId, idMap));
|
||||||
|
});
|
||||||
|
|
||||||
|
model.links.outDegree.forEach(item => {
|
||||||
|
list.push(...DFS(item, clusterId, idMap));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model instanceof SVLink) {
|
||||||
|
list.push(...DFS(model.node, clusterId, idMap));
|
||||||
|
list.push(...DFS(model.target, clusterId, idMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model instanceof SVNodeAppendage) {
|
||||||
|
list.push(...DFS(model.target, clusterId, idMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < models.length; i++) {
|
||||||
|
const model = models[i];
|
||||||
|
|
||||||
|
if (model[idName] !== undefined) {
|
||||||
|
delete model[idName];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = new Group(),
|
||||||
|
clusterList = DFS(model, i, idMap);
|
||||||
|
|
||||||
|
clusterList.forEach(item => {
|
||||||
|
group.add(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
clusterGroupList.push(group);
|
||||||
|
delete model[idName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusterGroupList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ 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";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
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';
|
||||||
@ -14,6 +14,8 @@ 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;
|
||||||
@ -67,7 +69,7 @@ export class LayoutProvider {
|
|||||||
|
|
||||||
let target = item.target,
|
let target = item.target,
|
||||||
targetBound: BoundingRect = target.getBound(),
|
targetBound: BoundingRect = target.getBound(),
|
||||||
g6AnchorPosition = item.target.shadowG6Item.getAnchorPoints()[anchor] as IPoint,
|
g6AnchorPosition = (<INode>item.target.shadowG6Item).getAnchorPoints()[anchor] as IPoint,
|
||||||
center: [number, number] = [
|
center: [number, number] = [
|
||||||
targetBound.x + targetBound.width / 2,
|
targetBound.x + targetBound.width / 2,
|
||||||
targetBound.y + targetBound.height / 2,
|
targetBound.y + targetBound.height / 2,
|
||||||
@ -214,10 +216,15 @@ export class LayoutProvider {
|
|||||||
indexLabelOptions = group.options.indexLabel || {},
|
indexLabelOptions = group.options.indexLabel || {},
|
||||||
addressLabelOption = group.options.addressLabel || {};
|
addressLabelOption = group.options.addressLabel || {};
|
||||||
|
|
||||||
this.layoutIndexLabel(group.indexLabel, indexLabelOptions);
|
const indexLabel = group.appendage.filter(item => item instanceof SVIndexLabel) as SVIndexLabel[],
|
||||||
this.layoutFreedLabel(group.freedLabel);
|
freedLabel = group.appendage.filter(item => item instanceof SVFreedLabel) as SVFreedLabel[],
|
||||||
this.layoutAddressLabel(group.addressLabel, addressLabelOption);
|
addressLabel = group.appendage.filter(item => item instanceof SVAddressLabel) as SVAddressLabel[],
|
||||||
this.layoutMarker(group.marker, markerOptions); // 布局外部指针
|
marker = group.appendage.filter(item => item instanceof SVMarker) as SVMarker[];
|
||||||
|
|
||||||
|
this.layoutIndexLabel(indexLabel, indexLabelOptions);
|
||||||
|
this.layoutFreedLabel(freedLabel);
|
||||||
|
this.layoutAddressLabel(addressLabel, addressLabelOption);
|
||||||
|
this.layoutMarker(marker, markerOptions); // 布局外部指针
|
||||||
});
|
});
|
||||||
|
|
||||||
return modelGroupList;
|
return modelGroupList;
|
||||||
@ -225,45 +232,36 @@ export class LayoutProvider {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 对泄漏区进行布局
|
* 对泄漏区进行布局
|
||||||
* @param leakModels
|
|
||||||
* @param accumulateLeakModels
|
* @param accumulateLeakModels
|
||||||
|
* // todo: 部分元素被抽离后的泄漏区
|
||||||
*/
|
*/
|
||||||
private layoutLeakModels(leakModels: SVModel[], accumulateLeakModels: SVModel[]) {
|
private layoutLeakArea(accumulateLeakModels: SVModel[]) {
|
||||||
const group: Group = new Group(),
|
const containerHeight = this.viewContainer.getG6Instance().getHeight(),
|
||||||
containerHeight = this.viewContainer.getG6Instance().getHeight(),
|
|
||||||
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||||
leakAreaY = containerHeight - leakAreaHeight,
|
leakAreaY = containerHeight - leakAreaHeight;
|
||||||
xOffset = 60;
|
|
||||||
|
|
||||||
let prevBound: BoundingRect;
|
let prevBound: BoundingRect;
|
||||||
|
|
||||||
// 避免在泄漏前拖拽节点导致的位置变化,先把节点位置重置为布局后的标准位置
|
// 避免在泄漏前拖拽节点导致的位置变化,先把节点位置重置为布局后的标准位置
|
||||||
leakModels.forEach(item => {
|
accumulateLeakModels.forEach(item => {
|
||||||
item.set({
|
item.set({
|
||||||
x: item.layoutX,
|
x: item.layoutX,
|
||||||
y: item.layoutY,
|
y: item.layoutY,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const globalLeakGroupBound: BoundingRect = accumulateLeakModels.length
|
const clusters = ModelConstructor.getClusters(accumulateLeakModels);
|
||||||
? Bound.union(...accumulateLeakModels.map(item => item.getBound()))
|
|
||||||
: { x: 0, y: leakAreaY, width: 0, height: 0 };
|
|
||||||
|
|
||||||
const layoutGroups = Util.groupBy(leakModels, 'group');
|
// 每一个簇从左往右布局就完事了,比之前的方法简单稳定很多
|
||||||
Object.keys(layoutGroups).forEach(key => {
|
clusters.forEach(item => {
|
||||||
group.add(...layoutGroups[key]);
|
const bound = item.getBound(),
|
||||||
|
x = prevBound ? prevBound.x + prevBound.width + this.leakClusterXInterval : this.leakAreaXoffset,
|
||||||
|
dx = x - bound.x,
|
||||||
|
dy = leakAreaY - bound.y;
|
||||||
|
|
||||||
const currentBound: BoundingRect = group.getBound(),
|
item.translate(dx, dy);
|
||||||
prevBoundEnd = prevBound ? prevBound.x + prevBound.width : 0,
|
Bound.translate(bound, dx, dy);
|
||||||
{ x: groupX, y: groupY } = currentBound,
|
prevBound = bound;
|
||||||
dx = globalLeakGroupBound.x + globalLeakGroupBound.width + prevBoundEnd + xOffset - groupX,
|
|
||||||
dy = globalLeakGroupBound.y - groupY;
|
|
||||||
|
|
||||||
group.translate(dx, dy);
|
|
||||||
group.clear();
|
|
||||||
Bound.translate(currentBound, dx, dy);
|
|
||||||
|
|
||||||
prevBound = currentBound;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,7 +318,7 @@ export class LayoutProvider {
|
|||||||
|
|
||||||
if (this.viewContainer.hasLeak) {
|
if (this.viewContainer.hasLeak) {
|
||||||
const boundBottomY = viewBound.y + viewBound.height;
|
const boundBottomY = viewBound.y + viewBound.height;
|
||||||
dy = height - leakAreaHeight - 100 - boundBottomY;
|
dy = height - leakAreaHeight - 130 - boundBottomY;
|
||||||
} else {
|
} else {
|
||||||
const boundCenterY = viewBound.y + viewBound.height / 2;
|
const boundCenterY = viewBound.y + viewBound.height / 2;
|
||||||
dy = centerY - boundCenterY;
|
dy = centerY - boundCenterY;
|
||||||
@ -332,19 +330,15 @@ export class LayoutProvider {
|
|||||||
/**
|
/**
|
||||||
* 布局
|
* 布局
|
||||||
* @param layoutGroupTable
|
* @param layoutGroupTable
|
||||||
* @param leakModels
|
* @param accumulateLeakModels
|
||||||
* @param hasLeak
|
|
||||||
* @param needFitCenter
|
|
||||||
*/
|
*/
|
||||||
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], leakModels: SVModel[]) {
|
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[]) {
|
||||||
this.preLayoutProcess(layoutGroupTable);
|
this.preLayoutProcess(layoutGroupTable);
|
||||||
|
|
||||||
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
|
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
|
||||||
const generalGroup: Group = this.layoutGroups(modelGroupList);
|
const generalGroup: Group = this.layoutGroups(modelGroupList);
|
||||||
|
|
||||||
if (leakModels.length) {
|
this.layoutLeakArea(accumulateLeakModels);
|
||||||
this.layoutLeakModels(leakModels, accumulateLeakModels);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fitCenter(generalGroup);
|
this.fitCenter(generalGroup);
|
||||||
this.postLayoutProcess(layoutGroupTable);
|
this.postLayoutProcess(layoutGroupTable);
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
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 { Animations } from './animation';
|
||||||
import { Renderer } from "./renderer";
|
import { Renderer } from './renderer';
|
||||||
|
|
||||||
|
|
||||||
export interface DiffResult {
|
export interface DiffResult {
|
||||||
CONTINUOUS: SVModel[];
|
CONTINUOUS: SVModel[];
|
||||||
@ -20,10 +19,7 @@ export interface DiffResult {
|
|||||||
ACCUMULATE_LEAK: 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;
|
||||||
@ -51,13 +47,22 @@ export class Reconcile {
|
|||||||
* @param accumulateLeakModels
|
* @param accumulateLeakModels
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private getAppendModels(prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): SVModel[] {
|
private getAppendModels(
|
||||||
|
prevModelList: SVModel[],
|
||||||
|
modelList: SVModel[],
|
||||||
|
accumulateLeakModels: SVModel[],
|
||||||
|
prevStep: boolean
|
||||||
|
): SVModel[] {
|
||||||
const appendModels = modelList.filter(item => !prevModelList.find(model => model.id === item.id));
|
const appendModels = modelList.filter(item => !prevModelList.find(model => model.id === item.id));
|
||||||
|
|
||||||
|
// 看看新增的节点是不是从泄漏区来的
|
||||||
|
// 目前的判断方式比较傻:看看泄漏区有没有相同id的节点,但是发现这个方法可能不可靠,不知道还有没有更好的办法
|
||||||
appendModels.forEach(item => {
|
appendModels.forEach(item => {
|
||||||
let removeIndex = accumulateLeakModels.findIndex(leakModel => item.id === leakModel.id);
|
const removeIndex = accumulateLeakModels.findIndex(leakModel => item.id === leakModel.id),
|
||||||
|
svModel = accumulateLeakModels[removeIndex];
|
||||||
|
|
||||||
if (removeIndex > -1) {
|
if (removeIndex > -1) {
|
||||||
|
svModel.leaked = false;
|
||||||
accumulateLeakModels.splice(removeIndex, 1);
|
accumulateLeakModels.splice(removeIndex, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -72,9 +77,13 @@ export class Reconcile {
|
|||||||
* @param modelList
|
* @param modelList
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private getLeakModels(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
private getLeakModels(
|
||||||
let potentialLeakModels: SVModel[] = prevModelList.filter(item =>
|
layoutGroupTable: LayoutGroupTable,
|
||||||
!modelList.find(model => model.id === item.id) && !item.freed
|
prevModelList: SVModel[],
|
||||||
|
modelList: SVModel[]
|
||||||
|
): SVModel[] {
|
||||||
|
let potentialLeakModels: SVModel[] = prevModelList.filter(
|
||||||
|
item => !modelList.find(model => model.id === item.id) && !item.freed
|
||||||
);
|
);
|
||||||
const leakModels: SVModel[] = [];
|
const leakModels: SVModel[] = [];
|
||||||
|
|
||||||
@ -87,7 +96,7 @@ export class Reconcile {
|
|||||||
|
|
||||||
Object.keys(groups).forEach(key => {
|
Object.keys(groups).forEach(key => {
|
||||||
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
|
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
|
||||||
if(leakRule && typeof leakRule === 'function') {
|
if (leakRule && typeof leakRule === 'function') {
|
||||||
potentialLeakModels.push(...leakRule(groups[key]));
|
potentialLeakModels.push(...leakRule(groups[key]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -97,7 +106,12 @@ export class Reconcile {
|
|||||||
item.leaked = true;
|
item.leaked = true;
|
||||||
leakModels.push(item);
|
leakModels.push(item);
|
||||||
|
|
||||||
item.appendages.forEach(appendage => {
|
item.getAppendagesList().forEach(appendage => {
|
||||||
|
// 外部指针先不加入泄漏区(这个需要讨论一下,我觉得不应该)
|
||||||
|
if (appendage instanceof SVMarker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
appendage.leaked = true;
|
appendage.leaked = true;
|
||||||
leakModels.push(appendage);
|
leakModels.push(appendage);
|
||||||
});
|
});
|
||||||
@ -111,11 +125,6 @@ export class Reconcile {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
leakModels.forEach(item => {
|
|
||||||
// 不能用上次的G6item了,不然布局的时候会没有动画
|
|
||||||
item.G6Item = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
return leakModels;
|
return leakModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +133,11 @@ export class Reconcile {
|
|||||||
* @param prevModelList
|
* @param prevModelList
|
||||||
* @param modelList
|
* @param modelList
|
||||||
*/
|
*/
|
||||||
private getRemoveModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
private getRemoveModels(
|
||||||
|
prevModelList: SVModel[],
|
||||||
|
modelList: SVModel[],
|
||||||
|
accumulateLeakModels: SVModel[]
|
||||||
|
): SVModel[] {
|
||||||
let removedModels: SVModel[] = [];
|
let removedModels: SVModel[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < prevModelList.length; i++) {
|
for (let i = 0; i < prevModelList.length; i++) {
|
||||||
@ -136,9 +149,24 @@ export class Reconcile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return removedModels;
|
// 假如某个节点从泄漏区移回可视化区域,那么与原来泄漏结构的连线应该消失掉
|
||||||
|
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) {
|
||||||
|
accumulateLeakModels.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
removedModels.push(leakModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removedModels.forEach(item => {
|
||||||
|
item.beforeDestroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
return removedModels;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 找出重新指向的外部指针
|
* 找出重新指向的外部指针
|
||||||
@ -150,9 +178,11 @@ export class Reconcile {
|
|||||||
const prevMarkers: SVMarker[] = prevModelList.filter(item => item instanceof SVMarker) as SVMarker[],
|
const prevMarkers: SVMarker[] = prevModelList.filter(item => item instanceof SVMarker) as SVMarker[],
|
||||||
markers: SVMarker[] = modelList.filter(item => item instanceof SVMarker) as SVMarker[];
|
markers: SVMarker[] = modelList.filter(item => item instanceof SVMarker) as SVMarker[];
|
||||||
|
|
||||||
return markers.filter(item => prevMarkers.find(prevItem => {
|
return markers.filter(item =>
|
||||||
return prevItem.id === item.id && prevItem.target.id !== item.target.id
|
prevMarkers.find(prevItem => {
|
||||||
}));
|
return prevItem.id === item.id && prevItem.target.id !== item.target.id;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -233,18 +263,16 @@ export class Reconcile {
|
|||||||
// 先将透明度改为0,隐藏掉
|
// 先将透明度改为0,隐藏掉
|
||||||
const AddressLabelG6Group = item.G6Item.getContainer();
|
const AddressLabelG6Group = item.G6Item.getContainer();
|
||||||
AddressLabelG6Group.attr({ opacity: 0 });
|
AddressLabelG6Group.attr({ opacity: 0 });
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Animations.FADE_IN(item.G6Item, {
|
Animations.FADE_IN(item.G6Item, {
|
||||||
duration,
|
duration,
|
||||||
timingFunction
|
timingFunction,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Animations.APPEND(item.G6Item, {
|
Animations.APPEND(item.G6Item, {
|
||||||
duration,
|
duration,
|
||||||
timingFunction
|
timingFunction,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -263,7 +291,7 @@ export class Reconcile {
|
|||||||
timingFunction,
|
timingFunction,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.renderer.removeModel(item);
|
this.renderer.removeModel(item);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -279,7 +307,7 @@ export class Reconcile {
|
|||||||
if (item instanceof SVAddressLabel) {
|
if (item instanceof SVAddressLabel) {
|
||||||
Animations.FADE_IN(item.G6Item, {
|
Animations.FADE_IN(item.G6Item, {
|
||||||
duration,
|
duration,
|
||||||
timingFunction
|
timingFunction,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,7 +329,6 @@ export class Reconcile {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理被释放的节点 models
|
* 处理被释放的节点 models
|
||||||
* @param freedModes
|
* @param freedModes
|
||||||
@ -316,14 +343,20 @@ export class Reconcile {
|
|||||||
item.set('style', { fill: '#ccc' });
|
item.set('style', { fill: '#ccc' });
|
||||||
nodeGroup.attr({ opacity: alpha });
|
nodeGroup.attr({ opacity: alpha });
|
||||||
|
|
||||||
if (item.marker) {
|
if (item.appendages.marker) {
|
||||||
const markerGroup = item.marker.G6Item.getContainer();
|
item.appendages.marker.forEach(marker => {
|
||||||
item.marker.set('style', { fill: '#ccc' });
|
const markerGroup = marker.G6Item.getContainer();
|
||||||
|
marker.set('style', { fill: '#ccc' });
|
||||||
markerGroup.attr({ opacity: alpha + 0.5 });
|
markerGroup.attr({ opacity: alpha + 0.5 });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
item.freedLabel.G6Item.toFront();
|
if (item.appendages.freedLabel) {
|
||||||
Animations.FADE_IN(item.freedLabel.G6Item, { duration, timingFunction });
|
item.appendages.freedLabel.forEach(freedLabel => {
|
||||||
|
freedLabel.G6Item.toFront();
|
||||||
|
Animations.FADE_IN(freedLabel.G6Item, { duration, timingFunction });
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
EventBus.emit('onFreed', freedModes);
|
EventBus.emit('onFreed', freedModes);
|
||||||
@ -347,18 +380,16 @@ export class Reconcile {
|
|||||||
|
|
||||||
if (item instanceof SVLink) {
|
if (item instanceof SVLink) {
|
||||||
item.set('style', {
|
item.set('style', {
|
||||||
stroke: changeHighlightColor
|
stroke: changeHighlightColor,
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
item.set('style', {
|
item.set('style', {
|
||||||
fill: changeHighlightColor
|
fill: changeHighlightColor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 进行diff
|
* 进行diff
|
||||||
* @param layoutGroupTable
|
* @param layoutGroupTable
|
||||||
@ -367,16 +398,25 @@ export class Reconcile {
|
|||||||
* @param accumulateLeakModels
|
* @param accumulateLeakModels
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public diff(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[], isEnterFunction: boolean): DiffResult {
|
public diff(
|
||||||
|
layoutGroupTable: LayoutGroupTable,
|
||||||
|
prevModelList: SVModel[],
|
||||||
|
modelList: SVModel[],
|
||||||
|
accumulateLeakModels: SVModel[],
|
||||||
|
isEnterFunction: boolean,
|
||||||
|
prevStep: boolean
|
||||||
|
): DiffResult {
|
||||||
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
|
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
|
||||||
const leakModels: SVModel[] = isEnterFunction? []: this.getLeakModels(layoutGroupTable, prevModelList, modelList);
|
const leakModels: SVModel[] = isEnterFunction
|
||||||
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels);
|
? []
|
||||||
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList);
|
: this.getLeakModels(layoutGroupTable, prevModelList, modelList);
|
||||||
|
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels, prevStep);
|
||||||
|
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList, accumulateLeakModels);
|
||||||
const updateModels: SVModel[] = [
|
const updateModels: SVModel[] = [
|
||||||
...this.getReTargetMarkers(prevModelList, modelList),
|
...this.getReTargetMarkers(prevModelList, modelList),
|
||||||
...this.getLabelChangeModels(prevModelList, modelList),
|
...this.getLabelChangeModels(prevModelList, modelList),
|
||||||
...appendModels,
|
...appendModels,
|
||||||
...leakModels
|
...leakModels,
|
||||||
];
|
];
|
||||||
const freedModels: SVNode[] = this.getFreedModels(prevModelList, modelList);
|
const freedModels: SVNode[] = this.getFreedModels(prevModelList, modelList);
|
||||||
|
|
||||||
@ -387,26 +427,17 @@ export class Reconcile {
|
|||||||
FREED: freedModels,
|
FREED: freedModels,
|
||||||
LEAKED: leakModels,
|
LEAKED: leakModels,
|
||||||
UPDATE: updateModels,
|
UPDATE: updateModels,
|
||||||
ACCUMULATE_LEAK: [...accumulateLeakModels]
|
ACCUMULATE_LEAK: [...accumulateLeakModels],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行调和操作
|
* 执行调和操作
|
||||||
* @param diffResult
|
* @param diffResult
|
||||||
* @param isFirstRender
|
* @param isFirstRender
|
||||||
*/
|
*/
|
||||||
public patch(diffResult: DiffResult) {
|
public patch(diffResult: DiffResult) {
|
||||||
const {
|
const { APPEND, REMOVE, FREED, LEAKED, UPDATE, CONTINUOUS, ACCUMULATE_LEAK } = diffResult;
|
||||||
APPEND,
|
|
||||||
REMOVE,
|
|
||||||
FREED,
|
|
||||||
LEAKED,
|
|
||||||
UPDATE,
|
|
||||||
CONTINUOUS,
|
|
||||||
ACCUMULATE_LEAK
|
|
||||||
} = diffResult;
|
|
||||||
|
|
||||||
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
|
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
|
||||||
|
|
||||||
@ -426,5 +457,5 @@ export class Reconcile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() { }
|
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;
|
||||||
@ -34,11 +32,11 @@ export class Renderer {
|
|||||||
return event.item['SVModel'].isNode();
|
return event.item['SVModel'].isNode();
|
||||||
},
|
},
|
||||||
getContent: event => this.getTooltipContent(event.item['SVModel'], { address: 'sourceId', data: 'data' }),
|
getContent: event => this.getTooltipContent(event.item['SVModel'], { address: 'sourceId', data: 'data' }),
|
||||||
itemTypes: ['node']
|
itemTypes: ['node'],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.shadowG6Instance = new Graph({
|
this.shadowG6Instance = new Graph({
|
||||||
container: DOMContainer.cloneNode() as HTMLElement
|
container: DOMContainer.cloneNode() as HTMLElement,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 初始化g6实例
|
// 初始化g6实例
|
||||||
@ -50,11 +48,11 @@ export class Renderer {
|
|||||||
animate: enable,
|
animate: enable,
|
||||||
animateCfg: {
|
animateCfg: {
|
||||||
duration: duration,
|
duration: duration,
|
||||||
easing: timingFunction
|
easing: timingFunction,
|
||||||
},
|
},
|
||||||
fitView: false,
|
fitView: false,
|
||||||
modes: behaviorsModes,
|
modes: behaviorsModes,
|
||||||
plugins: [tooltip]
|
plugins: [tooltip],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +97,7 @@ export class Renderer {
|
|||||||
this.shadowG6Instance.clear();
|
this.shadowG6Instance.clear();
|
||||||
this.shadowG6Instance.read(g6Data);
|
this.shadowG6Instance.read(g6Data);
|
||||||
renderModelList.forEach(item => {
|
renderModelList.forEach(item => {
|
||||||
|
item.G6Item = null;
|
||||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||||
item.shadowG6Instance = this.shadowG6Instance;
|
item.shadowG6Instance = this.shadowG6Instance;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
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';
|
||||||
|
|
||||||
export class ViewContainer {
|
export class ViewContainer {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
@ -29,7 +32,6 @@ export class ViewContainer {
|
|||||||
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
|
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
|
||||||
public clickSelectNode: SVNode; // 点击选中的节点
|
public clickSelectNode: SVNode; // 点击选中的节点
|
||||||
|
|
||||||
|
|
||||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||||
const behaviorsModes: Modes = InitG6Behaviors(engine, this);
|
const behaviorsModes: Modes = InitG6Behaviors(engine, this);
|
||||||
|
|
||||||
@ -57,7 +59,6 @@ export class ViewContainer {
|
|||||||
zoom && SolveZoomCanvasWithLeak(this);
|
zoom && SolveZoomCanvasWithLeak(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,11 +76,10 @@ export class ViewContainer {
|
|||||||
g6Instance.translate(-dx, -dy);
|
g6Instance.translate(-dx, -dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels, []);
|
this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels);
|
||||||
g6Instance.refresh();
|
g6Instance.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 g6 实例
|
* 获取 g6 实例
|
||||||
*/
|
*/
|
||||||
@ -131,7 +131,7 @@ export class ViewContainer {
|
|||||||
this.leakAreaY += dy;
|
this.leakAreaY += dy;
|
||||||
EventBus.emit('onLeakAreaUpdate', {
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
leakAreaY: this.leakAreaY,
|
leakAreaY: this.leakAreaY,
|
||||||
hasLeak: this.hasLeak
|
hasLeak: this.hasLeak,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,21 +140,23 @@ export class ViewContainer {
|
|||||||
* @param models
|
* @param models
|
||||||
* @param layoutFn
|
* @param layoutFn
|
||||||
*/
|
*/
|
||||||
render(layoutGroupTable: LayoutGroupTable, isEnterFunction: boolean) {
|
render(layoutGroupTable: LayoutGroupTable, isEnterFunction: boolean, prevStep: boolean) {
|
||||||
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
|
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
|
||||||
diffResult = this.reconcile.diff(this.layoutGroupTable, this.prevModelList, modelList, this.accumulateLeakModels, isEnterFunction),
|
diffResult = this.reconcile.diff(
|
||||||
renderModelList = [
|
this.layoutGroupTable,
|
||||||
...modelList,
|
this.prevModelList,
|
||||||
...diffResult.REMOVE,
|
modelList,
|
||||||
...diffResult.LEAKED,
|
this.accumulateLeakModels,
|
||||||
...diffResult.ACCUMULATE_LEAK
|
isEnterFunction,
|
||||||
];
|
prevStep
|
||||||
|
),
|
||||||
|
renderModelList = [...modelList, ...diffResult.REMOVE, ...diffResult.LEAKED, ...diffResult.ACCUMULATE_LEAK];
|
||||||
|
|
||||||
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
|
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
|
||||||
this.hasLeak = false;
|
this.hasLeak = false;
|
||||||
EventBus.emit('onLeakAreaUpdate', {
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
leakAreaY: this.leakAreaY,
|
leakAreaY: this.leakAreaY,
|
||||||
hasLeak: this.hasLeak
|
hasLeak: this.hasLeak,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,20 +164,19 @@ export class ViewContainer {
|
|||||||
this.hasLeak = true;
|
this.hasLeak = true;
|
||||||
EventBus.emit('onLeakAreaUpdate', {
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
leakAreaY: this.leakAreaY,
|
leakAreaY: this.leakAreaY,
|
||||||
hasLeak: this.hasLeak
|
hasLeak: this.hasLeak,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行向后累积
|
||||||
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
|
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
|
||||||
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels, diffResult.LEAKED); // 进行布局(设置model的x,y,样式等)
|
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels); // 进行布局(设置model的x,y,样式等)
|
||||||
|
|
||||||
this.beforeRender();
|
this.beforeRender();
|
||||||
this.renderer.render(renderModelList); // 渲染视图
|
this.renderer.render(renderModelList); // 渲染视图
|
||||||
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
|
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
|
||||||
this.afterRender();
|
this.afterRender();
|
||||||
|
|
||||||
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行累积
|
|
||||||
|
|
||||||
this.layoutGroupTable = layoutGroupTable;
|
this.layoutGroupTable = layoutGroupTable;
|
||||||
this.prevModelList = modelList;
|
this.prevModelList = modelList;
|
||||||
}
|
}
|
||||||
@ -193,10 +194,8 @@ export class ViewContainer {
|
|||||||
this.brushSelectedModels.length = 0;
|
this.brushSelectedModels.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把渲染后要触发的逻辑放在这里
|
* 把渲染后要触发的逻辑放在这里
|
||||||
*/
|
*/
|
||||||
@ -211,16 +210,5 @@ export class ViewContainer {
|
|||||||
/**
|
/**
|
||||||
* 把渲染前要触发的逻辑放在这里
|
* 把渲染前要触发的逻辑放在这里
|
||||||
*/
|
*/
|
||||||
private beforeRender() { }
|
private beforeRender() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -49,15 +49,15 @@ export class Engine {
|
|||||||
/**
|
/**
|
||||||
* 输入数据进行渲染
|
* 输入数据进行渲染
|
||||||
* @param sources
|
* @param sources
|
||||||
* @param force
|
* @param prevStep
|
||||||
*/
|
*/
|
||||||
public render(source: Sources, force: boolean = false) {
|
public render(source: Sources, prevStep: boolean = false) {
|
||||||
if (source === undefined || source === null) {
|
if (source === undefined || source === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
``
|
``
|
||||||
let stringSource = JSON.stringify(source);
|
let stringSource = JSON.stringify(source);
|
||||||
if (force === false && this.prevStringSource === stringSource) {
|
if (this.prevStringSource === stringSource) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ export class Engine {
|
|||||||
const layoutGroupTable = this.modelConstructor.construct(source);
|
const layoutGroupTable = this.modelConstructor.construct(source);
|
||||||
|
|
||||||
// 2 渲染(使用g6进行渲染)
|
// 2 渲染(使用g6进行渲染)
|
||||||
this.viewContainer.render(layoutGroupTable, source.isEnterFunction as boolean);
|
this.viewContainer.render(layoutGroupTable, source.isEnterFunction as boolean, prevStep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,5 @@ module.exports = {
|
|||||||
loader: 'ts-loader'
|
loader: 'ts-loader'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
// devtool: 'eval-source-map'
|
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user