feat: 重写泄漏区逻辑
This commit is contained in:
parent
5893ef2938
commit
3170bd77ec
@ -1,11 +1,11 @@
|
||||
SV.registerLayout('BinaryTree', {
|
||||
defineOptions() {
|
||||
return {
|
||||
element: {
|
||||
node: {
|
||||
default: {
|
||||
type: 'binary-tree-node',
|
||||
size: [60, 30],
|
||||
label: '[data]',
|
||||
label: '[id]',
|
||||
style: {
|
||||
fill: '#b83b5e',
|
||||
stroke: '#333',
|
||||
@ -58,7 +58,7 @@ SV.registerLayout('BinaryTree', {
|
||||
/**
|
||||
* 对子树进行递归布局
|
||||
*/
|
||||
layoutItem(node, parent, index, layoutOptions) {
|
||||
layoutItem(node, layoutOptions) {
|
||||
// 次双亲不进行布局
|
||||
if (!node) {
|
||||
return null;
|
||||
@ -69,43 +69,54 @@ SV.registerLayout('BinaryTree', {
|
||||
height = bound.height,
|
||||
group = new Group(node),
|
||||
leftGroup = null,
|
||||
rightGroup = null;
|
||||
rightGroup = null,
|
||||
leftBound = null,
|
||||
rightBound = null;
|
||||
|
||||
if (node.visited) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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]) {
|
||||
rightGroup = this.layoutItem(node.child[1], node, 1, layoutOptions);
|
||||
rightGroup = this.layoutItem(node.child[1], layoutOptions);
|
||||
}
|
||||
|
||||
// 处理左右子树相交问题
|
||||
if (leftGroup && rightGroup) {
|
||||
let intersection = Bound.intersect(leftGroup.getBound(), rightGroup.getBound()),
|
||||
move = 0;
|
||||
if (leftGroup) {
|
||||
leftBound = leftGroup.getBound();
|
||||
node.set('y', leftBound.y - layoutOptions.yInterval - height);
|
||||
}
|
||||
|
||||
if (intersection && intersection.width > 0) {
|
||||
move = (intersection.width + layoutOptions.xInterval) / 2;
|
||||
leftGroup.translate(-move, 0);
|
||||
rightGroup.translate(move, 0);
|
||||
if(rightGroup) {
|
||||
rightBound = rightGroup.getBound();
|
||||
|
||||
if(leftGroup) {
|
||||
rightGroup.translate(0, leftBound.y - rightBound.y)
|
||||
}
|
||||
|
||||
rightBound = rightGroup.getBound();
|
||||
node.set('y', rightBound.y - layoutOptions.yInterval - height);
|
||||
}
|
||||
|
||||
// 处理左右子树相交问题
|
||||
if (leftGroup && rightGroup) {
|
||||
let move = Math.abs(rightBound.x - layoutOptions.xInterval - leftBound.x - leftBound.width);
|
||||
if (move > 0) {
|
||||
leftGroup.translate(-move / 2, 0);
|
||||
rightGroup.translate(move / 2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (leftGroup) {
|
||||
let leftBound = leftGroup.getBound();
|
||||
|
||||
node.set('y', leftBound.y - layoutOptions.yInterval - height);
|
||||
leftBound = leftGroup.getBound();
|
||||
node.set('x', leftBound.x + leftBound.width + layoutOptions.xInterval / 2 - width);
|
||||
}
|
||||
|
||||
if(rightGroup) {
|
||||
let rightBound = rightGroup.getBound();
|
||||
|
||||
node.set('y', rightBound.y - layoutOptions.yInterval - height);
|
||||
rightBound = rightGroup.getBound();
|
||||
node.set('x', rightBound.x - layoutOptions.xInterval / 2 - width);
|
||||
}
|
||||
|
||||
@ -129,7 +140,7 @@ SV.registerLayout('BinaryTree', {
|
||||
*/
|
||||
layout(elements, layoutOptions) {
|
||||
let root = elements[0];
|
||||
this.layoutItem(root, null, -1, layoutOptions);
|
||||
this.layoutItem(root, layoutOptions);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -77,25 +77,37 @@ SV.registerLayout('LinkList', {
|
||||
layout: {
|
||||
xInterval: 50,
|
||||
yInterval: 50
|
||||
},
|
||||
behavior: {
|
||||
dragNode: false
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
layout(elements, layoutOptions) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
let node = elements[i],
|
||||
prev = elements[1 - 1],
|
||||
width = node.get('size')[0];
|
||||
|
||||
if (prev) {
|
||||
node.set('y', prev.get('y'));
|
||||
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
|
||||
}
|
||||
/**
|
||||
* 对子树进行递归布局
|
||||
* @param node
|
||||
* @param parent
|
||||
*/
|
||||
layoutItem(node, prev, layoutOptions) {
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let width = node.get('size')[0];
|
||||
|
||||
if (prev) {
|
||||
node.set('y', prev.get('y'));
|
||||
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
|
||||
}
|
||||
|
||||
if (node.next) {
|
||||
this.layoutItem(node.next, node, layoutOptions);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
layout(elements, layoutOptions) {
|
||||
let root = elements[0];
|
||||
this.layoutItem(root, null, layoutOptions);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
element: {
|
||||
default: {
|
||||
type: 'link-list-node',
|
||||
label: '[id]',
|
||||
label: '[data]',
|
||||
size: [60, 30],
|
||||
style: {
|
||||
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,237 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DEMO</title>
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DEMO</title>
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #ccc;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.down {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#leak {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
top: 100px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 4px;
|
||||
border-top: 1px dashed #000;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.75s ease-in-out;
|
||||
}
|
||||
|
||||
#leak>span {
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
.container {
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #ccc;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
<body>
|
||||
<div class="container" id="container">
|
||||
<div id="leak">
|
||||
<span>泄漏区</span>
|
||||
</div>
|
||||
</div>
|
||||
.down {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
<button id="btn-prev">prev</button>
|
||||
<button id="btn-next">next</button>
|
||||
<button id="resize">resize</button>
|
||||
<button id="relayout">relayout</button>
|
||||
<button id="switch-mode">switch mode</button>
|
||||
<button id="brush-select">brush-select</button>
|
||||
<span id="pos"></span>
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
<script src="./../dist/sv.js"></script>
|
||||
<script>
|
||||
const Group = SV.Group,
|
||||
Bound = SV.Bound,
|
||||
G6 = SV.G6,
|
||||
Vector = SV.Vector;
|
||||
</script>
|
||||
<script src="./Layouter/LinkList.js"></script>
|
||||
<script src="./Layouter/BinaryTree.js"></script>
|
||||
<script src="./Layouter/Stack.js"></script>
|
||||
<script src="./Layouter/LinkQueue.js"></script>
|
||||
<script src="./Layouter/GeneralizedList.js"></script>
|
||||
<script src="./Layouter/ChainHashTable.js"></script>
|
||||
<script src="./Layouter/Array.js"></script>
|
||||
<script src="./Layouter/HashTable.js"></script>
|
||||
<script src="./Layouter/LinkStack.js"></script>
|
||||
<script src="./Layouter/AdjoinMatrixGraph.js"></script>
|
||||
<script src="./Layouter/AdjoinTableGraph.js"></script>
|
||||
<script src="./Layouter/SqQueue.js"></script>
|
||||
<script src="./Layouter/PTree.js"></script>
|
||||
<script src="./Layouter/PCTree.js"></script>
|
||||
#leak {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
top: 100px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 4px;
|
||||
border-top: 1px dashed #000;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.75s ease-in-out;
|
||||
}
|
||||
|
||||
<script>
|
||||
let cur = SV(document.getElementById('container'), {
|
||||
view: {
|
||||
leakAreaHeight: 130,
|
||||
groupPadding: 40,
|
||||
},
|
||||
});
|
||||
#leak > span {
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
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
|
||||
}, ];
|
||||
<body>
|
||||
<div class="container" id="container">
|
||||
<div id="leak">
|
||||
<span>泄漏区</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
let dataIndex = 0,
|
||||
curData = data[dataIndex];
|
||||
<button id="btn-prev">prev</button>
|
||||
<button id="btn-next">next</button>
|
||||
<button id="resize">resize</button>
|
||||
<button id="relayout">relayout</button>
|
||||
<button id="switch-mode">switch mode</button>
|
||||
<button id="brush-select">brush-select</button>
|
||||
<span id="pos"></span>
|
||||
|
||||
let enableBrushSelect = false;
|
||||
<script src="./../dist/sv.js"></script>
|
||||
<script>
|
||||
const Group = SV.Group,
|
||||
Bound = SV.Bound,
|
||||
G6 = SV.G6,
|
||||
Vector = SV.Vector;
|
||||
</script>
|
||||
<script src="./Layouter/LinkList.js"></script>
|
||||
<script src="./Layouter/BinaryTree.js"></script>
|
||||
<script src="./Layouter/Stack.js"></script>
|
||||
<script src="./Layouter/LinkQueue.js"></script>
|
||||
<script src="./Layouter/GeneralizedList.js"></script>
|
||||
<script src="./Layouter/ChainHashTable.js"></script>
|
||||
<script src="./Layouter/Array.js"></script>
|
||||
<script src="./Layouter/HashTable.js"></script>
|
||||
<script src="./Layouter/LinkStack.js"></script>
|
||||
<script src="./Layouter/AdjoinMatrixGraph.js"></script>
|
||||
<script src="./Layouter/AdjoinTableGraph.js"></script>
|
||||
<script src="./Layouter/SqQueue.js"></script>
|
||||
<script src="./Layouter/PTree.js"></script>
|
||||
<script src="./Layouter/PCTree.js"></script>
|
||||
<script src="./data.js"></script>
|
||||
|
||||
const container = document.getElementById('container'),
|
||||
pos = document.getElementById('pos');
|
||||
<script>
|
||||
let cur = SV(document.getElementById('container'), {
|
||||
view: {
|
||||
leakAreaHeight: 130,
|
||||
groupPadding: 40,
|
||||
},
|
||||
});
|
||||
|
||||
const leak = document.getElementById('leak');
|
||||
let dataIndex = 0,
|
||||
curData = SOURCES_DATA[dataIndex];
|
||||
|
||||
cur.render(curData);
|
||||
let enableBrushSelect = false;
|
||||
|
||||
document.getElementById('btn-next').addEventListener('click', e => {
|
||||
curData = data[++dataIndex];
|
||||
cur.render(curData);
|
||||
});
|
||||
const container = document.getElementById('container'),
|
||||
pos = document.getElementById('pos');
|
||||
|
||||
document.getElementById('btn-prev').addEventListener('click', e => {
|
||||
curData = data[--dataIndex];
|
||||
cur.render(curData);
|
||||
});
|
||||
const leak = document.getElementById('leak');
|
||||
|
||||
document.getElementById('resize').addEventListener('click', e => {
|
||||
container.style.height = 800 + 'px';
|
||||
cur.resize(container.offsetWidth, container.offsetHeight);
|
||||
});
|
||||
cur.render(curData);
|
||||
|
||||
document.getElementById('relayout').addEventListener('click', e => {
|
||||
cur.reLayout();
|
||||
});
|
||||
document.getElementById('btn-next').addEventListener('click', e => {
|
||||
curData = SOURCES_DATA[++dataIndex];
|
||||
cur.render(curData);
|
||||
});
|
||||
|
||||
document.getElementById('switch-mode').addEventListener('click', e => {
|
||||
cur.updateStyle('Array', newArrayOption);
|
||||
});
|
||||
document.getElementById('btn-prev').addEventListener('click', e => {
|
||||
curData = SOURCES_DATA[--dataIndex];
|
||||
cur.render(curData);
|
||||
});
|
||||
|
||||
document.getElementById('brush-select').addEventListener('click', e => {
|
||||
enableBrushSelect = !enableBrushSelect;
|
||||
cur.switchBrushSelect(enableBrushSelect);
|
||||
});
|
||||
document.getElementById('resize').addEventListener('click', e => {
|
||||
container.style.height = 800 + 'px';
|
||||
cur.resize(container.offsetWidth, container.offsetHeight);
|
||||
});
|
||||
|
||||
cur.on('onLeakAreaUpdate', payload => {
|
||||
leak.style.opacity = payload.hasLeak ? 1 : 0;
|
||||
leak.style.top = payload.leakAreaY - 40 + 'px';
|
||||
});
|
||||
document.getElementById('relayout').addEventListener('click', e => {
|
||||
cur.reLayout();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------
|
||||
document.getElementById('switch-mode').addEventListener('click', e => {
|
||||
cur.updateStyle('Array', newArrayOption);
|
||||
});
|
||||
|
||||
container.addEventListener('mousemove', e => {
|
||||
let x = e.offsetX,
|
||||
y = e.offsetY;
|
||||
pos.innerHTML = `${x},${y}`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
document.getElementById('brush-select').addEventListener('click', e => {
|
||||
enableBrushSelect = !enableBrushSelect;
|
||||
cur.switchBrushSelect(enableBrushSelect);
|
||||
});
|
||||
|
||||
</html>
|
||||
cur.on('onLeakAreaUpdate', payload => {
|
||||
leak.style.opacity = payload.hasLeak ? 1 : 0;
|
||||
leak.style.top = payload.leakAreaY - 40 + 'px';
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------
|
||||
|
||||
container.addEventListener('mousemove', e => {
|
||||
let x = e.offsetX,
|
||||
y = e.offsetY;
|
||||
pos.innerHTML = `${x},${y}`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -76,7 +76,7 @@ export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
|
||||
item.setSelectedState(false);
|
||||
|
||||
if (item instanceof SVNode) {
|
||||
item.appendages.forEach(appendage => appendage.setSelectedState(false));
|
||||
item.getAppendagesList().forEach(appendage => appendage.setSelectedState(false));
|
||||
}
|
||||
});
|
||||
viewContainer.brushSelectedModels.length = 0;
|
||||
@ -100,7 +100,7 @@ export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
|
||||
y: node.G6Item.getModel().y
|
||||
});
|
||||
|
||||
node.appendages.forEach(item => {
|
||||
node.getAppendagesList().forEach(item => {
|
||||
item.setSelectedState(false);
|
||||
item.set({
|
||||
x: item.G6Item.getModel().x,
|
||||
@ -116,7 +116,7 @@ export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
|
||||
});
|
||||
|
||||
if(item instanceof SVNode) {
|
||||
item.appendages.forEach(appendage => {
|
||||
item.getAppendagesList().forEach(appendage => {
|
||||
appendage.set({
|
||||
x: appendage.G6Item.getModel().x,
|
||||
y: appendage.G6Item.getModel().y
|
||||
|
||||
@ -45,7 +45,7 @@ export function InitG6Behaviors(engine: Engine, viewContainer: ViewContainer): M
|
||||
// 这里之所以要把节点和其 appendages 的选中状态设置为true,是因为 g6 处理拖拽节点的逻辑是将所以已选中的元素一起拖动,
|
||||
// 这样 appendages 就可以很自然地跟着节点动(我是看源码才知道的)
|
||||
node.setSelectedState(true);
|
||||
node.appendages.forEach(item => {
|
||||
node.getAppendagesList().forEach(item => {
|
||||
item.setSelectedState(true);
|
||||
});
|
||||
|
||||
|
||||
@ -1,152 +1,161 @@
|
||||
import { Vector } from "./vector";
|
||||
|
||||
|
||||
import { Vector } from './vector';
|
||||
|
||||
// 包围盒类型
|
||||
export type BoundingRect = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
|
||||
// 包围盒操作
|
||||
export const Bound = {
|
||||
/**
|
||||
* 从点集生成包围盒
|
||||
* @param points
|
||||
*/
|
||||
fromPoints(points: Array<[number, number]>): BoundingRect {
|
||||
let maxX = -Infinity,
|
||||
minX = Infinity,
|
||||
maxY = -Infinity,
|
||||
minY = Infinity;
|
||||
|
||||
/**
|
||||
* 从点集生成包围盒
|
||||
* @param points
|
||||
*/
|
||||
fromPoints(points: Array<[number, number]>): BoundingRect {
|
||||
let maxX = -Infinity,
|
||||
minX = Infinity,
|
||||
maxY = -Infinity,
|
||||
minY = Infinity;
|
||||
points.map(item => {
|
||||
if (item[0] > maxX) maxX = item[0];
|
||||
if (item[0] < minX) minX = item[0];
|
||||
if (item[1] > maxY) maxY = item[1];
|
||||
if (item[1] < minY) minY = item[1];
|
||||
});
|
||||
|
||||
points.map(item => {
|
||||
if(item[0] > maxX) maxX = item[0];
|
||||
if(item[0] < minX) minX = item[0];
|
||||
if(item[1] > maxY) maxY = item[1];
|
||||
if(item[1] < minY) minY = item[1];
|
||||
});
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
};
|
||||
},
|
||||
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 由包围盒转化为四个顶点(顺时针)
|
||||
* @param bound
|
||||
*/
|
||||
toPoints(bound: BoundingRect): Array<[number, number]> {
|
||||
return [
|
||||
[bound.x, bound.y],
|
||||
[bound.x + bound.width, bound.y],
|
||||
[bound.x + bound.width, bound.y + bound.height],
|
||||
[bound.x, bound.y + bound.height],
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* 由包围盒转化为四个顶点(顺时针)
|
||||
* @param bound
|
||||
*/
|
||||
toPoints(bound: BoundingRect): Array<[number, number]> {
|
||||
return [
|
||||
[bound.x, bound.y],
|
||||
[bound.x + bound.width, bound.y],
|
||||
[bound.x + bound.width, bound.y + bound.height],
|
||||
[bound.x, bound.y + bound.height]
|
||||
];
|
||||
},
|
||||
/**
|
||||
* 求包围盒并集
|
||||
* @param arg
|
||||
*/
|
||||
union(...arg: BoundingRect[]): BoundingRect {
|
||||
if (arg.length === 0) {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 求包围盒并集
|
||||
* @param arg
|
||||
*/
|
||||
union(...arg: BoundingRect[]): BoundingRect {
|
||||
return arg.length > 1?
|
||||
arg.reduce((total, cur) => {
|
||||
let minX = total.x < cur.x? total.x: cur.x,
|
||||
maxX = total.x + total.width < cur.x + cur.width? cur.x + cur.width: total.x + total.width,
|
||||
minY = total.y < cur.y? total.y: cur.y,
|
||||
maxY = total.y + total.height < cur.y + cur.height? cur.y + cur.height: total.y + total.height;
|
||||
return 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 {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY
|
||||
};
|
||||
}): arg[0];
|
||||
},
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
};
|
||||
})
|
||||
: arg[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* 包围盒求交集
|
||||
* @param b1
|
||||
* @param b2
|
||||
*/
|
||||
intersect(b1: BoundingRect, b2: BoundingRect): BoundingRect {
|
||||
let x, y,
|
||||
maxX, maxY,
|
||||
overlapsX,
|
||||
overlapsY;
|
||||
/**
|
||||
* 包围盒求交集
|
||||
* @param b1
|
||||
* @param b2
|
||||
*/
|
||||
intersect(b1: BoundingRect, b2: BoundingRect): BoundingRect {
|
||||
let x,
|
||||
y,
|
||||
maxX,
|
||||
maxY,
|
||||
overlapsX,
|
||||
overlapsY,
|
||||
b1x = b1.x,
|
||||
b1mx = b1.x + b1.width,
|
||||
b2x = b2.x,
|
||||
b2mx = b2.x + b2.width,
|
||||
b1y = b1.y,
|
||||
b1my = b1.y + b1.height,
|
||||
b2y = b2.y,
|
||||
b2my = b2.y + b2.height;
|
||||
|
||||
if(b1.x < b2.x + b2.width && b1.x + b1.width > b2.x) {
|
||||
x = b1.x < b2.x? b2.x: b1.x;
|
||||
// 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;
|
||||
}
|
||||
|
||||
if(b1.y < b2.y + b2.height && b1.y + b1.height > b2.y) {
|
||||
y = b1.y < b2.y? b2.y: b1.y;
|
||||
maxY = b1.y + b1.height < b2.y + b2.height? b1.y + b1.height: b2.y + b2.height;
|
||||
overlapsY = maxY - y;
|
||||
}
|
||||
x = Math.max(b1x, b2x);
|
||||
maxX = Math.min(b1mx, b2mx);
|
||||
overlapsX = maxX - x;
|
||||
|
||||
if(!overlapsX || !overlapsY) return null;
|
||||
y = Math.max(b1y, b2y);
|
||||
maxY = Math.min(b1my, b2my);
|
||||
overlapsY = maxY - y;
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: overlapsX,
|
||||
height: overlapsY
|
||||
};
|
||||
},
|
||||
if (!overlapsX || !overlapsY) return null;
|
||||
|
||||
/**
|
||||
* 位移包围盒
|
||||
* @param bound
|
||||
* @param dx
|
||||
* @param dy
|
||||
*/
|
||||
translate(bound: BoundingRect, dx: number, dy: number) {
|
||||
bound.x += dx;
|
||||
bound.y += dy;
|
||||
},
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: overlapsX,
|
||||
height: overlapsY,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 求包围盒旋转后新形成的包围盒
|
||||
* @param bound
|
||||
* @param rot
|
||||
*/
|
||||
rotation(bound: BoundingRect, rot: number): BoundingRect {
|
||||
let cx = bound.x + bound.width / 2,
|
||||
cy = bound.y + bound.height / 2;
|
||||
/**
|
||||
* 位移包围盒
|
||||
* @param bound
|
||||
* @param dx
|
||||
* @param dy
|
||||
*/
|
||||
translate(bound: BoundingRect, dx: number, dy: number) {
|
||||
bound.x += dx;
|
||||
bound.y += dy;
|
||||
},
|
||||
|
||||
return Bound.fromPoints(Bound.toPoints(bound).map(item => Vector.rotation(rot, item, [cx, cy])));
|
||||
},
|
||||
/**
|
||||
* 求包围盒旋转后新形成的包围盒
|
||||
* @param bound
|
||||
* @param rot
|
||||
*/
|
||||
rotation(bound: BoundingRect, rot: number): BoundingRect {
|
||||
let cx = bound.x + bound.width / 2,
|
||||
cy = bound.y + bound.height / 2;
|
||||
|
||||
/**
|
||||
* 判断两个包围盒是否相交
|
||||
* @param b1
|
||||
* @param b2
|
||||
*/
|
||||
isOverlap(b1: BoundingRect, b2: BoundingRect): boolean {
|
||||
let maxX1 = b1.x + b1.width,
|
||||
maxY1 = b1.y + b1.height,
|
||||
maxX2 = b2.x + b2.width,
|
||||
maxY2 = b2.y + b2.height;
|
||||
return Bound.fromPoints(Bound.toPoints(bound).map(item => Vector.rotation(rot, item, [cx, cy])));
|
||||
},
|
||||
|
||||
if (b1.x < maxX2 && b2.x < maxX1 && b1.y < maxY2 && b2.y < maxY1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 判断两个包围盒是否相交
|
||||
* @param b1
|
||||
* @param b2
|
||||
*/
|
||||
isOverlap(b1: BoundingRect, b2: BoundingRect): boolean {
|
||||
let maxX1 = b1.x + b1.width,
|
||||
maxY1 = b1.y + b1.height,
|
||||
maxX2 = b2.x + b2.width,
|
||||
maxY2 = b2.y + b2.height;
|
||||
|
||||
if (b1.x < maxX2 && b2.x < maxX1 && b1.y < maxY2 && b2.y < maxY1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -122,21 +122,6 @@ export const Util = {
|
||||
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
|
||||
* @param modelList
|
||||
|
||||
@ -49,4 +49,11 @@ export class SVLink extends SVModel {
|
||||
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,219 +1,204 @@
|
||||
import { Util } from "../Common/util";
|
||||
import { Style } from "../options";
|
||||
import { BoundingRect } from "../Common/boundingRect";
|
||||
import { EdgeConfig, Item, NodeConfig } from "@antv/g6-core";
|
||||
import { Graph } from "@antv/g6-pc";
|
||||
import { Util } from '../Common/util';
|
||||
import { Style } from '../options';
|
||||
import { BoundingRect } from '../Common/boundingRect';
|
||||
import { EdgeConfig, Item, NodeConfig } from '@antv/g6-core';
|
||||
import { Graph } from '@antv/g6-pc';
|
||||
import merge from 'merge';
|
||||
import { ModelConstructor } from "./modelConstructor";
|
||||
|
||||
|
||||
|
||||
|
||||
export class SVModel {
|
||||
public id: string;
|
||||
public sourceType: string;
|
||||
public id: string;
|
||||
public sourceType: string;
|
||||
|
||||
public g6Instance: Graph;
|
||||
public shadowG6Instance: Graph;
|
||||
public group: string;
|
||||
public layout: string;
|
||||
public G6ModelProps: NodeConfig | EdgeConfig;
|
||||
public shadowG6Item: Item;
|
||||
public G6Item: Item;
|
||||
public g6Instance: Graph;
|
||||
public shadowG6Instance: Graph;
|
||||
public group: string;
|
||||
public layout: string;
|
||||
public G6ModelProps: NodeConfig | EdgeConfig;
|
||||
public shadowG6Item: Item;
|
||||
public G6Item: Item;
|
||||
|
||||
public preLayout: boolean; // 是否进入预备布局阶段
|
||||
public discarded: boolean;
|
||||
public freed: boolean;
|
||||
public leaked: boolean;
|
||||
public generalStyle: Partial<Style>;
|
||||
public preLayout: boolean; // 是否进入预备布局阶段
|
||||
public discarded: boolean;
|
||||
public freed: boolean;
|
||||
public leaked: boolean;
|
||||
public generalStyle: Partial<Style>;
|
||||
|
||||
private transformMatrix: number[];
|
||||
private modelType: string;
|
||||
private transformMatrix: number[];
|
||||
private modelType: string;
|
||||
|
||||
public layoutX: number;
|
||||
public layoutY: number;
|
||||
public layoutX: number;
|
||||
public layoutY: number;
|
||||
|
||||
constructor(id: string, type: string, group: string, layout: string, modelType: string) {
|
||||
this.id = id;
|
||||
this.sourceType = type;
|
||||
this.group = group;
|
||||
this.layout = layout;
|
||||
this.shadowG6Item = null;
|
||||
constructor(id: string, type: string, group: string, layout: string, modelType: string) {
|
||||
this.id = id;
|
||||
this.sourceType = type;
|
||||
this.group = group;
|
||||
this.layout = layout;
|
||||
this.shadowG6Item = null;
|
||||
this.G6Item = null;
|
||||
this.preLayout = false;
|
||||
this.discarded = false;
|
||||
this.freed = false;
|
||||
this.leaked = false;
|
||||
this.transformMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
||||
this.modelType = modelType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* 定义 G6 model 的属性
|
||||
* @param option
|
||||
*/
|
||||
generateG6ModelProps(options: unknown): NodeConfig | EdgeConfig {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 G6 model 的属性
|
||||
* @param attr
|
||||
*/
|
||||
get(attr: string): any {
|
||||
return this.G6ModelProps[attr];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 G6 model 的属性
|
||||
* @param attr
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
set(attr: string | object, value?: any) {
|
||||
if (this.discarded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof attr === 'object') {
|
||||
Object.keys(attr).map(item => {
|
||||
this.set(item, attr[item]);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.G6ModelProps[attr] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr === 'style' || attr === 'labelCfg') {
|
||||
this.G6ModelProps[attr] = merge(this.G6ModelProps[attr] || {}, value);
|
||||
} else {
|
||||
this.G6ModelProps[attr] = value;
|
||||
}
|
||||
|
||||
if (attr === 'rotation') {
|
||||
const matrix = Util.calcRotateMatrix(this.getMatrix(), value);
|
||||
this.setMatrix(matrix);
|
||||
}
|
||||
|
||||
// 更新G6Item
|
||||
if (this.G6Item) {
|
||||
if (this.preLayout) {
|
||||
const G6ItemModel = this.G6Item.getModel();
|
||||
G6ItemModel[attr] = value;
|
||||
} else {
|
||||
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新shadowG6Item
|
||||
if (this.shadowG6Item) {
|
||||
this.shadowG6Instance.updateItem(this.shadowG6Item, this.G6ModelProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param G6ModelProps
|
||||
*/
|
||||
updateG6ModelStyle(G6ModelProps: NodeConfig | EdgeConfig) {
|
||||
const newG6ModelProps = {
|
||||
style: {
|
||||
...G6ModelProps.style,
|
||||
},
|
||||
labelCfg: {
|
||||
...G6ModelProps.labelCfg,
|
||||
},
|
||||
};
|
||||
|
||||
this.G6ModelProps = merge.recursive(this.G6ModelProps, newG6ModelProps);
|
||||
|
||||
if (this.G6Item) {
|
||||
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
||||
}
|
||||
|
||||
if (this.shadowG6Item) {
|
||||
this.shadowG6Instance.updateItem(this.shadowG6Item, this.G6ModelProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包围盒
|
||||
* @returns
|
||||
*/
|
||||
getBound(): BoundingRect {
|
||||
return this.shadowG6Item.getBBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取变换矩阵
|
||||
*/
|
||||
getMatrix(): number[] {
|
||||
return [...this.transformMatrix];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置变换矩阵
|
||||
* @param matrix
|
||||
*/
|
||||
setMatrix(matrix: number[]) {
|
||||
this.transformMatrix = matrix;
|
||||
this.set('style', { matrix });
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否被选中的状态
|
||||
* @param isSelected
|
||||
*/
|
||||
setSelectedState(isSelected: boolean) {
|
||||
if (this.G6Item === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.G6Item.setState('selected', isSelected);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
getG6ModelProps(): NodeConfig | EdgeConfig {
|
||||
return Util.objectClone(this.G6ModelProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 model 类型
|
||||
* @returns
|
||||
*/
|
||||
getModelType(): string {
|
||||
return this.modelType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为节点model(SVNode)
|
||||
*/
|
||||
isNode(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
beforeDestroy () {}
|
||||
|
||||
destroy() {
|
||||
this.G6Item = null;
|
||||
this.preLayout = false;
|
||||
this.discarded = false;
|
||||
this.freed = false;
|
||||
this.leaked = false;
|
||||
this.transformMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
||||
this.modelType = modelType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* 定义 G6 model 的属性
|
||||
* @param option
|
||||
*/
|
||||
generateG6ModelProps(options: unknown): NodeConfig | EdgeConfig {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 G6 model 的属性
|
||||
* @param attr
|
||||
*/
|
||||
get(attr: string): any {
|
||||
return this.G6ModelProps[attr];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 G6 model 的属性
|
||||
* @param attr
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
set(attr: string | object, value?: any) {
|
||||
if (this.discarded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof attr === 'object') {
|
||||
Object.keys(attr).map(item => {
|
||||
this.set(item, attr[item]);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.G6ModelProps[attr] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr === 'style' || attr === 'labelCfg') {
|
||||
this.G6ModelProps[attr] = merge(this.G6ModelProps[attr] || {}, value);
|
||||
}
|
||||
else {
|
||||
this.G6ModelProps[attr] = value;
|
||||
}
|
||||
|
||||
if (attr === 'rotation') {
|
||||
const matrix = Util.calcRotateMatrix(this.getMatrix(), value);
|
||||
this.setMatrix(matrix);
|
||||
}
|
||||
|
||||
// 更新G6Item
|
||||
if (this.G6Item) {
|
||||
if (this.preLayout) {
|
||||
const G6ItemModel = this.G6Item.getModel();
|
||||
G6ItemModel[attr] = value;
|
||||
}
|
||||
else {
|
||||
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新shadowG6Item
|
||||
if (this.shadowG6Item) {
|
||||
this.shadowG6Instance.updateItem(this.shadowG6Item, this.G6ModelProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param G6ModelProps
|
||||
*/
|
||||
updateG6ModelStyle(G6ModelProps: NodeConfig | EdgeConfig) {
|
||||
const newG6ModelProps = {
|
||||
style: {
|
||||
...G6ModelProps.style
|
||||
},
|
||||
labelCfg: {
|
||||
...G6ModelProps.labelCfg
|
||||
}
|
||||
};
|
||||
|
||||
this.G6ModelProps = merge.recursive(this.G6ModelProps, newG6ModelProps);
|
||||
|
||||
if (this.G6Item) {
|
||||
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);
|
||||
}
|
||||
|
||||
if (this.shadowG6Item) {
|
||||
this.shadowG6Instance.updateItem(this.shadowG6Item, this.G6ModelProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包围盒
|
||||
* @returns
|
||||
*/
|
||||
getBound(): BoundingRect {
|
||||
return this.shadowG6Item.getBBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取变换矩阵
|
||||
*/
|
||||
getMatrix(): number[] {
|
||||
return [...this.transformMatrix];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置变换矩阵
|
||||
* @param matrix
|
||||
*/
|
||||
setMatrix(matrix: number[]) {
|
||||
this.transformMatrix = matrix;
|
||||
this.set('style', { matrix });
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否被选中的状态
|
||||
* @param isSelected
|
||||
*/
|
||||
setSelectedState(isSelected: boolean) {
|
||||
if(this.G6Item === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.G6Item.setState('selected', isSelected);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
getG6ModelProps(): NodeConfig | EdgeConfig {
|
||||
return Util.objectClone(this.G6ModelProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 model 类型
|
||||
* @returns
|
||||
*/
|
||||
getModelType(): string {
|
||||
return this.modelType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为节点model(SVNode)
|
||||
*/
|
||||
isNode(): boolean {
|
||||
return false;
|
||||
this.shadowG6Instance = null;
|
||||
this.shadowG6Item = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { SourceNode } from '../sources';
|
||||
import { ModelConstructor } from './modelConstructor';
|
||||
import { SVLink } from './SVLink';
|
||||
import { SVModel } from './SVModel';
|
||||
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker, SVNodeAppendage } from './SVNodeAppendage';
|
||||
import { SVNodeAppendage } from './SVNodeAppendage';
|
||||
|
||||
export class SVNode extends SVModel {
|
||||
public sourceId: string;
|
||||
@ -17,17 +17,7 @@ export class SVNode extends SVModel {
|
||||
|
||||
private label: string | string[];
|
||||
private disable: boolean;
|
||||
|
||||
public shadowG6Item: INode;
|
||||
public G6Item: INode;
|
||||
|
||||
public marker: SVMarker;
|
||||
public freedLabel: SVFreedLabel;
|
||||
public indexLabel: SVIndexLabel;
|
||||
public addressLabel: SVAddressLabel;
|
||||
public appendages: SVNodeAppendage[];
|
||||
|
||||
public modelConstructor: ModelConstructor;
|
||||
public appendages: { [key: string]: SVNodeAppendage[] };
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@ -53,7 +43,7 @@ export class SVNode extends SVModel {
|
||||
this.sourceId = sourceNode.id.toString();
|
||||
|
||||
this.links = { inDegree: [], outDegree: [] };
|
||||
this.appendages = [];
|
||||
this.appendages = {};
|
||||
this.sourceNode = sourceNode;
|
||||
this.label = label;
|
||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||
@ -94,7 +84,7 @@ export class SVNode extends SVModel {
|
||||
}
|
||||
|
||||
this.G6Item.setState('selected', isSelected);
|
||||
this.appendages.forEach(item => {
|
||||
this.getAppendagesList().forEach(item => {
|
||||
item.setSelectedState(isSelected);
|
||||
});
|
||||
}
|
||||
@ -103,11 +93,28 @@ export class SVNode extends SVModel {
|
||||
return this.sourceId;
|
||||
}
|
||||
|
||||
getAppendagesList(): SVNodeAppendage[] {
|
||||
const list = [];
|
||||
|
||||
Object.entries(this.appendages).forEach(item => {
|
||||
list.push(...item[1]);
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断这个节点是否来自相同group
|
||||
* @param node
|
||||
*/
|
||||
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,182 +1,208 @@
|
||||
import { INode, NodeConfig, EdgeConfig } from "@antv/g6-core";
|
||||
import { Util } from "../Common/util";
|
||||
import { AddressLabelOption, IndexLabelOption, MarkerOption, NodeLabelOption, Style } from "../options";
|
||||
import { SVModel } from "./SVModel";
|
||||
import { SVNode } from "./SVNode";
|
||||
|
||||
|
||||
|
||||
import { INode, NodeConfig, EdgeConfig } from '@antv/g6-core';
|
||||
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||
import { Util } from '../Common/util';
|
||||
import { AddressLabelOption, IndexLabelOption, MarkerOption, NodeLabelOption, Style } from '../options';
|
||||
import { SVModel } from './SVModel';
|
||||
import { SVNode } from './SVNode';
|
||||
|
||||
export class SVNodeAppendage extends SVModel {
|
||||
public target: SVNode;
|
||||
public target: SVNode;
|
||||
|
||||
constructor(id: string, type: string, group: string, layout: string, modelType: string, target: SVNode) {
|
||||
super(id, type, group, layout, modelType);
|
||||
constructor(id: string, type: string, group: string, layout: string, modelType: string, target: SVNode) {
|
||||
super(id, type, group, layout, modelType);
|
||||
|
||||
this.target = target;
|
||||
this.target.appendages.push(this);
|
||||
}
|
||||
this.target = target;
|
||||
|
||||
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 {
|
||||
constructor(id: string, type: string, group: string, layout: string, target: SVNode) {
|
||||
super(id, type, group, layout, 'freedLabel', target);
|
||||
constructor(id: string, type: string, group: string, layout: string, target: SVNode) {
|
||||
super(id, type, group, layout, 'freedLabel', target);
|
||||
this.G6ModelProps = this.generateG6ModelProps();
|
||||
}
|
||||
|
||||
this.target.freedLabel = this;
|
||||
this.G6ModelProps = this.generateG6ModelProps();
|
||||
}
|
||||
|
||||
generateG6ModelProps() {
|
||||
return {
|
||||
id: this.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
type: 'rect',
|
||||
label: '已释放',
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: '#b83b5e',
|
||||
opacity: 0.6
|
||||
}
|
||||
},
|
||||
size: [0, 0],
|
||||
style: {
|
||||
opacity: 0,
|
||||
stroke: null,
|
||||
fill: 'transparent'
|
||||
}
|
||||
};
|
||||
}
|
||||
generateG6ModelProps() {
|
||||
return {
|
||||
id: this.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
type: 'rect',
|
||||
label: '已释放',
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: '#b83b5e',
|
||||
opacity: 0.6,
|
||||
},
|
||||
},
|
||||
size: [0, 0],
|
||||
style: {
|
||||
opacity: 0,
|
||||
stroke: null,
|
||||
fill: 'transparent',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 被移动到泄漏区的节点上面显示的地址
|
||||
*/
|
||||
export class SVAddressLabel extends SVNodeAppendage {
|
||||
private sourceId: string;
|
||||
private sourceId: string;
|
||||
|
||||
constructor(id: string, type: string, group: string, layout: string, target: SVNode, options: AddressLabelOption) {
|
||||
super(id, type, group, layout, 'addressLabel', target);
|
||||
constructor(id: string, type: string, group: string, layout: string, target: SVNode, options: AddressLabelOption) {
|
||||
super(id, type, group, layout, 'addressLabel', target);
|
||||
|
||||
this.sourceId = target.sourceId;
|
||||
this.target.addressLabel = this;
|
||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||
}
|
||||
this.sourceId = target.sourceId;
|
||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||
}
|
||||
|
||||
generateG6ModelProps(options: AddressLabelOption) {
|
||||
return {
|
||||
id: this.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
type: 'rect',
|
||||
label: this.sourceId,
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: '#666',
|
||||
fontSize: 16,
|
||||
...options.style
|
||||
}
|
||||
},
|
||||
size: [0, 0],
|
||||
style: {
|
||||
stroke: null,
|
||||
fill: 'transparent'
|
||||
}
|
||||
};
|
||||
}
|
||||
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) {
|
||||
return {
|
||||
id: this.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
type: 'rect',
|
||||
label: this.sourceId,
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: '#666',
|
||||
fontSize: 16,
|
||||
...options.style,
|
||||
},
|
||||
},
|
||||
size: [0, 0],
|
||||
style: {
|
||||
stroke: null,
|
||||
fill: 'transparent',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 节点的下标文字
|
||||
*/
|
||||
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) {
|
||||
super(id, indexName, group, layout, 'indexLabel', target);
|
||||
constructor(
|
||||
id: string,
|
||||
indexName: string,
|
||||
group: string,
|
||||
layout: string,
|
||||
value: string,
|
||||
target: SVNode,
|
||||
options: IndexLabelOption
|
||||
) {
|
||||
super(id, indexName, group, layout, 'indexLabel', target);
|
||||
this.value = value;
|
||||
this.G6ModelProps = this.generateG6ModelProps(options) as NodeConfig;
|
||||
}
|
||||
|
||||
this.target.indexLabel = this;
|
||||
this.value = value;
|
||||
this.G6ModelProps = this.generateG6ModelProps(options) as NodeConfig;
|
||||
}
|
||||
|
||||
generateG6ModelProps(options: IndexLabelOption): NodeConfig | EdgeConfig {
|
||||
return {
|
||||
id: this.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
type: 'rect',
|
||||
label: this.value,
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: '#bbb',
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
fontSize: 14,
|
||||
fontStyle: 'italic',
|
||||
...options.style
|
||||
}
|
||||
},
|
||||
size: [0, 0],
|
||||
style: {
|
||||
stroke: null,
|
||||
fill: 'transparent'
|
||||
}
|
||||
};
|
||||
}
|
||||
generateG6ModelProps(options: IndexLabelOption): NodeConfig | EdgeConfig {
|
||||
return {
|
||||
id: this.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
type: 'rect',
|
||||
label: this.value,
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: '#bbb',
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
fontSize: 14,
|
||||
fontStyle: 'italic',
|
||||
...options.style,
|
||||
},
|
||||
},
|
||||
size: [0, 0],
|
||||
style: {
|
||||
stroke: null,
|
||||
fill: 'transparent',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 外部指针
|
||||
*/
|
||||
export class SVMarker extends SVNodeAppendage {
|
||||
public label: string | string[];
|
||||
public anchor: number;
|
||||
public label: string | string[];
|
||||
public anchor: number;
|
||||
|
||||
public shadowG6Item: INode;
|
||||
public G6Item: INode;
|
||||
public shadowG6Item: INode;
|
||||
public G6Item: INode;
|
||||
|
||||
constructor(id: string, type: string, group: string, layout: string, label: string | string[], target: SVNode, options: MarkerOption) {
|
||||
super(id, type, group, layout, 'marker', target);
|
||||
constructor(
|
||||
id: string,
|
||||
type: string,
|
||||
group: string,
|
||||
layout: string,
|
||||
label: string | string[],
|
||||
target: SVNode,
|
||||
options: MarkerOption
|
||||
) {
|
||||
super(id, type, group, layout, 'marker', target);
|
||||
|
||||
this.label = label;
|
||||
this.label = label;
|
||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||
}
|
||||
|
||||
this.target.marker = this;
|
||||
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||
}
|
||||
generateG6ModelProps(options: MarkerOption): NodeConfig {
|
||||
this.anchor = options.anchor;
|
||||
|
||||
generateG6ModelProps(options: MarkerOption): NodeConfig {
|
||||
this.anchor = options.anchor;
|
||||
const type = options.type,
|
||||
defaultSize: [number, number] = type === 'pointer' ? [8, 30] : [12, 12];
|
||||
|
||||
const type = options.type,
|
||||
defaultSize: [number, number] = type === 'pointer' ? [8, 30] : [12, 12];
|
||||
return {
|
||||
id: this.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotation: 0,
|
||||
type: options.type || 'marker',
|
||||
size: options.size || defaultSize,
|
||||
anchorPoints: null,
|
||||
label: typeof this.label === 'string' ? this.label : this.label.join(', '),
|
||||
style: Util.objectClone<Style>(options.style),
|
||||
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotation: 0,
|
||||
type: options.type || 'marker',
|
||||
size: options.size || defaultSize,
|
||||
anchorPoints: null,
|
||||
label: typeof this.label === 'string' ? this.label : this.label.join(', '),
|
||||
style: Util.objectClone<Style>(options.style),
|
||||
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions)
|
||||
};
|
||||
}
|
||||
|
||||
public getLabelSizeRadius(): number {
|
||||
const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox();
|
||||
return Math.max(width, height);
|
||||
}
|
||||
};
|
||||
public getLabelSizeRadius(): number {
|
||||
const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox();
|
||||
return Math.max(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,6 @@ import { Vector } from "./Common/vector";
|
||||
import { EngineOptions, LayoutCreator } from "./options";
|
||||
import { SourceNode } from "./sources";
|
||||
import { Util } from "./Common/util";
|
||||
import { SVModel } from "./Model/SVModel";
|
||||
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 { Group } from '../Common/group';
|
||||
import { Util } from '../Common/util';
|
||||
import { Vector } from '../Common/vector';
|
||||
import { Engine } from '../engine';
|
||||
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||
import { LayoutGroupTable, ModelConstructor } from '../Model/modelConstructor';
|
||||
import { SVModel } from '../Model/SVModel';
|
||||
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker } from '../Model/SVNodeAppendage';
|
||||
import { AddressLabelOption, IndexLabelOption, LayoutOptions, MarkerOption, ViewOptions } from '../options';
|
||||
@ -14,6 +14,8 @@ export class LayoutProvider {
|
||||
private engine: Engine;
|
||||
private viewOptions: ViewOptions;
|
||||
private viewContainer: ViewContainer;
|
||||
private leakAreaXoffset: number = 20;
|
||||
private leakClusterXInterval = 25;
|
||||
|
||||
constructor(engine: Engine, viewContainer: ViewContainer) {
|
||||
this.engine = engine;
|
||||
@ -67,7 +69,7 @@ export class LayoutProvider {
|
||||
|
||||
let target = item.target,
|
||||
targetBound: BoundingRect = target.getBound(),
|
||||
g6AnchorPosition = item.target.shadowG6Item.getAnchorPoints()[anchor] as IPoint,
|
||||
g6AnchorPosition = (<INode>item.target.shadowG6Item).getAnchorPoints()[anchor] as IPoint,
|
||||
center: [number, number] = [
|
||||
targetBound.x + targetBound.width / 2,
|
||||
targetBound.y + targetBound.height / 2,
|
||||
@ -214,10 +216,15 @@ export class LayoutProvider {
|
||||
indexLabelOptions = group.options.indexLabel || {},
|
||||
addressLabelOption = group.options.addressLabel || {};
|
||||
|
||||
this.layoutIndexLabel(group.indexLabel, indexLabelOptions);
|
||||
this.layoutFreedLabel(group.freedLabel);
|
||||
this.layoutAddressLabel(group.addressLabel, addressLabelOption);
|
||||
this.layoutMarker(group.marker, markerOptions); // 布局外部指针
|
||||
const indexLabel = group.appendage.filter(item => item instanceof SVIndexLabel) as SVIndexLabel[],
|
||||
freedLabel = group.appendage.filter(item => item instanceof SVFreedLabel) as SVFreedLabel[],
|
||||
addressLabel = group.appendage.filter(item => item instanceof SVAddressLabel) as SVAddressLabel[],
|
||||
marker = group.appendage.filter(item => item instanceof SVMarker) as SVMarker[];
|
||||
|
||||
this.layoutIndexLabel(indexLabel, indexLabelOptions);
|
||||
this.layoutFreedLabel(freedLabel);
|
||||
this.layoutAddressLabel(addressLabel, addressLabelOption);
|
||||
this.layoutMarker(marker, markerOptions); // 布局外部指针
|
||||
});
|
||||
|
||||
return modelGroupList;
|
||||
@ -225,45 +232,36 @@ export class LayoutProvider {
|
||||
|
||||
/**
|
||||
* 对泄漏区进行布局
|
||||
* @param leakModels
|
||||
* @param accumulateLeakModels
|
||||
* // todo: 部分元素被抽离后的泄漏区
|
||||
*/
|
||||
private layoutLeakModels(leakModels: SVModel[], accumulateLeakModels: SVModel[]) {
|
||||
const group: Group = new Group(),
|
||||
containerHeight = this.viewContainer.getG6Instance().getHeight(),
|
||||
private layoutLeakArea(accumulateLeakModels: SVModel[]) {
|
||||
const containerHeight = this.viewContainer.getG6Instance().getHeight(),
|
||||
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||
leakAreaY = containerHeight - leakAreaHeight,
|
||||
xOffset = 60;
|
||||
leakAreaY = containerHeight - leakAreaHeight;
|
||||
|
||||
let prevBound: BoundingRect;
|
||||
|
||||
// 避免在泄漏前拖拽节点导致的位置变化,先把节点位置重置为布局后的标准位置
|
||||
leakModels.forEach(item => {
|
||||
accumulateLeakModels.forEach(item => {
|
||||
item.set({
|
||||
x: item.layoutX,
|
||||
y: item.layoutY,
|
||||
});
|
||||
});
|
||||
|
||||
const globalLeakGroupBound: BoundingRect = accumulateLeakModels.length
|
||||
? Bound.union(...accumulateLeakModels.map(item => item.getBound()))
|
||||
: { x: 0, y: leakAreaY, width: 0, height: 0 };
|
||||
const clusters = ModelConstructor.getClusters(accumulateLeakModels);
|
||||
|
||||
const layoutGroups = Util.groupBy(leakModels, 'group');
|
||||
Object.keys(layoutGroups).forEach(key => {
|
||||
group.add(...layoutGroups[key]);
|
||||
// 每一个簇从左往右布局就完事了,比之前的方法简单稳定很多
|
||||
clusters.forEach(item => {
|
||||
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(),
|
||||
prevBoundEnd = prevBound ? prevBound.x + prevBound.width : 0,
|
||||
{ x: groupX, y: groupY } = currentBound,
|
||||
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;
|
||||
item.translate(dx, dy);
|
||||
Bound.translate(bound, dx, dy);
|
||||
prevBound = bound;
|
||||
});
|
||||
}
|
||||
|
||||
@ -320,7 +318,7 @@ export class LayoutProvider {
|
||||
|
||||
if (this.viewContainer.hasLeak) {
|
||||
const boundBottomY = viewBound.y + viewBound.height;
|
||||
dy = height - leakAreaHeight - 100 - boundBottomY;
|
||||
dy = height - leakAreaHeight - 130 - boundBottomY;
|
||||
} else {
|
||||
const boundCenterY = viewBound.y + viewBound.height / 2;
|
||||
dy = centerY - boundCenterY;
|
||||
@ -332,19 +330,15 @@ export class LayoutProvider {
|
||||
/**
|
||||
* 布局
|
||||
* @param layoutGroupTable
|
||||
* @param leakModels
|
||||
* @param hasLeak
|
||||
* @param needFitCenter
|
||||
* @param accumulateLeakModels
|
||||
*/
|
||||
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], leakModels: SVModel[]) {
|
||||
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[]) {
|
||||
this.preLayoutProcess(layoutGroupTable);
|
||||
|
||||
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
|
||||
const generalGroup: Group = this.layoutGroups(modelGroupList);
|
||||
|
||||
if (leakModels.length) {
|
||||
this.layoutLeakModels(leakModels, accumulateLeakModels);
|
||||
}
|
||||
this.layoutLeakArea(accumulateLeakModels);
|
||||
|
||||
this.fitCenter(generalGroup);
|
||||
this.postLayoutProcess(layoutGroupTable);
|
||||
|
||||
@ -1,430 +1,461 @@
|
||||
import { EventBus } from "../Common/eventBus";
|
||||
import { Util } from "../Common/util";
|
||||
import { Engine } from "../engine";
|
||||
import { LayoutGroupTable } from "../Model/modelConstructor";
|
||||
import { SVLink } from "../Model/SVLink";
|
||||
import { SVModel } from "../Model/SVModel";
|
||||
import { SVNode } from "../Model/SVNode";
|
||||
import { SVAddressLabel, SVMarker, SVNodeAppendage } from "../Model/SVNodeAppendage";
|
||||
import { Animations } from "./animation";
|
||||
import { Renderer } from "./renderer";
|
||||
|
||||
import { EventBus } from '../Common/eventBus';
|
||||
import { Util } from '../Common/util';
|
||||
import { Engine } from '../engine';
|
||||
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||
import { SVLink } from '../Model/SVLink';
|
||||
import { SVModel } from '../Model/SVModel';
|
||||
import { SVNode } from '../Model/SVNode';
|
||||
import { SVAddressLabel, SVMarker, SVNodeAppendage } from '../Model/SVNodeAppendage';
|
||||
import { Animations } from './animation';
|
||||
import { Renderer } from './renderer';
|
||||
|
||||
export interface DiffResult {
|
||||
CONTINUOUS: SVModel[];
|
||||
APPEND: SVModel[];
|
||||
REMOVE: SVModel[];
|
||||
FREED: SVNode[];
|
||||
LEAKED: SVModel[];
|
||||
UPDATE: SVModel[];
|
||||
ACCUMULATE_LEAK: SVModel[];
|
||||
CONTINUOUS: SVModel[];
|
||||
APPEND: SVModel[];
|
||||
REMOVE: SVModel[];
|
||||
FREED: SVNode[];
|
||||
LEAKED: SVModel[];
|
||||
UPDATE: SVModel[];
|
||||
ACCUMULATE_LEAK: SVModel[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class Reconcile {
|
||||
|
||||
private engine: Engine;
|
||||
private renderer: Renderer;
|
||||
private isFirstPatch: boolean;
|
||||
|
||||
constructor(engine: Engine, renderer: Renderer) {
|
||||
this.engine = engine;
|
||||
this.renderer = renderer;
|
||||
this.isFirstPatch = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上一次渲染存在的,这一次渲染也存在的models
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
*/
|
||||
private getContinuousModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||
const continuousModels = modelList.filter(item => prevModelList.find(prevModel => item.id === prevModel.id));
|
||||
return continuousModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新增的节点,这些节点有可能来自泄漏区(上一步的情况)
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @param accumulateLeakModels
|
||||
* @returns
|
||||
*/
|
||||
private getAppendModels(prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): SVModel[] {
|
||||
const appendModels = modelList.filter(item => !prevModelList.find(model => model.id === item.id));
|
||||
|
||||
appendModels.forEach(item => {
|
||||
let removeIndex = accumulateLeakModels.findIndex(leakModel => item.id === leakModel.id);
|
||||
|
||||
if (removeIndex > -1) {
|
||||
accumulateLeakModels.splice(removeIndex, 1);
|
||||
}
|
||||
});
|
||||
|
||||
return appendModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被泄露的节点
|
||||
* @param layoutGroupTable
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getLeakModels(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||
let potentialLeakModels: SVModel[] = prevModelList.filter(item =>
|
||||
!modelList.find(model => model.id === item.id) && !item.freed
|
||||
);
|
||||
const leakModels: SVModel[] = [];
|
||||
|
||||
// 先把节点拿出来
|
||||
const potentialLeakNodes = potentialLeakModels.filter(item => item.isNode()) as SVNode[],
|
||||
groups = Util.groupBy<SVNode>(potentialLeakNodes, 'group');
|
||||
|
||||
// 再把非节点的model拿出来
|
||||
potentialLeakModels = potentialLeakModels.filter(item => item.isNode() === false);
|
||||
|
||||
Object.keys(groups).forEach(key => {
|
||||
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
|
||||
if(leakRule && typeof leakRule === 'function') {
|
||||
potentialLeakModels.push(...leakRule(groups[key]));
|
||||
}
|
||||
});
|
||||
|
||||
potentialLeakModels.forEach(item => {
|
||||
if (item instanceof SVNode) {
|
||||
item.leaked = true;
|
||||
leakModels.push(item);
|
||||
|
||||
item.appendages.forEach(appendage => {
|
||||
appendage.leaked = true;
|
||||
leakModels.push(appendage);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
potentialLeakModels.forEach(item => {
|
||||
if (item instanceof SVLink && item.node.leaked !== false && item.target.leaked !== false) {
|
||||
item.leaked = true;
|
||||
leakModels.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
leakModels.forEach(item => {
|
||||
// 不能用上次的G6item了,不然布局的时候会没有动画
|
||||
item.G6Item = null;
|
||||
});
|
||||
|
||||
return leakModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出需要移除的节点
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
*/
|
||||
private getRemoveModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||
let removedModels: SVModel[] = [];
|
||||
|
||||
for (let i = 0; i < prevModelList.length; i++) {
|
||||
let prevModel = prevModelList[i],
|
||||
target = modelList.find(item => item.id === prevModel.id);
|
||||
|
||||
if (target === undefined && !prevModel.leaked) {
|
||||
removedModels.push(prevModel);
|
||||
}
|
||||
}
|
||||
|
||||
return removedModels;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 找出重新指向的外部指针
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getReTargetMarkers(prevModelList: SVModel[], modelList: SVModel[]): SVMarker[] {
|
||||
const prevMarkers: SVMarker[] = prevModelList.filter(item => item instanceof SVMarker) as SVMarker[],
|
||||
markers: SVMarker[] = modelList.filter(item => item instanceof SVMarker) as SVMarker[];
|
||||
|
||||
return markers.filter(item => prevMarkers.find(prevItem => {
|
||||
return prevItem.id === item.id && prevItem.target.id !== item.target.id
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出前后 label 发生变化的 model
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getLabelChangeModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||
let labelChangeModels: SVModel[] = [];
|
||||
|
||||
modelList.forEach(item => {
|
||||
const prevItem = prevModelList.find(prevItem => prevItem.id === item.id);
|
||||
|
||||
if (prevItem === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevLabel = prevItem.get('label'),
|
||||
label = item.get('label');
|
||||
|
||||
if (String(prevLabel) !== String(label)) {
|
||||
labelChangeModels.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return labelChangeModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被 free 的节点
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getFreedModels(prevModelList: SVModel[], modelList: SVModel[]): SVNode[] {
|
||||
const freedNodes = modelList.filter(item => item instanceof SVNode && item.freed) as SVNode[];
|
||||
|
||||
freedNodes.forEach(item => {
|
||||
const prev = prevModelList.find(prevModel => item.id === prevModel.id);
|
||||
|
||||
if (prev) {
|
||||
item.set('label', prev.get('label'));
|
||||
}
|
||||
});
|
||||
|
||||
return freedNodes;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 处理不变的models
|
||||
* @param continuousModels
|
||||
*/
|
||||
private handleContinuousModels(continuousModels: SVModel[]) {
|
||||
for (let i = 0; i < continuousModels.length; i++) {
|
||||
let model = continuousModels[i];
|
||||
|
||||
if (model instanceof SVNode) {
|
||||
const group = model.G6Item.getContainer();
|
||||
group.attr({ opacity: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理新增的 models
|
||||
* @param appendData
|
||||
*/
|
||||
private handleAppendModels(appendModels: SVModel[]) {
|
||||
let { duration, timingFunction } = this.engine.animationOptions;
|
||||
|
||||
appendModels.forEach(item => {
|
||||
if (item instanceof SVNodeAppendage) {
|
||||
// 先不显示泄漏区节点上面的地址文本
|
||||
if (item instanceof SVAddressLabel) {
|
||||
// 先将透明度改为0,隐藏掉
|
||||
const AddressLabelG6Group = item.G6Item.getContainer();
|
||||
AddressLabelG6Group.attr({ opacity: 0 });
|
||||
}
|
||||
else {
|
||||
Animations.FADE_IN(item.G6Item, {
|
||||
duration,
|
||||
timingFunction
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
Animations.APPEND(item.G6Item, {
|
||||
duration,
|
||||
timingFunction
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理被移除 models
|
||||
* @param removeData
|
||||
*/
|
||||
private handleRemoveModels(removeModels: SVModel[]) {
|
||||
let { duration, timingFunction } = this.engine.animationOptions;
|
||||
|
||||
removeModels.forEach(item => {
|
||||
Animations.REMOVE(item.G6Item, {
|
||||
duration,
|
||||
timingFunction,
|
||||
callback: () => {
|
||||
this.renderer.removeModel(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理泄漏区 models
|
||||
* @param leakModels
|
||||
*/
|
||||
private handleLeakModels(leakModels: SVModel[]) {
|
||||
let { duration, timingFunction } = this.engine.animationOptions;
|
||||
|
||||
leakModels.forEach(item => {
|
||||
if (item instanceof SVAddressLabel) {
|
||||
Animations.FADE_IN(item.G6Item, {
|
||||
duration,
|
||||
timingFunction
|
||||
});
|
||||
}
|
||||
|
||||
item.G6Item.enableCapture(false);
|
||||
});
|
||||
|
||||
EventBus.emit('onLeak', leakModels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理已经堆积的泄漏区 models
|
||||
* @param accumulateModels
|
||||
*/
|
||||
private handleAccumulateLeakModels(accumulateModels: SVModel[]) {
|
||||
accumulateModels.forEach(item => {
|
||||
if (item.generalStyle) {
|
||||
item.set('style', { ...item.generalStyle });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理被释放的节点 models
|
||||
* @param freedModes
|
||||
*/
|
||||
private handleFreedModels(freedModes: SVNode[]) {
|
||||
const { duration, timingFunction } = this.engine.animationOptions,
|
||||
alpha = 0.4;
|
||||
|
||||
freedModes.forEach(item => {
|
||||
const nodeGroup = item.G6Item.getContainer();
|
||||
|
||||
item.set('style', { fill: '#ccc' });
|
||||
nodeGroup.attr({ opacity: alpha });
|
||||
|
||||
if (item.marker) {
|
||||
const markerGroup = item.marker.G6Item.getContainer();
|
||||
item.marker.set('style', { fill: '#ccc' });
|
||||
markerGroup.attr({ opacity: alpha + 0.5 });
|
||||
}
|
||||
|
||||
item.freedLabel.G6Item.toFront();
|
||||
Animations.FADE_IN(item.freedLabel.G6Item, { duration, timingFunction });
|
||||
});
|
||||
|
||||
EventBus.emit('onFreed', freedModes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理发生变化的 models
|
||||
* @param models
|
||||
*/
|
||||
private handleChangeModels(models: SVModel[]) {
|
||||
const changeHighlightColor: string = this.engine.viewOptions.updateHighlight;
|
||||
|
||||
if (!changeHighlightColor || typeof changeHighlightColor !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
models.forEach(item => {
|
||||
if (item.generalStyle === undefined) {
|
||||
item.generalStyle = Util.objectClone(item.G6ModelProps.style);
|
||||
}
|
||||
|
||||
if (item instanceof SVLink) {
|
||||
item.set('style', {
|
||||
stroke: changeHighlightColor
|
||||
});
|
||||
}
|
||||
else {
|
||||
item.set('style', {
|
||||
fill: changeHighlightColor
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 进行diff
|
||||
* @param layoutGroupTable
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @param accumulateLeakModels
|
||||
* @returns
|
||||
*/
|
||||
public diff(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[], isEnterFunction: boolean): DiffResult {
|
||||
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
|
||||
const leakModels: SVModel[] = isEnterFunction? []: this.getLeakModels(layoutGroupTable, prevModelList, modelList);
|
||||
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels);
|
||||
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList);
|
||||
const updateModels: SVModel[] = [
|
||||
...this.getReTargetMarkers(prevModelList, modelList),
|
||||
...this.getLabelChangeModels(prevModelList, modelList),
|
||||
...appendModels,
|
||||
...leakModels
|
||||
];
|
||||
const freedModels: SVNode[] = this.getFreedModels(prevModelList, modelList);
|
||||
|
||||
return {
|
||||
CONTINUOUS: continuousModels,
|
||||
APPEND: appendModels,
|
||||
REMOVE: removeModels,
|
||||
FREED: freedModels,
|
||||
LEAKED: leakModels,
|
||||
UPDATE: updateModels,
|
||||
ACCUMULATE_LEAK: [...accumulateLeakModels]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行调和操作
|
||||
* @param diffResult
|
||||
* @param isFirstRender
|
||||
*/
|
||||
public patch(diffResult: DiffResult) {
|
||||
const {
|
||||
APPEND,
|
||||
REMOVE,
|
||||
FREED,
|
||||
LEAKED,
|
||||
UPDATE,
|
||||
CONTINUOUS,
|
||||
ACCUMULATE_LEAK
|
||||
} = diffResult;
|
||||
|
||||
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
|
||||
|
||||
// 第一次渲染的时候不高亮变化的元素
|
||||
if (this.isFirstPatch === false) {
|
||||
this.handleChangeModels(UPDATE);
|
||||
}
|
||||
|
||||
this.handleContinuousModels(CONTINUOUS);
|
||||
this.handleFreedModels(FREED);
|
||||
this.handleAppendModels(APPEND);
|
||||
this.handleLeakModels(LEAKED);
|
||||
this.handleRemoveModels(REMOVE);
|
||||
|
||||
if (this.isFirstPatch) {
|
||||
this.isFirstPatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
public destroy() { }
|
||||
}
|
||||
private engine: Engine;
|
||||
private renderer: Renderer;
|
||||
private isFirstPatch: boolean;
|
||||
|
||||
constructor(engine: Engine, renderer: Renderer) {
|
||||
this.engine = engine;
|
||||
this.renderer = renderer;
|
||||
this.isFirstPatch = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上一次渲染存在的,这一次渲染也存在的models
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
*/
|
||||
private getContinuousModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||
const continuousModels = modelList.filter(item => prevModelList.find(prevModel => item.id === prevModel.id));
|
||||
return continuousModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新增的节点,这些节点有可能来自泄漏区(上一步的情况)
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @param accumulateLeakModels
|
||||
* @returns
|
||||
*/
|
||||
private getAppendModels(
|
||||
prevModelList: SVModel[],
|
||||
modelList: SVModel[],
|
||||
accumulateLeakModels: SVModel[],
|
||||
prevStep: boolean
|
||||
): SVModel[] {
|
||||
const appendModels = modelList.filter(item => !prevModelList.find(model => model.id === item.id));
|
||||
|
||||
// 看看新增的节点是不是从泄漏区来的
|
||||
// 目前的判断方式比较傻:看看泄漏区有没有相同id的节点,但是发现这个方法可能不可靠,不知道还有没有更好的办法
|
||||
appendModels.forEach(item => {
|
||||
const removeIndex = accumulateLeakModels.findIndex(leakModel => item.id === leakModel.id),
|
||||
svModel = accumulateLeakModels[removeIndex];
|
||||
|
||||
if (removeIndex > -1) {
|
||||
svModel.leaked = false;
|
||||
accumulateLeakModels.splice(removeIndex, 1);
|
||||
}
|
||||
});
|
||||
|
||||
return appendModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被泄露的节点
|
||||
* @param layoutGroupTable
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getLeakModels(
|
||||
layoutGroupTable: LayoutGroupTable,
|
||||
prevModelList: SVModel[],
|
||||
modelList: SVModel[]
|
||||
): SVModel[] {
|
||||
let potentialLeakModels: SVModel[] = prevModelList.filter(
|
||||
item => !modelList.find(model => model.id === item.id) && !item.freed
|
||||
);
|
||||
const leakModels: SVModel[] = [];
|
||||
|
||||
// 先把节点拿出来
|
||||
const potentialLeakNodes = potentialLeakModels.filter(item => item.isNode()) as SVNode[],
|
||||
groups = Util.groupBy<SVNode>(potentialLeakNodes, 'group');
|
||||
|
||||
// 再把非节点的model拿出来
|
||||
potentialLeakModels = potentialLeakModels.filter(item => item.isNode() === false);
|
||||
|
||||
Object.keys(groups).forEach(key => {
|
||||
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
|
||||
if (leakRule && typeof leakRule === 'function') {
|
||||
potentialLeakModels.push(...leakRule(groups[key]));
|
||||
}
|
||||
});
|
||||
|
||||
potentialLeakModels.forEach(item => {
|
||||
if (item instanceof SVNode) {
|
||||
item.leaked = true;
|
||||
leakModels.push(item);
|
||||
|
||||
item.getAppendagesList().forEach(appendage => {
|
||||
// 外部指针先不加入泄漏区(这个需要讨论一下,我觉得不应该)
|
||||
if (appendage instanceof SVMarker) {
|
||||
return;
|
||||
}
|
||||
|
||||
appendage.leaked = true;
|
||||
leakModels.push(appendage);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
potentialLeakModels.forEach(item => {
|
||||
if (item instanceof SVLink && item.node.leaked !== false && item.target.leaked !== false) {
|
||||
item.leaked = true;
|
||||
leakModels.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return leakModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出需要移除的节点
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
*/
|
||||
private getRemoveModels(
|
||||
prevModelList: SVModel[],
|
||||
modelList: SVModel[],
|
||||
accumulateLeakModels: SVModel[]
|
||||
): SVModel[] {
|
||||
let removedModels: SVModel[] = [];
|
||||
|
||||
for (let i = 0; i < prevModelList.length; i++) {
|
||||
let prevModel = prevModelList[i],
|
||||
target = modelList.find(item => item.id === prevModel.id);
|
||||
|
||||
if (target === undefined && !prevModel.leaked) {
|
||||
removedModels.push(prevModel);
|
||||
}
|
||||
}
|
||||
|
||||
// 假如某个节点从泄漏区移回可视化区域,那么与原来泄漏结构的连线应该消失掉
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出重新指向的外部指针
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getReTargetMarkers(prevModelList: SVModel[], modelList: SVModel[]): SVMarker[] {
|
||||
const prevMarkers: SVMarker[] = prevModelList.filter(item => item instanceof SVMarker) as SVMarker[],
|
||||
markers: SVMarker[] = modelList.filter(item => item instanceof SVMarker) as SVMarker[];
|
||||
|
||||
return markers.filter(item =>
|
||||
prevMarkers.find(prevItem => {
|
||||
return prevItem.id === item.id && prevItem.target.id !== item.target.id;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出前后 label 发生变化的 model
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getLabelChangeModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||
let labelChangeModels: SVModel[] = [];
|
||||
|
||||
modelList.forEach(item => {
|
||||
const prevItem = prevModelList.find(prevItem => prevItem.id === item.id);
|
||||
|
||||
if (prevItem === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevLabel = prevItem.get('label'),
|
||||
label = item.get('label');
|
||||
|
||||
if (String(prevLabel) !== String(label)) {
|
||||
labelChangeModels.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return labelChangeModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被 free 的节点
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @returns
|
||||
*/
|
||||
private getFreedModels(prevModelList: SVModel[], modelList: SVModel[]): SVNode[] {
|
||||
const freedNodes = modelList.filter(item => item instanceof SVNode && item.freed) as SVNode[];
|
||||
|
||||
freedNodes.forEach(item => {
|
||||
const prev = prevModelList.find(prevModel => item.id === prevModel.id);
|
||||
|
||||
if (prev) {
|
||||
item.set('label', prev.get('label'));
|
||||
}
|
||||
});
|
||||
|
||||
return freedNodes;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 处理不变的models
|
||||
* @param continuousModels
|
||||
*/
|
||||
private handleContinuousModels(continuousModels: SVModel[]) {
|
||||
for (let i = 0; i < continuousModels.length; i++) {
|
||||
let model = continuousModels[i];
|
||||
|
||||
if (model instanceof SVNode) {
|
||||
const group = model.G6Item.getContainer();
|
||||
group.attr({ opacity: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理新增的 models
|
||||
* @param appendData
|
||||
*/
|
||||
private handleAppendModels(appendModels: SVModel[]) {
|
||||
let { duration, timingFunction } = this.engine.animationOptions;
|
||||
|
||||
appendModels.forEach(item => {
|
||||
if (item instanceof SVNodeAppendage) {
|
||||
// 先不显示泄漏区节点上面的地址文本
|
||||
if (item instanceof SVAddressLabel) {
|
||||
// 先将透明度改为0,隐藏掉
|
||||
const AddressLabelG6Group = item.G6Item.getContainer();
|
||||
AddressLabelG6Group.attr({ opacity: 0 });
|
||||
} else {
|
||||
Animations.FADE_IN(item.G6Item, {
|
||||
duration,
|
||||
timingFunction,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Animations.APPEND(item.G6Item, {
|
||||
duration,
|
||||
timingFunction,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理被移除 models
|
||||
* @param removeData
|
||||
*/
|
||||
private handleRemoveModels(removeModels: SVModel[]) {
|
||||
let { duration, timingFunction } = this.engine.animationOptions;
|
||||
|
||||
removeModels.forEach(item => {
|
||||
Animations.REMOVE(item.G6Item, {
|
||||
duration,
|
||||
timingFunction,
|
||||
callback: () => {
|
||||
this.renderer.removeModel(item);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理泄漏区 models
|
||||
* @param leakModels
|
||||
*/
|
||||
private handleLeakModels(leakModels: SVModel[]) {
|
||||
let { duration, timingFunction } = this.engine.animationOptions;
|
||||
|
||||
leakModels.forEach(item => {
|
||||
if (item instanceof SVAddressLabel) {
|
||||
Animations.FADE_IN(item.G6Item, {
|
||||
duration,
|
||||
timingFunction,
|
||||
});
|
||||
}
|
||||
|
||||
item.G6Item.enableCapture(false);
|
||||
});
|
||||
|
||||
EventBus.emit('onLeak', leakModels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理已经堆积的泄漏区 models
|
||||
* @param accumulateModels
|
||||
*/
|
||||
private handleAccumulateLeakModels(accumulateModels: SVModel[]) {
|
||||
accumulateModels.forEach(item => {
|
||||
if (item.generalStyle) {
|
||||
item.set('style', { ...item.generalStyle });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理被释放的节点 models
|
||||
* @param freedModes
|
||||
*/
|
||||
private handleFreedModels(freedModes: SVNode[]) {
|
||||
const { duration, timingFunction } = this.engine.animationOptions,
|
||||
alpha = 0.4;
|
||||
|
||||
freedModes.forEach(item => {
|
||||
const nodeGroup = item.G6Item.getContainer();
|
||||
|
||||
item.set('style', { fill: '#ccc' });
|
||||
nodeGroup.attr({ opacity: alpha });
|
||||
|
||||
if (item.appendages.marker) {
|
||||
item.appendages.marker.forEach(marker => {
|
||||
const markerGroup = marker.G6Item.getContainer();
|
||||
marker.set('style', { fill: '#ccc' });
|
||||
markerGroup.attr({ opacity: alpha + 0.5 });
|
||||
});
|
||||
}
|
||||
|
||||
if (item.appendages.freedLabel) {
|
||||
item.appendages.freedLabel.forEach(freedLabel => {
|
||||
freedLabel.G6Item.toFront();
|
||||
Animations.FADE_IN(freedLabel.G6Item, { duration, timingFunction });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
EventBus.emit('onFreed', freedModes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理发生变化的 models
|
||||
* @param models
|
||||
*/
|
||||
private handleChangeModels(models: SVModel[]) {
|
||||
const changeHighlightColor: string = this.engine.viewOptions.updateHighlight;
|
||||
|
||||
if (!changeHighlightColor || typeof changeHighlightColor !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
models.forEach(item => {
|
||||
if (item.generalStyle === undefined) {
|
||||
item.generalStyle = Util.objectClone(item.G6ModelProps.style);
|
||||
}
|
||||
|
||||
if (item instanceof SVLink) {
|
||||
item.set('style', {
|
||||
stroke: changeHighlightColor,
|
||||
});
|
||||
} else {
|
||||
item.set('style', {
|
||||
fill: changeHighlightColor,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行diff
|
||||
* @param layoutGroupTable
|
||||
* @param prevModelList
|
||||
* @param modelList
|
||||
* @param accumulateLeakModels
|
||||
* @returns
|
||||
*/
|
||||
public diff(
|
||||
layoutGroupTable: LayoutGroupTable,
|
||||
prevModelList: SVModel[],
|
||||
modelList: SVModel[],
|
||||
accumulateLeakModels: SVModel[],
|
||||
isEnterFunction: boolean,
|
||||
prevStep: boolean
|
||||
): DiffResult {
|
||||
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
|
||||
const leakModels: SVModel[] = isEnterFunction
|
||||
? []
|
||||
: 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[] = [
|
||||
...this.getReTargetMarkers(prevModelList, modelList),
|
||||
...this.getLabelChangeModels(prevModelList, modelList),
|
||||
...appendModels,
|
||||
...leakModels,
|
||||
];
|
||||
const freedModels: SVNode[] = this.getFreedModels(prevModelList, modelList);
|
||||
|
||||
return {
|
||||
CONTINUOUS: continuousModels,
|
||||
APPEND: appendModels,
|
||||
REMOVE: removeModels,
|
||||
FREED: freedModels,
|
||||
LEAKED: leakModels,
|
||||
UPDATE: updateModels,
|
||||
ACCUMULATE_LEAK: [...accumulateLeakModels],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行调和操作
|
||||
* @param diffResult
|
||||
* @param isFirstRender
|
||||
*/
|
||||
public patch(diffResult: DiffResult) {
|
||||
const { APPEND, REMOVE, FREED, LEAKED, UPDATE, CONTINUOUS, ACCUMULATE_LEAK } = diffResult;
|
||||
|
||||
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
|
||||
|
||||
// 第一次渲染的时候不高亮变化的元素
|
||||
if (this.isFirstPatch === false) {
|
||||
this.handleChangeModels(UPDATE);
|
||||
}
|
||||
|
||||
this.handleContinuousModels(CONTINUOUS);
|
||||
this.handleFreedModels(FREED);
|
||||
this.handleAppendModels(APPEND);
|
||||
this.handleLeakModels(LEAKED);
|
||||
this.handleRemoveModels(REMOVE);
|
||||
|
||||
if (this.isFirstPatch) {
|
||||
this.isFirstPatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
public destroy() {}
|
||||
}
|
||||
|
||||
@ -4,165 +4,164 @@ import { Util } from '../Common/util';
|
||||
import { Tooltip, Graph, GraphData, Modes } from '@antv/g6';
|
||||
import { InitG6Behaviors } from '../BehaviorHelper/initG6Behaviors';
|
||||
|
||||
|
||||
|
||||
export interface RenderModelPack {
|
||||
leaKModels: SVModel[];
|
||||
generalModel: SVModel[];
|
||||
leaKModels: SVModel[];
|
||||
generalModel: SVModel[];
|
||||
}
|
||||
|
||||
|
||||
export type g6Behavior = string | { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function; };
|
||||
|
||||
export type g6Behavior =
|
||||
| string
|
||||
| { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function };
|
||||
|
||||
export class Renderer {
|
||||
private engine: Engine;
|
||||
private g6Instance: Graph; // g6 实例
|
||||
private shadowG6Instance: Graph;
|
||||
private engine: Engine;
|
||||
private g6Instance: Graph; // g6 实例
|
||||
private shadowG6Instance: Graph;
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes) {
|
||||
this.engine = engine;
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes) {
|
||||
this.engine = engine;
|
||||
|
||||
const enable: boolean = this.engine.animationOptions.enable,
|
||||
duration: number = this.engine.animationOptions.duration,
|
||||
timingFunction: string = this.engine.animationOptions.timingFunction;
|
||||
const enable: boolean = this.engine.animationOptions.enable,
|
||||
duration: number = this.engine.animationOptions.duration,
|
||||
timingFunction: string = this.engine.animationOptions.timingFunction;
|
||||
|
||||
const tooltip = new Tooltip({
|
||||
offsetX: 10,
|
||||
offsetY: 20,
|
||||
shouldBegin(event) {
|
||||
return event.item['SVModel'].isNode();
|
||||
},
|
||||
getContent: event => this.getTooltipContent(event.item['SVModel'], { address: 'sourceId', data: 'data' }),
|
||||
itemTypes: ['node']
|
||||
});
|
||||
const tooltip = new Tooltip({
|
||||
offsetX: 10,
|
||||
offsetY: 20,
|
||||
shouldBegin(event) {
|
||||
return event.item['SVModel'].isNode();
|
||||
},
|
||||
getContent: event => this.getTooltipContent(event.item['SVModel'], { address: 'sourceId', data: 'data' }),
|
||||
itemTypes: ['node'],
|
||||
});
|
||||
|
||||
this.shadowG6Instance = new Graph({
|
||||
container: DOMContainer.cloneNode() as HTMLElement
|
||||
});
|
||||
this.shadowG6Instance = new Graph({
|
||||
container: DOMContainer.cloneNode() as HTMLElement,
|
||||
});
|
||||
|
||||
// 初始化g6实例
|
||||
this.g6Instance = new Graph({
|
||||
container: DOMContainer,
|
||||
width: DOMContainer.offsetWidth,
|
||||
height: DOMContainer.offsetHeight,
|
||||
groupByTypes: false,
|
||||
animate: enable,
|
||||
animateCfg: {
|
||||
duration: duration,
|
||||
easing: timingFunction
|
||||
},
|
||||
fitView: false,
|
||||
modes: behaviorsModes,
|
||||
plugins: [tooltip]
|
||||
});
|
||||
}
|
||||
// 初始化g6实例
|
||||
this.g6Instance = new Graph({
|
||||
container: DOMContainer,
|
||||
width: DOMContainer.offsetWidth,
|
||||
height: DOMContainer.offsetHeight,
|
||||
groupByTypes: false,
|
||||
animate: enable,
|
||||
animateCfg: {
|
||||
duration: duration,
|
||||
easing: timingFunction,
|
||||
},
|
||||
fitView: false,
|
||||
modes: behaviorsModes,
|
||||
plugins: [tooltip],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创造tooltip元素
|
||||
* @param model
|
||||
* @param items
|
||||
* @returns
|
||||
*/
|
||||
private getTooltipContent(model: SVModel, items: { [key: string]: string }): HTMLDivElement {
|
||||
const wrapper = document.createElement('div');
|
||||
/**
|
||||
* 创造tooltip元素
|
||||
* @param model
|
||||
* @param items
|
||||
* @returns
|
||||
*/
|
||||
private getTooltipContent(model: SVModel, items: { [key: string]: string }): HTMLDivElement {
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
if (model === null || model === undefined) {
|
||||
return wrapper;
|
||||
}
|
||||
if (model === null || model === undefined) {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
Object.keys(items).map(key => {
|
||||
let value = model[items[key]];
|
||||
if (value !== undefined && value !== null) {
|
||||
let item = document.createElement('div');
|
||||
item.innerHTML = `${key}:${value !== '' ? value : model.G6ModelProps['label']}`;
|
||||
wrapper.appendChild(item);
|
||||
}
|
||||
});
|
||||
Object.keys(items).map(key => {
|
||||
let value = model[items[key]];
|
||||
if (value !== undefined && value !== null) {
|
||||
let item = document.createElement('div');
|
||||
item.innerHTML = `${key}:${value !== '' ? value : model.G6ModelProps['label']}`;
|
||||
wrapper.appendChild(item);
|
||||
}
|
||||
});
|
||||
|
||||
if (model.freed) {
|
||||
let item = document.createElement('div');
|
||||
item.innerHTML = '(freed)';
|
||||
wrapper.appendChild(item);
|
||||
}
|
||||
if (model.freed) {
|
||||
let item = document.createElement('div');
|
||||
item.innerHTML = '(freed)';
|
||||
wrapper.appendChild(item);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
|
||||
* @param renderModelList
|
||||
*/
|
||||
public build(renderModelList: SVModel[]) {
|
||||
const g6Data: GraphData = Util.convertModelList2G6Data(renderModelList);
|
||||
/**
|
||||
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
|
||||
* @param renderModelList
|
||||
*/
|
||||
public build(renderModelList: SVModel[]) {
|
||||
const g6Data: GraphData = Util.convertModelList2G6Data(renderModelList);
|
||||
|
||||
this.shadowG6Instance.clear();
|
||||
this.shadowG6Instance.read(g6Data);
|
||||
renderModelList.forEach(item => {
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
item.shadowG6Instance = this.shadowG6Instance;
|
||||
});
|
||||
}
|
||||
this.shadowG6Instance.clear();
|
||||
this.shadowG6Instance.read(g6Data);
|
||||
renderModelList.forEach(item => {
|
||||
item.G6Item = null;
|
||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
||||
item.shadowG6Instance = this.shadowG6Instance;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param renderModelList
|
||||
* @param isFirstRender
|
||||
*/
|
||||
public render(renderModelList: SVModel[]) {
|
||||
const renderData: GraphData = Util.convertModelList2G6Data(renderModelList);
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param renderModelList
|
||||
* @param isFirstRender
|
||||
*/
|
||||
public render(renderModelList: SVModel[]) {
|
||||
const renderData: GraphData = Util.convertModelList2G6Data(renderModelList);
|
||||
|
||||
this.g6Instance.changeData(renderData);
|
||||
this.g6Instance.changeData(renderData);
|
||||
|
||||
renderModelList.forEach(item => {
|
||||
item.g6Instance = this.g6Instance;
|
||||
item.G6Item = this.g6Instance.findById(item.id);
|
||||
item.G6Item['SVModel'] = item;
|
||||
});
|
||||
renderModelList.forEach(item => {
|
||||
item.g6Instance = this.g6Instance;
|
||||
item.G6Item = this.g6Instance.findById(item.id);
|
||||
item.G6Item['SVModel'] = item;
|
||||
});
|
||||
|
||||
this.g6Instance.getEdges().forEach(item => item.toFront());
|
||||
this.g6Instance.paint();
|
||||
}
|
||||
this.g6Instance.getEdges().forEach(item => item.toFront());
|
||||
this.g6Instance.paint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从视图中移除一个 Model
|
||||
* @param model
|
||||
*/
|
||||
public removeModel(model: SVModel) {
|
||||
this.g6Instance.removeItem(model.G6Item);
|
||||
this.shadowG6Instance.removeItem(model.shadowG6Item);
|
||||
}
|
||||
/**
|
||||
* 从视图中移除一个 Model
|
||||
* @param model
|
||||
*/
|
||||
public removeModel(model: SVModel) {
|
||||
this.g6Instance.removeItem(model.G6Item);
|
||||
this.shadowG6Instance.removeItem(model.shadowG6Item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 G6 实例
|
||||
*/
|
||||
public getG6Instance() {
|
||||
return this.g6Instance;
|
||||
}
|
||||
/**
|
||||
* 获取 G6 实例
|
||||
*/
|
||||
public getG6Instance() {
|
||||
return this.g6Instance;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public refresh() {
|
||||
this.g6Instance.refresh();
|
||||
this.shadowG6Instance.refresh();
|
||||
}
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public refresh() {
|
||||
this.g6Instance.refresh();
|
||||
this.shadowG6Instance.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public changeSize(width: number, height: number) {
|
||||
this.g6Instance.changeSize(width, height);
|
||||
this.shadowG6Instance.changeSize(width, height);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public changeSize(width: number, height: number) {
|
||||
this.g6Instance.changeSize(width, height);
|
||||
this.shadowG6Instance.changeSize(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
public destroy() {
|
||||
this.shadowG6Instance.destroy();
|
||||
this.g6Instance.destroy();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
public destroy() {
|
||||
this.shadowG6Instance.destroy();
|
||||
this.g6Instance.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,226 +1,214 @@
|
||||
import { Engine } from "../engine";
|
||||
import { LayoutProvider } from "./layoutProvider";
|
||||
import { LayoutGroupTable } from "../Model/modelConstructor";
|
||||
import { Util } from "../Common/util";
|
||||
import { SVModel } from "../Model/SVModel";
|
||||
import { Renderer } from "./renderer";
|
||||
import { Reconcile } from "./reconcile";
|
||||
import { EventBus } from "../Common/eventBus";
|
||||
import { Group } from "../Common/group";
|
||||
import { Graph, Modes } from "@antv/g6-pc";
|
||||
import { InitG6Behaviors } from "../BehaviorHelper/initG6Behaviors";
|
||||
import { SVNode } from "../Model/SVNode";
|
||||
import { SolveBrushSelectDrag, SolveDragCanvasWithLeak, SolveNodeAppendagesDrag, SolveZoomCanvasWithLeak } from "../BehaviorHelper/behaviorIssueHelper";
|
||||
|
||||
|
||||
import { Engine } from '../engine';
|
||||
import { LayoutProvider } from './layoutProvider';
|
||||
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||
import { Util } from '../Common/util';
|
||||
import { SVModel } from '../Model/SVModel';
|
||||
import { Renderer } from './renderer';
|
||||
import { Reconcile } from './reconcile';
|
||||
import { EventBus } from '../Common/eventBus';
|
||||
import { Group } from '../Common/group';
|
||||
import { Graph, Modes } from '@antv/g6-pc';
|
||||
import { InitG6Behaviors } from '../BehaviorHelper/initG6Behaviors';
|
||||
import { SVNode } from '../Model/SVNode';
|
||||
import {
|
||||
SolveBrushSelectDrag,
|
||||
SolveDragCanvasWithLeak,
|
||||
SolveNodeAppendagesDrag,
|
||||
SolveZoomCanvasWithLeak,
|
||||
} from '../BehaviorHelper/behaviorIssueHelper';
|
||||
|
||||
export class ViewContainer {
|
||||
private engine: Engine;
|
||||
private layoutProvider: LayoutProvider;
|
||||
private reconcile: Reconcile;
|
||||
public renderer: Renderer;
|
||||
private engine: Engine;
|
||||
private layoutProvider: LayoutProvider;
|
||||
private reconcile: Reconcile;
|
||||
public renderer: Renderer;
|
||||
|
||||
private layoutGroupTable: LayoutGroupTable;
|
||||
private prevModelList: SVModel[];
|
||||
private accumulateLeakModels: SVModel[];
|
||||
private layoutGroupTable: LayoutGroupTable;
|
||||
private prevModelList: SVModel[];
|
||||
private accumulateLeakModels: SVModel[];
|
||||
|
||||
public hasLeak: boolean;
|
||||
public leakAreaY: number;
|
||||
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
|
||||
public clickSelectNode: SVNode; // 点击选中的节点
|
||||
public hasLeak: boolean;
|
||||
public leakAreaY: number;
|
||||
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
|
||||
public clickSelectNode: SVNode; // 点击选中的节点
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||
const behaviorsModes: Modes = InitG6Behaviors(engine, this);
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||
const behaviorsModes: Modes = InitG6Behaviors(engine, this);
|
||||
this.engine = engine;
|
||||
this.layoutProvider = new LayoutProvider(engine, this);
|
||||
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes);
|
||||
this.reconcile = new Reconcile(engine, this.renderer);
|
||||
this.layoutGroupTable = new Map();
|
||||
this.prevModelList = [];
|
||||
this.accumulateLeakModels = [];
|
||||
this.hasLeak = false; // 判断是否已经发生过泄漏
|
||||
this.brushSelectedModels = [];
|
||||
this.clickSelectNode = null;
|
||||
|
||||
this.engine = engine;
|
||||
this.layoutProvider = new LayoutProvider(engine, this);
|
||||
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes);
|
||||
this.reconcile = new Reconcile(engine, this.renderer);
|
||||
this.layoutGroupTable = new Map();
|
||||
this.prevModelList = [];
|
||||
this.accumulateLeakModels = [];
|
||||
this.hasLeak = false; // 判断是否已经发生过泄漏
|
||||
this.brushSelectedModels = [];
|
||||
this.clickSelectNode = null;
|
||||
const g6Instance = this.renderer.getG6Instance(),
|
||||
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||
height = this.getG6Instance().getHeight(),
|
||||
{ drag, zoom } = this.engine.behaviorOptions;
|
||||
|
||||
const g6Instance = this.renderer.getG6Instance(),
|
||||
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||
height = this.getG6Instance().getHeight(),
|
||||
{ drag, zoom } = this.engine.behaviorOptions;
|
||||
this.leakAreaY = height - leakAreaHeight;
|
||||
|
||||
this.leakAreaY = height - leakAreaHeight;
|
||||
SolveNodeAppendagesDrag(this);
|
||||
SolveBrushSelectDrag(this);
|
||||
drag && SolveDragCanvasWithLeak(this);
|
||||
zoom && SolveZoomCanvasWithLeak(this);
|
||||
}
|
||||
|
||||
SolveNodeAppendagesDrag(this);
|
||||
SolveBrushSelectDrag(this);
|
||||
drag && SolveDragCanvasWithLeak(this);
|
||||
zoom && SolveZoomCanvasWithLeak(this);
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 对主视图进行重新布局
|
||||
*/
|
||||
reLayout() {
|
||||
const g6Instance = this.getG6Instance(),
|
||||
group = g6Instance.getGroup(),
|
||||
matrix = group.getMatrix();
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
if (matrix) {
|
||||
let dx = matrix[6],
|
||||
dy = matrix[7];
|
||||
|
||||
/**
|
||||
* 对主视图进行重新布局
|
||||
*/
|
||||
reLayout() {
|
||||
const g6Instance = this.getG6Instance(),
|
||||
group = g6Instance.getGroup(),
|
||||
matrix = group.getMatrix();
|
||||
g6Instance.translate(-dx, -dy);
|
||||
}
|
||||
|
||||
if (matrix) {
|
||||
let dx = matrix[6],
|
||||
dy = matrix[7];
|
||||
this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels);
|
||||
g6Instance.refresh();
|
||||
}
|
||||
|
||||
g6Instance.translate(-dx, -dy);
|
||||
}
|
||||
/**
|
||||
* 获取 g6 实例
|
||||
*/
|
||||
getG6Instance(): Graph {
|
||||
return this.renderer.getG6Instance();
|
||||
}
|
||||
|
||||
this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels, []);
|
||||
g6Instance.refresh();
|
||||
}
|
||||
/**
|
||||
* 获取泄漏区里面的元素
|
||||
* @returns
|
||||
*/
|
||||
getAccumulateLeakModels(): SVModel[] {
|
||||
return this.accumulateLeakModels;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
getLayoutGroupTable(): LayoutGroupTable {
|
||||
return this.layoutGroupTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 g6 实例
|
||||
*/
|
||||
getG6Instance(): Graph {
|
||||
return this.renderer.getG6Instance();
|
||||
}
|
||||
/**
|
||||
* 刷新视图
|
||||
*/
|
||||
refresh() {
|
||||
this.renderer.getG6Instance().refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取泄漏区里面的元素
|
||||
* @returns
|
||||
*/
|
||||
getAccumulateLeakModels(): SVModel[] {
|
||||
return this.accumulateLeakModels;
|
||||
}
|
||||
/**
|
||||
* 重新调整容器尺寸
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
resize(width: number, height: number) {
|
||||
const g6Instance = this.getG6Instance(),
|
||||
prevContainerHeight = g6Instance.getHeight(),
|
||||
globalGroup: Group = new Group();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
getLayoutGroupTable(): LayoutGroupTable {
|
||||
return this.layoutGroupTable;
|
||||
}
|
||||
globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels);
|
||||
this.renderer.changeSize(width, height);
|
||||
|
||||
/**
|
||||
* 刷新视图
|
||||
*/
|
||||
refresh() {
|
||||
this.renderer.getG6Instance().refresh();
|
||||
}
|
||||
const containerHeight = g6Instance.getHeight(),
|
||||
dy = containerHeight - prevContainerHeight;
|
||||
|
||||
/**
|
||||
* 重新调整容器尺寸
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
resize(width: number, height: number) {
|
||||
const g6Instance = this.getG6Instance(),
|
||||
prevContainerHeight = g6Instance.getHeight(),
|
||||
globalGroup: Group = new Group();
|
||||
globalGroup.translate(0, dy);
|
||||
this.renderer.refresh();
|
||||
|
||||
globalGroup.add(...this.prevModelList, ...this.accumulateLeakModels);
|
||||
this.renderer.changeSize(width, height);
|
||||
this.leakAreaY += dy;
|
||||
EventBus.emit('onLeakAreaUpdate', {
|
||||
leakAreaY: this.leakAreaY,
|
||||
hasLeak: this.hasLeak,
|
||||
});
|
||||
}
|
||||
|
||||
const containerHeight = g6Instance.getHeight(),
|
||||
dy = containerHeight - prevContainerHeight;
|
||||
/**
|
||||
* 渲染所有视图
|
||||
* @param models
|
||||
* @param layoutFn
|
||||
*/
|
||||
render(layoutGroupTable: LayoutGroupTable, isEnterFunction: boolean, prevStep: boolean) {
|
||||
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
|
||||
diffResult = this.reconcile.diff(
|
||||
this.layoutGroupTable,
|
||||
this.prevModelList,
|
||||
modelList,
|
||||
this.accumulateLeakModels,
|
||||
isEnterFunction,
|
||||
prevStep
|
||||
),
|
||||
renderModelList = [...modelList, ...diffResult.REMOVE, ...diffResult.LEAKED, ...diffResult.ACCUMULATE_LEAK];
|
||||
|
||||
globalGroup.translate(0, dy);
|
||||
this.renderer.refresh();
|
||||
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
|
||||
this.hasLeak = false;
|
||||
EventBus.emit('onLeakAreaUpdate', {
|
||||
leakAreaY: this.leakAreaY,
|
||||
hasLeak: this.hasLeak,
|
||||
});
|
||||
}
|
||||
|
||||
this.leakAreaY += dy;
|
||||
EventBus.emit('onLeakAreaUpdate', {
|
||||
leakAreaY: this.leakAreaY,
|
||||
hasLeak: this.hasLeak
|
||||
});
|
||||
}
|
||||
if (diffResult.LEAKED.length) {
|
||||
this.hasLeak = true;
|
||||
EventBus.emit('onLeakAreaUpdate', {
|
||||
leakAreaY: this.leakAreaY,
|
||||
hasLeak: this.hasLeak,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染所有视图
|
||||
* @param models
|
||||
* @param layoutFn
|
||||
*/
|
||||
render(layoutGroupTable: LayoutGroupTable, isEnterFunction: boolean) {
|
||||
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
|
||||
diffResult = this.reconcile.diff(this.layoutGroupTable, this.prevModelList, modelList, this.accumulateLeakModels, isEnterFunction),
|
||||
renderModelList = [
|
||||
...modelList,
|
||||
...diffResult.REMOVE,
|
||||
...diffResult.LEAKED,
|
||||
...diffResult.ACCUMULATE_LEAK
|
||||
];
|
||||
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行向后累积
|
||||
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
|
||||
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels); // 进行布局(设置model的x,y,样式等)
|
||||
|
||||
if (this.hasLeak === true && this.accumulateLeakModels.length === 0) {
|
||||
this.hasLeak = false;
|
||||
EventBus.emit('onLeakAreaUpdate', {
|
||||
leakAreaY: this.leakAreaY,
|
||||
hasLeak: this.hasLeak
|
||||
});
|
||||
}
|
||||
this.beforeRender();
|
||||
this.renderer.render(renderModelList); // 渲染视图
|
||||
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
|
||||
this.afterRender();
|
||||
|
||||
if (diffResult.LEAKED.length) {
|
||||
this.hasLeak = true;
|
||||
EventBus.emit('onLeakAreaUpdate', {
|
||||
leakAreaY: this.leakAreaY,
|
||||
hasLeak: this.hasLeak
|
||||
});
|
||||
}
|
||||
this.layoutGroupTable = layoutGroupTable;
|
||||
this.prevModelList = modelList;
|
||||
}
|
||||
|
||||
this.renderer.build(renderModelList); // 首先在离屏canvas渲染先
|
||||
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels, diffResult.LEAKED); // 进行布局(设置model的x,y,样式等)
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
this.reconcile.destroy();
|
||||
this.layoutProvider = null;
|
||||
this.layoutGroupTable = null;
|
||||
this.prevModelList.length = 0;
|
||||
this.accumulateLeakModels.length = 0;
|
||||
this.brushSelectedModels.length = 0;
|
||||
}
|
||||
|
||||
this.beforeRender();
|
||||
this.renderer.render(renderModelList); // 渲染视图
|
||||
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
|
||||
this.afterRender();
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行累积
|
||||
/**
|
||||
* 把渲染后要触发的逻辑放在这里
|
||||
*/
|
||||
private afterRender() {
|
||||
this.prevModelList.forEach(item => {
|
||||
if (item.leaked === false) {
|
||||
item.discarded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.layoutGroupTable = layoutGroupTable;
|
||||
this.prevModelList = modelList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
this.reconcile.destroy();
|
||||
this.layoutProvider = null;
|
||||
this.layoutGroupTable = null;
|
||||
this.prevModelList.length = 0;
|
||||
this.accumulateLeakModels.length = 0;
|
||||
this.brushSelectedModels.length = 0;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* 把渲染后要触发的逻辑放在这里
|
||||
*/
|
||||
private afterRender() {
|
||||
this.prevModelList.forEach(item => {
|
||||
if (item.leaked === false) {
|
||||
item.discarded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 把渲染前要触发的逻辑放在这里
|
||||
*/
|
||||
private beforeRender() { }
|
||||
/**
|
||||
* 把渲染前要触发的逻辑放在这里
|
||||
*/
|
||||
private beforeRender() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -49,15 +49,15 @@ export class Engine {
|
||||
/**
|
||||
* 输入数据进行渲染
|
||||
* @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) {
|
||||
return;
|
||||
}
|
||||
``
|
||||
let stringSource = JSON.stringify(source);
|
||||
if (force === false && this.prevStringSource === stringSource) {
|
||||
if (this.prevStringSource === stringSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ export class Engine {
|
||||
const layoutGroupTable = this.modelConstructor.construct(source);
|
||||
|
||||
// 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'
|
||||
}
|
||||
]
|
||||
},
|
||||
// devtool: 'eval-source-map'
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user