Revert "Merge branch 'feat-stage' into 'main'"
This reverts merge request !1
This commit is contained in:
parent
346b6c42fd
commit
2baed0b927
@ -19,7 +19,7 @@ const isNeighbor = function (itemA, itemB) {
|
||||
}
|
||||
|
||||
|
||||
SV.registerLayout('AdjoinMatrixGraph', {
|
||||
SV.registerLayouter('AdjoinMatrixGraph', {
|
||||
|
||||
sourcesPreprocess(sources) {
|
||||
let dataLength = sources.length;
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
|
||||
SV.registerLayout('AdjoinTableGraph', {
|
||||
SV.registerLayouter('AdjoinTableGraph', {
|
||||
|
||||
sourcesPreprocess(sources, options) {
|
||||
let dataLength = sources.length;
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
|
||||
SV.registerLayout('Array', {
|
||||
SV.registerLayouter('Array', {
|
||||
|
||||
sourcesPreprocess(sources) {
|
||||
const firstElement = sources[0];
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
SV.registerLayout('BinaryTree', {
|
||||
SV.registerLayouter('BinaryTree', {
|
||||
defineOptions() {
|
||||
return {
|
||||
element: {
|
||||
|
@ -6,7 +6,7 @@
|
||||
/**
|
||||
* 连地址哈希表
|
||||
*/
|
||||
SV.registerLayout('ChainHashTable', {
|
||||
SV.registerLayouter('ChainHashTable', {
|
||||
|
||||
defineOptions() {
|
||||
return {
|
||||
|
@ -80,7 +80,7 @@ SV.registerShape('three-cell-node', {
|
||||
|
||||
|
||||
|
||||
SV.registerLayout('GeneralizedList', {
|
||||
SV.registerLayouter('GeneralizedList', {
|
||||
|
||||
defineOptions() {
|
||||
return {
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
|
||||
SV.registerLayout('HashTable', {
|
||||
SV.registerLayouter('HashTable', {
|
||||
|
||||
sourcesPreprocess(sources) {
|
||||
const firstElement = sources[0];
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
SV.registerLayout('LinkList', {
|
||||
SV.registerLayouter('LinkList', {
|
||||
|
||||
sourcesPreprocess(sources) {
|
||||
let root = sources[0];
|
||||
@ -15,7 +15,7 @@ SV.registerLayout('LinkList', {
|
||||
|
||||
defineOptions() {
|
||||
return {
|
||||
node: {
|
||||
element: {
|
||||
default: {
|
||||
type: 'link-list-node',
|
||||
label: '[data]',
|
||||
@ -107,6 +107,7 @@ SV.registerLayout('LinkList', {
|
||||
|
||||
layout(elements, layoutOptions) {
|
||||
let root = elements[0];
|
||||
|
||||
this.layoutItem(root, null, layoutOptions);
|
||||
}
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
|
||||
SV.registerLayout('LinkQueue', {
|
||||
SV.registerLayouter('LinkQueue', {
|
||||
|
||||
defineOptions() {
|
||||
return {
|
||||
|
@ -3,7 +3,7 @@
|
||||
/**
|
||||
* 单链表
|
||||
*/
|
||||
SV.registerLayout('LinkStack', {
|
||||
SV.registerLayouter('LinkStack', {
|
||||
sourcesPreprocess(sources) {
|
||||
const headNode = sources[0];
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
SV.registerLayout('Stack', {
|
||||
SV.registerLayouter('Stack', {
|
||||
|
||||
sourcesPreprocess(sources, options) {
|
||||
const stackBottomNode = sources[sources.length - 1];
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 三叉树
|
||||
*/
|
||||
SV.registerLayout('TriTree', {
|
||||
SV.registerLayouter('TriTree', {
|
||||
defineOptions() {
|
||||
return {
|
||||
/**
|
||||
|
@ -25,43 +25,38 @@
|
||||
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#freed {
|
||||
width: 200px;
|
||||
height: 300px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
#leak {
|
||||
position: absolute;
|
||||
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;
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container" id="container">
|
||||
<div id="leak">
|
||||
<span>泄漏区</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" id="container"></div>
|
||||
|
||||
<button id="btn-prev">prev</button>
|
||||
<button id="btn-next">next</button>
|
||||
<button id="resize">resize</button>
|
||||
<button id="hide">隐藏</button>
|
||||
<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>
|
||||
|
||||
@ -87,8 +82,9 @@
|
||||
const curSelectData = { element: null, style: null };
|
||||
|
||||
let cur = SV(document.getElementById('container'), {
|
||||
freedContainer: document.getElementById('freed'),
|
||||
leakContainer: document.getElementById('leak'),
|
||||
view: {
|
||||
leakAreaHeight: 0.25,
|
||||
groupPadding: 40,
|
||||
},
|
||||
});
|
||||
@ -97,42 +93,54 @@
|
||||
let data = [{
|
||||
"LinkList0": {
|
||||
"data": [
|
||||
{ id: 0, data: 'A', next: 1 },
|
||||
{ id: 1, data: 'B' }
|
||||
{
|
||||
"id": "0x617fb0",
|
||||
"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"
|
||||
}
|
||||
}, {
|
||||
"LinkList0": {
|
||||
},
|
||||
"LinkList1": {
|
||||
"data": [
|
||||
{ id: 0, data: 'A', next: 1 },
|
||||
{ id: 1, data: 'B', next: 2 },
|
||||
{ id: 2, data: 'C' }
|
||||
],
|
||||
"layouter": "LinkList"
|
||||
}
|
||||
}, {
|
||||
"LinkList0": {
|
||||
"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' }
|
||||
{
|
||||
"freed": true,
|
||||
"id": "0x617fd0",
|
||||
"data": "",
|
||||
"next": "0x605010",
|
||||
"rootExternal": [
|
||||
"tmpNode"
|
||||
],
|
||||
"type": "default"
|
||||
}
|
||||
],
|
||||
"layouter": "LinkList"
|
||||
}
|
||||
@ -145,9 +153,17 @@
|
||||
|
||||
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 => {
|
||||
curData = data[++dataIndex];
|
||||
cur.render(curData);
|
||||
// curSelectData.element = null;
|
||||
// curSelectData.style = null;
|
||||
});
|
||||
|
||||
document.getElementById('btn-prev').addEventListener('click', e => {
|
||||
@ -163,21 +179,49 @@
|
||||
pos.innerHTML = `${x},${y}`;
|
||||
});
|
||||
|
||||
document.getElementById('resize').addEventListener('click', e => {
|
||||
container.style.height = 300 + 'px';
|
||||
cur.resize(container.offsetWidth, container.offsetHeight);
|
||||
|
||||
document.getElementById('hide').addEventListener('click', () => {
|
||||
cur.selectElement(1000, ele => {
|
||||
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>
|
||||
</body>
|
||||
|
||||
|
2
dist/sv.js
vendored
2
dist/sv.js
vendored
File diff suppressed because one or more lines are too long
@ -1,9 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@antv/g6": "^4.4.1",
|
||||
"@antv/g6": "^4.2.1",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"typescript": "^3.2.2",
|
||||
"webpack": "^4.28.2"
|
||||
"webpack": "^4.28.2",
|
||||
"zrender": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-cli": "^3.2.3"
|
||||
|
@ -1,54 +0,0 @@
|
||||
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;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,116 +0,0 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
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;
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
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,7 +1,6 @@
|
||||
import { Util } from "./util";
|
||||
import { BoundingRect, Bound } from "./boundingRect";
|
||||
import { SVModel } from "../Model/SVModel";
|
||||
import { ext } from '@antv/matrix-util';
|
||||
import { Element, Model } from "../Model/modelData";
|
||||
|
||||
|
||||
|
||||
@ -10,12 +9,12 @@ import { ext } from '@antv/matrix-util';
|
||||
*/
|
||||
export class Group {
|
||||
id: string;
|
||||
private models: Array<SVModel | Group> = [];
|
||||
private models: Array<Model | Group> = [];
|
||||
|
||||
constructor(...arg: Array<SVModel | Group>) {
|
||||
constructor(...arg: Array<Model | Group>) {
|
||||
this.id = Util.generateId();
|
||||
|
||||
if (arg) {
|
||||
if(arg) {
|
||||
this.add(...arg);
|
||||
}
|
||||
}
|
||||
@ -24,7 +23,7 @@ export class Group {
|
||||
* 添加element
|
||||
* @param arg
|
||||
*/
|
||||
add(...arg: Array<SVModel | Group>) {
|
||||
add(...arg: Array<Model | Group>) {
|
||||
arg.map(ele => {
|
||||
this.models.push(ele);
|
||||
});
|
||||
@ -34,7 +33,7 @@ export class Group {
|
||||
* 移除 model
|
||||
* @param element
|
||||
*/
|
||||
remove(model: SVModel | Group) {
|
||||
remove(model: Model | Group) {
|
||||
Util.removeFromList(this.models, item => item.id === model.id);
|
||||
}
|
||||
|
||||
@ -42,9 +41,9 @@ export class Group {
|
||||
* 获取group的包围盒
|
||||
*/
|
||||
getBound(): BoundingRect {
|
||||
return this.models.length ?
|
||||
Bound.union(...this.models.map(item => item.getBound())) :
|
||||
{ x: 0, y: 0, width: 0, height: 0 };
|
||||
return this.models.length?
|
||||
Bound.union(...this.models.map(item => item.getBound())):
|
||||
{ x: 0, y: 0, width: 0, height: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,10 +62,6 @@ export class Group {
|
||||
return bound;
|
||||
}
|
||||
|
||||
getModels(): Array<SVModel | Group> {
|
||||
return this.models;
|
||||
}
|
||||
|
||||
/**
|
||||
* 位移group
|
||||
* @param dx
|
||||
@ -74,7 +69,7 @@ export class Group {
|
||||
*/
|
||||
translate(dx: number, dy: number) {
|
||||
this.models.map(item => {
|
||||
if (item instanceof Group) {
|
||||
if(item instanceof Group) {
|
||||
item.translate(dx, dy);
|
||||
}
|
||||
else {
|
||||
@ -84,27 +79,6 @@ 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
|
||||
*/
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { EdgeConfig, GraphData, NodeConfig } from "@antv/g6-core";
|
||||
import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor";
|
||||
import { SVLink } from "../Model/SVLink";
|
||||
import { SVModel } from "../Model/SVModel";
|
||||
import { G6EdgeModel, G6NodeModel, Link, Model } from "../Model/modelData";
|
||||
import { SV } from "../StructV";
|
||||
import { G6Data } from "../View/renderer";
|
||||
|
||||
|
||||
/**
|
||||
@ -90,8 +89,8 @@ export const Util = {
|
||||
* @param groupTable
|
||||
* @returns
|
||||
*/
|
||||
convertGroupTable2ModelList(groupTable: LayoutGroupTable): SVModel[] {
|
||||
const list: SVModel[] = [];
|
||||
convertGroupTable2ModelList(groupTable: LayoutGroupTable): Model[] {
|
||||
const list: Model[] = [];
|
||||
|
||||
groupTable.forEach(item => {
|
||||
list.push(...item.modelList);
|
||||
@ -105,13 +104,13 @@ export const Util = {
|
||||
* @param layoutGroup
|
||||
* @returns
|
||||
*/
|
||||
convertG6Data(layoutGroup: LayoutGroup): GraphData {
|
||||
let nodes = [...layoutGroup.node, ...layoutGroup.marker],
|
||||
convertG6Data(layoutGroup: LayoutGroup): G6Data {
|
||||
let nodes = [...layoutGroup.element, ...layoutGroup.marker],
|
||||
edges = layoutGroup.link;
|
||||
|
||||
return {
|
||||
nodes: nodes.map(item => item.getG6ModelProps()) as NodeConfig[],
|
||||
edges: edges.map(item => item.getG6ModelProps()) as EdgeConfig[]
|
||||
nodes: nodes.map(item => item.cloneProps()) as G6NodeModel[],
|
||||
edges: edges.map(item => item.cloneProps()) as G6EdgeModel[]
|
||||
};
|
||||
},
|
||||
|
||||
@ -119,10 +118,10 @@ export const Util = {
|
||||
* 将 modelList 转换到 G6Data
|
||||
* @param modelList
|
||||
*/
|
||||
convertModelList2G6Data(modelList: SVModel[]): GraphData {
|
||||
convertModelList2G6Data(modelList: Model[]): G6Data {
|
||||
return {
|
||||
nodes: <NodeConfig[]>(modelList.filter(item => !(item instanceof SVLink)).map(item => item.getG6ModelProps())),
|
||||
edges: <EdgeConfig[]>(modelList.filter(item => item instanceof SVLink).map(item => item.getG6ModelProps()))
|
||||
nodes: <G6NodeModel[]>(modelList.filter(item => !(item instanceof Link)).map(item => item.cloneProps())),
|
||||
edges: <G6EdgeModel[]>(modelList.filter(item => item instanceof Link).map(item => item.cloneProps()))
|
||||
}
|
||||
},
|
||||
|
||||
@ -138,4 +137,3 @@ export const Util = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
31
src/Lib/g6.js
Normal file
31
src/Lib/g6.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,53 +0,0 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
};
|
@ -1,51 +0,0 @@
|
||||
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;
|
||||
}
|
||||
};
|
@ -1,170 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,151 +0,0 @@
|
||||
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,25 +1,20 @@
|
||||
import { Util } from "../Common/util";
|
||||
import { Engine } from "../engine";
|
||||
import { LayoutCreator, LayoutGroupOptions, LinkOption, MarkerOption, NodeOption } from "../options";
|
||||
import { sourceLinkData, LinkTarget, Sources, SourceNode } from "../sources";
|
||||
import { ElementIndexOption, ElementOption, Layouter, LayoutGroupOptions, LinkOption, MarkerOption } from "../options";
|
||||
import { sourceLinkData, SourceElement, LinkTarget, Sources } from "../sources";
|
||||
import { SV } from "../StructV";
|
||||
import { SVLink } from "./SVLink";
|
||||
import { SVMarker } from "./SVMarker";
|
||||
import { SVModel } from "./SVModel";
|
||||
import { SVFreedLabel, SVLeakAddress, SVNode } from "./SVNode";
|
||||
import { Element, Link, Marker, Model } from "./modelData";
|
||||
|
||||
|
||||
export type LayoutGroup = {
|
||||
name: string;
|
||||
node: SVNode[];
|
||||
freedLabel: SVFreedLabel[];
|
||||
leakAddress: SVLeakAddress[];
|
||||
link: SVLink[];
|
||||
marker: SVMarker[];
|
||||
layoutCreator: LayoutCreator;
|
||||
layout: string;
|
||||
element: Element[];
|
||||
link: Link[];
|
||||
marker: Marker[];
|
||||
layouter: Layouter;
|
||||
layouterName: string;
|
||||
options: LayoutGroupOptions;
|
||||
modelList: SVModel[];
|
||||
modelList: Model[];
|
||||
isHide: boolean;
|
||||
};
|
||||
|
||||
@ -38,66 +33,56 @@ export class ModelConstructor {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建svnode,svlink 和 svmarker
|
||||
* 构建element,link和marker
|
||||
* @param sourceList
|
||||
*/
|
||||
public construct(sources: Sources): LayoutGroupTable {
|
||||
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
||||
layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout,
|
||||
layouterMap: { [key: string]: Layouter } = SV.registeredLayouter,
|
||||
optionsTable = this.engine.optionsTable;
|
||||
|
||||
Object.keys(sources).forEach(group => {
|
||||
let sourceGroup = sources[group],
|
||||
layout = sourceGroup.layouter,
|
||||
layoutCreator: LayoutCreator = layoutMap[layout];
|
||||
Object.keys(sources).forEach(name => {
|
||||
let sourceGroup = sources[name],
|
||||
layouterName = sourceGroup.layouter,
|
||||
layouter: Layouter = layouterMap[sourceGroup.layouter];
|
||||
|
||||
if (!layout || !layoutCreator) {
|
||||
if (!layouterName || !layouter) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sourceDataString: string = JSON.stringify(sourceGroup.data),
|
||||
prevString: string = this.prevSourcesStringMap[group],
|
||||
nodeList: SVNode[] = [],
|
||||
freedLabelList: SVFreedLabel[] = [],
|
||||
leakAddress: SVLeakAddress[] = [],
|
||||
markerList: SVMarker[] = [];
|
||||
prevString: string = this.prevSourcesStringMap[name],
|
||||
elementList: Element[] = [],
|
||||
markerList: Marker[] = [];
|
||||
|
||||
if (prevString === sourceDataString) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options: LayoutGroupOptions = optionsTable[layout],
|
||||
sourceData = layoutCreator.sourcesPreprocess(sourceGroup.data, options),
|
||||
nodeOptions = options.node || options['element'] || {},
|
||||
const options: LayoutGroupOptions = optionsTable[layouterName],
|
||||
sourceData = layouter.sourcesPreprocess(sourceGroup.data, options),
|
||||
elementOptions = options.element || {},
|
||||
markerOptions = options.marker || {};
|
||||
|
||||
nodeList = this.constructNodes(nodeOptions, group, sourceData, layout);
|
||||
leakAddress = nodeList.map(item => item.leakAddress);
|
||||
markerList = this.constructMarkers(group, layout, markerOptions, nodeList);
|
||||
nodeList.forEach(item => {
|
||||
if(item.freedLabel) {
|
||||
freedLabelList.push(item.freedLabel);
|
||||
}
|
||||
});
|
||||
elementList = this.constructElements(elementOptions, name, sourceData, layouterName);
|
||||
markerList = this.constructMarkers(name, markerOptions, elementList);
|
||||
|
||||
layoutGroupTable.set(group, {
|
||||
name: group,
|
||||
node: nodeList,
|
||||
freedLabel: freedLabelList,
|
||||
leakAddress: leakAddress,
|
||||
layoutGroupTable.set(name, {
|
||||
name,
|
||||
element: elementList,
|
||||
link: [],
|
||||
marker: markerList,
|
||||
options: options,
|
||||
layoutCreator,
|
||||
modelList: [...nodeList, ...markerList, ...freedLabelList, ...leakAddress],
|
||||
layout,
|
||||
layouter: layouter,
|
||||
modelList: [...elementList, ...markerList],
|
||||
layouterName,
|
||||
isHide: false
|
||||
});
|
||||
});
|
||||
|
||||
layoutGroupTable.forEach((layoutGroup: LayoutGroup, group: string) => {
|
||||
layoutGroupTable.forEach((layoutGroup: LayoutGroup) => {
|
||||
const linkOptions = layoutGroup.options.link || {},
|
||||
linkList: SVLink[] = this.constructLinks(linkOptions, layoutGroup.node, layoutGroupTable, group, layoutGroup.layout, );
|
||||
linkList: Link[] = this.constructLinks(linkOptions, layoutGroup.element, layoutGroupTable);
|
||||
|
||||
layoutGroup.link = linkList;
|
||||
layoutGroup.modelList.push(...linkList);
|
||||
@ -117,16 +102,16 @@ export class ModelConstructor {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从源数据构建 node 集
|
||||
* @param nodeOptions
|
||||
* @param group
|
||||
* 从源数据构建 element 集
|
||||
* @param elementOptions
|
||||
* @param groupName
|
||||
* @param sourceList
|
||||
* @param layout
|
||||
* @param layouterName
|
||||
* @returns
|
||||
*/
|
||||
private constructNodes(nodeOptions: { [key: string]: NodeOption }, group: string, sourceList: SourceNode[], layout: string): SVNode[] {
|
||||
let defaultSourceNodeType: string = 'default',
|
||||
nodeList: SVNode[] = [];
|
||||
private constructElements(elementOptions: { [key: string]: ElementOption }, groupName: string, sourceList: SourceElement[], layouterName: string): Element[] {
|
||||
let defaultElementType: string = 'default',
|
||||
elementList: Element[] = [];
|
||||
|
||||
sourceList.forEach(item => {
|
||||
if (item === null) {
|
||||
@ -134,62 +119,62 @@ export class ModelConstructor {
|
||||
}
|
||||
|
||||
if (item.type === undefined || item.type === null) {
|
||||
item.type = defaultSourceNodeType;
|
||||
item.type = defaultElementType;
|
||||
}
|
||||
|
||||
nodeList.push(this.createNode(item, item.type, group, layout, nodeOptions[item.type]));
|
||||
elementList.push(this.createElement(item, item.type, groupName, layouterName, elementOptions[item.type]));
|
||||
});
|
||||
|
||||
return nodeList;
|
||||
return elementList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置和 node 集构建 link 集
|
||||
* 从配置和 element 集构建 link 集
|
||||
* @param linkOptions
|
||||
* @param nodes
|
||||
* @param elements
|
||||
* @param layoutGroupTable
|
||||
* @returns
|
||||
*/
|
||||
private constructLinks(linkOptions: { [key: string]: LinkOption }, nodes: SVNode[], layoutGroupTable: LayoutGroupTable, group: string, layout: string): SVLink[] {
|
||||
let linkList: SVLink[] = [],
|
||||
private constructLinks(linkOptions: { [key: string]: LinkOption }, elements: Element[], layoutGroupTable: LayoutGroupTable): Link[] {
|
||||
let linkList: Link[] = [],
|
||||
linkNames = Object.keys(linkOptions);
|
||||
|
||||
linkNames.forEach(name => {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node: SVNode = nodes[i],
|
||||
sourceLinkData: sourceLinkData = node.sourceNode[name],
|
||||
targetNode: SVNode | SVNode[] = null,
|
||||
link: SVLink = null;
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
let element: Element = elements[i],
|
||||
sourceLinkData: sourceLinkData = element.sourceElement[name],
|
||||
targetElement: Element | Element[] = null,
|
||||
link: Link = null;
|
||||
|
||||
if (sourceLinkData === undefined || sourceLinkData === null) {
|
||||
node[name] = null;
|
||||
element[name] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 SVNode -------------------
|
||||
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 Element -------------------
|
||||
if (Array.isArray(sourceLinkData)) {
|
||||
node[name] = sourceLinkData.map((item, index) => {
|
||||
targetNode = this.fetchTargetNodes(layoutGroupTable, node, item);
|
||||
element[name] = sourceLinkData.map((item, index) => {
|
||||
targetElement = this.fetchTargetElements(layoutGroupTable, element, item);
|
||||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
||||
|
||||
if (targetNode) {
|
||||
link = this.createLink(name, group, layout, node, targetNode, index, linkOptions[name]);
|
||||
if (targetElement) {
|
||||
link = this.createLink(name, element, targetElement, index, linkOptions[name]);
|
||||
linkList.push(link);
|
||||
}
|
||||
|
||||
return isGeneralLink ? targetNode : null;
|
||||
return isGeneralLink ? targetElement : null;
|
||||
});
|
||||
}
|
||||
else {
|
||||
targetNode = this.fetchTargetNodes(layoutGroupTable, node, sourceLinkData);
|
||||
targetElement = this.fetchTargetElements(layoutGroupTable, element, sourceLinkData);
|
||||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
||||
|
||||
if (targetNode) {
|
||||
link = this.createLink(name, group, layout, node, targetNode, null, linkOptions[name]);
|
||||
if (targetElement) {
|
||||
link = this.createLink(name, element, targetElement, null, linkOptions[name]);
|
||||
linkList.push(link);
|
||||
}
|
||||
|
||||
node[name] = isGeneralLink ? targetNode : null;
|
||||
element[name] = isGeneralLink ? targetElement : null;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -198,25 +183,25 @@ export class ModelConstructor {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置和 node 集构建 marker 集
|
||||
* 从配置和 element 集构建 marker 集
|
||||
* @param markerOptions
|
||||
* @param nodes
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
private constructMarkers(group: string, layout: string, markerOptions: { [key: string]: MarkerOption }, nodes: SVNode[]): SVMarker[] {
|
||||
let markerList: SVMarker[] = [],
|
||||
private constructMarkers(groupName: string, markerOptions: { [key: string]: MarkerOption }, elements: Element[]): Marker[] {
|
||||
let markerList: Marker[] = [],
|
||||
markerNames = Object.keys(markerOptions);
|
||||
|
||||
markerNames.forEach(name => {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i],
|
||||
markerData = node[name];
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
let element = elements[i],
|
||||
markerData = element[name];
|
||||
|
||||
// 若没有指针字段的结点则跳过
|
||||
if (!markerData) continue;
|
||||
|
||||
let id = `${group}.${name}.${Array.isArray(markerData) ? markerData.join('-') : markerData}`,
|
||||
marker = this.createMarker(id, name, markerData, group, layout, node, markerOptions[name]);
|
||||
let id = `${groupName}.${name}.${Array.isArray(markerData) ? markerData.join('-') : markerData}`,
|
||||
marker = this.createMarker(id, name, markerData, element, markerOptions[name]);
|
||||
|
||||
markerList.push(marker);
|
||||
}
|
||||
@ -228,19 +213,19 @@ export class ModelConstructor {
|
||||
/**
|
||||
* 求解label文本
|
||||
* @param label
|
||||
* @param sourceNode
|
||||
* @param sourceElement
|
||||
*/
|
||||
private resolveNodeLabel(label: string | string[], sourceNode: SourceNode): string {
|
||||
private resolveElementLabel(label: string | string[], sourceElement: SourceElement): string {
|
||||
let targetLabel: any = '';
|
||||
|
||||
if (Array.isArray(label)) {
|
||||
targetLabel = label.map(item => this.parserNodeContent(sourceNode, item) ?? '');
|
||||
targetLabel = label.map(item => this.parserElementContent(sourceElement, item) ?? '');
|
||||
}
|
||||
else {
|
||||
targetLabel = this.parserNodeContent(sourceNode, label);
|
||||
targetLabel = this.parserElementContent(sourceElement, label);
|
||||
}
|
||||
|
||||
if (targetLabel === 'undefined') {
|
||||
if(targetLabel === 'undefined') {
|
||||
targetLabel = '';
|
||||
}
|
||||
|
||||
@ -248,67 +233,90 @@ export class ModelConstructor {
|
||||
}
|
||||
|
||||
/**
|
||||
* 元素工厂,创建 Node
|
||||
* @param sourceNode
|
||||
* @param sourceNodeType
|
||||
* @param group
|
||||
* @param layout
|
||||
* @param options
|
||||
* 求解index文本
|
||||
* @param indexOptions
|
||||
* @param sourceElement
|
||||
*/
|
||||
private createNode(sourceNode: SourceNode, sourceNodeType: string, group: string, layout: string, options: NodeOption): SVNode {
|
||||
let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode),
|
||||
id = sourceNodeType + '.' + sourceNode.id.toString(),
|
||||
node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options);
|
||||
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);
|
||||
private resolveElementIndex(indexOptions: ElementIndexOption, sourceElement: SourceElement) {
|
||||
if(indexOptions === undefined || indexOptions === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return node;
|
||||
Object.keys(indexOptions).map(key => {
|
||||
let indexOptionItem = indexOptions[key];
|
||||
indexOptionItem.value = sourceElement[key] ?? '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 元素工厂,创建Element
|
||||
* @param sourceElement
|
||||
* @param elementName
|
||||
* @param groupName
|
||||
* @param layouterName
|
||||
* @param options
|
||||
*/
|
||||
private createElement(sourceElement: SourceElement, elementName: string, groupName: string, layouterName: string, options: ElementOption): Element {
|
||||
let element: Element = undefined,
|
||||
label: string | string[] = this.resolveElementLabel(options.label, sourceElement),
|
||||
id = elementName + '.' + sourceElement.id.toString();
|
||||
|
||||
|
||||
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
|
||||
* @param id
|
||||
* @param markerName
|
||||
* @param markerData
|
||||
* @param group
|
||||
* @param layout
|
||||
* @param label
|
||||
* @param target
|
||||
* @param options
|
||||
* @returns
|
||||
* @param options
|
||||
*/
|
||||
private createMarker(id: string, markerName: string, markerData: string | string[], group: string, layout: string, target: SVNode, options: MarkerOption): SVMarker {
|
||||
return new SVMarker(id, markerName, group, layout, markerData, target, options);;
|
||||
private createMarker(id: string, markerName: string, markerData: string | string[], target: Element, options: MarkerOption): Marker {
|
||||
let marker = undefined;
|
||||
|
||||
marker = new Marker(id, markerName, markerData, target);
|
||||
marker.initProps(options);
|
||||
|
||||
return marker;
|
||||
};
|
||||
|
||||
/**
|
||||
* 连线工厂,创建Link
|
||||
* @param linkName
|
||||
* @param group
|
||||
* @param layout
|
||||
* @param node
|
||||
* @param element
|
||||
* @param target
|
||||
* @param index
|
||||
* @param options
|
||||
* @returns
|
||||
* @param options
|
||||
*/
|
||||
private createLink(linkName: string, group: string, layout: string, node: SVNode, target: SVNode, index: number, options: LinkOption): SVLink {
|
||||
let id = `${linkName}(${node.id}-${target.id})`;
|
||||
return new SVLink(id, linkName, group, layout, node, target, index, options);
|
||||
private createLink(linkName: string, element: Element, target: Element, index: number, options: LinkOption): Link {
|
||||
let link = undefined,
|
||||
id = `${linkName}(${element.id}-${target.id})`;
|
||||
|
||||
link = new Link(id, linkName, element, target, index);
|
||||
link.initProps(options);
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析元素文本内容
|
||||
* @param sourceNode
|
||||
* @param sourceElement
|
||||
* @param formatLabel
|
||||
*/
|
||||
private parserNodeContent(sourceNode: SourceNode, formatLabel: string): string {
|
||||
private parserElementContent(sourceElement: SourceElement, formatLabel: string): string {
|
||||
let fields = Util.textParser(formatLabel);
|
||||
|
||||
if (Array.isArray(fields)) {
|
||||
let values = fields.map(item => sourceNode[item]);
|
||||
let values = fields.map(item => sourceElement[item]);
|
||||
|
||||
values.map((item, index) => {
|
||||
formatLabel = formatLabel.replace('[' + fields[index] + ']', item);
|
||||
@ -320,17 +328,17 @@ export class ModelConstructor {
|
||||
|
||||
/**
|
||||
* 由source中的连接字段获取真实的连接目标元素
|
||||
* @param nodeContainer
|
||||
* @param node
|
||||
* @param elementContainer
|
||||
* @param element
|
||||
* @param linkTarget
|
||||
*/
|
||||
private fetchTargetNodes(layoutGroupTable: LayoutGroupTable, node: SVNode, linkTarget: LinkTarget): SVNode {
|
||||
let group: string = node.group,
|
||||
sourceNodeType = node.sourceType,
|
||||
nodeList: SVNode[],
|
||||
private fetchTargetElements(layoutGroupTable: LayoutGroupTable, element: Element, linkTarget: LinkTarget): Element {
|
||||
let groupName: string = element.groupName,
|
||||
elementName = element.type,
|
||||
elementList: Element[],
|
||||
targetId = linkTarget,
|
||||
targetGroupName = group,
|
||||
targetNode = null;
|
||||
targetGroupName = groupName,
|
||||
targetElement = null;
|
||||
|
||||
if (linkTarget === null || linkTarget === undefined) {
|
||||
return null;
|
||||
@ -345,13 +353,13 @@ export class ModelConstructor {
|
||||
targetId = info.pop();
|
||||
|
||||
if (info.length > 1) {
|
||||
sourceNodeType = info.pop();
|
||||
elementName = info.pop();
|
||||
targetGroupName = info.pop();
|
||||
}
|
||||
else {
|
||||
let field = info.pop();
|
||||
if (layoutGroupTable.get(targetGroupName).node.find(item => item.sourceType === field)) {
|
||||
sourceNodeType = field;
|
||||
if (layoutGroupTable.get(targetGroupName).element.find(item => item.type === field)) {
|
||||
elementName = field;
|
||||
}
|
||||
else if (layoutGroupTable.has(field)) {
|
||||
targetGroupName = field;
|
||||
@ -361,15 +369,15 @@ export class ModelConstructor {
|
||||
}
|
||||
}
|
||||
|
||||
nodeList = layoutGroupTable.get(targetGroupName).node.filter(item => item.sourceType === sourceNodeType);
|
||||
elementList = layoutGroupTable.get(targetGroupName).element.filter(item => item.type === elementName);
|
||||
|
||||
// 若目标node不存在,返回null
|
||||
if (nodeList === undefined) {
|
||||
// 若目标element不存在,返回null
|
||||
if (elementList === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
targetNode = nodeList.find(item => item.sourceId === targetId);
|
||||
return targetNode || null;
|
||||
targetElement = elementList.find(item => item.sourceId === targetId);
|
||||
return targetElement || null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
337
src/Model/modelData.ts
Normal file
337
src/Model/modelData.ts
Normal file
@ -0,0 +1,337 @@
|
||||
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 G6 from '@antv/g6';
|
||||
import * as G6 from "./../Lib/g6.js";
|
||||
|
||||
|
||||
export default G6.registerNode('binary-tree-node', {
|
||||
|
@ -1,12 +1,11 @@
|
||||
import G6 from '@antv/g6';
|
||||
import * as G6 from "../Lib/g6.js";
|
||||
|
||||
|
||||
export default G6.registerNode('clen-queue-pointer', {
|
||||
draw(cfg, group) {
|
||||
let id = cfg.id as string;
|
||||
|
||||
const index = parseInt(id.split('-')[1]);
|
||||
const len = parseInt(id.split('-')[2]);
|
||||
// console.log(cfg);
|
||||
const index = cfg.id.split('-')[1];
|
||||
const len = cfg.id.split('-')[2];
|
||||
const keyShape = group.addShape('path', {
|
||||
attrs: {
|
||||
x: 0,
|
||||
@ -17,7 +16,6 @@ export default G6.registerNode('clen-queue-pointer', {
|
||||
},
|
||||
name: 'pointer-path'
|
||||
});
|
||||
|
||||
const angle = index * Math.PI * 2 / len;
|
||||
if (cfg.label) {
|
||||
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
|
||||
@ -34,10 +32,7 @@ export default G6.registerNode('clen-queue-pointer', {
|
||||
},
|
||||
name: 'bgRect'
|
||||
});
|
||||
|
||||
let label = cfg.label as string;
|
||||
|
||||
let pointerText = label.split('-')[0];
|
||||
let pointerText = cfg.label.split('-')[0];
|
||||
let y = pointerText=="front"?30:15;
|
||||
const text = group.addShape('text', {
|
||||
attrs: {
|
||||
|
@ -1,4 +1,6 @@
|
||||
import G6 from '@antv/g6';
|
||||
|
||||
|
||||
import * as G6 from "./../Lib/g6.js";
|
||||
|
||||
|
||||
export default G6.registerNode('cursor', {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import G6 from '@antv/g6';
|
||||
import * as G6 from "./../Lib/g6.js";
|
||||
|
||||
|
||||
export default G6.registerNode('indexed-node', {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import G6 from '@antv/g6';
|
||||
import * as G6 from "./../Lib/g6.js";
|
||||
|
||||
|
||||
export default G6.registerNode('link-list-node', {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import G6 from '@antv/g6';
|
||||
import * as G6 from "../Lib/g6.js";
|
||||
|
||||
|
||||
export default G6.registerNode('pointer', {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import G6 from '@antv/g6';
|
||||
import * as G6 from "./../Lib/g6.js";
|
||||
|
||||
|
||||
export default G6.registerNode('tri-tree-node', {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import G6 from '@antv/g6';
|
||||
import * as G6 from "./../Lib/g6.js";
|
||||
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { Engine } from "./engine";
|
||||
import { Bound } from "./Common/boundingRect";
|
||||
import { Group } from "./Common/group";
|
||||
import pointer from "./RegisteredShape/pointer";
|
||||
import G6, { Util } from '@antv/g6';
|
||||
import * as G6 from "./Lib/g6.js";
|
||||
import linkListNode from "./RegisteredShape/linkListNode";
|
||||
import binaryTreeNode from "./RegisteredShape/binaryTreeNode";
|
||||
import CLenQueuePointer from "./RegisteredShape/clenQueuePointer";
|
||||
@ -10,10 +10,9 @@ import twoCellNode from "./RegisteredShape/twoCellNode";
|
||||
import Cursor from "./RegisteredShape/cursor";
|
||||
import { Vector } from "./Common/vector";
|
||||
import indexedNode from "./RegisteredShape/indexedNode";
|
||||
import { EngineOptions, LayoutCreator } from "./options";
|
||||
import { SVNode } from "./Model/SVNode";
|
||||
import { SourceNode } from "./sources";
|
||||
|
||||
import { EngineOptions, Layouter } from "./options";
|
||||
import { SourceElement } from "./sources";
|
||||
import { Element } from "./Model/modelData";
|
||||
|
||||
|
||||
export interface StructV {
|
||||
@ -26,16 +25,16 @@ export interface StructV {
|
||||
|
||||
registeredShape: any[];
|
||||
|
||||
registeredLayout: { [key: string]: LayoutCreator },
|
||||
registeredLayouter: { [key: string]: Layouter },
|
||||
|
||||
registerShape: Function,
|
||||
|
||||
/**
|
||||
* 注册一个布局器
|
||||
* @param name
|
||||
* @param layout
|
||||
* @param layouter
|
||||
*/
|
||||
registerLayout(name: string, layoutCreator: LayoutCreator);
|
||||
registerLayouter(name: string, layouter);
|
||||
}
|
||||
|
||||
|
||||
@ -46,10 +45,10 @@ export const SV: StructV = function(DOMContainer: HTMLElement, engineOptions: En
|
||||
SV.Group = Group;
|
||||
SV.Bound = Bound;
|
||||
SV.Vector = Vector;
|
||||
SV.Mat3 = Util.mat3;
|
||||
SV.Mat3 = G6.Util.mat3;
|
||||
SV.G6 = G6;
|
||||
|
||||
SV.registeredLayout = {};
|
||||
SV.registeredLayouter = {};
|
||||
SV.registeredShape = [
|
||||
pointer,
|
||||
linkListNode,
|
||||
@ -61,25 +60,25 @@ SV.registeredShape = [
|
||||
];
|
||||
|
||||
SV.registerShape = G6.registerNode;
|
||||
SV.registerLayout = function(name: string, layoutCreator: LayoutCreator) {
|
||||
SV.registerLayouter = function(name: string, layouter: Layouter) {
|
||||
|
||||
if(typeof layoutCreator.sourcesPreprocess !== 'function') {
|
||||
layoutCreator.sourcesPreprocess = function(data: SourceNode[]): SourceNode[] {
|
||||
if(typeof layouter.sourcesPreprocess !== 'function') {
|
||||
layouter.sourcesPreprocess = function(data: SourceElement[]): SourceElement[] {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
if(typeof layoutCreator.defineLeakRule !== 'function') {
|
||||
layoutCreator.defineLeakRule = function(nodes: SVNode[]): SVNode[] {
|
||||
return nodes;
|
||||
if(typeof layouter.defineLeakRule !== 'function') {
|
||||
layouter.defineLeakRule = function(elements: Element[]): Element[] {
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
|
||||
if(typeof layoutCreator.defineOptions !== 'function' || typeof layoutCreator.layout !== 'function') {
|
||||
if(typeof layouter.defineOptions !== 'function' || typeof layouter.layout !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
SV.registeredLayout[name] = layoutCreator;
|
||||
SV.registeredLayouter[name] = layouter;
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Util } from '@antv/g6';
|
||||
import { Model } from "../Model/modelData";
|
||||
import { SV } from "../StructV";
|
||||
|
||||
|
||||
export type animationConfig = {
|
||||
duration: number;
|
||||
timingFunction: string;
|
||||
payload?: any;
|
||||
callback?: Function;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@ -13,36 +14,39 @@ export type animationConfig = {
|
||||
* 动画表
|
||||
*/
|
||||
export const Animations = {
|
||||
|
||||
|
||||
/**
|
||||
* 添加节点 / 边时的动画效果
|
||||
* @param G6Item
|
||||
* @param model
|
||||
* @param animationConfig
|
||||
*/
|
||||
APPEND(G6Item: any, animationConfig: animationConfig) {
|
||||
const type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = Util.mat3,
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
animate_append(model: Model, animationConfig: animationConfig) {
|
||||
model.G6Item === null && console.log(model);
|
||||
|
||||
if (type === 'node') {
|
||||
const G6Item = model.G6Item,
|
||||
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(),
|
||||
targetMatrix = Mat3.clone(matrix);
|
||||
|
||||
Mat3.scale(matrix, matrix, [0, 0]);
|
||||
Mat3.scale(targetMatrix, targetMatrix, [1, 1]);
|
||||
|
||||
group.attr({ matrix, opacity: 0 });
|
||||
group.animate({ matrix: targetMatrix, opacity: 1 }, animateCfg);
|
||||
group.attr({ opacity: 0, matrix });
|
||||
group.animate({ opacity: 1, matrix: targetMatrix }, animateCfg);
|
||||
}
|
||||
|
||||
if (type === 'edge') {
|
||||
if(type === 'edge') {
|
||||
const line = group.get('children')[0],
|
||||
length = line.getTotalLength();
|
||||
length = line.getTotalLength();
|
||||
|
||||
line.attr({ lineDash: [0, length], opacity: 0 });
|
||||
line.animate({ lineDash: [length, 0], opacity: 1 }, animateCfg);
|
||||
@ -51,50 +55,52 @@ export const Animations = {
|
||||
|
||||
/**
|
||||
* 移除节点 / 边时的动画效果
|
||||
* @param G6Item
|
||||
* @param model
|
||||
* @param animationConfig
|
||||
*/
|
||||
REMOVE(G6Item: any, animationConfig: animationConfig) {
|
||||
const type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = Util.mat3,
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
animate_remove(model: Model, animationConfig: animationConfig) {
|
||||
const G6Item = model.G6Item,
|
||||
type = G6Item.getType(),
|
||||
group = G6Item.getContainer(),
|
||||
Mat3 = SV.Mat3,
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
|
||||
if (type === 'node') {
|
||||
if(type === 'node') {
|
||||
let matrix = Mat3.clone(group.getMatrix());
|
||||
|
||||
Mat3.scale(matrix, matrix, [0, 0]);
|
||||
group.animate({ opacity: 0, matrix }, animateCfg);
|
||||
}
|
||||
|
||||
if (type === 'edge') {
|
||||
if(type === 'edge') {
|
||||
const line = group.get('children')[0],
|
||||
length = line.getTotalLength();
|
||||
length = line.getTotalLength();
|
||||
|
||||
line.animate({ lineDash: [0, length], opacity: 0 }, animateCfg);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param G6Item
|
||||
* 移动节点 / 边的动画
|
||||
* @param model
|
||||
* @param animationConfig
|
||||
*/
|
||||
FADE_IN(G6Item: any, animationConfig: animationConfig) {
|
||||
const group = G6Item.getContainer(),
|
||||
animateCfg = {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
animate_fadeOut(model: Model, animationConfig: animationConfig) {
|
||||
const G6Item = model.G6Item,
|
||||
group = G6Item.getContainer(),
|
||||
animateCfg = {
|
||||
duration: 1200,
|
||||
easing: animationConfig.timingFunction,
|
||||
callback: animationConfig.callback
|
||||
};
|
||||
|
||||
group.attr({ opacity: 0 });
|
||||
group.animate({ opacity: 1 }, animateCfg);
|
||||
group.animate({ opacity: 0 }, animateCfg);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
268
src/View/container/container.ts
Normal file
268
src/View/container/container.ts
Normal file
@ -0,0 +1,268 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
43
src/View/container/freed.ts
Normal file
43
src/View/container/freed.ts
Normal file
@ -0,0 +1,43 @@
|
||||
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 [];
|
||||
}
|
||||
};
|
8
src/View/container/leak.ts
Normal file
8
src/View/container/leak.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Container } from "./container";
|
||||
|
||||
/**
|
||||
* 泄漏区可视化视图
|
||||
*/
|
||||
export class LeakContainer extends Container {
|
||||
|
||||
};
|
203
src/View/container/main.ts
Normal file
203
src/View/container/main.ts
Normal file
@ -0,0 +1,203 @@
|
||||
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,35 +1,30 @@
|
||||
import { IPoint } from '@antv/g6-core';
|
||||
import { Bound, BoundingRect } from '../Common/boundingRect';
|
||||
import { Group } from '../Common/group';
|
||||
import { Vector } from '../Common/vector';
|
||||
import { Engine } from '../engine';
|
||||
import { LayoutGroupTable } from '../Model/modelConstructor';
|
||||
import { SVMarker } from '../Model/SVMarker';
|
||||
import { SVModel } from '../Model/SVModel';
|
||||
import { SVFreedLabel, SVLeakAddress, SVNode } from '../Model/SVNode';
|
||||
import { Element, Model, Marker } from '../Model/modelData';
|
||||
import { LayoutOptions, MarkerOption, ViewOptions } from '../options';
|
||||
import { ViewContainer } from './viewContainer';
|
||||
import { Container } from './container/container';
|
||||
|
||||
|
||||
export class LayoutProvider {
|
||||
export class Layouter {
|
||||
private engine: Engine;
|
||||
private viewOptions: ViewOptions;
|
||||
private viewContainer: ViewContainer;
|
||||
|
||||
constructor(engine: Engine, viewContainer: ViewContainer) {
|
||||
constructor(engine: Engine) {
|
||||
this.engine = engine;
|
||||
this.viewOptions = this.engine.viewOptions;
|
||||
this.viewContainer = viewContainer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化布局参数
|
||||
* @param nodes
|
||||
* @param elements
|
||||
* @param markers
|
||||
*/
|
||||
private initLayoutValue(nodes: SVNode[], markers: SVMarker[]) {
|
||||
[...nodes, ...markers].forEach(item => {
|
||||
private initLayoutValue(elements: Element[], markers: Marker[]) {
|
||||
[...elements, ...markers].forEach(item => {
|
||||
item.set('rotation', item.get('rotation'));
|
||||
item.set({ x: 0, y: 0 });
|
||||
});
|
||||
@ -40,21 +35,21 @@ export class LayoutProvider {
|
||||
* @param marker
|
||||
* @param markerOptions
|
||||
*/
|
||||
private layoutMarker(markers: SVMarker[], markerOptions: { [key: string]: MarkerOption }) {
|
||||
private layoutMarker(markers: Marker[], markerOptions: { [key: string]: MarkerOption }) {
|
||||
markers.forEach(item => {
|
||||
const options: MarkerOption = markerOptions[item.sourceType],
|
||||
const options: MarkerOption = markerOptions[item.getType()],
|
||||
offset = options.offset ?? 8,
|
||||
anchor = item.anchor ?? 0,
|
||||
labelOffset = options.labelOffset ?? 2;
|
||||
|
||||
let target = item.target,
|
||||
targetBound: BoundingRect = target.getBound(),
|
||||
g6AnchorPosition = item.target.shadowG6Item.getAnchorPoints()[anchor] as IPoint,
|
||||
anchorPosition = item.target.G6Item.getAnchorPoints()[anchor],
|
||||
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
|
||||
markerPosition: [number, number],
|
||||
markerEndPosition: [number, number];
|
||||
|
||||
let anchorPosition: [number, number] = [g6AnchorPosition.x, g6AnchorPosition.y];
|
||||
anchorPosition = [anchorPosition.x, anchorPosition.y];
|
||||
|
||||
let anchorVector = Vector.subtract(anchorPosition, center),
|
||||
angle = 0, len = Vector.length(anchorVector) + offset;
|
||||
@ -84,38 +79,23 @@ export class LayoutProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局节点的‘已释放’文本
|
||||
* @param freedLabels
|
||||
* 将视图调整至画布中心
|
||||
* @param container
|
||||
* @param models
|
||||
*/
|
||||
private layoutFreedLabel(freedLabels: SVFreedLabel[]) {
|
||||
freedLabels.forEach(item => {
|
||||
const freedNodeBound = item.node.getBound();
|
||||
private fitCenter(container: Container, group: Group) {
|
||||
let width = container.getG6Instance().getWidth(),
|
||||
height = container.getG6Instance().getHeight(),
|
||||
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;
|
||||
|
||||
item.set({
|
||||
x: freedNodeBound.x + freedNodeBound.width / 2,
|
||||
y: freedNodeBound.y + freedNodeBound.height * 1.5,
|
||||
size: [freedNodeBound.width, 0]
|
||||
});
|
||||
});
|
||||
group.translate(dx, dy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局泄漏区节点上面的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进行布局
|
||||
* @param layoutGroupTable
|
||||
@ -125,58 +105,31 @@ export class LayoutProvider {
|
||||
|
||||
layoutGroupTable.forEach(group => {
|
||||
const options: LayoutOptions = group.options.layout,
|
||||
modelList: SVModel[] = group.modelList,
|
||||
modelList: Model[] = group.modelList,
|
||||
modelGroup: Group = new Group();
|
||||
|
||||
modelList.forEach(item => {
|
||||
modelGroup.add(item);
|
||||
});
|
||||
|
||||
this.initLayoutValue(group.node, group.marker); // 初始化布局参数
|
||||
group.layoutCreator.layout(group.node, options); // 布局节点
|
||||
this.initLayoutValue(group.element, group.marker); // 初始化布局参数
|
||||
group.layouter.layout(group.element, options); // 布局节点
|
||||
modelGroupList.push(modelGroup);
|
||||
});
|
||||
|
||||
layoutGroupTable.forEach(group => {
|
||||
this.layoutFreedLabel(group.freedLabel);
|
||||
this.layoutLeakAddress(group.leakAddress);
|
||||
this.layoutMarker(group.marker, group.options.marker); // 布局外部指针
|
||||
});
|
||||
|
||||
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
|
||||
*/
|
||||
private layoutGroups(modelGroupList: Group[]): Group {
|
||||
private layoutGroups(container: Container, modelGroupList: Group[]): Group {
|
||||
let wrapperGroup: Group = new Group(),
|
||||
group: Group,
|
||||
prevBound: BoundingRect,
|
||||
@ -221,42 +174,69 @@ export class LayoutProvider {
|
||||
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;
|
||||
|
||||
/**
|
||||
* 将视图调整至画布中心
|
||||
* @param models
|
||||
* @param leakBound
|
||||
*/
|
||||
private fitCenter(group: Group, leakBound: BoundingRect) {
|
||||
let width = this.viewContainer.getG6Instance().getWidth(),
|
||||
height = this.viewContainer.getG6Instance().getHeight() - leakBound.height;
|
||||
let target = marker.target,
|
||||
targetBound: BoundingRect = target.getBound(),
|
||||
anchorPosition = marker.target.G6Item.getAnchorPoints()[anchor],
|
||||
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
|
||||
markerPosition: [number, number],
|
||||
markerEndPosition: [number, number];
|
||||
|
||||
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;
|
||||
anchorPosition = [anchorPosition.x, anchorPosition.y];
|
||||
|
||||
group.translate(dx, dy);
|
||||
let anchorVector = Vector.subtract(anchorPosition, center),
|
||||
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 leakModels
|
||||
* @param hasLeak
|
||||
*/
|
||||
public layoutAll(layoutGroupTable: LayoutGroupTable, accumulateLeakModels: SVModel[], leakModels: SVModel[]) {
|
||||
public layoutAll(container: Container, layoutGroupTable: LayoutGroupTable) {
|
||||
layoutGroupTable.forEach(item => {
|
||||
item.modelList.forEach(model => {
|
||||
model.G6Item = model.shadowG6Item;
|
||||
});
|
||||
});
|
||||
|
||||
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 }
|
||||
|
||||
if (leakModels.length) {
|
||||
leakBound = this.layoutLeakModels(leakModels, accumulateLeakModels);
|
||||
}
|
||||
const wrapperGroup: Group = this.layoutGroups(container, modelGroupList);
|
||||
this.fitCenter(container, wrapperGroup);
|
||||
|
||||
this.fitCenter(globalGroup, leakBound);
|
||||
layoutGroupTable.forEach(item => {
|
||||
item.modelList.forEach(model => {
|
||||
model.G6Item = model.renderG6Item;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1,406 +0,0 @@
|
||||
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,16 +1,15 @@
|
||||
import { Engine } from '../engine';
|
||||
import { SVModel } from '../Model/SVModel';
|
||||
import { G6EdgeModel, G6NodeModel } from '../Model/modelData';
|
||||
import { Util } from '../Common/util';
|
||||
import G6 from '@antv/g6';
|
||||
import { InitViewBehaviors } from '../BehaviorHelper/initViewBehaviors';
|
||||
import { Graph, GraphData, IGroup } from '@antv/g6-pc';
|
||||
import { SV } from '../StructV';
|
||||
import { Model } from './../Model/modelData';
|
||||
|
||||
|
||||
|
||||
export interface RenderModelPack {
|
||||
leaKModels: SVModel[];
|
||||
generalModel: SVModel[];
|
||||
}
|
||||
export interface G6Data {
|
||||
nodes: G6NodeModel[];
|
||||
edges: G6EdgeModel[];
|
||||
};
|
||||
|
||||
|
||||
export type g6Behavior = string | { type: string; shouldBegin?: Function; shouldUpdate?: Function; shouldEnd?: Function; };
|
||||
@ -18,34 +17,26 @@ export type g6Behavior = string | { type: string; shouldBegin?: Function; should
|
||||
|
||||
export class Renderer {
|
||||
private engine: Engine;
|
||||
private g6Instance: Graph; // g6 实例
|
||||
private shadowG6Instance: Graph;
|
||||
|
||||
constructor(engine: Engine, DOMContainer: HTMLElement) {
|
||||
private 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.DOMContainer = DOMContainer;
|
||||
this.isFirstRender = true;
|
||||
|
||||
const enable: boolean = this.engine.animationOptions.enable,
|
||||
duration: number = this.engine.animationOptions.duration,
|
||||
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
|
||||
});
|
||||
duration: number = this.engine.animationOptions.duration,
|
||||
timingFunction: string = this.engine.animationOptions.timingFunction;
|
||||
|
||||
// 初始化g6实例
|
||||
this.g6Instance = new G6.Graph({
|
||||
this.g6Instance = new SV.G6.Graph({
|
||||
container: DOMContainer,
|
||||
width: DOMContainer.offsetWidth,
|
||||
width: DOMContainer.offsetWidth,
|
||||
height: DOMContainer.offsetHeight,
|
||||
groupByTypes: false,
|
||||
animate: enable,
|
||||
@ -55,74 +46,68 @@ export class Renderer {
|
||||
},
|
||||
fitView: false,
|
||||
modes: {
|
||||
default: InitViewBehaviors(this.engine.optionsTable)
|
||||
default: []
|
||||
},
|
||||
plugins: [tooltip]
|
||||
...g6Options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创造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 getIsFirstRender(): boolean {
|
||||
return this.isFirstRender;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对每一个 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;
|
||||
});
|
||||
public setIsFirstRender(value: boolean) {
|
||||
this.isFirstRender = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从视图中移除一个 Model
|
||||
* @param model
|
||||
*/
|
||||
public removeModel(model: SVModel) {
|
||||
this.g6Instance.removeItem(model.G6Item);
|
||||
this.shadowG6Instance.removeItem(model.shadowG6Item);
|
||||
public removeModel(model: Model) {
|
||||
this.g6Instance.removeItem(model.renderG6Item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,7 +121,7 @@ export class Renderer {
|
||||
* 销毁
|
||||
*/
|
||||
public destroy() {
|
||||
this.shadowG6Instance.destroy();
|
||||
this.g6Instance.destroy();
|
||||
this.DOMContainer = null;
|
||||
}
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
291
src/View/viewManager.ts
Normal file
291
src/View/viewManager.ts
Normal file
@ -0,0 +1,291 @@
|
||||
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,18 +1,17 @@
|
||||
import { Element, Link, Marker } from "./Model/modelData";
|
||||
import { Sources } from "./sources";
|
||||
import { ModelConstructor } from "./Model/modelConstructor";
|
||||
import { LayoutGroupTable, ModelConstructor } from "./Model/modelConstructor";
|
||||
import { AnimationOptions, EngineOptions, InteractionOptions, LayoutGroupOptions, ViewOptions } from "./options";
|
||||
import { ViewManager } from "./View/viewManager";
|
||||
import { SV } from "./StructV";
|
||||
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 {
|
||||
private modelConstructor: ModelConstructor = null;
|
||||
private viewContainer: ViewContainer
|
||||
private viewManager: ViewManager
|
||||
private prevStringSourceData: string;
|
||||
private layoutGroupTable: LayoutGroupTable;
|
||||
|
||||
public engineOptions: EngineOptions;
|
||||
public viewOptions: ViewOptions;
|
||||
@ -23,19 +22,21 @@ export class Engine {
|
||||
|
||||
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions) {
|
||||
this.optionsTable = {};
|
||||
this.engineOptions = Object.assign({}, engineOptions);
|
||||
|
||||
this.engineOptions = Object.assign({
|
||||
freedContainer: null,
|
||||
leakContainer: null
|
||||
}, engineOptions);
|
||||
|
||||
this.viewOptions = Object.assign({
|
||||
fitCenter: true,
|
||||
fitView: false,
|
||||
groupPadding: 20,
|
||||
leakAreaHeight: 0.3,
|
||||
updateHighlight: '#fc5185'
|
||||
groupPadding: 20
|
||||
}, engineOptions.view);
|
||||
|
||||
this.animationOptions = Object.assign({
|
||||
enable: true,
|
||||
duration: 750,
|
||||
duration: 900,
|
||||
timingFunction: 'easePolyOut'
|
||||
}, engineOptions.animation);
|
||||
|
||||
@ -43,25 +44,26 @@ export class Engine {
|
||||
drag: true,
|
||||
zoom: true,
|
||||
dragNode: true,
|
||||
selectNode: true
|
||||
selectNode: true,
|
||||
changeHighlight: '#fc5185'
|
||||
}, engineOptions.interaction);
|
||||
|
||||
// 初始化布局器配置项
|
||||
Object.keys(SV.registeredLayout).forEach(layout => {
|
||||
if(this.optionsTable[layout] === undefined) {
|
||||
const options: LayoutGroupOptions = SV.registeredLayout[layout].defineOptions();
|
||||
Object.keys(SV.registeredLayouter).forEach(layouter => {
|
||||
if(this.optionsTable[layouter] === undefined) {
|
||||
const options: LayoutGroupOptions = SV.registeredLayouter[layouter].defineOptions();
|
||||
|
||||
options.behavior = Object.assign({
|
||||
dragNode: true,
|
||||
selectNode: true
|
||||
}, options.behavior);
|
||||
|
||||
this.optionsTable[layout] = options;
|
||||
this.optionsTable[layouter] = options;
|
||||
}
|
||||
});
|
||||
|
||||
this.modelConstructor = new ModelConstructor(this);
|
||||
this.viewContainer = new ViewContainer(this, DOMContainer);
|
||||
this.viewManager = new ViewManager(this, DOMContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,10 +75,6 @@ export class Engine {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.viewContainer.getG6Instance().isAnimating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stringSourceData = JSON.stringify(sourceData);
|
||||
if(this.prevStringSourceData === stringSourceData) {
|
||||
return;
|
||||
@ -84,70 +82,74 @@ export class Engine {
|
||||
this.prevStringSourceData = stringSourceData;
|
||||
|
||||
// 1 转换模型(data => model)
|
||||
const layoutGroupTable = this.modelConstructor.construct(sourceData);
|
||||
this.layoutGroupTable = this.modelConstructor.construct(sourceData);
|
||||
|
||||
// 2 渲染(使用g6进行渲染)
|
||||
this.viewContainer.render(layoutGroupTable);
|
||||
this.viewManager.renderAll(this.layoutGroupTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新布局
|
||||
*/
|
||||
public reLayout() {
|
||||
this.viewContainer.reLayout();
|
||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
|
||||
// layoutGroupTable.forEach(group => {
|
||||
// group.modelList.forEach(item => {
|
||||
// if(item instanceof SVLink) return;
|
||||
this.viewManager.reLayout(layoutGroupTable);
|
||||
|
||||
// let model = item.G6Item.getModel(),
|
||||
// x = item.get('x'),
|
||||
// y = item.get('y');
|
||||
layoutGroupTable.forEach(group => {
|
||||
group.modelList.forEach(item => {
|
||||
if(item instanceof Link) return;
|
||||
|
||||
// model.x = x;
|
||||
// model.y = y;
|
||||
// });
|
||||
// });
|
||||
let model = item.G6Item.getModel(),
|
||||
x = item.get('x'),
|
||||
y = item.get('y');
|
||||
|
||||
model.x = x;
|
||||
model.y = y;
|
||||
});
|
||||
});
|
||||
|
||||
this.viewManager.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 G6 实例
|
||||
*/
|
||||
public getGraphInstance() {
|
||||
return this.viewContainer.getG6Instance();
|
||||
return this.viewManager.getG6Instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 element
|
||||
* @param group
|
||||
*/
|
||||
public getNodes(group?: string): SVNode[] {
|
||||
public getElements(group?: string): Element[] {
|
||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
|
||||
if(group && layoutGroupTable.has('group')) {
|
||||
return layoutGroupTable.get('group').node;
|
||||
return layoutGroupTable.get('group').element;
|
||||
}
|
||||
|
||||
const nodes: SVNode[] = [];
|
||||
const elements: Element[] = [];
|
||||
layoutGroupTable.forEach(item => {
|
||||
nodes.push(...item.node);
|
||||
elements.push(...item.element);
|
||||
})
|
||||
|
||||
return nodes;
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 marker
|
||||
* @param group
|
||||
*/
|
||||
public getMarkers(group?: string): SVMarker[] {
|
||||
public getMarkers(group?: string): Marker[] {
|
||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
|
||||
if(group && layoutGroupTable.has('group')) {
|
||||
return layoutGroupTable.get('group').marker;
|
||||
}
|
||||
|
||||
const markers: SVMarker[] = [];
|
||||
const markers: Marker[] = [];
|
||||
layoutGroupTable.forEach(item => {
|
||||
markers.push(...item.marker);
|
||||
})
|
||||
@ -159,14 +161,14 @@ export class Engine {
|
||||
* 获取所有 link
|
||||
* @param group
|
||||
*/
|
||||
public getLinks(group?: string): SVLink[] {
|
||||
public getLinks(group?: string): Link[] {
|
||||
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
|
||||
if(group && layoutGroupTable.has('group')) {
|
||||
return layoutGroupTable.get('group').link;
|
||||
}
|
||||
|
||||
const links: SVLink[] = [];
|
||||
const links: Link[] = [];
|
||||
layoutGroupTable.forEach(item => {
|
||||
links.push(...item.link);
|
||||
})
|
||||
@ -180,11 +182,10 @@ export class Engine {
|
||||
*/
|
||||
public hideGroups(groupNames: string | string[]) {
|
||||
const names = Array.isArray(groupNames)? groupNames: [groupNames],
|
||||
instance = this.viewContainer.getG6Instance(),
|
||||
layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
|
||||
instance = this.viewManager.getG6Instance();
|
||||
|
||||
layoutGroupTable.forEach(item => {
|
||||
const hasName = names.find(name => name === item.layout);
|
||||
this.layoutGroupTable.forEach(item => {
|
||||
const hasName = names.find(name => name === item.layouterName);
|
||||
|
||||
if(hasName && !item.isHide) {
|
||||
item.modelList.forEach(model => instance.hideItem(model.G6Item));
|
||||
@ -203,7 +204,7 @@ export class Engine {
|
||||
* @param id
|
||||
*/
|
||||
public findElement(id: string) {
|
||||
const elements = this.getNodes();
|
||||
const elements = this.getElements();
|
||||
const stringId = id.toString();
|
||||
const targetElement = elements.find(item => item.sourceId === stringId);
|
||||
|
||||
@ -212,11 +213,12 @@ export class Engine {
|
||||
|
||||
/**
|
||||
* 调整容器尺寸
|
||||
* @param containerName
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public resize(width: number, height: number) {
|
||||
this.viewContainer.resize(width, height);
|
||||
public resize(containerName: string, width: number, height: number) {
|
||||
this.viewManager.resize(containerName, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -234,13 +236,8 @@ export class Engine {
|
||||
return;
|
||||
}
|
||||
|
||||
if(eventName === 'onLeakAreaUpdate') {
|
||||
EventBus.on(eventName, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewContainer.getG6Instance().on(eventName, event => {
|
||||
callback(event.item['SVModel']);
|
||||
this.viewManager.getG6Instance().on(eventName, event => {
|
||||
callback(event.item.SVModel);
|
||||
});
|
||||
}
|
||||
|
||||
@ -249,6 +246,6 @@ export class Engine {
|
||||
*/
|
||||
public destroy() {
|
||||
this.modelConstructor.destroy();
|
||||
this.viewContainer.destroy();
|
||||
this.viewManager.destroy();
|
||||
}
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { SVNode } from "./Model/SVNode";
|
||||
import { SourceNode } from "./sources";
|
||||
import { Element } from "./Model/modelData";
|
||||
import { SourceElement } from "./sources";
|
||||
|
||||
|
||||
export interface Style {
|
||||
@ -15,14 +15,14 @@ export interface Style {
|
||||
};
|
||||
|
||||
|
||||
export interface NodeLabelOption {
|
||||
export interface ElementLabelOption {
|
||||
position: string;
|
||||
offset: number;
|
||||
style: Style;
|
||||
};
|
||||
|
||||
|
||||
export interface NodeIndexOption extends NodeLabelOption {
|
||||
export interface ElementIndexOption extends ElementLabelOption {
|
||||
position: 'top' | 'right' | 'bottom' | 'left';
|
||||
value: string;
|
||||
style: Style;
|
||||
@ -38,32 +38,31 @@ export interface LinkLabelOption {
|
||||
};
|
||||
|
||||
|
||||
export interface ModelOption {
|
||||
|
||||
export interface ElementOption {
|
||||
type: string;
|
||||
size: number | [number, number];
|
||||
rotation: number;
|
||||
anchorPoints: [number, number];
|
||||
label: string | string[];
|
||||
labelOptions: ElementLabelOption;
|
||||
indexOptions: ElementIndexOption;
|
||||
style: Style;
|
||||
}
|
||||
|
||||
|
||||
export interface NodeOption extends ModelOption {
|
||||
size: number | [number, number];
|
||||
rotation: number;
|
||||
label: string | string[];
|
||||
anchorPoints: number[][];
|
||||
indexOptions: NodeIndexOption;
|
||||
labelOptions: NodeLabelOption;
|
||||
}
|
||||
|
||||
|
||||
export interface LinkOption extends ModelOption {
|
||||
export interface LinkOption {
|
||||
type: string;
|
||||
sourceAnchor: number | ((index: number) => number);
|
||||
targetAnchor: number | ((index: number) => number);
|
||||
label: string;
|
||||
curveOffset: number;
|
||||
labelOptions: LinkLabelOption;
|
||||
style: Style;
|
||||
}
|
||||
|
||||
|
||||
export interface MarkerOption extends NodeOption {
|
||||
export interface MarkerOption extends ElementOption {
|
||||
type: 'pointer' | 'cursor' | 'clen-queue-pointer';
|
||||
anchor: number;
|
||||
offset: number;
|
||||
@ -84,7 +83,7 @@ export interface BehaviorOptions {
|
||||
|
||||
|
||||
export interface LayoutGroupOptions {
|
||||
node: { [key: string]: NodeOption };
|
||||
element: { [key: string]: ElementOption };
|
||||
link?: { [key: string]: LinkOption }
|
||||
marker?: { [key: string]: MarkerOption }
|
||||
layout?: LayoutOptions;
|
||||
@ -100,9 +99,8 @@ export interface LayoutGroupOptions {
|
||||
|
||||
export interface ViewOptions {
|
||||
fitCenter: boolean;
|
||||
fitView: boolean;
|
||||
groupPadding: number;
|
||||
updateHighlight: string;
|
||||
leakAreaHeight: number;
|
||||
}
|
||||
|
||||
|
||||
@ -114,22 +112,25 @@ export interface AnimationOptions {
|
||||
|
||||
|
||||
export interface InteractionOptions {
|
||||
changeHighlight: string;
|
||||
drag: boolean;
|
||||
zoom: boolean;
|
||||
}
|
||||
|
||||
export interface EngineOptions {
|
||||
freedContainer?: HTMLElement;
|
||||
leakContainer?: HTMLElement;
|
||||
view?: ViewOptions;
|
||||
animation?: AnimationOptions;
|
||||
interaction?: InteractionOptions;
|
||||
};
|
||||
|
||||
|
||||
export interface LayoutCreator {
|
||||
export interface Layouter {
|
||||
defineOptions(): LayoutGroupOptions;
|
||||
sourcesPreprocess?(sources: SourceNode[], options: LayoutGroupOptions): SourceNode[];
|
||||
defineLeakRule?(nodes: SVNode[]): SVNode[];
|
||||
layout(nodes: SVNode[], layoutOptions: LayoutOptions);
|
||||
sourcesPreprocess?(sources: SourceElement[], options: LayoutGroupOptions): SourceElement[];
|
||||
defineLeakRule?(elements: Element[]): Element[];
|
||||
layout(elements: Element[], layoutOptions: LayoutOptions);
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
@ -6,17 +6,17 @@ export type LinkTarget = number | string;
|
||||
export type sourceLinkData = LinkTarget | LinkTarget[];
|
||||
|
||||
// 结点指针声明
|
||||
export type sourceMarkerData = string | string[];
|
||||
export type sourcePointerData = string | string[];
|
||||
|
||||
// 源数据单元
|
||||
export interface SourceNode {
|
||||
export interface SourceElement {
|
||||
id: string | number;
|
||||
[key: string]: any | sourceLinkData | sourceMarkerData;
|
||||
[key: string]: any | sourceLinkData | sourcePointerData;
|
||||
}
|
||||
|
||||
|
||||
export type Sources = {
|
||||
[key: string]: { data: SourceNode[]; layouter: string; }
|
||||
[key: string]: { data: SourceElement[]; layouter: string; }
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user