feat: 重构
This commit is contained in:
parent
8347476980
commit
085effbe14
@ -19,7 +19,7 @@ const isNeighbor = function (itemA, itemB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('AdjoinMatrixGraph', {
|
SV.registerLayout('AdjoinMatrixGraph', {
|
||||||
|
|
||||||
sourcesPreprocess(sources) {
|
sourcesPreprocess(sources) {
|
||||||
let dataLength = sources.length;
|
let dataLength = sources.length;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('AdjoinTableGraph', {
|
SV.registerLayout('AdjoinTableGraph', {
|
||||||
|
|
||||||
sourcesPreprocess(sources, options) {
|
sourcesPreprocess(sources, options) {
|
||||||
let dataLength = sources.length;
|
let dataLength = sources.length;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('Array', {
|
SV.registerLayout('Array', {
|
||||||
|
|
||||||
sourcesPreprocess(sources) {
|
sourcesPreprocess(sources) {
|
||||||
const firstElement = sources[0];
|
const firstElement = sources[0];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('BinaryTree', {
|
SV.registerLayout('BinaryTree', {
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
/**
|
/**
|
||||||
* 连地址哈希表
|
* 连地址哈希表
|
||||||
*/
|
*/
|
||||||
SV.registerLayouter('ChainHashTable', {
|
SV.registerLayout('ChainHashTable', {
|
||||||
|
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -80,7 +80,7 @@ SV.registerShape('three-cell-node', {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('GeneralizedList', {
|
SV.registerLayout('GeneralizedList', {
|
||||||
|
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('HashTable', {
|
SV.registerLayout('HashTable', {
|
||||||
|
|
||||||
sourcesPreprocess(sources) {
|
sourcesPreprocess(sources) {
|
||||||
const firstElement = sources[0];
|
const firstElement = sources[0];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('LinkList', {
|
SV.registerLayout('LinkList', {
|
||||||
|
|
||||||
sourcesPreprocess(sources) {
|
sourcesPreprocess(sources) {
|
||||||
let root = sources[0];
|
let root = sources[0];
|
||||||
@ -15,7 +15,7 @@ SV.registerLayouter('LinkList', {
|
|||||||
|
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
element: {
|
node: {
|
||||||
default: {
|
default: {
|
||||||
type: 'link-list-node',
|
type: 'link-list-node',
|
||||||
label: '[data]',
|
label: '[data]',
|
||||||
@ -107,7 +107,6 @@ SV.registerLayouter('LinkList', {
|
|||||||
|
|
||||||
layout(elements, layoutOptions) {
|
layout(elements, layoutOptions) {
|
||||||
let root = elements[0];
|
let root = elements[0];
|
||||||
|
|
||||||
this.layoutItem(root, null, layoutOptions);
|
this.layoutItem(root, null, layoutOptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('LinkQueue', {
|
SV.registerLayout('LinkQueue', {
|
||||||
|
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
/**
|
/**
|
||||||
* 单链表
|
* 单链表
|
||||||
*/
|
*/
|
||||||
SV.registerLayouter('LinkStack', {
|
SV.registerLayout('LinkStack', {
|
||||||
sourcesPreprocess(sources) {
|
sourcesPreprocess(sources) {
|
||||||
const headNode = sources[0];
|
const headNode = sources[0];
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
SV.registerLayouter('Stack', {
|
SV.registerLayout('Stack', {
|
||||||
|
|
||||||
sourcesPreprocess(sources, options) {
|
sourcesPreprocess(sources, options) {
|
||||||
const stackBottomNode = sources[sources.length - 1];
|
const stackBottomNode = sources[sources.length - 1];
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* 三叉树
|
* 三叉树
|
||||||
*/
|
*/
|
||||||
SV.registerLayouter('TriTree', {
|
SV.registerLayout('TriTree', {
|
||||||
defineOptions() {
|
defineOptions() {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -25,38 +25,43 @@
|
|||||||
|
|
||||||
#container {
|
#container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400px;
|
height: 500px;
|
||||||
}
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
#freed {
|
|
||||||
width: 200px;
|
|
||||||
height: 300px;
|
|
||||||
margin-right: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#leak {
|
#leak {
|
||||||
width: 400px;
|
position: absolute;
|
||||||
height: 300px;
|
left: 0;
|
||||||
|
top: 100px;
|
||||||
|
opacity: 0;
|
||||||
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="container" id="container"></div>
|
<div class="container" id="container">
|
||||||
|
<div id="leak">
|
||||||
|
<span>泄漏区</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button id="btn-prev">prev</button>
|
<button id="btn-prev">prev</button>
|
||||||
<button id="btn-next">next</button>
|
<button id="btn-next">next</button>
|
||||||
<button id="hide">隐藏</button>
|
<button id="resize">resize</button>
|
||||||
<span id="pos"></span>
|
<span id="pos"></span>
|
||||||
|
|
||||||
<div class="down">
|
|
||||||
<div id="freed" class="container"></div>
|
|
||||||
<div id="leak" class="container"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script src="./../dist/sv.js"></script>
|
<script src="./../dist/sv.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
@ -82,9 +87,8 @@
|
|||||||
const curSelectData = { element: null, style: null };
|
const curSelectData = { element: null, style: null };
|
||||||
|
|
||||||
let cur = SV(document.getElementById('container'), {
|
let cur = SV(document.getElementById('container'), {
|
||||||
freedContainer: document.getElementById('freed'),
|
|
||||||
leakContainer: document.getElementById('leak'),
|
|
||||||
view: {
|
view: {
|
||||||
|
leakAreaHeight: 0.25,
|
||||||
groupPadding: 40,
|
groupPadding: 40,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -93,54 +97,42 @@
|
|||||||
let data = [{
|
let data = [{
|
||||||
"LinkList0": {
|
"LinkList0": {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{ id: 0, data: 'A', next: 1 },
|
||||||
"id": "0x617fb0",
|
{ id: 1, data: 'B' }
|
||||||
"data": "N",
|
|
||||||
"next": "0x617ff0",
|
|
||||||
"rootExternal": [
|
|
||||||
"before"
|
|
||||||
],
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x617ff0",
|
|
||||||
"data": "A",
|
|
||||||
"next": "0x618010",
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x618010",
|
|
||||||
"data": "A",
|
|
||||||
"next": "0x618030",
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x618030",
|
|
||||||
"data": "B",
|
|
||||||
"next": "0x618050",
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0x618050",
|
|
||||||
"data": "N",
|
|
||||||
"next": null,
|
|
||||||
"type": "default"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"layouter": "LinkList"
|
"layouter": "LinkList"
|
||||||
},
|
}
|
||||||
"LinkList1": {
|
}, {
|
||||||
|
"LinkList0": {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{ id: 0, data: 'A', next: 1 },
|
||||||
"freed": true,
|
{ id: 1, data: 'B', next: 2 },
|
||||||
"id": "0x617fd0",
|
{ id: 2, data: 'C' }
|
||||||
"data": "",
|
],
|
||||||
"next": "0x605010",
|
"layouter": "LinkList"
|
||||||
"rootExternal": [
|
}
|
||||||
"tmpNode"
|
}, {
|
||||||
],
|
"LinkList0": {
|
||||||
"type": "default"
|
"data": [
|
||||||
}
|
{ id: 0, data: 'A', next: 1 },
|
||||||
|
{ id: 1, data: 'B', next: 2 },
|
||||||
|
{ id: 2, data: 'C', next: 3 },
|
||||||
|
{ id: 3, data: 'D' }
|
||||||
|
],
|
||||||
|
"layouter": "LinkList"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"LinkList0": {
|
||||||
|
"data": [
|
||||||
|
{ id: 0, data: 'A', next: 1 },
|
||||||
|
{ id: 1, data: 'B', next: 2, freed: true }
|
||||||
|
],
|
||||||
|
"layouter": "LinkList"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"LinkList0": {
|
||||||
|
"data": [
|
||||||
|
{ id: 0, data: 'A' }
|
||||||
],
|
],
|
||||||
"layouter": "LinkList"
|
"layouter": "LinkList"
|
||||||
}
|
}
|
||||||
@ -153,17 +145,9 @@
|
|||||||
|
|
||||||
cur.render(curData);
|
cur.render(curData);
|
||||||
|
|
||||||
let f = true;
|
|
||||||
document.getElementById('hide').addEventListener('click', e => {
|
|
||||||
f ? cur.hideGroups(['BinaryTree']) : cur.hideGroups(['LinkList']);
|
|
||||||
f = !f;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('btn-next').addEventListener('click', e => {
|
document.getElementById('btn-next').addEventListener('click', e => {
|
||||||
curData = data[++dataIndex];
|
curData = data[++dataIndex];
|
||||||
cur.render(curData);
|
cur.render(curData);
|
||||||
// curSelectData.element = null;
|
|
||||||
// curSelectData.style = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('btn-prev').addEventListener('click', e => {
|
document.getElementById('btn-prev').addEventListener('click', e => {
|
||||||
@ -179,49 +163,21 @@
|
|||||||
pos.innerHTML = `${x},${y}`;
|
pos.innerHTML = `${x},${y}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('resize').addEventListener('click', e => {
|
||||||
document.getElementById('hide').addEventListener('click', () => {
|
container.style.height = 300 + 'px';
|
||||||
cur.selectElement(1000, ele => {
|
cur.resize(container.offsetWidth, container.offsetHeight);
|
||||||
ele.set('label', '6666');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const leak = document.getElementById('leak');
|
||||||
|
|
||||||
|
cur.on('onLeakAreaUpdate', payload => {
|
||||||
|
leak.style.opacity = payload.hasLeak ? 1 : 0;
|
||||||
|
leak.style.top = payload.leakAreaY - 40 + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 选中一个element
|
|
||||||
*/
|
|
||||||
function selectElement(element) {
|
|
||||||
if (element === curSelectData.element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curSelectData.element) {
|
|
||||||
const style = curSelectData.style;
|
|
||||||
curSelectData.element.set("style", style);
|
|
||||||
}
|
|
||||||
|
|
||||||
curSelectData.element = element;
|
|
||||||
curSelectData.style = { ...element.get("style") };
|
|
||||||
this.setSelectElementStyle(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置选中的节点的颜色
|
|
||||||
*/
|
|
||||||
function setSelectElementStyle(element) {
|
|
||||||
element.set("style", {
|
|
||||||
fill: "#e23e57",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cur.on('node:click', ele => {
|
|
||||||
selectElement(ele);
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
2
dist/sv.js
vendored
2
dist/sv.js
vendored
File diff suppressed because one or more lines are too long
@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antv/g6": "^4.2.1",
|
"@antv/g6": "^4.4.1",
|
||||||
"awesome-typescript-loader": "^5.2.1",
|
"awesome-typescript-loader": "^5.2.1",
|
||||||
"typescript": "^3.2.2",
|
"typescript": "^3.2.2",
|
||||||
"webpack": "^4.28.2",
|
"webpack": "^4.28.2"
|
||||||
"zrender": "^5.0.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"webpack-cli": "^3.2.3"
|
"webpack-cli": "^3.2.3"
|
||||||
|
|||||||
54
src/BehaviorHelper/dragCanavsWithLeak.ts
Normal file
54
src/BehaviorHelper/dragCanavsWithLeak.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { EventBus } from "../Common/eventBus";
|
||||||
|
import { ViewContainer } from "../View/viewContainer";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化视图拖拽功能
|
||||||
|
* @param g6Instance
|
||||||
|
* @param hasLeak
|
||||||
|
*/
|
||||||
|
export function InitDragCanvasWithLeak(viewContainer: ViewContainer) {
|
||||||
|
let g6Instance = viewContainer.getG6Instance(),
|
||||||
|
startPositionY = 0,
|
||||||
|
currentLeakAreaY = 0;
|
||||||
|
|
||||||
|
g6Instance.on('canvas:dragstart', event => {
|
||||||
|
startPositionY = event.canvasY;
|
||||||
|
currentLeakAreaY = viewContainer.leakAreaY;
|
||||||
|
});
|
||||||
|
|
||||||
|
g6Instance.on('canvas:drag', event => {
|
||||||
|
let zoom = g6Instance.getZoom(),
|
||||||
|
dy = (event.canvasY - startPositionY) / zoom,
|
||||||
|
leakAreaY = currentLeakAreaY + dy;
|
||||||
|
|
||||||
|
viewContainer.leakAreaY = leakAreaY;
|
||||||
|
if(viewContainer.hasLeak) {
|
||||||
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
|
leakAreaY: viewContainer.leakAreaY,
|
||||||
|
hasLeak: viewContainer.hasLeak
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
g6Instance.on('canvas:dragend', event => {
|
||||||
|
startPositionY = 0;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
116
src/BehaviorHelper/fixNodeMarkerDrag.ts
Normal file
116
src/BehaviorHelper/fixNodeMarkerDrag.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { Graph } from "@antv/g6-pc";
|
||||||
|
import { SVNode } from "../Model/SVNode";
|
||||||
|
import { LayoutGroupOptions } from "../options";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在初始化渲染器之后,修正节点拖拽时,外部指针没有跟着动的问题
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function FixNodeMarkerDrag(g6Instance: Graph, optionsTable: { [key: string]: LayoutGroupOptions }) {
|
||||||
|
let dragActive: boolean = false;
|
||||||
|
|
||||||
|
const nodeData = {
|
||||||
|
node: null,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const markerData = {
|
||||||
|
marker: null,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const freedLabelData = {
|
||||||
|
freedLabel: null,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
g6Instance.on('node:dragstart', event => {
|
||||||
|
nodeData.node = event.item['SVModel'];
|
||||||
|
let node: SVNode = nodeData.node;
|
||||||
|
|
||||||
|
if (node.isNode() === false || node.leaked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragNode = optionsTable[node.layout].behavior.dragNode;
|
||||||
|
|
||||||
|
if (dragNode === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(dragNode) && dragNode.find(item => item === node.sourceType) === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragActive = true;
|
||||||
|
nodeData.startX = event.canvasX;
|
||||||
|
nodeData.startY = event.canvasY;
|
||||||
|
|
||||||
|
if (node.marker) {
|
||||||
|
markerData.marker = node.marker;
|
||||||
|
markerData.startX = markerData.marker.get('x');
|
||||||
|
markerData.startY = markerData.marker.get('y');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node.freedLabel) {
|
||||||
|
freedLabelData.freedLabel = node.freedLabel;
|
||||||
|
freedLabelData.startX = freedLabelData.freedLabel.get('x');
|
||||||
|
freedLabelData.startY = freedLabelData.freedLabel.get('y');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
g6Instance.on('node:dragend', event => {
|
||||||
|
if(!dragActive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let distanceX = event.canvasX - nodeData.startX,
|
||||||
|
distanceY = event.canvasY - nodeData.startY,
|
||||||
|
nodeX = nodeData.node.get('x'),
|
||||||
|
nodeY = nodeData.node.get('y');
|
||||||
|
|
||||||
|
nodeData.node.set({
|
||||||
|
x: nodeX + distanceX,
|
||||||
|
y: nodeY + distanceY
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeData.node = null;
|
||||||
|
nodeData.startX = 0;
|
||||||
|
nodeData.startY = 0;
|
||||||
|
markerData.marker = null;
|
||||||
|
markerData.startX = 0;
|
||||||
|
markerData.startY = 0;
|
||||||
|
freedLabelData.freedLabel = null;
|
||||||
|
freedLabelData.startX = 0;
|
||||||
|
freedLabelData.startY = 0;
|
||||||
|
dragActive = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
g6Instance.on('node:drag', ev => {
|
||||||
|
if (!dragActive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dx = ev.canvasX - nodeData.startX,
|
||||||
|
dy = ev.canvasY - nodeData.startY,
|
||||||
|
zoom = g6Instance.getZoom();
|
||||||
|
|
||||||
|
if(markerData.marker) {
|
||||||
|
markerData.marker.set({
|
||||||
|
x: markerData.startX + dx / zoom,
|
||||||
|
y: markerData.startY + dy / zoom
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(freedLabelData.freedLabel) {
|
||||||
|
freedLabelData.freedLabel.set({
|
||||||
|
x: freedLabelData.startX + dx / zoom,
|
||||||
|
y: freedLabelData.startY + dy / zoom
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
78
src/BehaviorHelper/initViewBehaviors.ts
Normal file
78
src/BehaviorHelper/initViewBehaviors.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { SVModel } from "../Model/SVModel";
|
||||||
|
import { LayoutGroupOptions } from "../options";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化g6 交互options
|
||||||
|
* @param optionsTable
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function InitViewBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }) {
|
||||||
|
const dragNodeTable: { [key: string]: boolean | string[] } = {},
|
||||||
|
selectNodeTable: { [key: string]: boolean | string[] } = {},
|
||||||
|
defaultModes = [];
|
||||||
|
|
||||||
|
Object.keys(optionsTable).forEach(item => {
|
||||||
|
dragNodeTable[item] = optionsTable[item].behavior.dragNode;
|
||||||
|
selectNodeTable[item] = optionsTable[item].behavior.selectNode;
|
||||||
|
});
|
||||||
|
|
||||||
|
const dragNodeFilter = event => {
|
||||||
|
let g6Item = event.item,
|
||||||
|
node: SVModel = g6Item.SVModel;
|
||||||
|
|
||||||
|
if (g6Item === null || node.isNode() === false || node.leaked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dragNode = optionsTable[node.layout].behavior.dragNode;
|
||||||
|
|
||||||
|
if (typeof dragNode === 'boolean') {
|
||||||
|
return dragNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(dragNode) && dragNode.indexOf(node.sourceType) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectNodeFilter = event => {
|
||||||
|
let g6Item = event.item,
|
||||||
|
node: SVModel = g6Item.SVModel;
|
||||||
|
|
||||||
|
if (g6Item === null || node.isNode() === false || node.leaked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectNode = optionsTable[node.layout].behavior.selectNode;
|
||||||
|
|
||||||
|
if (typeof selectNode === 'boolean') {
|
||||||
|
return selectNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(selectNode) && selectNode.indexOf(node.sourceType) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultModes.push({
|
||||||
|
type: 'drag-node',
|
||||||
|
shouldBegin: dragNodeFilter
|
||||||
|
});
|
||||||
|
|
||||||
|
defaultModes.push({
|
||||||
|
type: 'drag-canvas'
|
||||||
|
});
|
||||||
|
|
||||||
|
defaultModes.push({
|
||||||
|
type: 'click-select',
|
||||||
|
shouldBegin: selectNodeFilter
|
||||||
|
});
|
||||||
|
|
||||||
|
return defaultModes;
|
||||||
|
}
|
||||||
59
src/BehaviorHelper/zoomCanavs.ts
Normal file
59
src/BehaviorHelper/zoomCanavs.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { Graph, IGroup } from "@antv/g6-pc";
|
||||||
|
import { ext } from '@antv/matrix-util';
|
||||||
|
|
||||||
|
const transform = ext.transform;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化视图缩放功能
|
||||||
|
* @param g6Instance
|
||||||
|
* @param generalModelsGroup
|
||||||
|
*/
|
||||||
|
export function InitZoomCanvas(g6Instance: Graph, g6GeneralGroup: IGroup) {
|
||||||
|
const minZoom = 0.2,
|
||||||
|
maxZoom = 2,
|
||||||
|
step = 0.15;
|
||||||
|
|
||||||
|
g6Instance.on('wheel', event => {
|
||||||
|
let delta = event.wheelDelta,
|
||||||
|
matrix = g6GeneralGroup.getMatrix(),
|
||||||
|
center = [event.x, event.y],
|
||||||
|
targetScale = 1;
|
||||||
|
|
||||||
|
if (delta > 0) {
|
||||||
|
targetScale += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 0) {
|
||||||
|
targetScale -= step;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix = transform(matrix, [
|
||||||
|
['t', -center[0], -center[1]],
|
||||||
|
['s', targetScale, targetScale],
|
||||||
|
['t', center[0], center[1]],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ((minZoom && matrix[0] < minZoom) || (maxZoom && matrix[0] > maxZoom)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g6GeneralGroup.setMatrix(matrix);
|
||||||
|
g6Instance.paint();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { Util } from "./util";
|
import { Util } from "./util";
|
||||||
import { BoundingRect, Bound } from "./boundingRect";
|
import { BoundingRect, Bound } from "./boundingRect";
|
||||||
import { Element, Model } from "../Model/modelData";
|
import { SVModel } from "../Model/SVModel";
|
||||||
|
import { ext } from '@antv/matrix-util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -9,12 +10,12 @@ import { Element, Model } from "../Model/modelData";
|
|||||||
*/
|
*/
|
||||||
export class Group {
|
export class Group {
|
||||||
id: string;
|
id: string;
|
||||||
private models: Array<Model | Group> = [];
|
private models: Array<SVModel | Group> = [];
|
||||||
|
|
||||||
constructor(...arg: Array<Model | Group>) {
|
constructor(...arg: Array<SVModel | Group>) {
|
||||||
this.id = Util.generateId();
|
this.id = Util.generateId();
|
||||||
|
|
||||||
if(arg) {
|
if (arg) {
|
||||||
this.add(...arg);
|
this.add(...arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,7 +24,7 @@ export class Group {
|
|||||||
* 添加element
|
* 添加element
|
||||||
* @param arg
|
* @param arg
|
||||||
*/
|
*/
|
||||||
add(...arg: Array<Model | Group>) {
|
add(...arg: Array<SVModel | Group>) {
|
||||||
arg.map(ele => {
|
arg.map(ele => {
|
||||||
this.models.push(ele);
|
this.models.push(ele);
|
||||||
});
|
});
|
||||||
@ -33,7 +34,7 @@ export class Group {
|
|||||||
* 移除 model
|
* 移除 model
|
||||||
* @param element
|
* @param element
|
||||||
*/
|
*/
|
||||||
remove(model: Model | Group) {
|
remove(model: SVModel | Group) {
|
||||||
Util.removeFromList(this.models, item => item.id === model.id);
|
Util.removeFromList(this.models, item => item.id === model.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +42,9 @@ export class Group {
|
|||||||
* 获取group的包围盒
|
* 获取group的包围盒
|
||||||
*/
|
*/
|
||||||
getBound(): BoundingRect {
|
getBound(): BoundingRect {
|
||||||
return this.models.length?
|
return this.models.length ?
|
||||||
Bound.union(...this.models.map(item => item.getBound())):
|
Bound.union(...this.models.map(item => item.getBound())) :
|
||||||
{ x: 0, y: 0, width: 0, height: 0 };
|
{ x: 0, y: 0, width: 0, height: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,6 +63,10 @@ export class Group {
|
|||||||
return bound;
|
return bound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getModels(): Array<SVModel | Group> {
|
||||||
|
return this.models;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 位移group
|
* 位移group
|
||||||
* @param dx
|
* @param dx
|
||||||
@ -69,7 +74,7 @@ export class Group {
|
|||||||
*/
|
*/
|
||||||
translate(dx: number, dy: number) {
|
translate(dx: number, dy: number) {
|
||||||
this.models.map(item => {
|
this.models.map(item => {
|
||||||
if(item instanceof Group) {
|
if (item instanceof Group) {
|
||||||
item.translate(dx, dy);
|
item.translate(dx, dy);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -79,6 +84,27 @@ export class Group {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩放group
|
||||||
|
* @param center
|
||||||
|
* @param ratio
|
||||||
|
*/
|
||||||
|
scale(center: [number, number], ratio: number) {
|
||||||
|
this.models.map(item => {
|
||||||
|
if (item instanceof Group) {
|
||||||
|
item.scale(center, ratio);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const matrix = ext.transform(item.getMatrix(), [
|
||||||
|
['t', -center[0], -center[1]],
|
||||||
|
['s', ratio, ratio],
|
||||||
|
['t', center[0], center[1]],
|
||||||
|
]);
|
||||||
|
item.setMatrix(matrix);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空group
|
* 清空group
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
import { EdgeConfig, GraphData, NodeConfig } from "@antv/g6-core";
|
||||||
import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor";
|
import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor";
|
||||||
import { G6EdgeModel, G6NodeModel, Link, Model } from "../Model/modelData";
|
import { SVLink } from "../Model/SVLink";
|
||||||
|
import { SVModel } from "../Model/SVModel";
|
||||||
import { SV } from "../StructV";
|
import { SV } from "../StructV";
|
||||||
import { G6Data } from "../View/renderer";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,8 +90,8 @@ export const Util = {
|
|||||||
* @param groupTable
|
* @param groupTable
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
convertGroupTable2ModelList(groupTable: LayoutGroupTable): Model[] {
|
convertGroupTable2ModelList(groupTable: LayoutGroupTable): SVModel[] {
|
||||||
const list: Model[] = [];
|
const list: SVModel[] = [];
|
||||||
|
|
||||||
groupTable.forEach(item => {
|
groupTable.forEach(item => {
|
||||||
list.push(...item.modelList);
|
list.push(...item.modelList);
|
||||||
@ -104,13 +105,13 @@ export const Util = {
|
|||||||
* @param layoutGroup
|
* @param layoutGroup
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
convertG6Data(layoutGroup: LayoutGroup): G6Data {
|
convertG6Data(layoutGroup: LayoutGroup): GraphData {
|
||||||
let nodes = [...layoutGroup.element, ...layoutGroup.marker],
|
let nodes = [...layoutGroup.node, ...layoutGroup.marker],
|
||||||
edges = layoutGroup.link;
|
edges = layoutGroup.link;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: nodes.map(item => item.cloneProps()) as G6NodeModel[],
|
nodes: nodes.map(item => item.getG6ModelProps()) as NodeConfig[],
|
||||||
edges: edges.map(item => item.cloneProps()) as G6EdgeModel[]
|
edges: edges.map(item => item.getG6ModelProps()) as EdgeConfig[]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -118,10 +119,10 @@ export const Util = {
|
|||||||
* 将 modelList 转换到 G6Data
|
* 将 modelList 转换到 G6Data
|
||||||
* @param modelList
|
* @param modelList
|
||||||
*/
|
*/
|
||||||
convertModelList2G6Data(modelList: Model[]): G6Data {
|
convertModelList2G6Data(modelList: SVModel[]): GraphData {
|
||||||
return {
|
return {
|
||||||
nodes: <G6NodeModel[]>(modelList.filter(item => !(item instanceof Link)).map(item => item.cloneProps())),
|
nodes: <NodeConfig[]>(modelList.filter(item => !(item instanceof SVLink)).map(item => item.getG6ModelProps())),
|
||||||
edges: <G6EdgeModel[]>(modelList.filter(item => item instanceof Link).map(item => item.cloneProps()))
|
edges: <EdgeConfig[]>(modelList.filter(item => item instanceof SVLink).map(item => item.getG6ModelProps()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -137,3 +138,4 @@ export const Util = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
53
src/Model/SVLink.ts
Normal file
53
src/Model/SVLink.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { EdgeConfig, IEdge } from "@antv/g6-core";
|
||||||
|
import { Util } from "../Common/util";
|
||||||
|
import { LinkLabelOption, LinkOption, Style } from "../options";
|
||||||
|
import { SVModel } from "./SVModel";
|
||||||
|
import { SVNode } from "./SVNode";
|
||||||
|
|
||||||
|
export class SVLink extends SVModel {
|
||||||
|
public node: SVNode;
|
||||||
|
public target: SVNode;
|
||||||
|
public linkIndex: number;
|
||||||
|
|
||||||
|
public shadowG6Item: IEdge;
|
||||||
|
public G6Item: IEdge;
|
||||||
|
|
||||||
|
constructor(id: string, type: string, group: string, layout: string, node: SVNode, target: SVNode, index: number, options: LinkOption) {
|
||||||
|
super(id, type, group, layout);
|
||||||
|
|
||||||
|
this.node = node;
|
||||||
|
this.target = target;
|
||||||
|
this.linkIndex = index;
|
||||||
|
|
||||||
|
node.links.outDegree.push(this);
|
||||||
|
target.links.inDegree.push(this);
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected generateG6ModelProps(options: LinkOption): EdgeConfig {
|
||||||
|
let sourceAnchor = options.sourceAnchor,
|
||||||
|
targetAnchor = options.targetAnchor;
|
||||||
|
|
||||||
|
if(options.sourceAnchor && typeof options.sourceAnchor === 'function' && this.linkIndex !== null) {
|
||||||
|
sourceAnchor = options.sourceAnchor(this.linkIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.targetAnchor && typeof options.targetAnchor === 'function' && this.linkIndex !== null) {
|
||||||
|
targetAnchor = options.targetAnchor(this.linkIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
type: options.type,
|
||||||
|
source: this.node.id,
|
||||||
|
target: this.target.id,
|
||||||
|
sourceAnchor,
|
||||||
|
targetAnchor,
|
||||||
|
label: options.label,
|
||||||
|
style: Util.objectClone<Style>(options.style),
|
||||||
|
labelCfg: Util.objectClone<LinkLabelOption>(options.labelOptions),
|
||||||
|
curveOffset: options.curveOffset
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
51
src/Model/SVMarker.ts
Normal file
51
src/Model/SVMarker.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { INode, NodeConfig } from "@antv/g6-core";
|
||||||
|
import { Util } from "../Common/util";
|
||||||
|
import { MarkerOption, NodeLabelOption, Style } from "../options";
|
||||||
|
import { SVModel } from "./SVModel";
|
||||||
|
import { SVNode } from "./SVNode";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class SVMarker extends SVModel {
|
||||||
|
public target: SVNode;
|
||||||
|
public label: string | string[];
|
||||||
|
public anchor: number;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
this.target = target;
|
||||||
|
this.label = label;
|
||||||
|
|
||||||
|
this.target.marker = this;
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected generateG6ModelProps(options: MarkerOption): NodeConfig {
|
||||||
|
this.anchor = options.anchor;
|
||||||
|
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLabelSizeRadius(): number {
|
||||||
|
const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox();
|
||||||
|
return width > height? width: height;
|
||||||
|
}
|
||||||
|
};
|
||||||
170
src/Model/SVModel.ts
Normal file
170
src/Model/SVModel.ts
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import { Util } from "../Common/util";
|
||||||
|
import { ModelOption, Style } from "../options";
|
||||||
|
import { BoundingRect } from "../Common/boundingRect";
|
||||||
|
import { EdgeConfig, Item, NodeConfig } from "@antv/g6-core";
|
||||||
|
import { Point } from "@antv/g-base";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class SVModel {
|
||||||
|
public id: string;
|
||||||
|
public sourceType: string;
|
||||||
|
|
||||||
|
public group: string;
|
||||||
|
public layout: string;
|
||||||
|
public G6ModelProps: NodeConfig | EdgeConfig;
|
||||||
|
public shadowG6Item: Item;
|
||||||
|
public G6Item: Item;
|
||||||
|
|
||||||
|
public discarded: boolean;
|
||||||
|
public freed: boolean;
|
||||||
|
public leaked: boolean;
|
||||||
|
public generalStyle: Partial<Style>;
|
||||||
|
|
||||||
|
private transformMatrix: number[];
|
||||||
|
|
||||||
|
constructor(id: string, type: string, group: string, layout: string) {
|
||||||
|
this.id = id;
|
||||||
|
this.sourceType = type;
|
||||||
|
this.group = group;
|
||||||
|
this.layout = layout;
|
||||||
|
this.shadowG6Item = null;
|
||||||
|
this.G6Item = null;
|
||||||
|
this.discarded = false;
|
||||||
|
this.freed = false;
|
||||||
|
this.leaked = false;
|
||||||
|
this.transformMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* 定义 G6 model 的属性
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
protected generateG6ModelProps(options: ModelOption) {
|
||||||
|
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') {
|
||||||
|
Object.assign(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(attr === 'x' || attr === 'y') {
|
||||||
|
this.G6Item.updatePosition({ [attr]: value } as Point);
|
||||||
|
this.G6Item.refresh();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.G6Item.update(this.G6ModelProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新shadowG6Item
|
||||||
|
if(this.shadowG6Item) {
|
||||||
|
if(attr === 'x' || attr === 'y') {
|
||||||
|
this.shadowG6Item.updatePosition({ [attr]: value } as Point);
|
||||||
|
this.shadowG6Item.refresh();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.shadowG6Item.update(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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getG6ModelProps(): NodeConfig | EdgeConfig {
|
||||||
|
return Util.objectClone(this.G6ModelProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为节点model(SVNode)
|
||||||
|
*/
|
||||||
|
isNode(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
151
src/Model/SVNode.ts
Normal file
151
src/Model/SVNode.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { INode, NodeConfig } from "@antv/g6-core";
|
||||||
|
import { Util } from "../Common/util";
|
||||||
|
import { NodeIndexOption, NodeLabelOption, NodeOption, Style } from "../options";
|
||||||
|
import { SourceNode } from "../sources";
|
||||||
|
import { SVLink } from "./SVLink";
|
||||||
|
import { SVMarker } from "./SVMarker";
|
||||||
|
import { SVModel } from "./SVModel";
|
||||||
|
|
||||||
|
|
||||||
|
export class SVFreedLabel extends SVModel {
|
||||||
|
public node: SVNode;
|
||||||
|
|
||||||
|
constructor(id: string, type: string, group: string, layout: string, node: SVNode) {
|
||||||
|
super(id, type, group, layout);
|
||||||
|
|
||||||
|
this.node = node;
|
||||||
|
this.node.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: {
|
||||||
|
stroke: null,
|
||||||
|
fill: 'transparent'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class SVLeakAddress extends SVModel {
|
||||||
|
public node: SVNode;
|
||||||
|
private sourceId: string;
|
||||||
|
|
||||||
|
constructor(id: string, type: string, group: string, layout: string, node: SVNode) {
|
||||||
|
super(id, type, group, layout);
|
||||||
|
|
||||||
|
this.node = node;
|
||||||
|
this.sourceId = node.sourceId;
|
||||||
|
this.node.leakAddress = this;
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateG6ModelProps() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
type: 'rect',
|
||||||
|
label: this.sourceId,
|
||||||
|
labelCfg: {
|
||||||
|
style: {
|
||||||
|
fill: '#666',
|
||||||
|
fontSize: 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size: [0, 0],
|
||||||
|
style: {
|
||||||
|
stroke: null,
|
||||||
|
fill: 'transparent'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class SVNode extends SVModel {
|
||||||
|
public sourceId: string;
|
||||||
|
public sourceNode: SourceNode;
|
||||||
|
public marker: SVMarker;
|
||||||
|
public freedLabel: SVFreedLabel;
|
||||||
|
public leakAddress: SVLeakAddress;
|
||||||
|
public links: {
|
||||||
|
inDegree: SVLink[];
|
||||||
|
outDegree: SVLink[];
|
||||||
|
};
|
||||||
|
private label: string | string[];
|
||||||
|
|
||||||
|
public shadowG6Item: INode;
|
||||||
|
public G6Item: INode;
|
||||||
|
|
||||||
|
constructor(id: string, type: string, group: string, layout: string, sourceNode: SourceNode, label: string | string[], options: NodeOption) {
|
||||||
|
super(id, type, group, layout);
|
||||||
|
|
||||||
|
this.group = group;
|
||||||
|
this.layout = layout;
|
||||||
|
|
||||||
|
Object.keys(sourceNode).map(prop => {
|
||||||
|
if (prop !== 'id') {
|
||||||
|
this[prop] = sourceNode[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sourceNode = sourceNode;
|
||||||
|
this.sourceId = sourceNode.id.toString();
|
||||||
|
|
||||||
|
this.marker = null;
|
||||||
|
this.links = { inDegree: [], outDegree: [] };
|
||||||
|
this.sourceNode = sourceNode;
|
||||||
|
this.label = label;
|
||||||
|
this.G6ModelProps = this.generateG6ModelProps(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected generateG6ModelProps(options: NodeOption): NodeConfig {
|
||||||
|
let indexOptions = Util.objectClone<NodeIndexOption>(options.indexOptions);
|
||||||
|
|
||||||
|
if (indexOptions) {
|
||||||
|
Object.keys(indexOptions).map(key => {
|
||||||
|
let indexOptionItem = indexOptions[key];
|
||||||
|
indexOptionItem.value = this.sourceNode[key] ?? '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...this.sourceNode,
|
||||||
|
id: this.id,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
rotation: options.rotation || 0,
|
||||||
|
type: options.type,
|
||||||
|
size: options.size || [60, 30],
|
||||||
|
anchorPoints: options.anchorPoints,
|
||||||
|
label: this.label as string,
|
||||||
|
style: Util.objectClone<Style>(options.style),
|
||||||
|
labelCfg: Util.objectClone<NodeLabelOption>(options.labelOptions),
|
||||||
|
indexCfg: indexOptions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isNode(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,20 +1,25 @@
|
|||||||
import { Util } from "../Common/util";
|
import { Util } from "../Common/util";
|
||||||
import { Engine } from "../engine";
|
import { Engine } from "../engine";
|
||||||
import { ElementIndexOption, ElementOption, Layouter, LayoutGroupOptions, LinkOption, MarkerOption } from "../options";
|
import { LayoutCreator, LayoutGroupOptions, LinkOption, MarkerOption, NodeOption } from "../options";
|
||||||
import { sourceLinkData, SourceElement, LinkTarget, Sources } from "../sources";
|
import { sourceLinkData, LinkTarget, Sources, SourceNode } from "../sources";
|
||||||
import { SV } from "../StructV";
|
import { SV } from "../StructV";
|
||||||
import { Element, Link, Marker, Model } from "./modelData";
|
import { SVLink } from "./SVLink";
|
||||||
|
import { SVMarker } from "./SVMarker";
|
||||||
|
import { SVModel } from "./SVModel";
|
||||||
|
import { SVFreedLabel, SVLeakAddress, SVNode } from "./SVNode";
|
||||||
|
|
||||||
|
|
||||||
export type LayoutGroup = {
|
export type LayoutGroup = {
|
||||||
name: string;
|
name: string;
|
||||||
element: Element[];
|
node: SVNode[];
|
||||||
link: Link[];
|
freedLabel: SVFreedLabel[];
|
||||||
marker: Marker[];
|
leakAddress: SVLeakAddress[];
|
||||||
layouter: Layouter;
|
link: SVLink[];
|
||||||
layouterName: string;
|
marker: SVMarker[];
|
||||||
|
layoutCreator: LayoutCreator;
|
||||||
|
layout: string;
|
||||||
options: LayoutGroupOptions;
|
options: LayoutGroupOptions;
|
||||||
modelList: Model[];
|
modelList: SVModel[];
|
||||||
isHide: boolean;
|
isHide: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,56 +38,66 @@ export class ModelConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建element,link和marker
|
* 构建svnode,svlink 和 svmarker
|
||||||
* @param sourceList
|
* @param sourceList
|
||||||
*/
|
*/
|
||||||
public construct(sources: Sources): LayoutGroupTable {
|
public construct(sources: Sources): LayoutGroupTable {
|
||||||
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
||||||
layouterMap: { [key: string]: Layouter } = SV.registeredLayouter,
|
layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout,
|
||||||
optionsTable = this.engine.optionsTable;
|
optionsTable = this.engine.optionsTable;
|
||||||
|
|
||||||
Object.keys(sources).forEach(name => {
|
Object.keys(sources).forEach(group => {
|
||||||
let sourceGroup = sources[name],
|
let sourceGroup = sources[group],
|
||||||
layouterName = sourceGroup.layouter,
|
layout = sourceGroup.layouter,
|
||||||
layouter: Layouter = layouterMap[sourceGroup.layouter];
|
layoutCreator: LayoutCreator = layoutMap[layout];
|
||||||
|
|
||||||
if (!layouterName || !layouter) {
|
if (!layout || !layoutCreator) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sourceDataString: string = JSON.stringify(sourceGroup.data),
|
let sourceDataString: string = JSON.stringify(sourceGroup.data),
|
||||||
prevString: string = this.prevSourcesStringMap[name],
|
prevString: string = this.prevSourcesStringMap[group],
|
||||||
elementList: Element[] = [],
|
nodeList: SVNode[] = [],
|
||||||
markerList: Marker[] = [];
|
freedLabelList: SVFreedLabel[] = [],
|
||||||
|
leakAddress: SVLeakAddress[] = [],
|
||||||
|
markerList: SVMarker[] = [];
|
||||||
|
|
||||||
if (prevString === sourceDataString) {
|
if (prevString === sourceDataString) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: LayoutGroupOptions = optionsTable[layouterName],
|
const options: LayoutGroupOptions = optionsTable[layout],
|
||||||
sourceData = layouter.sourcesPreprocess(sourceGroup.data, options),
|
sourceData = layoutCreator.sourcesPreprocess(sourceGroup.data, options),
|
||||||
elementOptions = options.element || {},
|
nodeOptions = options.node || options['element'] || {},
|
||||||
markerOptions = options.marker || {};
|
markerOptions = options.marker || {};
|
||||||
|
|
||||||
elementList = this.constructElements(elementOptions, name, sourceData, layouterName);
|
nodeList = this.constructNodes(nodeOptions, group, sourceData, layout);
|
||||||
markerList = this.constructMarkers(name, markerOptions, elementList);
|
leakAddress = nodeList.map(item => item.leakAddress);
|
||||||
|
markerList = this.constructMarkers(group, layout, markerOptions, nodeList);
|
||||||
|
nodeList.forEach(item => {
|
||||||
|
if(item.freedLabel) {
|
||||||
|
freedLabelList.push(item.freedLabel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
layoutGroupTable.set(name, {
|
layoutGroupTable.set(group, {
|
||||||
name,
|
name: group,
|
||||||
element: elementList,
|
node: nodeList,
|
||||||
|
freedLabel: freedLabelList,
|
||||||
|
leakAddress: leakAddress,
|
||||||
link: [],
|
link: [],
|
||||||
marker: markerList,
|
marker: markerList,
|
||||||
options: options,
|
options: options,
|
||||||
layouter: layouter,
|
layoutCreator,
|
||||||
modelList: [...elementList, ...markerList],
|
modelList: [...nodeList, ...markerList, ...freedLabelList, ...leakAddress],
|
||||||
layouterName,
|
layout,
|
||||||
isHide: false
|
isHide: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
layoutGroupTable.forEach((layoutGroup: LayoutGroup) => {
|
layoutGroupTable.forEach((layoutGroup: LayoutGroup, group: string) => {
|
||||||
const linkOptions = layoutGroup.options.link || {},
|
const linkOptions = layoutGroup.options.link || {},
|
||||||
linkList: Link[] = this.constructLinks(linkOptions, layoutGroup.element, layoutGroupTable);
|
linkList: SVLink[] = this.constructLinks(linkOptions, layoutGroup.node, layoutGroupTable, group, layoutGroup.layout, );
|
||||||
|
|
||||||
layoutGroup.link = linkList;
|
layoutGroup.link = linkList;
|
||||||
layoutGroup.modelList.push(...linkList);
|
layoutGroup.modelList.push(...linkList);
|
||||||
@ -102,16 +117,16 @@ export class ModelConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从源数据构建 element 集
|
* 从源数据构建 node 集
|
||||||
* @param elementOptions
|
* @param nodeOptions
|
||||||
* @param groupName
|
* @param group
|
||||||
* @param sourceList
|
* @param sourceList
|
||||||
* @param layouterName
|
* @param layout
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructElements(elementOptions: { [key: string]: ElementOption }, groupName: string, sourceList: SourceElement[], layouterName: string): Element[] {
|
private constructNodes(nodeOptions: { [key: string]: NodeOption }, group: string, sourceList: SourceNode[], layout: string): SVNode[] {
|
||||||
let defaultElementType: string = 'default',
|
let defaultSourceNodeType: string = 'default',
|
||||||
elementList: Element[] = [];
|
nodeList: SVNode[] = [];
|
||||||
|
|
||||||
sourceList.forEach(item => {
|
sourceList.forEach(item => {
|
||||||
if (item === null) {
|
if (item === null) {
|
||||||
@ -119,62 +134,62 @@ export class ModelConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === undefined || item.type === null) {
|
if (item.type === undefined || item.type === null) {
|
||||||
item.type = defaultElementType;
|
item.type = defaultSourceNodeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
elementList.push(this.createElement(item, item.type, groupName, layouterName, elementOptions[item.type]));
|
nodeList.push(this.createNode(item, item.type, group, layout, nodeOptions[item.type]));
|
||||||
});
|
});
|
||||||
|
|
||||||
return elementList;
|
return nodeList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从配置和 element 集构建 link 集
|
* 从配置和 node 集构建 link 集
|
||||||
* @param linkOptions
|
* @param linkOptions
|
||||||
* @param elements
|
* @param nodes
|
||||||
* @param layoutGroupTable
|
* @param layoutGroupTable
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructLinks(linkOptions: { [key: string]: LinkOption }, elements: Element[], layoutGroupTable: LayoutGroupTable): Link[] {
|
private constructLinks(linkOptions: { [key: string]: LinkOption }, nodes: SVNode[], layoutGroupTable: LayoutGroupTable, group: string, layout: string): SVLink[] {
|
||||||
let linkList: Link[] = [],
|
let linkList: SVLink[] = [],
|
||||||
linkNames = Object.keys(linkOptions);
|
linkNames = Object.keys(linkOptions);
|
||||||
|
|
||||||
linkNames.forEach(name => {
|
linkNames.forEach(name => {
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
let element: Element = elements[i],
|
let node: SVNode = nodes[i],
|
||||||
sourceLinkData: sourceLinkData = element.sourceElement[name],
|
sourceLinkData: sourceLinkData = node.sourceNode[name],
|
||||||
targetElement: Element | Element[] = null,
|
targetNode: SVNode | SVNode[] = null,
|
||||||
link: Link = null;
|
link: SVLink = null;
|
||||||
|
|
||||||
if (sourceLinkData === undefined || sourceLinkData === null) {
|
if (sourceLinkData === undefined || sourceLinkData === null) {
|
||||||
element[name] = null;
|
node[name] = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 Element -------------------
|
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 SVNode -------------------
|
||||||
if (Array.isArray(sourceLinkData)) {
|
if (Array.isArray(sourceLinkData)) {
|
||||||
element[name] = sourceLinkData.map((item, index) => {
|
node[name] = sourceLinkData.map((item, index) => {
|
||||||
targetElement = this.fetchTargetElements(layoutGroupTable, element, item);
|
targetNode = this.fetchTargetNodes(layoutGroupTable, node, item);
|
||||||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
||||||
|
|
||||||
if (targetElement) {
|
if (targetNode) {
|
||||||
link = this.createLink(name, element, targetElement, index, linkOptions[name]);
|
link = this.createLink(name, group, layout, node, targetNode, index, linkOptions[name]);
|
||||||
linkList.push(link);
|
linkList.push(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isGeneralLink ? targetElement : null;
|
return isGeneralLink ? targetNode : null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
targetElement = this.fetchTargetElements(layoutGroupTable, element, sourceLinkData);
|
targetNode = this.fetchTargetNodes(layoutGroupTable, node, sourceLinkData);
|
||||||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
||||||
|
|
||||||
if (targetElement) {
|
if (targetNode) {
|
||||||
link = this.createLink(name, element, targetElement, null, linkOptions[name]);
|
link = this.createLink(name, group, layout, node, targetNode, null, linkOptions[name]);
|
||||||
linkList.push(link);
|
linkList.push(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
element[name] = isGeneralLink ? targetElement : null;
|
node[name] = isGeneralLink ? targetNode : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -183,25 +198,25 @@ export class ModelConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从配置和 element 集构建 marker 集
|
* 从配置和 node 集构建 marker 集
|
||||||
* @param markerOptions
|
* @param markerOptions
|
||||||
* @param elements
|
* @param nodes
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private constructMarkers(groupName: string, markerOptions: { [key: string]: MarkerOption }, elements: Element[]): Marker[] {
|
private constructMarkers(group: string, layout: string, markerOptions: { [key: string]: MarkerOption }, nodes: SVNode[]): SVMarker[] {
|
||||||
let markerList: Marker[] = [],
|
let markerList: SVMarker[] = [],
|
||||||
markerNames = Object.keys(markerOptions);
|
markerNames = Object.keys(markerOptions);
|
||||||
|
|
||||||
markerNames.forEach(name => {
|
markerNames.forEach(name => {
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
let element = elements[i],
|
let node = nodes[i],
|
||||||
markerData = element[name];
|
markerData = node[name];
|
||||||
|
|
||||||
// 若没有指针字段的结点则跳过
|
// 若没有指针字段的结点则跳过
|
||||||
if (!markerData) continue;
|
if (!markerData) continue;
|
||||||
|
|
||||||
let id = `${groupName}.${name}.${Array.isArray(markerData) ? markerData.join('-') : markerData}`,
|
let id = `${group}.${name}.${Array.isArray(markerData) ? markerData.join('-') : markerData}`,
|
||||||
marker = this.createMarker(id, name, markerData, element, markerOptions[name]);
|
marker = this.createMarker(id, name, markerData, group, layout, node, markerOptions[name]);
|
||||||
|
|
||||||
markerList.push(marker);
|
markerList.push(marker);
|
||||||
}
|
}
|
||||||
@ -213,19 +228,19 @@ export class ModelConstructor {
|
|||||||
/**
|
/**
|
||||||
* 求解label文本
|
* 求解label文本
|
||||||
* @param label
|
* @param label
|
||||||
* @param sourceElement
|
* @param sourceNode
|
||||||
*/
|
*/
|
||||||
private resolveElementLabel(label: string | string[], sourceElement: SourceElement): string {
|
private resolveNodeLabel(label: string | string[], sourceNode: SourceNode): string {
|
||||||
let targetLabel: any = '';
|
let targetLabel: any = '';
|
||||||
|
|
||||||
if (Array.isArray(label)) {
|
if (Array.isArray(label)) {
|
||||||
targetLabel = label.map(item => this.parserElementContent(sourceElement, item) ?? '');
|
targetLabel = label.map(item => this.parserNodeContent(sourceNode, item) ?? '');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
targetLabel = this.parserElementContent(sourceElement, label);
|
targetLabel = this.parserNodeContent(sourceNode, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(targetLabel === 'undefined') {
|
if (targetLabel === 'undefined') {
|
||||||
targetLabel = '';
|
targetLabel = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,90 +248,67 @@ export class ModelConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 求解index文本
|
* 元素工厂,创建 Node
|
||||||
* @param indexOptions
|
* @param sourceNode
|
||||||
* @param sourceElement
|
* @param sourceNodeType
|
||||||
*/
|
* @param group
|
||||||
private resolveElementIndex(indexOptions: ElementIndexOption, sourceElement: SourceElement) {
|
* @param layout
|
||||||
if(indexOptions === undefined || indexOptions === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(indexOptions).map(key => {
|
|
||||||
let indexOptionItem = indexOptions[key];
|
|
||||||
indexOptionItem.value = sourceElement[key] ?? '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 元素工厂,创建Element
|
|
||||||
* @param sourceElement
|
|
||||||
* @param elementName
|
|
||||||
* @param groupName
|
|
||||||
* @param layouterName
|
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
private createElement(sourceElement: SourceElement, elementName: string, groupName: string, layouterName: string, options: ElementOption): Element {
|
private createNode(sourceNode: SourceNode, sourceNodeType: string, group: string, layout: string, options: NodeOption): SVNode {
|
||||||
let element: Element = undefined,
|
let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode),
|
||||||
label: string | string[] = this.resolveElementLabel(options.label, sourceElement),
|
id = sourceNodeType + '.' + sourceNode.id.toString(),
|
||||||
id = elementName + '.' + sourceElement.id.toString();
|
node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options);
|
||||||
|
node.leakAddress = new SVLeakAddress(`${id}-leak-adress`, sourceNodeType, group, layout, node);
|
||||||
|
|
||||||
|
if(node.freed) {
|
||||||
|
node.freedLabel = new SVFreedLabel(`${id}-freed-label`, sourceNodeType, group, layout, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
element = new Element(id, elementName, groupName, layouterName, sourceElement);
|
|
||||||
element.initProps(options);
|
|
||||||
element.set('label', label);
|
|
||||||
element.sourceElement = sourceElement;
|
|
||||||
// 处理element的index文本
|
|
||||||
this.resolveElementIndex(element.get('indexCfg'), sourceElement);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 外部指针工厂,创建marker
|
* 外部指针工厂,创建marker
|
||||||
* @param id
|
* @param id
|
||||||
* @param markerName
|
* @param markerName
|
||||||
* @param label
|
* @param markerData
|
||||||
|
* @param group
|
||||||
|
* @param layout
|
||||||
* @param target
|
* @param target
|
||||||
* @param options
|
* @param options
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
private createMarker(id: string, markerName: string, markerData: string | string[], target: Element, options: MarkerOption): Marker {
|
private createMarker(id: string, markerName: string, markerData: string | string[], group: string, layout: string, target: SVNode, options: MarkerOption): SVMarker {
|
||||||
let marker = undefined;
|
return new SVMarker(id, markerName, group, layout, markerData, target, options);;
|
||||||
|
|
||||||
marker = new Marker(id, markerName, markerData, target);
|
|
||||||
marker.initProps(options);
|
|
||||||
|
|
||||||
return marker;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连线工厂,创建Link
|
* 连线工厂,创建Link
|
||||||
* @param linkName
|
* @param linkName
|
||||||
* @param element
|
* @param group
|
||||||
|
* @param layout
|
||||||
|
* @param node
|
||||||
* @param target
|
* @param target
|
||||||
* @param index
|
* @param index
|
||||||
* @param options
|
* @param options
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
private createLink(linkName: string, element: Element, target: Element, index: number, options: LinkOption): Link {
|
private createLink(linkName: string, group: string, layout: string, node: SVNode, target: SVNode, index: number, options: LinkOption): SVLink {
|
||||||
let link = undefined,
|
let id = `${linkName}(${node.id}-${target.id})`;
|
||||||
id = `${linkName}(${element.id}-${target.id})`;
|
return new SVLink(id, linkName, group, layout, node, target, index, options);
|
||||||
|
|
||||||
link = new Link(id, linkName, element, target, index);
|
|
||||||
link.initProps(options);
|
|
||||||
|
|
||||||
return link;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析元素文本内容
|
* 解析元素文本内容
|
||||||
* @param sourceElement
|
* @param sourceNode
|
||||||
* @param formatLabel
|
* @param formatLabel
|
||||||
*/
|
*/
|
||||||
private parserElementContent(sourceElement: SourceElement, formatLabel: string): string {
|
private parserNodeContent(sourceNode: SourceNode, formatLabel: string): string {
|
||||||
let fields = Util.textParser(formatLabel);
|
let fields = Util.textParser(formatLabel);
|
||||||
|
|
||||||
if (Array.isArray(fields)) {
|
if (Array.isArray(fields)) {
|
||||||
let values = fields.map(item => sourceElement[item]);
|
let values = fields.map(item => sourceNode[item]);
|
||||||
|
|
||||||
values.map((item, index) => {
|
values.map((item, index) => {
|
||||||
formatLabel = formatLabel.replace('[' + fields[index] + ']', item);
|
formatLabel = formatLabel.replace('[' + fields[index] + ']', item);
|
||||||
@ -328,17 +320,17 @@ export class ModelConstructor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 由source中的连接字段获取真实的连接目标元素
|
* 由source中的连接字段获取真实的连接目标元素
|
||||||
* @param elementContainer
|
* @param nodeContainer
|
||||||
* @param element
|
* @param node
|
||||||
* @param linkTarget
|
* @param linkTarget
|
||||||
*/
|
*/
|
||||||
private fetchTargetElements(layoutGroupTable: LayoutGroupTable, element: Element, linkTarget: LinkTarget): Element {
|
private fetchTargetNodes(layoutGroupTable: LayoutGroupTable, node: SVNode, linkTarget: LinkTarget): SVNode {
|
||||||
let groupName: string = element.groupName,
|
let group: string = node.group,
|
||||||
elementName = element.type,
|
sourceNodeType = node.sourceType,
|
||||||
elementList: Element[],
|
nodeList: SVNode[],
|
||||||
targetId = linkTarget,
|
targetId = linkTarget,
|
||||||
targetGroupName = groupName,
|
targetGroupName = group,
|
||||||
targetElement = null;
|
targetNode = null;
|
||||||
|
|
||||||
if (linkTarget === null || linkTarget === undefined) {
|
if (linkTarget === null || linkTarget === undefined) {
|
||||||
return null;
|
return null;
|
||||||
@ -353,13 +345,13 @@ export class ModelConstructor {
|
|||||||
targetId = info.pop();
|
targetId = info.pop();
|
||||||
|
|
||||||
if (info.length > 1) {
|
if (info.length > 1) {
|
||||||
elementName = info.pop();
|
sourceNodeType = info.pop();
|
||||||
targetGroupName = info.pop();
|
targetGroupName = info.pop();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let field = info.pop();
|
let field = info.pop();
|
||||||
if (layoutGroupTable.get(targetGroupName).element.find(item => item.type === field)) {
|
if (layoutGroupTable.get(targetGroupName).node.find(item => item.sourceType === field)) {
|
||||||
elementName = field;
|
sourceNodeType = field;
|
||||||
}
|
}
|
||||||
else if (layoutGroupTable.has(field)) {
|
else if (layoutGroupTable.has(field)) {
|
||||||
targetGroupName = field;
|
targetGroupName = field;
|
||||||
@ -369,15 +361,15 @@ export class ModelConstructor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
elementList = layoutGroupTable.get(targetGroupName).element.filter(item => item.type === elementName);
|
nodeList = layoutGroupTable.get(targetGroupName).node.filter(item => item.sourceType === sourceNodeType);
|
||||||
|
|
||||||
// 若目标element不存在,返回null
|
// 若目标node不存在,返回null
|
||||||
if (elementList === undefined) {
|
if (nodeList === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
targetElement = elementList.find(item => item.sourceId === targetId);
|
targetNode = nodeList.find(item => item.sourceId === targetId);
|
||||||
return targetElement || null;
|
return targetNode || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,337 +0,0 @@
|
|||||||
import { Util } from "../Common/util";
|
|
||||||
import { ElementIndexOption, ElementLabelOption, ElementOption, LinkLabelOption, LinkOption, MarkerOption, Style } from "../options";
|
|
||||||
import { SourceElement } from "../sources";
|
|
||||||
import { BoundingRect } from "../Common/boundingRect";
|
|
||||||
import { SV } from './../StructV';
|
|
||||||
|
|
||||||
|
|
||||||
export interface G6NodeModel {
|
|
||||||
id: string;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
rotation: number;
|
|
||||||
type: string;
|
|
||||||
size: number | [number, number];
|
|
||||||
anchorPoints: [number, number];
|
|
||||||
label: string | string[];
|
|
||||||
style: Style;
|
|
||||||
labelCfg: ElementLabelOption;
|
|
||||||
indexCfg?: ElementIndexOption;
|
|
||||||
markerId: string;
|
|
||||||
SVLayouter: string;
|
|
||||||
SVModelType: string;
|
|
||||||
SVModelName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export interface G6EdgeModel {
|
|
||||||
id: string;
|
|
||||||
source: string | number;
|
|
||||||
target: string | number;
|
|
||||||
type: string;
|
|
||||||
curveOffset: number;
|
|
||||||
sourceAnchor: number | ((index: number) => number);
|
|
||||||
targetAnchor: number | ((index: number) => number);
|
|
||||||
label: string;
|
|
||||||
style: Style;
|
|
||||||
labelCfg: LinkLabelOption;
|
|
||||||
SVModelType: string;
|
|
||||||
SVModelName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export class Model {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
isLeak: boolean;
|
|
||||||
isDestroy: boolean;
|
|
||||||
modelType: string;
|
|
||||||
|
|
||||||
props: G6NodeModel | G6EdgeModel;
|
|
||||||
shadowG6Item;
|
|
||||||
renderG6Item;
|
|
||||||
G6Item;
|
|
||||||
|
|
||||||
constructor(id: string, type: string) {
|
|
||||||
this.id = id;
|
|
||||||
this.type = type;
|
|
||||||
this.shadowG6Item = null;
|
|
||||||
this.renderG6Item = null;
|
|
||||||
this.G6Item = null;
|
|
||||||
this.props = <G6NodeModel | G6EdgeModel>{ };
|
|
||||||
this.isLeak = false;
|
|
||||||
this.isDestroy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* 定义 G6 model 的属性
|
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
protected defineProps(option: ElementOption | LinkOption | MarkerOption) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化 G6 model 的属性
|
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
initProps(option: ElementOption | LinkOption | MarkerOption) {
|
|
||||||
this.props = this.defineProps(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 克隆 G6 model 的属性
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
cloneProps() {
|
|
||||||
return Util.objectClone(this.props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 G6 model 的属性
|
|
||||||
* @param attr
|
|
||||||
*/
|
|
||||||
get(attr: string): any {
|
|
||||||
return this.props[attr];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 G6 model 的属性
|
|
||||||
* @param attr
|
|
||||||
* @param value
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
set(attr: string | object, value?: any) {
|
|
||||||
if(this.isDestroy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof attr === 'object') {
|
|
||||||
Object.keys(attr).map(item => {
|
|
||||||
this.set(item, attr[item]);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.props[attr] === value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(attr === 'style' || attr === 'labelCfg') {
|
|
||||||
Object.assign(this.props[attr], value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.props[attr] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.G6Item === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(attr === 'rotation') {
|
|
||||||
const matrix = Util.calcRotateMatrix(this.getMatrix(), value);
|
|
||||||
this.set('style', { matrix });
|
|
||||||
}
|
|
||||||
|
|
||||||
if(attr === 'x' || attr === 'y') {
|
|
||||||
this.G6Item.updatePosition({
|
|
||||||
[attr]: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.G6Item.update(this.props);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取包围盒
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
getBound(): BoundingRect {
|
|
||||||
return this.G6Item.getBBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取变换矩阵
|
|
||||||
*/
|
|
||||||
getMatrix(): number[] {
|
|
||||||
if(this.G6Item === null) return null;
|
|
||||||
const Mat3 = SV.G6.Util.mat3;
|
|
||||||
return Mat3.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
getType(): string {
|
|
||||||
return this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
getModelType(): string {
|
|
||||||
return this.modelType;
|
|
||||||
}
|
|
||||||
|
|
||||||
getId(): string {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class Element extends Model {
|
|
||||||
sourceElement: SourceElement;
|
|
||||||
sourceId: string;
|
|
||||||
groupName: string;
|
|
||||||
layouterName: string;
|
|
||||||
freed: boolean;
|
|
||||||
markers: { [key: string]: Marker };
|
|
||||||
links: {
|
|
||||||
inDegree: Link[];
|
|
||||||
outDegree: Link[];
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(id: string, type: string, group: string, layouter: string, sourceElement: SourceElement) {
|
|
||||||
super(id, type);
|
|
||||||
this.modelType = 'element';
|
|
||||||
|
|
||||||
if(type === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.groupName = group;
|
|
||||||
this.layouterName = layouter;
|
|
||||||
this.freed = false;
|
|
||||||
|
|
||||||
Object.keys(sourceElement).map(prop => {
|
|
||||||
if(prop !== 'id') {
|
|
||||||
this[prop] = sourceElement[prop];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sourceId = this.id.split('.')[1];
|
|
||||||
this.sourceElement = sourceElement;
|
|
||||||
this.markers = { };
|
|
||||||
this.links = { inDegree: [], outDegree: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected defineProps(option: ElementOption): G6NodeModel {
|
|
||||||
return {
|
|
||||||
...this.sourceElement,
|
|
||||||
id: this.id,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
rotation: option.rotation || 0,
|
|
||||||
type: option.type,
|
|
||||||
size: option.size || [60, 30],
|
|
||||||
anchorPoints: option.anchorPoints,
|
|
||||||
label: option.label,
|
|
||||||
style: Util.objectClone<Style>(option.style),
|
|
||||||
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
|
||||||
indexCfg: Util.objectClone<ElementIndexOption>(option.indexOptions),
|
|
||||||
markerId: null,
|
|
||||||
SVLayouter: this.layouterName,
|
|
||||||
SVModelType: 'element',
|
|
||||||
SVModelName: this.type
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getSourceId(): string {
|
|
||||||
return this.sourceId;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class Link extends Model {
|
|
||||||
element: Element;
|
|
||||||
target: Element;
|
|
||||||
index: number;
|
|
||||||
|
|
||||||
constructor(id: string, type: string, element: Element, target: Element, index: number) {
|
|
||||||
super(id, type);
|
|
||||||
this.modelType = 'link';
|
|
||||||
|
|
||||||
this.element = element;
|
|
||||||
this.target = target;
|
|
||||||
this.index = index;
|
|
||||||
|
|
||||||
element.links.outDegree.push(this);
|
|
||||||
target.links.inDegree.push(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected defineProps(option: LinkOption): G6EdgeModel {
|
|
||||||
let sourceAnchor = option.sourceAnchor,
|
|
||||||
targetAnchor = option.targetAnchor;
|
|
||||||
|
|
||||||
if(option.sourceAnchor && typeof option.sourceAnchor === 'function' && this.index !== null) {
|
|
||||||
sourceAnchor = option.sourceAnchor(this.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(option.targetAnchor && typeof option.targetAnchor === 'function' && this.index !== null) {
|
|
||||||
targetAnchor = option.targetAnchor(this.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
type: option.type,
|
|
||||||
source: this.element.id,
|
|
||||||
target: this.target.id,
|
|
||||||
sourceAnchor,
|
|
||||||
targetAnchor,
|
|
||||||
label: option.label,
|
|
||||||
style: Util.objectClone<Style>(option.style),
|
|
||||||
labelCfg: Util.objectClone<LinkLabelOption>(option.labelOptions),
|
|
||||||
curveOffset: option.curveOffset,
|
|
||||||
SVModelType: 'link',
|
|
||||||
SVModelName: this.type
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class Marker extends Model {
|
|
||||||
target: Element;
|
|
||||||
label: string | string[];
|
|
||||||
anchor: number;
|
|
||||||
|
|
||||||
constructor(id: string, type: string, label: string | string[], target: Element) {
|
|
||||||
super(id, type);
|
|
||||||
this.modelType = 'marker';
|
|
||||||
|
|
||||||
this.target = target;
|
|
||||||
this.label = label;
|
|
||||||
|
|
||||||
this.target.set('markerId', id);
|
|
||||||
this.target.markers[type] = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLabelSizeRadius(): number {
|
|
||||||
const { width, height } = this.G6Item.getContainer().getChildren()[2].getBBox();
|
|
||||||
return width > height? width: height;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected defineProps(option: MarkerOption): G6NodeModel {
|
|
||||||
this.anchor = option.anchor;
|
|
||||||
|
|
||||||
const type = option.type,
|
|
||||||
defaultSize: [number, number] = type === 'pointer'? [8, 30]: [12, 12];
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
rotation: 0,
|
|
||||||
type: option.type || 'marker',
|
|
||||||
size: option.size || defaultSize,
|
|
||||||
anchorPoints: null,
|
|
||||||
label: typeof this.label === 'string'? this.label: this.label.join(', '),
|
|
||||||
style: Util.objectClone<Style>(option.style),
|
|
||||||
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
|
|
||||||
markerId: null,
|
|
||||||
SVLayouter: null,
|
|
||||||
SVModelType: 'marker',
|
|
||||||
SVModelName: this.type
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import * as G6 from "./../Lib/g6.js";
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
|
||||||
export default G6.registerNode('binary-tree-node', {
|
export default G6.registerNode('binary-tree-node', {
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import * as G6 from "../Lib/g6.js";
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
|
||||||
export default G6.registerNode('clen-queue-pointer', {
|
export default G6.registerNode('clen-queue-pointer', {
|
||||||
draw(cfg, group) {
|
draw(cfg, group) {
|
||||||
// console.log(cfg);
|
let id = cfg.id as string;
|
||||||
const index = cfg.id.split('-')[1];
|
|
||||||
const len = cfg.id.split('-')[2];
|
const index = parseInt(id.split('-')[1]);
|
||||||
|
const len = parseInt(id.split('-')[2]);
|
||||||
const keyShape = group.addShape('path', {
|
const keyShape = group.addShape('path', {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: 0,
|
x: 0,
|
||||||
@ -16,6 +17,7 @@ export default G6.registerNode('clen-queue-pointer', {
|
|||||||
},
|
},
|
||||||
name: 'pointer-path'
|
name: 'pointer-path'
|
||||||
});
|
});
|
||||||
|
|
||||||
const angle = index * Math.PI * 2 / len;
|
const angle = index * Math.PI * 2 / len;
|
||||||
if (cfg.label) {
|
if (cfg.label) {
|
||||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||||
@ -32,7 +34,10 @@ export default G6.registerNode('clen-queue-pointer', {
|
|||||||
},
|
},
|
||||||
name: 'bgRect'
|
name: 'bgRect'
|
||||||
});
|
});
|
||||||
let pointerText = cfg.label.split('-')[0];
|
|
||||||
|
let label = cfg.label as string;
|
||||||
|
|
||||||
|
let pointerText = label.split('-')[0];
|
||||||
let y = pointerText=="front"?30:15;
|
let y = pointerText=="front"?30:15;
|
||||||
const text = group.addShape('text', {
|
const text = group.addShape('text', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
import * as G6 from "./../Lib/g6.js";
|
|
||||||
|
|
||||||
|
|
||||||
export default G6.registerNode('cursor', {
|
export default G6.registerNode('cursor', {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import * as G6 from "./../Lib/g6.js";
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
|
||||||
export default G6.registerNode('indexed-node', {
|
export default G6.registerNode('indexed-node', {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import * as G6 from "./../Lib/g6.js";
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
|
||||||
export default G6.registerNode('link-list-node', {
|
export default G6.registerNode('link-list-node', {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import * as G6 from "../Lib/g6.js";
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
|
||||||
export default G6.registerNode('pointer', {
|
export default G6.registerNode('pointer', {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import * as G6 from "./../Lib/g6.js";
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
|
||||||
export default G6.registerNode('tri-tree-node', {
|
export default G6.registerNode('tri-tree-node', {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import * as G6 from "./../Lib/g6.js";
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Engine } from "./engine";
|
|||||||
import { Bound } from "./Common/boundingRect";
|
import { Bound } from "./Common/boundingRect";
|
||||||
import { Group } from "./Common/group";
|
import { Group } from "./Common/group";
|
||||||
import pointer from "./RegisteredShape/pointer";
|
import pointer from "./RegisteredShape/pointer";
|
||||||
import * as G6 from "./Lib/g6.js";
|
import G6, { Util } from '@antv/g6';
|
||||||
import linkListNode from "./RegisteredShape/linkListNode";
|
import linkListNode from "./RegisteredShape/linkListNode";
|
||||||
import binaryTreeNode from "./RegisteredShape/binaryTreeNode";
|
import binaryTreeNode from "./RegisteredShape/binaryTreeNode";
|
||||||
import CLenQueuePointer from "./RegisteredShape/clenQueuePointer";
|
import CLenQueuePointer from "./RegisteredShape/clenQueuePointer";
|
||||||
@ -10,9 +10,10 @@ import twoCellNode from "./RegisteredShape/twoCellNode";
|
|||||||
import Cursor from "./RegisteredShape/cursor";
|
import Cursor from "./RegisteredShape/cursor";
|
||||||
import { Vector } from "./Common/vector";
|
import { Vector } from "./Common/vector";
|
||||||
import indexedNode from "./RegisteredShape/indexedNode";
|
import indexedNode from "./RegisteredShape/indexedNode";
|
||||||
import { EngineOptions, Layouter } from "./options";
|
import { EngineOptions, LayoutCreator } from "./options";
|
||||||
import { SourceElement } from "./sources";
|
import { SVNode } from "./Model/SVNode";
|
||||||
import { Element } from "./Model/modelData";
|
import { SourceNode } from "./sources";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface StructV {
|
export interface StructV {
|
||||||
@ -25,16 +26,16 @@ export interface StructV {
|
|||||||
|
|
||||||
registeredShape: any[];
|
registeredShape: any[];
|
||||||
|
|
||||||
registeredLayouter: { [key: string]: Layouter },
|
registeredLayout: { [key: string]: LayoutCreator },
|
||||||
|
|
||||||
registerShape: Function,
|
registerShape: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册一个布局器
|
* 注册一个布局器
|
||||||
* @param name
|
* @param name
|
||||||
* @param layouter
|
* @param layout
|
||||||
*/
|
*/
|
||||||
registerLayouter(name: string, layouter);
|
registerLayout(name: string, layoutCreator: LayoutCreator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -45,10 +46,10 @@ export const SV: StructV = function(DOMContainer: HTMLElement, engineOptions: En
|
|||||||
SV.Group = Group;
|
SV.Group = Group;
|
||||||
SV.Bound = Bound;
|
SV.Bound = Bound;
|
||||||
SV.Vector = Vector;
|
SV.Vector = Vector;
|
||||||
SV.Mat3 = G6.Util.mat3;
|
SV.Mat3 = Util.mat3;
|
||||||
SV.G6 = G6;
|
SV.G6 = G6;
|
||||||
|
|
||||||
SV.registeredLayouter = {};
|
SV.registeredLayout = {};
|
||||||
SV.registeredShape = [
|
SV.registeredShape = [
|
||||||
pointer,
|
pointer,
|
||||||
linkListNode,
|
linkListNode,
|
||||||
@ -60,25 +61,25 @@ SV.registeredShape = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
SV.registerShape = G6.registerNode;
|
SV.registerShape = G6.registerNode;
|
||||||
SV.registerLayouter = function(name: string, layouter: Layouter) {
|
SV.registerLayout = function(name: string, layoutCreator: LayoutCreator) {
|
||||||
|
|
||||||
if(typeof layouter.sourcesPreprocess !== 'function') {
|
if(typeof layoutCreator.sourcesPreprocess !== 'function') {
|
||||||
layouter.sourcesPreprocess = function(data: SourceElement[]): SourceElement[] {
|
layoutCreator.sourcesPreprocess = function(data: SourceNode[]): SourceNode[] {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof layouter.defineLeakRule !== 'function') {
|
if(typeof layoutCreator.defineLeakRule !== 'function') {
|
||||||
layouter.defineLeakRule = function(elements: Element[]): Element[] {
|
layoutCreator.defineLeakRule = function(nodes: SVNode[]): SVNode[] {
|
||||||
return elements;
|
return nodes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof layouter.defineOptions !== 'function' || typeof layouter.layout !== 'function') {
|
if(typeof layoutCreator.defineOptions !== 'function' || typeof layoutCreator.layout !== 'function') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SV.registeredLayouter[name] = layouter;
|
SV.registeredLayout[name] = layoutCreator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { Model } from "../Model/modelData";
|
import { Util } from '@antv/g6';
|
||||||
import { SV } from "../StructV";
|
|
||||||
|
|
||||||
|
|
||||||
export type animationConfig = {
|
export type animationConfig = {
|
||||||
duration: number;
|
duration: number;
|
||||||
timingFunction: string;
|
timingFunction: string;
|
||||||
payload?: any;
|
|
||||||
callback?: Function;
|
callback?: Function;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -14,39 +13,36 @@ export type animationConfig = {
|
|||||||
* 动画表
|
* 动画表
|
||||||
*/
|
*/
|
||||||
export const Animations = {
|
export const Animations = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加节点 / 边时的动画效果
|
* 添加节点 / 边时的动画效果
|
||||||
* @param model
|
* @param G6Item
|
||||||
* @param animationConfig
|
* @param animationConfig
|
||||||
*/
|
*/
|
||||||
animate_append(model: Model, animationConfig: animationConfig) {
|
APPEND(G6Item: any, animationConfig: animationConfig) {
|
||||||
model.G6Item === null && console.log(model);
|
const type = G6Item.getType(),
|
||||||
|
group = G6Item.getContainer(),
|
||||||
|
Mat3 = Util.mat3,
|
||||||
|
animateCfg = {
|
||||||
|
duration: animationConfig.duration,
|
||||||
|
easing: animationConfig.timingFunction,
|
||||||
|
callback: animationConfig.callback
|
||||||
|
};
|
||||||
|
|
||||||
const G6Item = model.G6Item,
|
if (type === 'node') {
|
||||||
type = G6Item.getType(),
|
|
||||||
group = G6Item.getContainer(),
|
|
||||||
Mat3 = SV.Mat3,
|
|
||||||
animateCfg = {
|
|
||||||
duration: animationConfig.duration,
|
|
||||||
easing: animationConfig.timingFunction,
|
|
||||||
callback: animationConfig.callback
|
|
||||||
};
|
|
||||||
|
|
||||||
if(type === 'node') {
|
|
||||||
let matrix = group.getMatrix(),
|
let matrix = group.getMatrix(),
|
||||||
targetMatrix = Mat3.clone(matrix);
|
targetMatrix = Mat3.clone(matrix);
|
||||||
|
|
||||||
Mat3.scale(matrix, matrix, [0, 0]);
|
Mat3.scale(matrix, matrix, [0, 0]);
|
||||||
Mat3.scale(targetMatrix, targetMatrix, [1, 1]);
|
Mat3.scale(targetMatrix, targetMatrix, [1, 1]);
|
||||||
|
|
||||||
group.attr({ opacity: 0, matrix });
|
group.attr({ matrix, opacity: 0 });
|
||||||
group.animate({ opacity: 1, matrix: targetMatrix }, animateCfg);
|
group.animate({ matrix: targetMatrix, opacity: 1 }, animateCfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type === 'edge') {
|
if (type === 'edge') {
|
||||||
const line = group.get('children')[0],
|
const line = group.get('children')[0],
|
||||||
length = line.getTotalLength();
|
length = line.getTotalLength();
|
||||||
|
|
||||||
line.attr({ lineDash: [0, length], opacity: 0 });
|
line.attr({ lineDash: [0, length], opacity: 0 });
|
||||||
line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg);
|
line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg);
|
||||||
@ -55,52 +51,50 @@ export const Animations = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除节点 / 边时的动画效果
|
* 移除节点 / 边时的动画效果
|
||||||
* @param model
|
* @param G6Item
|
||||||
* @param animationConfig
|
* @param animationConfig
|
||||||
*/
|
*/
|
||||||
animate_remove(model: Model, animationConfig: animationConfig) {
|
REMOVE(G6Item: any, animationConfig: animationConfig) {
|
||||||
const G6Item = model.G6Item,
|
const type = G6Item.getType(),
|
||||||
type = G6Item.getType(),
|
group = G6Item.getContainer(),
|
||||||
group = G6Item.getContainer(),
|
Mat3 = Util.mat3,
|
||||||
Mat3 = SV.Mat3,
|
animateCfg = {
|
||||||
animateCfg = {
|
duration: animationConfig.duration,
|
||||||
duration: animationConfig.duration,
|
easing: animationConfig.timingFunction,
|
||||||
easing: animationConfig.timingFunction,
|
callback: animationConfig.callback
|
||||||
callback: animationConfig.callback
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if(type === 'node') {
|
if (type === 'node') {
|
||||||
let matrix = Mat3.clone(group.getMatrix());
|
let matrix = Mat3.clone(group.getMatrix());
|
||||||
|
|
||||||
Mat3.scale(matrix, matrix, [0, 0]);
|
Mat3.scale(matrix, matrix, [0, 0]);
|
||||||
group.animate({ opacity: 0, matrix }, animateCfg);
|
group.animate({ opacity: 0, matrix }, animateCfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type === 'edge') {
|
if (type === 'edge') {
|
||||||
const line = group.get('children')[0],
|
const line = group.get('children')[0],
|
||||||
length = line.getTotalLength();
|
length = line.getTotalLength();
|
||||||
|
|
||||||
line.animate({ lineDash: [0, length], opacity: 0 }, animateCfg);
|
line.animate({ lineDash: [0, length], opacity: 0 }, animateCfg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移动节点 / 边的动画
|
*
|
||||||
* @param model
|
* @param G6Item
|
||||||
* @param animationConfig
|
* @param animationConfig
|
||||||
*/
|
*/
|
||||||
animate_fadeOut(model: Model, animationConfig: animationConfig) {
|
FADE_IN(G6Item: any, animationConfig: animationConfig) {
|
||||||
const G6Item = model.G6Item,
|
const group = G6Item.getContainer(),
|
||||||
group = G6Item.getContainer(),
|
animateCfg = {
|
||||||
animateCfg = {
|
duration: animationConfig.duration,
|
||||||
duration: 1200,
|
easing: animationConfig.timingFunction,
|
||||||
easing: animationConfig.timingFunction,
|
callback: animationConfig.callback
|
||||||
callback: animationConfig.callback
|
};
|
||||||
};
|
|
||||||
|
|
||||||
group.animate({ opacity: 0 }, animateCfg);
|
group.attr({ opacity: 0 });
|
||||||
|
group.animate({ opacity: 1 }, animateCfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,268 +0,0 @@
|
|||||||
import { Engine } from "../../engine";
|
|
||||||
import { Model, Marker } from "../../Model/modelData";
|
|
||||||
import { AnimationOptions, InteractionOptions, LayoutGroupOptions } from "../../options";
|
|
||||||
import { SV } from "../../StructV";
|
|
||||||
import { Animations } from "../animation";
|
|
||||||
import { g6Behavior, Renderer } from "../renderer";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class Container {
|
|
||||||
protected engine: Engine;
|
|
||||||
protected DOMContainer: HTMLElement; // 可视化视图容器
|
|
||||||
protected renderer: Renderer; // 渲染器
|
|
||||||
protected prevModelList: Model[]; // 上一次渲染的模型列表
|
|
||||||
|
|
||||||
protected animationsOptions: AnimationOptions;
|
|
||||||
protected interactionOptions: InteractionOptions;
|
|
||||||
|
|
||||||
protected afterAppendModelsCallbacks: ((models: Model[]) => void)[] = [];
|
|
||||||
protected afterRemoveModelsCallbacks: ((models: Model[]) => void)[] = [];
|
|
||||||
|
|
||||||
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any } = {}) {
|
|
||||||
this.engine = engine;
|
|
||||||
this.DOMContainer = DOMContainer;
|
|
||||||
this.animationsOptions = engine.animationOptions;
|
|
||||||
this.interactionOptions = engine.interactionOptions;
|
|
||||||
this.prevModelList = [];
|
|
||||||
|
|
||||||
const g6Plugins = [];
|
|
||||||
|
|
||||||
if (g6Options.tooltip) {
|
|
||||||
const tooltip = new SV.G6.Tooltip({
|
|
||||||
offsetX: 10,
|
|
||||||
offsetY: 20,
|
|
||||||
shouldBegin(event) {
|
|
||||||
return event.item.getModel().SVModelType === 'element';
|
|
||||||
},
|
|
||||||
getContent: event => this.getTooltipContent(event.item.SVModel, { address: 'sourceId', data: 'data' }),
|
|
||||||
itemTypes: ['node']
|
|
||||||
});
|
|
||||||
|
|
||||||
g6Plugins.push(tooltip);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.renderer = new Renderer(engine, DOMContainer, {
|
|
||||||
fitCenter: g6Options.fitCenter,
|
|
||||||
modes: {
|
|
||||||
default: this.initBehaviors(this.engine.optionsTable)
|
|
||||||
},
|
|
||||||
plugins: g6Plugins
|
|
||||||
});
|
|
||||||
|
|
||||||
this.afterInitRenderer();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private getTooltipContent(model: Model, items: { [key: string]: string }): HTMLElement {
|
|
||||||
const wrapper = document.createElement('div');
|
|
||||||
|
|
||||||
|
|
||||||
Object.keys(items).map(key => {
|
|
||||||
let value = model[items[key]];
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
let item = document.createElement('div');
|
|
||||||
item.innerHTML = `${key}:${value}`;
|
|
||||||
wrapper.appendChild(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化交互行为
|
|
||||||
* @param optionsTable
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
protected initBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }): g6Behavior[] {
|
|
||||||
return ['drag-canvas', 'zoom-canvas'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对比上一次和该次 modelList 找出新添加的节点和边
|
|
||||||
* @param prevList
|
|
||||||
* @param list
|
|
||||||
*/
|
|
||||||
protected getAppendModels(prevList: Model[], list: Model[]): Model[] {
|
|
||||||
return list.filter(item => !prevList.find(n => n.id === item.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对比上一次和该次 modelList 找出被删除的节点和边
|
|
||||||
* @param prevList
|
|
||||||
* @param list
|
|
||||||
*/
|
|
||||||
protected getRemoveModels(prevList: Model[], list: Model[]): Model[] {
|
|
||||||
return prevList.filter(item => !list.find(n => n.id === item.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 找出重新指向的外部指针
|
|
||||||
* @param list
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
protected findReTargetMarkers(list: Model[]): Marker[] {
|
|
||||||
let prevMarkers = this.prevModelList.filter(item => item instanceof Marker),
|
|
||||||
markers = list.filter(item => item instanceof Marker);
|
|
||||||
|
|
||||||
return <Marker[]>markers.filter(item => prevMarkers.find(prevItem => {
|
|
||||||
return prevItem.id === item.id && (<Marker>prevItem).target.id !== (<Marker>item).target.id
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 找出前后 label 发生变化的 model
|
|
||||||
* @param list
|
|
||||||
*/
|
|
||||||
protected findLabelChangeModels(list: Model[]): Model[] {
|
|
||||||
let labelChangeModels: Model[] = [];
|
|
||||||
|
|
||||||
list.forEach(item => {
|
|
||||||
const prevItem = this.prevModelList.find(prevItem => prevItem.id === item.id);
|
|
||||||
|
|
||||||
if (prevItem === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevLabel = prevItem.get('label'),
|
|
||||||
label = item.get('label');
|
|
||||||
|
|
||||||
if (prevLabel !== label) {
|
|
||||||
labelChangeModels.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return labelChangeModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理新增的 G6Item(主要是动画)
|
|
||||||
* @param appendData
|
|
||||||
*/
|
|
||||||
protected handleAppendModels(appendModels: Model[]) {
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
appendModels.forEach(item => {
|
|
||||||
Animations.animate_append(item, {
|
|
||||||
duration: this.animationsOptions.duration,
|
|
||||||
timingFunction: this.animationsOptions.timingFunction,
|
|
||||||
callback: () => {
|
|
||||||
counter++;
|
|
||||||
|
|
||||||
if (counter === appendModels.length) {
|
|
||||||
this.afterAppendModelsCallbacks.map(item => item(appendModels));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理被移除(也就是泄露)的 G6Item(主要是动画)
|
|
||||||
* @param removeData
|
|
||||||
*/
|
|
||||||
protected handleRemoveModels(removeModels: Model[]) {
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
removeModels.forEach(item => {
|
|
||||||
Animations.animate_remove(item, {
|
|
||||||
duration: this.animationsOptions.duration,
|
|
||||||
timingFunction: this.animationsOptions.timingFunction,
|
|
||||||
callback: () => {
|
|
||||||
if (item.isLeak === false) {
|
|
||||||
this.renderer.removeModel(item);
|
|
||||||
item.renderG6Item = item.G6Item = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
counter++;
|
|
||||||
|
|
||||||
if (counter === removeModels.length) {
|
|
||||||
this.afterRemoveModelsCallbacks.map(item => item(removeModels));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理发生变化的 models
|
|
||||||
* @param models
|
|
||||||
*/
|
|
||||||
protected handleChangeModels(models: Model[]) { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化渲染器之后的回调
|
|
||||||
*/
|
|
||||||
protected afterInitRenderer() { }
|
|
||||||
|
|
||||||
// ------------------------------------------ hook ---------------------------------------------
|
|
||||||
|
|
||||||
afterAppendModels(callback: (models: Model[]) => void) {
|
|
||||||
this.afterAppendModelsCallbacks.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
afterRemoveModels(callback: (models: Model[]) => void) {
|
|
||||||
this.afterRemoveModelsCallbacks.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染函数
|
|
||||||
* @param modelList
|
|
||||||
*/
|
|
||||||
public render(modelList: Model[]) {
|
|
||||||
const appendModels: Model[] = this.getAppendModels(this.prevModelList, modelList),
|
|
||||||
removeModels: Model[] = this.getRemoveModels(this.prevModelList, modelList),
|
|
||||||
changeModels: Model[] = [
|
|
||||||
...appendModels,
|
|
||||||
...this.findLabelChangeModels(modelList),
|
|
||||||
...this.findReTargetMarkers(modelList)
|
|
||||||
];
|
|
||||||
|
|
||||||
// 渲染视图
|
|
||||||
this.renderer.render(modelList, removeModels);
|
|
||||||
|
|
||||||
// 处理副作用
|
|
||||||
this.handleAppendModels(appendModels);
|
|
||||||
this.handleRemoveModels(removeModels);
|
|
||||||
this.handleChangeModels(changeModels);
|
|
||||||
|
|
||||||
if (this.renderer.getIsFirstRender()) {
|
|
||||||
this.renderer.setIsFirstRender(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prevModelList = modelList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 g6 实例
|
|
||||||
*/
|
|
||||||
public getG6Instance() {
|
|
||||||
return this.renderer.getG6Instance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 销毁
|
|
||||||
*/
|
|
||||||
public destroy() {
|
|
||||||
this.renderer.destroy();
|
|
||||||
this.DOMContainer = null;
|
|
||||||
this.prevModelList = [];
|
|
||||||
this.animationsOptions = this.interactionOptions = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import { BoundingRect } from "../../Common/boundingRect";
|
|
||||||
import { Group } from "../../Common/group";
|
|
||||||
import { Model } from "../../Model/modelData";
|
|
||||||
import { Container } from "./container";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 释放区可视化视图
|
|
||||||
*/
|
|
||||||
export class FreedContainer extends Container {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将释放区的节点位置移到容器中央
|
|
||||||
* @param freedModels
|
|
||||||
*/
|
|
||||||
fitCenter(freedModels: Model[]) {
|
|
||||||
freedModels.forEach(item => {
|
|
||||||
item.G6Item = item.shadowG6Item;
|
|
||||||
});
|
|
||||||
|
|
||||||
let width = this.getG6Instance().getWidth(),
|
|
||||||
height = this.getG6Instance().getHeight(),
|
|
||||||
group = new Group();
|
|
||||||
|
|
||||||
group.add(...freedModels);
|
|
||||||
|
|
||||||
let viewBound: BoundingRect = group.getBound(),
|
|
||||||
centerX = width / 2, centerY = height / 2,
|
|
||||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
|
||||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
|
||||||
dx = centerX - boundCenterX,
|
|
||||||
dy = centerY - boundCenterY;
|
|
||||||
|
|
||||||
group.translate(dx, dy);
|
|
||||||
|
|
||||||
freedModels.forEach(item => {
|
|
||||||
item.G6Item = item.renderG6Item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected initBehaviors(): string[] {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Container } from "./container";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 泄漏区可视化视图
|
|
||||||
*/
|
|
||||||
export class LeakContainer extends Container {
|
|
||||||
|
|
||||||
};
|
|
||||||
@ -1,203 +0,0 @@
|
|||||||
import { Engine } from "../../engine";
|
|
||||||
import { Link, Model } from "../../Model/modelData";
|
|
||||||
import { InteractionOptions, LayoutGroupOptions } from "../../options";
|
|
||||||
import { Container } from "./container";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主可视化视图
|
|
||||||
*/
|
|
||||||
export class MainContainer extends Container {
|
|
||||||
|
|
||||||
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any } = { }) {
|
|
||||||
super(engine, DOMContainer, g6Options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param optionsTable
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
protected initBehaviors(optionsTable: { [key: string]: LayoutGroupOptions }) {
|
|
||||||
const dragNodeTable: { [key: string]: boolean | string[] } = { },
|
|
||||||
selectNodeTable: { [key: string]: boolean | string[] } = { },
|
|
||||||
interactionOptions: InteractionOptions = this.engine.interactionOptions,
|
|
||||||
defaultModes = [];
|
|
||||||
|
|
||||||
Object.keys(optionsTable).forEach(item => {
|
|
||||||
dragNodeTable[item] = optionsTable[item].behavior.dragNode;
|
|
||||||
selectNodeTable[item] = optionsTable[item].behavior.selectNode;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(interactionOptions.drag) {
|
|
||||||
defaultModes.push('drag-canvas');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(interactionOptions.zoom) {
|
|
||||||
defaultModes.push('zoom-canvas');
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragNodeFilter = node => {
|
|
||||||
let model = node.item.getModel();
|
|
||||||
|
|
||||||
if(node.item === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(model.SVModelType === 'marker') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dragNode = optionsTable[model.SVLayouter].behavior.dragNode;
|
|
||||||
|
|
||||||
if(typeof dragNode === 'boolean') {
|
|
||||||
return dragNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Array.isArray(dragNode) && dragNode.indexOf(model.SVModelName) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectNodeFilter = node => {
|
|
||||||
let model = node.item.getModel();
|
|
||||||
|
|
||||||
if(node.item === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(model.SVModelType === 'marker') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let selectNode = optionsTable[model.SVLayouter].behavior.selectNode;
|
|
||||||
|
|
||||||
if(typeof selectNode === 'boolean') {
|
|
||||||
return selectNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Array.isArray(selectNode) && selectNode.indexOf(model.SVModelName) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultModes.push({
|
|
||||||
type: 'drag-node',
|
|
||||||
shouldBegin: dragNodeFilter
|
|
||||||
});
|
|
||||||
|
|
||||||
defaultModes.push({
|
|
||||||
type: 'click-select',
|
|
||||||
shouldBegin: selectNodeFilter
|
|
||||||
});
|
|
||||||
|
|
||||||
return defaultModes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在初始化渲染器之后,修正节点拖拽时,外部指针没有跟着动的问题
|
|
||||||
* @param dragNodeTable
|
|
||||||
*/
|
|
||||||
protected afterInitRenderer() {
|
|
||||||
let g6Instance = this.getG6Instance(),
|
|
||||||
marker = null,
|
|
||||||
markerX = 0,
|
|
||||||
markerY = 0,
|
|
||||||
dragStartX = 0,
|
|
||||||
dragStartY = 0,
|
|
||||||
element = null;
|
|
||||||
|
|
||||||
g6Instance.on('node:dragstart', ev => {
|
|
||||||
const model = ev.item.getModel();
|
|
||||||
element = ev.item.SVModel;
|
|
||||||
|
|
||||||
if(model.SVModelType === 'marker') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragNode = this.engine.optionsTable[model.SVLayouter].behavior.dragNode;
|
|
||||||
|
|
||||||
if(dragNode === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Array.isArray(dragNode) && dragNode.find(item => item === model.SVModelName) === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dragStartX = ev.canvasX;
|
|
||||||
dragStartY = ev.canvasY;
|
|
||||||
marker = g6Instance.findById(model.markerId);
|
|
||||||
|
|
||||||
if(marker) {
|
|
||||||
markerX = marker.getModel().x,
|
|
||||||
markerY = marker.getModel().y;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
g6Instance.on('node:dragend', ev => {
|
|
||||||
let distanceX = ev.canvasX - dragStartX,
|
|
||||||
distanceY = ev.canvasY - dragStartY,
|
|
||||||
elementX = element.get('x'),
|
|
||||||
elementY = element.get('y');
|
|
||||||
|
|
||||||
element.set({
|
|
||||||
x: elementX + distanceX,
|
|
||||||
y: elementY + distanceY
|
|
||||||
});
|
|
||||||
|
|
||||||
marker = null;
|
|
||||||
markerX = 0,
|
|
||||||
markerY = 0,
|
|
||||||
dragStartX = 0,
|
|
||||||
dragStartY = 0;
|
|
||||||
element = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
g6Instance.on('node:drag', ev => {
|
|
||||||
if(!marker) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dx = ev.canvasX - dragStartX,
|
|
||||||
dy = ev.canvasY - dragStartY,
|
|
||||||
zoom = g6Instance.getZoom();
|
|
||||||
|
|
||||||
marker.updatePosition({
|
|
||||||
x: markerX + dx / zoom,
|
|
||||||
y: markerY + dy / zoom
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected handleChangeModels(models: Model[]) {
|
|
||||||
const changeHighlightColor: string = this.interactionOptions.changeHighlight;
|
|
||||||
|
|
||||||
// 第一次渲染的时候不高亮变化的元素
|
|
||||||
if(this.renderer.getIsFirstRender()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!changeHighlightColor || typeof changeHighlightColor !== 'string') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
models.forEach(item => {
|
|
||||||
if(item instanceof Link) {
|
|
||||||
item.set('style', {
|
|
||||||
stroke: changeHighlightColor
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
item.set('style', {
|
|
||||||
fill: changeHighlightColor
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,30 +1,35 @@
|
|||||||
|
import { IPoint } from '@antv/g6-core';
|
||||||
import { Bound, BoundingRect } from '../Common/boundingRect';
|
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||||
import { Group } from '../Common/group';
|
import { Group } from '../Common/group';
|
||||||
import { Vector } from '../Common/vector';
|
import { Vector } from '../Common/vector';
|
||||||
import { Engine } from '../engine';
|
import { Engine } from '../engine';
|
||||||
import { LayoutGroupTable } from '../Model/modelConstructor';
|
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||||
import { Element, Model, Marker } from '../Model/modelData';
|
import { SVMarker } from '../Model/SVMarker';
|
||||||
|
import { SVModel } from '../Model/SVModel';
|
||||||
|
import { SVFreedLabel, SVLeakAddress, SVNode } from '../Model/SVNode';
|
||||||
import { LayoutOptions, MarkerOption, ViewOptions } from '../options';
|
import { LayoutOptions, MarkerOption, ViewOptions } from '../options';
|
||||||
import { Container } from './container/container';
|
import { ViewContainer } from './viewContainer';
|
||||||
|
|
||||||
|
|
||||||
export class Layouter {
|
export class LayoutProvider {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private viewOptions: ViewOptions;
|
private viewOptions: ViewOptions;
|
||||||
|
private viewContainer: ViewContainer;
|
||||||
|
|
||||||
constructor(engine: Engine) {
|
constructor(engine: Engine, viewContainer: ViewContainer) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.viewOptions = this.engine.viewOptions;
|
this.viewOptions = this.engine.viewOptions;
|
||||||
|
this.viewContainer = viewContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化布局参数
|
* 初始化布局参数
|
||||||
* @param elements
|
* @param nodes
|
||||||
* @param markers
|
* @param markers
|
||||||
*/
|
*/
|
||||||
private initLayoutValue(elements: Element[], markers: Marker[]) {
|
private initLayoutValue(nodes: SVNode[], markers: SVMarker[]) {
|
||||||
[...elements, ...markers].forEach(item => {
|
[...nodes, ...markers].forEach(item => {
|
||||||
item.set('rotation', item.get('rotation'));
|
item.set('rotation', item.get('rotation'));
|
||||||
item.set({ x: 0, y: 0 });
|
item.set({ x: 0, y: 0 });
|
||||||
});
|
});
|
||||||
@ -35,21 +40,21 @@ export class Layouter {
|
|||||||
* @param marker
|
* @param marker
|
||||||
* @param markerOptions
|
* @param markerOptions
|
||||||
*/
|
*/
|
||||||
private layoutMarker(markers: Marker[], markerOptions: { [key: string]: MarkerOption }) {
|
private layoutMarker(markers: SVMarker[], markerOptions: { [key: string]: MarkerOption }) {
|
||||||
markers.forEach(item => {
|
markers.forEach(item => {
|
||||||
const options: MarkerOption = markerOptions[item.getType()],
|
const options: MarkerOption = markerOptions[item.sourceType],
|
||||||
offset = options.offset ?? 8,
|
offset = options.offset ?? 8,
|
||||||
anchor = item.anchor ?? 0,
|
anchor = item.anchor ?? 0,
|
||||||
labelOffset = options.labelOffset ?? 2;
|
labelOffset = options.labelOffset ?? 2;
|
||||||
|
|
||||||
let target = item.target,
|
let target = item.target,
|
||||||
targetBound: BoundingRect = target.getBound(),
|
targetBound: BoundingRect = target.getBound(),
|
||||||
anchorPosition = item.target.G6Item.getAnchorPoints()[anchor],
|
g6AnchorPosition = item.target.shadowG6Item.getAnchorPoints()[anchor] as IPoint,
|
||||||
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
|
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
|
||||||
markerPosition: [number, number],
|
markerPosition: [number, number],
|
||||||
markerEndPosition: [number, number];
|
markerEndPosition: [number, number];
|
||||||
|
|
||||||
anchorPosition = [anchorPosition.x, anchorPosition.y];
|
let anchorPosition: [number, number] = [g6AnchorPosition.x, g6AnchorPosition.y];
|
||||||
|
|
||||||
let anchorVector = Vector.subtract(anchorPosition, center),
|
let anchorVector = Vector.subtract(anchorPosition, center),
|
||||||
angle = 0, len = Vector.length(anchorVector) + offset;
|
angle = 0, len = Vector.length(anchorVector) + offset;
|
||||||
@ -79,23 +84,38 @@ export class Layouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将视图调整至画布中心
|
* 布局节点的‘已释放’文本
|
||||||
* @param container
|
* @param freedLabels
|
||||||
* @param models
|
|
||||||
*/
|
*/
|
||||||
private fitCenter(container: Container, group: Group) {
|
private layoutFreedLabel(freedLabels: SVFreedLabel[]) {
|
||||||
let width = container.getG6Instance().getWidth(),
|
freedLabels.forEach(item => {
|
||||||
height = container.getG6Instance().getHeight(),
|
const freedNodeBound = item.node.getBound();
|
||||||
viewBound: BoundingRect = group.getBound(),
|
|
||||||
centerX = width / 2, centerY = height / 2,
|
|
||||||
boundCenterX = viewBound.x + viewBound.width / 2,
|
|
||||||
boundCenterY = viewBound.y + viewBound.height / 2,
|
|
||||||
dx = centerX - boundCenterX,
|
|
||||||
dy = centerY - boundCenterY;
|
|
||||||
|
|
||||||
group.translate(dx, dy);
|
item.set({
|
||||||
|
x: freedNodeBound.x + freedNodeBound.width / 2,
|
||||||
|
y: freedNodeBound.y + freedNodeBound.height * 1.5,
|
||||||
|
size: [freedNodeBound.width, 0]
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布局泄漏区节点上面的address label
|
||||||
|
* @param leakAddress
|
||||||
|
*/
|
||||||
|
private layoutLeakAddress(leakAddress: SVLeakAddress[]) {
|
||||||
|
leakAddress.forEach(item => {
|
||||||
|
const nodeBound = item.node.getBound();
|
||||||
|
|
||||||
|
item.set({
|
||||||
|
x: nodeBound.x + nodeBound.width / 2,
|
||||||
|
y: nodeBound.y - 16,
|
||||||
|
size: [nodeBound.width, 0]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对每个组内部的model进行布局
|
* 对每个组内部的model进行布局
|
||||||
* @param layoutGroupTable
|
* @param layoutGroupTable
|
||||||
@ -105,31 +125,58 @@ export class Layouter {
|
|||||||
|
|
||||||
layoutGroupTable.forEach(group => {
|
layoutGroupTable.forEach(group => {
|
||||||
const options: LayoutOptions = group.options.layout,
|
const options: LayoutOptions = group.options.layout,
|
||||||
modelList: Model[] = group.modelList,
|
modelList: SVModel[] = group.modelList,
|
||||||
modelGroup: Group = new Group();
|
modelGroup: Group = new Group();
|
||||||
|
|
||||||
modelList.forEach(item => {
|
modelList.forEach(item => {
|
||||||
modelGroup.add(item);
|
modelGroup.add(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.initLayoutValue(group.element, group.marker); // 初始化布局参数
|
this.initLayoutValue(group.node, group.marker); // 初始化布局参数
|
||||||
group.layouter.layout(group.element, options); // 布局节点
|
group.layoutCreator.layout(group.node, options); // 布局节点
|
||||||
modelGroupList.push(modelGroup);
|
modelGroupList.push(modelGroup);
|
||||||
});
|
});
|
||||||
|
|
||||||
layoutGroupTable.forEach(group => {
|
layoutGroupTable.forEach(group => {
|
||||||
|
this.layoutFreedLabel(group.freedLabel);
|
||||||
|
this.layoutLeakAddress(group.leakAddress);
|
||||||
this.layoutMarker(group.marker, group.options.marker); // 布局外部指针
|
this.layoutMarker(group.marker, group.options.marker); // 布局外部指针
|
||||||
});
|
});
|
||||||
|
|
||||||
return modelGroupList;
|
return modelGroupList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对泄漏区进行布局
|
||||||
|
* @param leakModels
|
||||||
|
* @param accumulateLeakModels
|
||||||
|
*/
|
||||||
|
private layoutLeakModels(leakModels: SVModel[], accumulateLeakModels: SVModel[]) {
|
||||||
|
const group: Group = new Group(),
|
||||||
|
containerHeight = this.viewContainer.getG6Instance().getHeight(),
|
||||||
|
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||||
|
xOffset = 50;
|
||||||
|
|
||||||
|
group.add(...leakModels);
|
||||||
|
const currentLeakGroupBound: BoundingRect = group.getBound(),
|
||||||
|
globalLeakGroupBound: BoundingRect = accumulateLeakModels.length ?
|
||||||
|
Bound.union(...accumulateLeakModels.map(item => item.getBound())) :
|
||||||
|
{ x: 0, y: containerHeight * (1 - leakAreaHeight), width: 0, height: 0 };
|
||||||
|
|
||||||
|
const { x: groupX, y: groupY } = currentLeakGroupBound,
|
||||||
|
dx = globalLeakGroupBound.x + globalLeakGroupBound.width + xOffset - groupX,
|
||||||
|
dy = globalLeakGroupBound.y - groupY;
|
||||||
|
|
||||||
|
group.translate(dx, dy);
|
||||||
|
|
||||||
|
return globalLeakGroupBound;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对所有组进行相互布局
|
* 对所有组进行相互布局
|
||||||
* @param container
|
|
||||||
* @param modelGroupTable
|
* @param modelGroupTable
|
||||||
*/
|
*/
|
||||||
private layoutGroups(container: Container, modelGroupList: Group[]): Group {
|
private layoutGroups(modelGroupList: Group[]): Group {
|
||||||
let wrapperGroup: Group = new Group(),
|
let wrapperGroup: Group = new Group(),
|
||||||
group: Group,
|
group: Group,
|
||||||
prevBound: BoundingRect,
|
prevBound: BoundingRect,
|
||||||
@ -174,69 +221,42 @@ export class Layouter {
|
|||||||
return wrapperGroup;
|
return wrapperGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public layoutSingleMarker(marker: Marker, markerOptions: { [key: string]: MarkerOption }) {
|
|
||||||
const options: MarkerOption = markerOptions[marker.getType()],
|
|
||||||
offset = options.offset ?? 8,
|
|
||||||
anchor = marker.anchor ?? 0,
|
|
||||||
labelOffset = options.labelOffset ?? 2;
|
|
||||||
|
|
||||||
let target = marker.target,
|
/**
|
||||||
targetBound: BoundingRect = target.getBound(),
|
* 将视图调整至画布中心
|
||||||
anchorPosition = marker.target.G6Item.getAnchorPoints()[anchor],
|
* @param models
|
||||||
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
|
* @param leakBound
|
||||||
markerPosition: [number, number],
|
*/
|
||||||
markerEndPosition: [number, number];
|
private fitCenter(group: Group, leakBound: BoundingRect) {
|
||||||
|
let width = this.viewContainer.getG6Instance().getWidth(),
|
||||||
|
height = this.viewContainer.getG6Instance().getHeight() - leakBound.height;
|
||||||
|
|
||||||
anchorPosition = [anchorPosition.x, anchorPosition.y];
|
const viewBound: BoundingRect = group.getBound(),
|
||||||
|
centerX = width / 2, centerY = height / 2,
|
||||||
|
boundCenterX = viewBound.x + viewBound.width / 2,
|
||||||
|
boundCenterY = viewBound.y + viewBound.height / 2,
|
||||||
|
dx = centerX - boundCenterX,
|
||||||
|
dy = centerY - boundCenterY;
|
||||||
|
|
||||||
let anchorVector = Vector.subtract(anchorPosition, center),
|
group.translate(dx, dy);
|
||||||
angle = 0, len = Vector.length(anchorVector) + offset;
|
|
||||||
|
|
||||||
if (anchorVector[0] === 0) {
|
|
||||||
angle = anchorVector[1] > 0 ? -Math.PI : 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
angle = Math.sign(anchorVector[0]) * (Math.PI / 2 - Math.atan(anchorVector[1] / anchorVector[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
const markerHeight = marker.get('size')[1],
|
|
||||||
labelRadius = marker.getLabelSizeRadius() / 2;
|
|
||||||
|
|
||||||
anchorVector = Vector.normalize(anchorVector);
|
|
||||||
markerPosition = Vector.location(center, anchorVector, len);
|
|
||||||
markerEndPosition = Vector.location(center, anchorVector, markerHeight + len + labelRadius + labelOffset);
|
|
||||||
markerEndPosition = Vector.subtract(markerEndPosition, markerPosition);
|
|
||||||
|
|
||||||
marker.set({
|
|
||||||
x: markerPosition[0],
|
|
||||||
y: markerPosition[1],
|
|
||||||
rotation: angle,
|
|
||||||
markerEndPosition
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 布局
|
* 布局
|
||||||
* @param container
|
|
||||||
* @param layoutGroupTable
|
* @param layoutGroupTable
|
||||||
|
* @param leakModels
|
||||||
|
* @param hasLeak
|
||||||
*/
|
*/
|
||||||
public layoutAll(container: Container, layoutGroupTable: LayoutGroupTable) {
|
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], leakModels: SVModel[]) {
|
||||||
layoutGroupTable.forEach(item => {
|
|
||||||
item.modelList.forEach(model => {
|
|
||||||
model.G6Item = model.shadowG6Item;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
|
const modelGroupList: Group[] = this.layoutModels(layoutGroupTable);
|
||||||
|
const globalGroup: Group = this.layoutGroups(modelGroupList);
|
||||||
|
const containerHeight = this.viewContainer.getG6Instance().getHeight();
|
||||||
|
let leakBound: BoundingRect = { x: 0, y: containerHeight, width: 0, height: 0 }
|
||||||
|
|
||||||
const wrapperGroup: Group = this.layoutGroups(container, modelGroupList);
|
if (leakModels.length) {
|
||||||
this.fitCenter(container, wrapperGroup);
|
leakBound = this.layoutLeakModels(leakModels, accumulateLeakModels);
|
||||||
|
}
|
||||||
|
|
||||||
layoutGroupTable.forEach(item => {
|
this.fitCenter(globalGroup, leakBound);
|
||||||
item.modelList.forEach(model => {
|
|
||||||
model.G6Item = model.renderG6Item;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
406
src/View/reconcile.ts
Normal file
406
src/View/reconcile.ts
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
import { EventBus } from "../Common/eventBus";
|
||||||
|
import { Util } from "../Common/util";
|
||||||
|
import { Engine } from "../engine";
|
||||||
|
import { SVLink } from "../Model/SVLink";
|
||||||
|
import { SVMarker } from "../Model/SVMarker";
|
||||||
|
import { SVModel } from "../Model/SVModel";
|
||||||
|
import { SVLeakAddress, SVNode } from "../Model/SVNode";
|
||||||
|
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[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 prevModelList
|
||||||
|
* @param modelList
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLeakModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
|
||||||
|
const potentialLeakModels: SVModel[] = prevModelList.filter(item =>
|
||||||
|
!modelList.find(model => model.id === item.id) && !item.freed
|
||||||
|
);
|
||||||
|
const leakModels: SVModel[] = [];
|
||||||
|
|
||||||
|
potentialLeakModels.forEach(item => {
|
||||||
|
if (item instanceof SVNode) {
|
||||||
|
item.leaked = true;
|
||||||
|
leakModels.push(item);
|
||||||
|
|
||||||
|
if (item.marker) {
|
||||||
|
item.marker.leaked = true;
|
||||||
|
leakModels.push(item.marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(item.freedLabel) {
|
||||||
|
item.marker.leaked = true;
|
||||||
|
leakModels.push(item.freedLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.leakAddress.leaked = true;
|
||||||
|
leakModels.push(item.leakAddress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 (prevLabel !== 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 SVLeakAddress) {
|
||||||
|
const leakAddressG6Group = item.G6Item.getContainer();
|
||||||
|
leakAddressG6Group.attr({ opacity: 0 });
|
||||||
|
}
|
||||||
|
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 SVLeakAddress) {
|
||||||
|
Animations.FADE_IN(item.G6Item, {
|
||||||
|
duration,
|
||||||
|
timingFunction
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 prevLayoutGroupTable
|
||||||
|
* @param layoutGroupTable
|
||||||
|
* @param accumulateLeakModels
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public diff(prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): DiffResult {
|
||||||
|
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
|
||||||
|
const leakModels: SVModel[] = this.getLeakModels(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;
|
||||||
|
|
||||||
|
// 第一次渲染的时候不高亮变化的元素
|
||||||
|
if (this.isFirstPatch === false) {
|
||||||
|
this.handleChangeModels(UPDATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleContinuousModels(CONTINUOUS);
|
||||||
|
this.handleFreedModels(FREED);
|
||||||
|
this.handleAppendModels(APPEND);
|
||||||
|
this.handleLeakModels(LEAKED);
|
||||||
|
this.handleRemoveModels(REMOVE);
|
||||||
|
this.handleAccumulateLeakModels(ACCUMULATE_LEAK);
|
||||||
|
|
||||||
|
if(this.isFirstPatch) {
|
||||||
|
this.isFirstPatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,16 @@
|
|||||||
import { Engine } from '../engine';
|
import { Engine } from '../engine';
|
||||||
import { G6EdgeModel, G6NodeModel } from '../Model/modelData';
|
import { SVModel } from '../Model/SVModel';
|
||||||
import { Util } from '../Common/util';
|
import { Util } from '../Common/util';
|
||||||
import { SV } from '../StructV';
|
import G6 from '@antv/g6';
|
||||||
import { Model } from './../Model/modelData';
|
import { InitViewBehaviors } from '../BehaviorHelper/initViewBehaviors';
|
||||||
|
import { Graph, GraphData, IGroup } from '@antv/g6-pc';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface G6Data {
|
export interface RenderModelPack {
|
||||||
nodes: G6NodeModel[];
|
leaKModels: SVModel[];
|
||||||
edges: G6EdgeModel[];
|
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; };
|
||||||
@ -17,26 +18,34 @@ export type g6Behavior = string | { type: string; shouldBegin?: Function; should
|
|||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
|
private g6Instance: Graph; // g6 实例
|
||||||
|
private shadowG6Instance: Graph;
|
||||||
|
|
||||||
private DOMContainer: HTMLElement; // 主可视化视图容器
|
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||||
private g6Instance; // g6 实例
|
|
||||||
|
|
||||||
private prevRenderModelList: Model[] = null;
|
|
||||||
private isFirstRender: boolean; // 是否为第一次渲染
|
|
||||||
|
|
||||||
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any }) {
|
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.DOMContainer = DOMContainer;
|
|
||||||
this.isFirstRender = true;
|
|
||||||
|
|
||||||
const enable: boolean = this.engine.animationOptions.enable,
|
const enable: boolean = this.engine.animationOptions.enable,
|
||||||
duration: number = this.engine.animationOptions.duration,
|
duration: number = this.engine.animationOptions.duration,
|
||||||
timingFunction: string = this.engine.animationOptions.timingFunction;
|
timingFunction: string = this.engine.animationOptions.timingFunction;
|
||||||
|
|
||||||
|
const tooltip = new G6.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 G6.Graph({
|
||||||
|
container: DOMContainer.cloneNode() as HTMLElement
|
||||||
|
});
|
||||||
|
|
||||||
// 初始化g6实例
|
// 初始化g6实例
|
||||||
this.g6Instance = new SV.G6.Graph({
|
this.g6Instance = new G6.Graph({
|
||||||
container: DOMContainer,
|
container: DOMContainer,
|
||||||
width: DOMContainer.offsetWidth,
|
width: DOMContainer.offsetWidth,
|
||||||
height: DOMContainer.offsetHeight,
|
height: DOMContainer.offsetHeight,
|
||||||
groupByTypes: false,
|
groupByTypes: false,
|
||||||
animate: enable,
|
animate: enable,
|
||||||
@ -46,68 +55,74 @@ export class Renderer {
|
|||||||
},
|
},
|
||||||
fitView: false,
|
fitView: false,
|
||||||
modes: {
|
modes: {
|
||||||
default: []
|
default: InitViewBehaviors(this.engine.optionsTable)
|
||||||
},
|
},
|
||||||
...g6Options
|
plugins: [tooltip]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getIsFirstRender(): boolean {
|
/**
|
||||||
return this.isFirstRender;
|
* 创造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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(items).map(key => {
|
||||||
|
let value = model[items[key]];
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
let item = document.createElement('div');
|
||||||
|
item.innerHTML = `${key}:${value}`;
|
||||||
|
wrapper.appendChild(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setIsFirstRender(value: boolean) {
|
/**
|
||||||
this.isFirstRender = value;
|
* 对每一个 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染函数
|
||||||
|
* @param renderModelList
|
||||||
|
* @param isFirstRender
|
||||||
|
*/
|
||||||
|
public render(renderModelList: SVModel[]) {
|
||||||
|
const renderData: GraphData = Util.convertModelList2G6Data(renderModelList);
|
||||||
|
|
||||||
|
this.g6Instance.changeData(renderData);
|
||||||
|
|
||||||
|
renderModelList.forEach(item => {
|
||||||
|
item.G6Item = this.g6Instance.findById(item.id);
|
||||||
|
item.G6Item['SVModel'] = item;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从视图中移除一个 Model
|
* 从视图中移除一个 Model
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
public removeModel(model: Model) {
|
public removeModel(model: SVModel) {
|
||||||
this.g6Instance.removeItem(model.renderG6Item);
|
this.g6Instance.removeItem(model.G6Item);
|
||||||
}
|
this.shadowG6Instance.removeItem(model.shadowG6Item);
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染函数
|
|
||||||
* @param modelList
|
|
||||||
*/
|
|
||||||
public render(modelList: Model[], removeModels: Model[]) {
|
|
||||||
const renderModelList = [...modelList, ...removeModels],
|
|
||||||
renderData: G6Data = Util.convertModelList2G6Data(renderModelList);
|
|
||||||
|
|
||||||
if(this.isFirstRender) {
|
|
||||||
this.g6Instance.read(renderData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.g6Instance.changeData(renderData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.engine.viewOptions.fitView) {
|
|
||||||
this.g6Instance.fitView();
|
|
||||||
}
|
|
||||||
|
|
||||||
modelList.forEach(item => {
|
|
||||||
item.renderG6Item = this.g6Instance.findById(item.id);
|
|
||||||
|
|
||||||
if(item.renderG6Item) {
|
|
||||||
item.G6Item = item.renderG6Item;
|
|
||||||
item.renderG6Item.SVModel = item;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 将上一次的model全部标记为已销毁
|
|
||||||
if(this.prevRenderModelList) {
|
|
||||||
this.prevRenderModelList.forEach(item => { item.isDestroy = true; });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prevRenderModelList = renderModelList;
|
|
||||||
|
|
||||||
// 把所有连线置顶
|
|
||||||
if(this.isFirstRender) {
|
|
||||||
this.g6Instance.getEdges().forEach(item => item.toFront());
|
|
||||||
this.g6Instance.paint();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,7 +136,7 @@ export class Renderer {
|
|||||||
* 销毁
|
* 销毁
|
||||||
*/
|
*/
|
||||||
public destroy() {
|
public destroy() {
|
||||||
|
this.shadowG6Instance.destroy();
|
||||||
this.g6Instance.destroy();
|
this.g6Instance.destroy();
|
||||||
this.DOMContainer = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
184
src/View/viewContainer.ts
Normal file
184
src/View/viewContainer.ts
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
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 { FixNodeMarkerDrag } from "../BehaviorHelper/fixNodeMarkerDrag";
|
||||||
|
import { InitDragCanvasWithLeak } from "../BehaviorHelper/dragCanavsWithLeak";
|
||||||
|
import { EventBus } from "../Common/eventBus";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class ViewContainer {
|
||||||
|
private engine: Engine;
|
||||||
|
private layoutProvider: LayoutProvider;
|
||||||
|
private reconcile: Reconcile;
|
||||||
|
public renderer: Renderer;
|
||||||
|
|
||||||
|
private modelList: SVModel[];
|
||||||
|
private prevLayoutGroupTable: LayoutGroupTable;
|
||||||
|
private prevModelList: SVModel[];
|
||||||
|
private accumulateLeakModels: SVModel[];
|
||||||
|
|
||||||
|
public hasLeak: boolean;
|
||||||
|
public leakAreaY: number;
|
||||||
|
|
||||||
|
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.layoutProvider = new LayoutProvider(engine, this);
|
||||||
|
this.renderer = new Renderer(engine, DOMContainer);
|
||||||
|
this.reconcile = new Reconcile(engine, this.renderer);
|
||||||
|
this.prevLayoutGroupTable = new Map();
|
||||||
|
this.modelList = [];
|
||||||
|
this.prevModelList = [];
|
||||||
|
this.accumulateLeakModels = [];
|
||||||
|
this.hasLeak = false; // 判断是否已经发生过泄漏
|
||||||
|
|
||||||
|
const g6Instance = this.renderer.getG6Instance(),
|
||||||
|
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
|
||||||
|
height = this.getG6Instance().getHeight(),
|
||||||
|
{ drag, zoom } = this.engine.interactionOptions;
|
||||||
|
|
||||||
|
this.leakAreaY = height * (1- leakAreaHeight);
|
||||||
|
|
||||||
|
if(drag) {
|
||||||
|
InitDragCanvasWithLeak(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(zoom) {
|
||||||
|
// InitZoomCanvas(g6Instance, g6GeneralGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
FixNodeMarkerDrag(g6Instance, this.engine.optionsTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对主视图进行重新布局
|
||||||
|
*/
|
||||||
|
reLayout() {
|
||||||
|
this.layoutProvider.layoutAll(this.prevLayoutGroupTable, [], this.accumulateLeakModels);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 g6 实例
|
||||||
|
*/
|
||||||
|
getG6Instance() {
|
||||||
|
return this.renderer.getG6Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新视图
|
||||||
|
*/
|
||||||
|
refresh() {
|
||||||
|
this.renderer.getG6Instance().refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新调整容器尺寸
|
||||||
|
* @param width
|
||||||
|
* @param height
|
||||||
|
*/
|
||||||
|
resize(width: number, height: number) {
|
||||||
|
this.renderer.getG6Instance().changeSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染所有视图
|
||||||
|
* @param models
|
||||||
|
* @param layoutFn
|
||||||
|
*/
|
||||||
|
render(layoutGroupTable: LayoutGroupTable) {
|
||||||
|
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
|
||||||
|
diffResult = this.reconcile.diff(this.prevModelList, modelList, this.accumulateLeakModels),
|
||||||
|
renderModelList = [
|
||||||
|
...modelList,
|
||||||
|
...diffResult.REMOVE,
|
||||||
|
...diffResult.LEAKED,
|
||||||
|
...diffResult.ACCUMULATE_LEAK
|
||||||
|
];
|
||||||
|
|
||||||
|
if(this.hasLeak === true && this.accumulateLeakModels.length === 0) {
|
||||||
|
this.hasLeak = false;
|
||||||
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
|
leakAreaY: this.leakAreaY,
|
||||||
|
hasLeak: this.hasLeak
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diffResult.LEAKED.length) {
|
||||||
|
this.hasLeak = true;
|
||||||
|
EventBus.emit('onLeakAreaUpdate', {
|
||||||
|
leakAreaY: this.leakAreaY,
|
||||||
|
hasLeak: this.hasLeak
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modelList = modelList;
|
||||||
|
this.renderer.build(renderModelList);
|
||||||
|
|
||||||
|
// 进行布局(设置model的x,y,样式等)
|
||||||
|
this.layoutProvider.layoutAll(layoutGroupTable, this.accumulateLeakModels, diffResult.LEAKED);
|
||||||
|
|
||||||
|
this.beforeRender();
|
||||||
|
this.renderer.render(renderModelList); // 渲染视图
|
||||||
|
this.reconcile.patch(diffResult); // 对视图上的某些变化进行对应的动作,比如:节点创建动画,节点消失动画等
|
||||||
|
this.afterRender();
|
||||||
|
|
||||||
|
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行累积
|
||||||
|
|
||||||
|
this.prevLayoutGroupTable = layoutGroupTable;
|
||||||
|
this.prevModelList = modelList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
this.renderer.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把渲染前要触发的逻辑放在这里
|
||||||
|
*/
|
||||||
|
private afterRender() {
|
||||||
|
const g6Instance = this.renderer.getG6Instance();
|
||||||
|
|
||||||
|
// 把所有连线置顶
|
||||||
|
g6Instance.getEdges().forEach(item => item.toFront());
|
||||||
|
g6Instance.paint();
|
||||||
|
|
||||||
|
this.prevModelList.forEach(item => {
|
||||||
|
if(item.leaked === false) {
|
||||||
|
item.discarded = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把渲染后要触发的逻辑放在这里
|
||||||
|
*/
|
||||||
|
private beforeRender() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,291 +0,0 @@
|
|||||||
import { Engine } from "../engine";
|
|
||||||
import { Element, Link, Marker, Model } from "../Model/modelData";
|
|
||||||
import { EngineOptions } from "../options";
|
|
||||||
import { Container } from "./container/container";
|
|
||||||
import { SV } from '../StructV';
|
|
||||||
import { MainContainer } from "./container/main";
|
|
||||||
import { FreedContainer } from "./container/freed";
|
|
||||||
import { LeakContainer } from "./container/leak";
|
|
||||||
import { Layouter } from "./layouter";
|
|
||||||
import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor";
|
|
||||||
import { Util } from "../Common/util";
|
|
||||||
import { EventBus } from "../Common/eventBus";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class ViewManager {
|
|
||||||
private engine: Engine;
|
|
||||||
private layouter: Layouter;
|
|
||||||
private mainContainer: Container;
|
|
||||||
private freedContainer: FreedContainer;
|
|
||||||
private leakContainer: LeakContainer;
|
|
||||||
|
|
||||||
private prevLayoutGroupTable: LayoutGroupTable;
|
|
||||||
private prevModelList: Model[];
|
|
||||||
private prevFreedElements: Element[];
|
|
||||||
|
|
||||||
private shadowG6Instance;
|
|
||||||
|
|
||||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
|
||||||
this.engine = engine;
|
|
||||||
this.layouter = new Layouter(engine);
|
|
||||||
this.mainContainer = new MainContainer(engine, DOMContainer, { tooltip: true });
|
|
||||||
this.prevLayoutGroupTable = new Map();
|
|
||||||
this.prevFreedElements = [];
|
|
||||||
this.prevModelList = [];
|
|
||||||
|
|
||||||
const options: EngineOptions = this.engine.engineOptions;
|
|
||||||
|
|
||||||
if (options.freedContainer) {
|
|
||||||
this.freedContainer = new FreedContainer(engine, options.freedContainer, { fitCenter: true, tooltip: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.leakContainer) {
|
|
||||||
this.leakContainer = new LeakContainer(engine, options.leakContainer, { fitCenter: true, tooltip: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.shadowG6Instance = new SV.G6.Graph({
|
|
||||||
container: DOMContainer.cloneNode()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对每一个 model 在离屏 Canvas 上构建 G6 item,用作布局
|
|
||||||
* @param constructList
|
|
||||||
*/
|
|
||||||
private build(modelList: Model[]) {
|
|
||||||
modelList.forEach(item => {
|
|
||||||
const type = item instanceof Link ? 'edge' : 'node';
|
|
||||||
this.shadowG6Instance.addItem(type, item.cloneProps());
|
|
||||||
item.shadowG6Item = this.shadowG6Instance.findById(item.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param freedElement
|
|
||||||
* @param prevLayoutGroup
|
|
||||||
*/
|
|
||||||
private handleFreedLabel(freedElement: Element[], prevLayoutGroup: LayoutGroup) {
|
|
||||||
if (prevLayoutGroup === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevElementList: Element[] = prevLayoutGroup.element;
|
|
||||||
|
|
||||||
freedElement.map(item => {
|
|
||||||
let prevElement = prevElementList.find(el => el.id === item.id),
|
|
||||||
prevLabel = prevElement.get('label') ?? '';
|
|
||||||
|
|
||||||
item.set('label', prevLabel);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取被 free 的节点
|
|
||||||
* @param layoutGroupTable
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
private getFreedModelList(prevLayoutGroupTable: LayoutGroupTable, layoutGroupTable: LayoutGroupTable): Model[] {
|
|
||||||
let freedElements: Element[] = [],
|
|
||||||
freedMarkers: Marker[] = [],
|
|
||||||
removeModels: Model[] = [],
|
|
||||||
freedGroupName: string;
|
|
||||||
|
|
||||||
layoutGroupTable.forEach((group, key) => {
|
|
||||||
let targetElements: Element[] = group.element.filter(item => item.freed);
|
|
||||||
|
|
||||||
targetElements.forEach(fItem => {
|
|
||||||
removeModels.push(
|
|
||||||
...Util.removeFromList(group.element, item => item.id === fItem.id),
|
|
||||||
...Util.removeFromList(group.link, item => item.element.id === fItem.id || item.target.id === fItem.id),
|
|
||||||
...Util.removeFromList(group.marker, item => item.target.id === fItem.id),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
removeModels.map(model => {
|
|
||||||
Util.removeFromList(group.modelList, item => item.id === model.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 找出最新的freed节点(防止上一次的freed节点被遗留在该次渲染,此时会出现大于一个freed节点)
|
|
||||||
targetElements.forEach(item => {
|
|
||||||
if (this.prevFreedElements.find(prevEle => prevEle.id === item.id) === undefined) {
|
|
||||||
freedGroupName = key;
|
|
||||||
freedElements.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
freedElements.map(item => {
|
|
||||||
const markers = Object.keys(item.markers).map(name => item.markers[name]);
|
|
||||||
freedMarkers.push(...markers);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.handleFreedLabel(freedElements, prevLayoutGroupTable.get(freedGroupName));
|
|
||||||
this.prevFreedElements = freedElements;
|
|
||||||
|
|
||||||
const freedItems = [...freedElements, ...freedMarkers];
|
|
||||||
freedItems.forEach(item => {
|
|
||||||
item.set('style', {
|
|
||||||
fill: '#ccc'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return freedItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取被泄露的节点
|
|
||||||
* @param prevModelList
|
|
||||||
* @param modelList
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
private getLeakModelList(prevModelList: Model[], modelList: Model[]): Model[] {
|
|
||||||
const leakModelList: Model[] = prevModelList.filter(item => !modelList.find(n => n.id === item.id)),
|
|
||||||
elements: Element[] = <Element[]>leakModelList.filter(item => item instanceof Element && item.freed === false),
|
|
||||||
links: Link[] = <Link[]>leakModelList.filter(item => item instanceof Link),
|
|
||||||
elementIds: string[] = [],
|
|
||||||
res: Model[] = [];
|
|
||||||
|
|
||||||
elements.forEach(item => {
|
|
||||||
elementIds.push(item.id);
|
|
||||||
|
|
||||||
item.set('style', {
|
|
||||||
fill: '#ccc'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < links.length; i++) {
|
|
||||||
let sourceId = links[i].element.id,
|
|
||||||
targetId = links[i].target.id;
|
|
||||||
|
|
||||||
links[i].set('style', {
|
|
||||||
stroke: '#333'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (elementIds.find(item => item === sourceId) === undefined || elementIds.find(item => item === targetId) === undefined) {
|
|
||||||
links.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.push(...elements, ...links);
|
|
||||||
|
|
||||||
res.map(item => {
|
|
||||||
item.isLeak = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对主视图进行重新布局
|
|
||||||
* @param layoutGroupTable
|
|
||||||
*/
|
|
||||||
reLayout(layoutGroupTable: LayoutGroupTable) {
|
|
||||||
this.layouter.layoutAll(this.mainContainer, layoutGroupTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 g6 实例
|
|
||||||
*/
|
|
||||||
getG6Instance() {
|
|
||||||
return this.mainContainer.getG6Instance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新视图
|
|
||||||
*/
|
|
||||||
refresh() {
|
|
||||||
this.mainContainer.getG6Instance().refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新调整容器尺寸
|
|
||||||
* @param containerName
|
|
||||||
* @param width
|
|
||||||
* @param height
|
|
||||||
*/
|
|
||||||
resize(containerName: string, width: number, height: number) {
|
|
||||||
if (containerName === 'main') {
|
|
||||||
this.mainContainer.getG6Instance().changeSize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containerName === 'freed') {
|
|
||||||
this.freedContainer.getG6Instance().changeSize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containerName === 'leak') {
|
|
||||||
this.leakContainer.getG6Instance().changeSize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染所有视图
|
|
||||||
* @param models
|
|
||||||
* @param layoutFn
|
|
||||||
*/
|
|
||||||
renderAll(layoutGroupTable: LayoutGroupTable) {
|
|
||||||
this.shadowG6Instance.clear();
|
|
||||||
|
|
||||||
let modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
|
|
||||||
leakModelList = [],
|
|
||||||
freedModelList = [];
|
|
||||||
|
|
||||||
this.build(modelList);
|
|
||||||
|
|
||||||
if (this.leakContainer) {
|
|
||||||
leakModelList = this.getLeakModelList(this.prevModelList, modelList);
|
|
||||||
this.build(leakModelList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 进行布局(设置model的x,y)
|
|
||||||
this.layouter.layoutAll(this.mainContainer, layoutGroupTable);
|
|
||||||
|
|
||||||
freedModelList = this.getFreedModelList(this.prevLayoutGroupTable, layoutGroupTable);
|
|
||||||
|
|
||||||
if (this.freedContainer && freedModelList.length) {
|
|
||||||
this.freedContainer.fitCenter(freedModelList);
|
|
||||||
this.freedContainer.render(freedModelList);
|
|
||||||
EventBus.emit('onFreed', freedModelList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从新获取一次,因为第一次获取没有把freed节点筛选出去
|
|
||||||
modelList = Util.convertGroupTable2ModelList(layoutGroupTable);
|
|
||||||
this.mainContainer.render(modelList);
|
|
||||||
|
|
||||||
if (this.leakContainer && leakModelList.length) {
|
|
||||||
this.leakContainer.render(leakModelList);
|
|
||||||
EventBus.emit('onLeak', leakModelList);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prevLayoutGroupTable = layoutGroupTable;
|
|
||||||
this.prevModelList = modelList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 销毁
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
this.shadowG6Instance.destroy();
|
|
||||||
this.mainContainer.destroy();
|
|
||||||
this.freedContainer && this.freedContainer.destroy();
|
|
||||||
this.leakContainer && this.leakContainer.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
115
src/engine.ts
115
src/engine.ts
@ -1,17 +1,18 @@
|
|||||||
import { Element, Link, Marker } from "./Model/modelData";
|
|
||||||
import { Sources } from "./sources";
|
import { Sources } from "./sources";
|
||||||
import { LayoutGroupTable, ModelConstructor } from "./Model/modelConstructor";
|
import { ModelConstructor } from "./Model/modelConstructor";
|
||||||
import { AnimationOptions, EngineOptions, InteractionOptions, LayoutGroupOptions, ViewOptions } from "./options";
|
import { AnimationOptions, EngineOptions, InteractionOptions, LayoutGroupOptions, ViewOptions } from "./options";
|
||||||
import { ViewManager } from "./View/viewManager";
|
|
||||||
import { SV } from "./StructV";
|
import { SV } from "./StructV";
|
||||||
import { EventBus } from "./Common/eventBus";
|
import { EventBus } from "./Common/eventBus";
|
||||||
|
import { ViewContainer } from "./View/viewContainer";
|
||||||
|
import { SVLink } from "./Model/SVLink";
|
||||||
|
import { SVNode } from "./Model/SVNode";
|
||||||
|
import { SVMarker } from "./Model/SVMarker";
|
||||||
|
|
||||||
|
|
||||||
export class Engine {
|
export class Engine {
|
||||||
private modelConstructor: ModelConstructor = null;
|
private modelConstructor: ModelConstructor = null;
|
||||||
private viewManager: ViewManager
|
private viewContainer: ViewContainer
|
||||||
private prevStringSourceData: string;
|
private prevStringSourceData: string;
|
||||||
private layoutGroupTable: LayoutGroupTable;
|
|
||||||
|
|
||||||
public engineOptions: EngineOptions;
|
public engineOptions: EngineOptions;
|
||||||
public viewOptions: ViewOptions;
|
public viewOptions: ViewOptions;
|
||||||
@ -22,21 +23,19 @@ export class Engine {
|
|||||||
|
|
||||||
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions) {
|
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions) {
|
||||||
this.optionsTable = {};
|
this.optionsTable = {};
|
||||||
|
this.engineOptions = Object.assign({}, engineOptions);
|
||||||
this.engineOptions = Object.assign({
|
|
||||||
freedContainer: null,
|
|
||||||
leakContainer: null
|
|
||||||
}, engineOptions);
|
|
||||||
|
|
||||||
this.viewOptions = Object.assign({
|
this.viewOptions = Object.assign({
|
||||||
fitCenter: true,
|
fitCenter: true,
|
||||||
fitView: false,
|
fitView: false,
|
||||||
groupPadding: 20
|
groupPadding: 20,
|
||||||
|
leakAreaHeight: 0.3,
|
||||||
|
updateHighlight: '#fc5185'
|
||||||
}, engineOptions.view);
|
}, engineOptions.view);
|
||||||
|
|
||||||
this.animationOptions = Object.assign({
|
this.animationOptions = Object.assign({
|
||||||
enable: true,
|
enable: true,
|
||||||
duration: 900,
|
duration: 750,
|
||||||
timingFunction: 'easePolyOut'
|
timingFunction: 'easePolyOut'
|
||||||
}, engineOptions.animation);
|
}, engineOptions.animation);
|
||||||
|
|
||||||
@ -44,26 +43,25 @@ export class Engine {
|
|||||||
drag: true,
|
drag: true,
|
||||||
zoom: true,
|
zoom: true,
|
||||||
dragNode: true,
|
dragNode: true,
|
||||||
selectNode: true,
|
selectNode: true
|
||||||
changeHighlight: '#fc5185'
|
|
||||||
}, engineOptions.interaction);
|
}, engineOptions.interaction);
|
||||||
|
|
||||||
// 初始化布局器配置项
|
// 初始化布局器配置项
|
||||||
Object.keys(SV.registeredLayouter).forEach(layouter => {
|
Object.keys(SV.registeredLayout).forEach(layout => {
|
||||||
if(this.optionsTable[layouter] === undefined) {
|
if(this.optionsTable[layout] === undefined) {
|
||||||
const options: LayoutGroupOptions = SV.registeredLayouter[layouter].defineOptions();
|
const options: LayoutGroupOptions = SV.registeredLayout[layout].defineOptions();
|
||||||
|
|
||||||
options.behavior = Object.assign({
|
options.behavior = Object.assign({
|
||||||
dragNode: true,
|
dragNode: true,
|
||||||
selectNode: true
|
selectNode: true
|
||||||
}, options.behavior);
|
}, options.behavior);
|
||||||
|
|
||||||
this.optionsTable[layouter] = options;
|
this.optionsTable[layout] = options;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.modelConstructor = new ModelConstructor(this);
|
this.modelConstructor = new ModelConstructor(this);
|
||||||
this.viewManager = new ViewManager(this, DOMContainer);
|
this.viewContainer = new ViewContainer(this, DOMContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,6 +73,10 @@ export class Engine {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.viewContainer.getG6Instance().isAnimating()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let stringSourceData = JSON.stringify(sourceData);
|
let stringSourceData = JSON.stringify(sourceData);
|
||||||
if(this.prevStringSourceData === stringSourceData) {
|
if(this.prevStringSourceData === stringSourceData) {
|
||||||
return;
|
return;
|
||||||
@ -82,74 +84,70 @@ export class Engine {
|
|||||||
this.prevStringSourceData = stringSourceData;
|
this.prevStringSourceData = stringSourceData;
|
||||||
|
|
||||||
// 1 转换模型(data => model)
|
// 1 转换模型(data => model)
|
||||||
this.layoutGroupTable = this.modelConstructor.construct(sourceData);
|
const layoutGroupTable = this.modelConstructor.construct(sourceData);
|
||||||
|
|
||||||
// 2 渲染(使用g6进行渲染)
|
// 2 渲染(使用g6进行渲染)
|
||||||
this.viewManager.renderAll(this.layoutGroupTable);
|
this.viewContainer.render(layoutGroupTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新布局
|
* 重新布局
|
||||||
*/
|
*/
|
||||||
public reLayout() {
|
public reLayout() {
|
||||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
this.viewContainer.reLayout();
|
||||||
|
|
||||||
this.viewManager.reLayout(layoutGroupTable);
|
// layoutGroupTable.forEach(group => {
|
||||||
|
// group.modelList.forEach(item => {
|
||||||
|
// if(item instanceof SVLink) return;
|
||||||
|
|
||||||
layoutGroupTable.forEach(group => {
|
// let model = item.G6Item.getModel(),
|
||||||
group.modelList.forEach(item => {
|
// x = item.get('x'),
|
||||||
if(item instanceof Link) return;
|
// y = item.get('y');
|
||||||
|
|
||||||
let model = item.G6Item.getModel(),
|
// model.x = x;
|
||||||
x = item.get('x'),
|
// model.y = y;
|
||||||
y = item.get('y');
|
// });
|
||||||
|
// });
|
||||||
model.x = x;
|
|
||||||
model.y = y;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.viewManager.refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 G6 实例
|
* 获取 G6 实例
|
||||||
*/
|
*/
|
||||||
public getGraphInstance() {
|
public getGraphInstance() {
|
||||||
return this.viewManager.getG6Instance();
|
return this.viewContainer.getG6Instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有 element
|
* 获取所有 element
|
||||||
* @param group
|
* @param group
|
||||||
*/
|
*/
|
||||||
public getElements(group?: string): Element[] {
|
public getNodes(group?: string): SVNode[] {
|
||||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
|
|
||||||
if(group && layoutGroupTable.has('group')) {
|
if(group && layoutGroupTable.has('group')) {
|
||||||
return layoutGroupTable.get('group').element;
|
return layoutGroupTable.get('group').node;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements: Element[] = [];
|
const nodes: SVNode[] = [];
|
||||||
layoutGroupTable.forEach(item => {
|
layoutGroupTable.forEach(item => {
|
||||||
elements.push(...item.element);
|
nodes.push(...item.node);
|
||||||
})
|
})
|
||||||
|
|
||||||
return elements;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有 marker
|
* 获取所有 marker
|
||||||
* @param group
|
* @param group
|
||||||
*/
|
*/
|
||||||
public getMarkers(group?: string): Marker[] {
|
public getMarkers(group?: string): SVMarker[] {
|
||||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
|
|
||||||
if(group && layoutGroupTable.has('group')) {
|
if(group && layoutGroupTable.has('group')) {
|
||||||
return layoutGroupTable.get('group').marker;
|
return layoutGroupTable.get('group').marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
const markers: Marker[] = [];
|
const markers: SVMarker[] = [];
|
||||||
layoutGroupTable.forEach(item => {
|
layoutGroupTable.forEach(item => {
|
||||||
markers.push(...item.marker);
|
markers.push(...item.marker);
|
||||||
})
|
})
|
||||||
@ -161,14 +159,14 @@ export class Engine {
|
|||||||
* 获取所有 link
|
* 获取所有 link
|
||||||
* @param group
|
* @param group
|
||||||
*/
|
*/
|
||||||
public getLinks(group?: string): Link[] {
|
public getLinks(group?: string): SVLink[] {
|
||||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
|
|
||||||
if(group && layoutGroupTable.has('group')) {
|
if(group && layoutGroupTable.has('group')) {
|
||||||
return layoutGroupTable.get('group').link;
|
return layoutGroupTable.get('group').link;
|
||||||
}
|
}
|
||||||
|
|
||||||
const links: Link[] = [];
|
const links: SVLink[] = [];
|
||||||
layoutGroupTable.forEach(item => {
|
layoutGroupTable.forEach(item => {
|
||||||
links.push(...item.link);
|
links.push(...item.link);
|
||||||
})
|
})
|
||||||
@ -182,10 +180,11 @@ export class Engine {
|
|||||||
*/
|
*/
|
||||||
public hideGroups(groupNames: string | string[]) {
|
public hideGroups(groupNames: string | string[]) {
|
||||||
const names = Array.isArray(groupNames)? groupNames: [groupNames],
|
const names = Array.isArray(groupNames)? groupNames: [groupNames],
|
||||||
instance = this.viewManager.getG6Instance();
|
instance = this.viewContainer.getG6Instance(),
|
||||||
|
layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||||
|
|
||||||
this.layoutGroupTable.forEach(item => {
|
layoutGroupTable.forEach(item => {
|
||||||
const hasName = names.find(name => name === item.layouterName);
|
const hasName = names.find(name => name === item.layout);
|
||||||
|
|
||||||
if(hasName && !item.isHide) {
|
if(hasName && !item.isHide) {
|
||||||
item.modelList.forEach(model => instance.hideItem(model.G6Item));
|
item.modelList.forEach(model => instance.hideItem(model.G6Item));
|
||||||
@ -204,7 +203,7 @@ export class Engine {
|
|||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
public findElement(id: string) {
|
public findElement(id: string) {
|
||||||
const elements = this.getElements();
|
const elements = this.getNodes();
|
||||||
const stringId = id.toString();
|
const stringId = id.toString();
|
||||||
const targetElement = elements.find(item => item.sourceId === stringId);
|
const targetElement = elements.find(item => item.sourceId === stringId);
|
||||||
|
|
||||||
@ -213,12 +212,11 @@ export class Engine {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 调整容器尺寸
|
* 调整容器尺寸
|
||||||
* @param containerName
|
|
||||||
* @param width
|
* @param width
|
||||||
* @param height
|
* @param height
|
||||||
*/
|
*/
|
||||||
public resize(containerName: string, width: number, height: number) {
|
public resize(width: number, height: number) {
|
||||||
this.viewManager.resize(containerName, width, height);
|
this.viewContainer.resize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,8 +234,13 @@ export class Engine {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.viewManager.getG6Instance().on(eventName, event => {
|
if(eventName === 'onLeakAreaUpdate') {
|
||||||
callback(event.item.SVModel);
|
EventBus.on(eventName, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewContainer.getG6Instance().on(eventName, event => {
|
||||||
|
callback(event.item['SVModel']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +249,6 @@ export class Engine {
|
|||||||
*/
|
*/
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.modelConstructor.destroy();
|
this.modelConstructor.destroy();
|
||||||
this.viewManager.destroy();
|
this.viewContainer.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Element } from "./Model/modelData";
|
import { SVNode } from "./Model/SVNode";
|
||||||
import { SourceElement } from "./sources";
|
import { SourceNode } from "./sources";
|
||||||
|
|
||||||
|
|
||||||
export interface Style {
|
export interface Style {
|
||||||
@ -15,14 +15,14 @@ export interface Style {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export interface ElementLabelOption {
|
export interface NodeLabelOption {
|
||||||
position: string;
|
position: string;
|
||||||
offset: number;
|
offset: number;
|
||||||
style: Style;
|
style: Style;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export interface ElementIndexOption extends ElementLabelOption {
|
export interface NodeIndexOption extends NodeLabelOption {
|
||||||
position: 'top' | 'right' | 'bottom' | 'left';
|
position: 'top' | 'right' | 'bottom' | 'left';
|
||||||
value: string;
|
value: string;
|
||||||
style: Style;
|
style: Style;
|
||||||
@ -38,31 +38,32 @@ export interface LinkLabelOption {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export interface ModelOption {
|
||||||
export interface ElementOption {
|
|
||||||
type: string;
|
type: string;
|
||||||
size: number | [number, number];
|
|
||||||
rotation: number;
|
|
||||||
anchorPoints: [number, number];
|
|
||||||
label: string | string[];
|
|
||||||
labelOptions: ElementLabelOption;
|
|
||||||
indexOptions: ElementIndexOption;
|
|
||||||
style: Style;
|
style: Style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface LinkOption {
|
export interface NodeOption extends ModelOption {
|
||||||
type: string;
|
size: number | [number, number];
|
||||||
|
rotation: number;
|
||||||
|
label: string | string[];
|
||||||
|
anchorPoints: number[][];
|
||||||
|
indexOptions: NodeIndexOption;
|
||||||
|
labelOptions: NodeLabelOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface LinkOption extends ModelOption {
|
||||||
sourceAnchor: number | ((index: number) => number);
|
sourceAnchor: number | ((index: number) => number);
|
||||||
targetAnchor: number | ((index: number) => number);
|
targetAnchor: number | ((index: number) => number);
|
||||||
label: string;
|
label: string;
|
||||||
curveOffset: number;
|
curveOffset: number;
|
||||||
labelOptions: LinkLabelOption;
|
labelOptions: LinkLabelOption;
|
||||||
style: Style;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface MarkerOption extends ElementOption {
|
export interface MarkerOption extends NodeOption {
|
||||||
type: 'pointer' | 'cursor' | 'clen-queue-pointer';
|
type: 'pointer' | 'cursor' | 'clen-queue-pointer';
|
||||||
anchor: number;
|
anchor: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
@ -83,7 +84,7 @@ export interface BehaviorOptions {
|
|||||||
|
|
||||||
|
|
||||||
export interface LayoutGroupOptions {
|
export interface LayoutGroupOptions {
|
||||||
element: { [key: string]: ElementOption };
|
node: { [key: string]: NodeOption };
|
||||||
link?: { [key: string]: LinkOption }
|
link?: { [key: string]: LinkOption }
|
||||||
marker?: { [key: string]: MarkerOption }
|
marker?: { [key: string]: MarkerOption }
|
||||||
layout?: LayoutOptions;
|
layout?: LayoutOptions;
|
||||||
@ -99,8 +100,9 @@ export interface LayoutGroupOptions {
|
|||||||
|
|
||||||
export interface ViewOptions {
|
export interface ViewOptions {
|
||||||
fitCenter: boolean;
|
fitCenter: boolean;
|
||||||
fitView: boolean;
|
|
||||||
groupPadding: number;
|
groupPadding: number;
|
||||||
|
updateHighlight: string;
|
||||||
|
leakAreaHeight: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -112,25 +114,22 @@ export interface AnimationOptions {
|
|||||||
|
|
||||||
|
|
||||||
export interface InteractionOptions {
|
export interface InteractionOptions {
|
||||||
changeHighlight: string;
|
|
||||||
drag: boolean;
|
drag: boolean;
|
||||||
zoom: boolean;
|
zoom: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EngineOptions {
|
export interface EngineOptions {
|
||||||
freedContainer?: HTMLElement;
|
|
||||||
leakContainer?: HTMLElement;
|
|
||||||
view?: ViewOptions;
|
view?: ViewOptions;
|
||||||
animation?: AnimationOptions;
|
animation?: AnimationOptions;
|
||||||
interaction?: InteractionOptions;
|
interaction?: InteractionOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export interface Layouter {
|
export interface LayoutCreator {
|
||||||
defineOptions(): LayoutGroupOptions;
|
defineOptions(): LayoutGroupOptions;
|
||||||
sourcesPreprocess?(sources: SourceElement[], options: LayoutGroupOptions): SourceElement[];
|
sourcesPreprocess?(sources: SourceNode[], options: LayoutGroupOptions): SourceNode[];
|
||||||
defineLeakRule?(elements: Element[]): Element[];
|
defineLeakRule?(nodes: SVNode[]): SVNode[];
|
||||||
layout(elements: Element[], layoutOptions: LayoutOptions);
|
layout(nodes: SVNode[], layoutOptions: LayoutOptions);
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,17 +6,17 @@ export type LinkTarget = number | string;
|
|||||||
export type sourceLinkData = LinkTarget | LinkTarget[];
|
export type sourceLinkData = LinkTarget | LinkTarget[];
|
||||||
|
|
||||||
// 结点指针声明
|
// 结点指针声明
|
||||||
export type sourcePointerData = string | string[];
|
export type sourceMarkerData = string | string[];
|
||||||
|
|
||||||
// 源数据单元
|
// 源数据单元
|
||||||
export interface SourceElement {
|
export interface SourceNode {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
[key: string]: any | sourceLinkData | sourcePointerData;
|
[key: string]: any | sourceLinkData | sourceMarkerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type Sources = {
|
export type Sources = {
|
||||||
[key: string]: { data: SourceElement[]; layouter: string; }
|
[key: string]: { data: SourceNode[]; layouter: string; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user