feat: 增加框选功能

This commit is contained in:
黎智洲 2021-12-22 21:56:52 +08:00
parent 3626a6eb19
commit 3d0f613811
30 changed files with 609 additions and 14483 deletions

View File

@ -34,7 +34,7 @@ SV.registerLayout('ChainHashTable', {
start: {
type: 'line',
sourceAnchor: 1,
targetAnchor: 5,
targetAnchor: 6,
style: {
stroke: '#333',
endArrow: {
@ -77,7 +77,7 @@ SV.registerLayout('ChainHashTable', {
xInterval: 50,
yInterval: 50
},
interaction: {
behavior: {
dragNode: ['node']
}
};

View File

@ -77,6 +77,9 @@ SV.registerLayout('LinkList', {
layout: {
xInterval: 50,
yInterval: 50
},
behavior: {
dragNode: false
}
};
},

View File

@ -62,6 +62,7 @@
<button id="resize">resize</button>
<button id="relayout">relayout</button>
<button id="switch-mode">switch mode</button>
<button id="brush-select">brush-select</button>
<span id="pos"></span>
<script src="./../dist/sv.js"></script>
@ -89,8 +90,6 @@
<script>
const curSelectData = { element: null, style: null };
let cur = SV(document.getElementById('container'), {
view: {
leakAreaHeight: 130,
@ -101,18 +100,82 @@
let data = [{
Array: {
data: [{ id: 1, data: 1, external: 'list' }, { id: 2, data: 2 }, { id: 3, data: 3 }],
layouter: 'Array'
data: [
{ id: 1, data: 1, child: [2, 3], external: 'exx' },
{ id: 2, data: 2 },
{ id: 3, data: 3, child: [6, 4] },
{ id: 4, data: 4 },
{ id: 6, data: 6 }
],
layouter: 'BinaryTree'
},
L: {
data: [
{ id: 11, data: 11, next: 22, external: 'tt' },
{ id: 22, data: 22, next: 33 },
{ id: 33, data: 33, next: 44 },
{ id: 44, data: 44, freed: true }
],
layouter: 'LinkList'
},
"ChainHashTable": {
"data": [
{
"type": "head",
"id": "0x618090",
"data": "T"
},
{
"type": "node",
"id": "0x618030",
"data": "N"
},
{
"type": "head",
"id": "0x6180f0",
"data": "U",
"start": "node#0x618030"
},
{
"type": "node",
"id": "0x617ff0",
"data": "V",
"next": "node#0x617fd0"
},
{
"type": "node",
"id": "0x617fd0",
"data": "A"
},
{
"type": "head",
"id": "0x618010",
"data": "O",
"start": "node#0x617ff0"
},
{
"type": "node",
"id": "0x618050",
"data": "K"
},
{
"type": "head",
"id": "0x6180b0",
"data": "R",
"start": "node#0x618050"
}
],
"layouter": "ChainHashTable"
}
}, {
Array: {
data: [{ id: 1, data: 1 }, { id: 2, data: 2 }],
layouter: 'Array'
}
}, {
Array: {
data: [{ id: 1, data: 1 }, { id: 5, data: 5 }],
layouter: 'Array'
data: [
{ id: 1, data: 1, child: [2, 3], external: 'exx' },
{ id: 2, data: 2 },
{ id: 3, data: 3, child: [6, 7] },
{ id: 7, data: 7 }
],
layouter: 'BinaryTree'
}
}];
@ -123,6 +186,13 @@
let dataIndex = 0,
curData = data[dataIndex];
let enableBrushSelect = false;
const container = document.getElementById('container'),
pos = document.getElementById('pos');
const leak = document.getElementById('leak');
cur.render(curData);
document.getElementById('btn-next').addEventListener('click', e => {
@ -135,14 +205,6 @@
cur.render(curData);
});
const container = document.getElementById('container'),
pos = document.getElementById('pos');
container.addEventListener('mousemove', e => {
let x = e.offsetX, y = e.offsetY;
pos.innerHTML = `${x},${y}`;
});
document.getElementById('resize').addEventListener('click', e => {
container.style.height = 800 + 'px';
cur.resize(container.offsetWidth, container.offsetHeight);
@ -156,7 +218,10 @@
cur.updateStyle('Array', newArrayOption);
});
const leak = document.getElementById('leak');
document.getElementById('brush-select').addEventListener('click', e => {
enableBrushSelect = !enableBrushSelect;
cur.switchBrushSelect(enableBrushSelect);
});
cur.on('onLeakAreaUpdate', payload => {
leak.style.opacity = payload.hasLeak ? 1 : 0;
@ -165,6 +230,12 @@
// -------------------------------------------------------------------------------------------------------
container.addEventListener('mousemove', e => {
let x = e.offsetX, y = e.offsetY;
pos.innerHTML = `${x},${y}`;
});
</script>
</body>

14147
dist/sv.js vendored

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,199 @@
import { Graph, INode } from "@antv/g6";
import { EventBus } from "../Common/eventBus";
import { LayoutGroupTable } from "../Model/modelConstructor";
import { SVModel } from "../Model/SVModel";
import { SVNode } from "../Model/SVNode";
import { ViewContainer } from "../View/viewContainer";
/**
*
* @param node
* @param dragNodeOption
*/
const checkNodeDragAlone = function (node: SVNode, dragNodeOption: boolean | string[]): boolean {
const nodeSourceType = node.sourceType;
if (Array.isArray(dragNodeOption)) {
return dragNodeOption.includes(nodeSourceType);
}
if (dragNodeOption === undefined || dragNodeOption === true) {
return true;
}
return false;
}
/**
*
* 1. dragNodeOption true undefined时
* 2. dragNodeOption type时
* 3. dragNodeOption false type的节点
*/
export const DetermineNodeDrag = function (layoutGroupTable: LayoutGroupTable, node: SVNode, brushSelectedModels: SVModel[]) {
const layoutGroup = layoutGroupTable.get(node.group),
dragNodeOption = layoutGroup.options.behavior?.dragNode,
canNodeDragAlone = checkNodeDragAlone(node, dragNodeOption);
if (canNodeDragAlone) {
return true;
}
const nodeSourceType = node.sourceType,
nodeModelType = node.getModelType(),
modelList = (<SVModel[]>layoutGroup[nodeModelType]).filter(item => item.sourceType === nodeSourceType),
brushSelectedSameTypeModels = brushSelectedModels.filter(item => {
return item.group === node.group &&
item.getModelType() === nodeModelType &&
item.sourceType === nodeSourceType
});
return modelList.length === brushSelectedSameTypeModels.length;
}
/**
* appendage
*
*/
export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
const g6Instance: Graph = viewContainer.getG6Instance();
g6Instance.on('node:dragstart', event => {
let node: SVNode = event.item['SVModel'];
if (node instanceof SVNode === false) {
return;
}
const isNodeSelected = viewContainer.brushSelectedModels.find(item => item.id === node.id);
// 如果在框选完成之后,拖拽了被框选之外的其他节点,那么取消已框选的节点的选中状态
if (isNodeSelected === undefined) {
viewContainer.brushSelectedModels.forEach(item => {
item.setSelectedState(false);
if (item instanceof SVNode) {
item.appendages.forEach(appendage => appendage.setSelectedState(false));
}
});
viewContainer.brushSelectedModels.length = 0;
}
});
g6Instance.on('node:dragend', event => {
let node: SVNode = event.item['SVModel'];
if (node instanceof SVNode === false) {
return;
}
const isNodeSelected = viewContainer.brushSelectedModels.find(item => item.id === node.id);
// 如果当前拖拽的节点是在已框选选中的节点之中,那么不需要取消选中的状态,否则需要取消
if (isNodeSelected === undefined) {
node.setSelectedState(false);
node.set({
x: node.G6Item.getModel().x,
y: node.G6Item.getModel().y
});
node.appendages.forEach(item => {
item.setSelectedState(false);
});
}
viewContainer.brushSelectedModels.forEach(item => {
item.set({
x: item.G6Item.getModel().x,
y: item.G6Item.getModel().y
});
});
});
}
/**
*
* @param viewContainer
*/
export function SolveBrushSelectDrag(viewContainer: ViewContainer) {
const g6Instance: Graph = viewContainer.getG6Instance();
// 当框选完成后,监听被框选节点的数量变化事件,将被框选的节点添加到 brushSelectedModels 数组里面
g6Instance.on('nodeselectchange', event => {
const selectedItems = event.selectedItems as { nodes: INode[]; },
tmpSelectedModelList = [];
// 如果是点击选中,不理会
if (event.target) {
return;
}
// 先清空上一次框选保存的内容
viewContainer.brushSelectedModels.length = 0;
// 首先将已框选中的节点加到一个临时队列
selectedItems.nodes.forEach(item => {
tmpSelectedModelList.push(item['SVModel']);
});
// 之后逐个检测被框选中的节点是否可以拖拽,可以拖拽的才加入到真正的框选队列
selectedItems.nodes.forEach(item => {
const node: SVNode = item['SVModel'];
if (DetermineNodeDrag(viewContainer.getLayoutGroupTable(), node, tmpSelectedModelList)) {
viewContainer.brushSelectedModels.push(node);
}
else {
node.setSelectedState(false);
}
});
});
}
/**
*
* @param g6Instance
* @param hasLeak
*/
export function SolveDragCanvasWithLeak(viewContainer: ViewContainer) {
let g6Instance = viewContainer.getG6Instance(),
prevDy = 0;
g6Instance.on('viewportchange', event => {
if (event.action !== 'translate') {
return false;
}
let translateX = event.matrix[7],
dy = translateX - prevDy;
prevDy = translateX;
viewContainer.leakAreaY = viewContainer.leakAreaY + dy;
if (viewContainer.hasLeak) {
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: viewContainer.leakAreaY,
hasLeak: viewContainer.hasLeak
});
}
});
}
/**
*
* @param g6Instance
* @param generalModelsGroup
*/
export function SolveZoomCanvasWithLeak(viewContainer: ViewContainer) {
}

View File

@ -1,49 +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(),
prevDy = 0;
g6Instance.on('viewportchange', event => {
if(event.action !== 'translate') {
return false;
}
let translateX = event.matrix[7],
dy = translateX- prevDy;
prevDy = translateX;
viewContainer.leakAreaY = viewContainer.leakAreaY + dy;
if (viewContainer.hasLeak) {
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: viewContainer.leakAreaY,
hasLeak: viewContainer.hasLeak
});
}
});
}

View File

@ -1,71 +0,0 @@
import { Graph } from "@antv/g6";
import { SVNode } from "../Model/SVNode";
import { SVNodeAppendage } from "../Model/SVNodeAppendage";
/**
* appendage
*
*/
export function FixNodeMarkerDrag(g6Instance: Graph) {
let dragActive: boolean = false,
nodeData: { node: SVNode, startX: number, startY: number },
appendagesData: { appendage: SVNodeAppendage, startX: number, startY: number }[] = [];
g6Instance.on('node:dragstart', event => {
let node: SVNode = event.item['SVModel'];
if (node.isNode() === false || node.leaked) {
return false;
}
dragActive = true;
nodeData = {
node,
startX: event.canvasX,
startY: event.canvasY
};
node.appendages.forEach(item => {
appendagesData.push({
appendage: item,
startX: item.get('x'),
startY: item.get('y')
});
});
});
g6Instance.on('node:dragend', event => {
if(!dragActive) {
return false;
}
let node: SVNode = nodeData.node;
node.set({
x: node.G6Item.getModel().x,
y: node.G6Item.getModel().y
});
nodeData = null;
appendagesData.length = 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();
appendagesData.forEach(item => {
item.appendage.set({
x: item.startX + dx / zoom,
y: item.startY + dy / zoom
});
});
});
}

View File

@ -0,0 +1,113 @@
import { Graph, INode, Modes } from "@antv/g6";
import { Engine } from "../engine";
import { SVModel } from "../Model/SVModel";
import { SVNode } from "../Model/SVNode";
import { ViewContainer } from "../View/viewContainer";
import { DetermineNodeDrag } from "./behaviorIssueHelper";
/**
* g6 options
* @param optionsTable
* @returns
*/
export function InitG6Behaviors(engine: Engine, viewContainer: ViewContainer): Modes {
const dragNodeFilter = event => {
let g6Item = event.item,
node: SVNode = g6Item.SVModel;
if (g6Item === null || node.isNode() === false) {
return false;
}
const enableDrag = DetermineNodeDrag(viewContainer.getLayoutGroupTable(), node, viewContainer.brushSelectedModels);
if(enableDrag === false) {
return false;
}
// 在拖拽某个节点前先处理一下上次点击选中的节点不然会上次选中的节点会跟着一起拖产生bug
if(viewContainer.clickSelectNode) {
const isPrevSelectInBrush = viewContainer.brushSelectedModels.find(item => item.id === viewContainer.clickSelectNode.id);
if(isPrevSelectInBrush === undefined) {
viewContainer.clickSelectNode.setSelectedState(false);
viewContainer.clickSelectNode = null;
}
}
// 这里之所以要把节点和其 appendages 的选中状态设置为true是因为 g6 处理拖拽节点的逻辑是将所以已选中的元素一起拖动,
// 这样 appendages 就可以很自然地跟着节点动(我是看源码才知道的)
node.setSelectedState(true);
node.appendages.forEach(item => {
item.setSelectedState(true);
});
return true;
};
const selectNodeFilter = event => {
let g6Item = event.item,
node: SVNode = g6Item.SVModel;
if (g6Item === null || node.isNode() === false) {
return false;
}
viewContainer.clickSelectNode = node;
return true;
};
const brushSelectNodeFilter = G6Item => {
const model: SVModel = G6Item.SVModel;
// 泄漏的元素不能被框选
if (model.leaked || model.isNode() === false) {
return false;
}
model.setSelectedState(true);
return true;
};
// --------------------------------------------------------------------------------
const dragNode = {
type: 'drag-node',
shouldBegin: dragNodeFilter
};
const dragCanvas = {
type: 'drag-canvas'
};
const zoomCanvas = {
type: 'zoom-canvas'
};
const clickSelect = {
type: 'click-select',
multiple: false,
shouldBegin: selectNodeFilter
};
const brushSelect = {
type: 'brush-select',
trigger: 'drag',
includeEdges: false,
shouldUpdate: brushSelectNodeFilter
}
return {
default: [dragNode, dragCanvas, clickSelect],
brush: [brushSelect, dragNode]
};
}

View File

@ -1,54 +0,0 @@
import { SVModel } from "../Model/SVModel";
/**
* g6 options
* @param optionsTable
* @returns
*/
export function InitViewBehaviors() {
const defaultModes = [];
const dragNodeFilter = event => {
let g6Item = event.item,
node: SVModel = g6Item.SVModel;
if (g6Item === null || node.isNode() === false || node.leaked) {
return false;
}
return true;
}
const selectNodeFilter = event => {
let g6Item = event.item,
node: SVModel = g6Item.SVModel;
if (g6Item === null || node.isNode() === false || node.leaked) {
return false;
}
return true;
}
defaultModes.push({
type: 'drag-node',
shouldBegin: dragNodeFilter
});
defaultModes.push({
type: 'drag-canvas'
});
// defaultModes.push({
// type: 'zoom-canvas'
// });
defaultModes.push({
type: 'click-select',
shouldBegin: selectNodeFilter
});
return defaultModes;
}

View File

@ -1,67 +0,0 @@
import { EventBus } from "../Common/eventBus";
import { ViewContainer } from "../View/viewContainer";
/**
*
*/
/**
*
* @param g6Instance
* @param generalModelsGroup
*/
export function InitZoomCanvasWithLeak(viewContainer: ViewContainer) {
let g6Instance = viewContainer.getG6Instance(),
prevDy = 0;
let prevZoom = 1;
// g6Instance.on('viewportchange', event => {
// if(event.action !== 'zoom') {
// return false;
// }
// console.log(event.matrix);
// viewContainer.leakAreaY = event.matrix[4] * viewContainer.leakAreaY + event.matrix[7];
// if (viewContainer.hasLeak) {
// EventBus.emit('onLeakAreaUpdate', {
// leakAreaY: viewContainer.leakAreaY,
// hasLeak: viewContainer.hasLeak
// });
// }
// });
g6Instance.on('wheelzoom', event => {
let dy = event.y - viewContainer.leakAreaY,
dZoom = prevZoom - g6Instance.getZoom();
prevZoom = g6Instance.getZoom();
viewContainer.leakAreaY = viewContainer.leakAreaY + dy * dZoom;
if (viewContainer.hasLeak) {
EventBus.emit('onLeakAreaUpdate', {
leakAreaY: viewContainer.leakAreaY,
hasLeak: viewContainer.hasLeak
});
}
});
}

View File

@ -1,4 +1,4 @@
import { EdgeConfig, GraphData, NodeConfig } from "@antv/g6-core";
import { EdgeConfig, GraphData, NodeConfig, registerNode } from "@antv/g6-core";
import { LayoutGroup, LayoutGroupTable } from "../Model/modelConstructor";
import { SVLink } from "../Model/SVLink";
import { SVModel } from "../Model/SVModel";
@ -14,10 +14,10 @@ export const Util = {
* id
*/
generateId(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        let r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
},
/**
@ -25,7 +25,7 @@ export const Util = {
* @param obj
*/
objectClone<T extends Object>(obj: T): T {
return obj? JSON.parse(JSON.stringify(obj)): null;
return obj ? JSON.parse(JSON.stringify(obj)) : null;
},
/**
@ -36,8 +36,8 @@ export const Util = {
removeFromList<T>(list: T[], fn: (item: T) => boolean): T[] {
const res: T[] = [];
for(let i = 0; i < list.length; i++) {
if(fn(list[i])) {
for (let i = 0; i < list.length; i++) {
if (fn(list[i])) {
let removeItem = list.splice(i, 1);
res.push(...removeItem);
i--;
@ -53,7 +53,7 @@ export const Util = {
* @param errorText
*/
assert(condition: boolean, errorText: string): void | never {
if(condition) {
if (condition) {
throw errorText;
}
},
@ -65,7 +65,7 @@ export const Util = {
textParser(text: string): string[] | string {
let fieldReg = /\[[^\]]*\]/g;
if(fieldReg.test(text)) {
if (fieldReg.test(text)) {
let contents = text.match(fieldReg),
values = contents.map(item => item.replace(/\[|\]/g, ''));
return values;
@ -80,9 +80,9 @@ export const Util = {
* @param value
*/
clamp(value: number, max: number, min: number): number {
if(value <= max && value >= min) return value;
if(value > max) return max;
if(value < min) return min;
if (value <= max && value >= min) return value;
if (value > max) return max;
if (value < min) return min;
},
/**
@ -109,8 +109,8 @@ export const Util = {
let nodes = [...layoutGroup.node, ...layoutGroup.marker],
edges = layoutGroup.link;
return {
nodes: nodes.map(item => item.getG6ModelProps()) as NodeConfig[],
return {
nodes: nodes.map(item => item.getG6ModelProps()) as NodeConfig[],
edges: edges.map(item => item.getG6ModelProps()) as EdgeConfig[]
};
},
@ -135,6 +135,12 @@ export const Util = {
const Mat3 = G6Util.mat3;
Mat3.rotate(matrix, matrix, rotation);
return matrix;
},
registerShape(shapeName: string, shapeDefinition, extendShapeType?: string) {
// 不定义updateg6的自定义节点里面的update好像有bug
shapeDefinition.update = undefined;
return registerNode(shapeName, shapeDefinition, extendShapeType);
}
};

View File

@ -164,6 +164,18 @@ export class SVModel {
this.set('style', { matrix });
}
/**
*
* @param isSelected
*/
setSelectedState(isSelected: boolean) {
if(this.G6Item === null) {
return;
}
this.G6Item.setState('selected', isSelected);
}
/**
*
* @returns
@ -186,6 +198,8 @@ export class SVModel {
isNode(): boolean {
return false;
}
}

View File

@ -76,6 +76,21 @@ export class SVNode extends SVModel {
return true;
}
/**
*
* @param isSelected
*/
setSelectedState(isSelected: boolean) {
if (this.G6Item === null) {
return;
}
this.G6Item.setState('selected', isSelected);
this.appendages.forEach(item => {
item.setSelectedState(isSelected);
});
}
getSourceId(): string {
return this.sourceId;
}

View File

@ -47,6 +47,7 @@ export class SVFreedLabel extends SVNodeAppendage {
},
size: [0, 0],
style: {
opacity: 0,
stroke: null,
fill: 'transparent'
}

View File

@ -92,7 +92,7 @@ export class ModelConstructor {
indexLabel: indexLabelList,
link: [],
marker: markerList,
options: options,
options,
layoutCreator,
modelList: [
...nodeList,

View File

@ -1,7 +1,7 @@
import G6 from '@antv/g6';
import { Util } from "../Common/util";
export default G6.registerNode('array-node', {
export default Util.registerShape('array-node', {
getAnchorPoints() {
return [
[0.5, 0],

View File

@ -1,12 +1,12 @@
import { registerNode } from '@antv/g6';
import { Util } from '../Common/util';
export default registerNode('binary-tree-node', {
export default Util.registerShape('binary-tree-node', {
draw(cfg, group) {
cfg.size = cfg.size;
const width = cfg.size[0],
height = cfg.size[1];
height = cfg.size[1];
const wrapperRect = group.addShape('rect', {
attrs: {
@ -27,7 +27,7 @@ export default registerNode('binary-tree-node', {
y: height / 2,
width: width / 2,
height: height,
fill: cfg.color || cfg.style.fill,
fill: cfg.style.fill,
stroke: cfg.style.stroke || '#333',
cursor: cfg.style.cursor
},
@ -64,4 +64,4 @@ export default registerNode('binary-tree-node', {
[0.125, 0.5]
];
},
});
}, 'rect');

View File

@ -1,7 +1,36 @@
import { registerNode, Util } from '@antv/g6';
import G6 from "@antv/g6";
import { Util } from "../Common/util";
export default registerNode('clen-queue-pointer', {
export default function rotate(shape, angle, transform) {
const matrix1 = shape.getMatrix();
const newMatrix1 = transform(matrix1, [
['r', angle],
]);
shape.setMatrix(newMatrix1);
}
function translate(shape, x, y, transform) {
const matrix1 = shape.getMatrix();
const newMatrix1 = transform(matrix1, [
['t', x, y],
]);
shape.setMatrix(newMatrix1);
}
function culcuRotate(angle, R) {
let offsetX = Math.cos(angle) * R;
let offsetY = -Math.sin(angle) * R;
console.log(offsetX, offsetY, R);
return {
offsetX,
offsetY,
}
}
Util.registerShape('clen-queue-pointer', {
draw(cfg, group) {
let id = cfg.id as string;
@ -55,10 +84,10 @@ export default registerNode('clen-queue-pointer', {
});
// rotate(text, angle, G6.Util.transform);
translate(text, 0, -75, Util.transform);
translate(text, 0, -75, G6.Util.transform);
}
rotate(keyShape, angle, Util.transform);
translate(keyShape, 0, -75, Util.transform);
rotate(keyShape, angle, G6.Util.transform);
translate(keyShape, 0, -75, G6.Util.transform);
return keyShape;
@ -89,26 +118,3 @@ export default registerNode('clen-queue-pointer', {
});
function rotate(shape, angle, transform) {
const matrix1 = shape.getMatrix();
const newMatrix1 = transform(matrix1, [
['r', angle],
]);
shape.setMatrix(newMatrix1);
}
function translate(shape, x, y, transform) {
const matrix1 = shape.getMatrix();
const newMatrix1 = transform(matrix1, [
['t', x, y],
]);
shape.setMatrix(newMatrix1);
}
function culcuRotate(angle, R) {
let offsetX = Math.cos(angle) * R;
let offsetY = -Math.sin(angle) * R;
console.log(offsetX, offsetY, R);
return {
offsetX,
offsetY,
}
}

View File

@ -1,7 +1,7 @@
import { registerNode } from '@antv/g6';
import { Util } from '../Common/util';
export default registerNode('cursor', {
export default Util.registerShape('cursor', {
draw(cfg, group) {
const keyShape = group.addShape('path', {
attrs: {

View File

@ -1,7 +1,8 @@
import { registerNode } from '@antv/g6';
import { Util } from "../Common/util";
export default registerNode('link-list-node', {
export default Util.registerShape('link-list-node', {
draw(cfg, group) {
cfg.size = cfg.size || [30, 10];
@ -66,4 +67,4 @@ export default registerNode('link-list-node', {
[0, 0.5]
];
}
});
}, 'rect');

View File

@ -1,7 +1,7 @@
import { registerNode } from '@antv/g6';
import { Util } from '../Common/util';
export default registerNode('pointer', {
export default Util.registerShape('pointer', {
draw(cfg, group) {
const keyShape = group.addShape('path', {
attrs: {

View File

@ -1,7 +1,7 @@
import { registerNode } from '@antv/g6';
import { Util } from '../Common/util';
export default registerNode('tri-tree-node', {
export default Util.registerShape('tri-tree-node', {
draw(cfg, group) {
cfg.size = cfg.size;
@ -97,4 +97,4 @@ export default registerNode('tri-tree-node', {
[0.5, 0.125]
];
},
});
}, 'rect');

View File

@ -1,8 +1,8 @@
import { registerNode } from '@antv/g6';
import { Util } from '../Common/util';
export default registerNode('two-cell-node', {
export default Util.registerShape('two-cell-node', {
draw(cfg, group) {
cfg.size = cfg.size || [30, 10];
@ -99,4 +99,4 @@ export default registerNode('two-cell-node', {
[0, 0.5]
];
}
});
}, 'rect');

View File

@ -1,7 +1,7 @@
import { Engine } from "./engine";
import { Bound } from "./Common/boundingRect";
import { Group } from "./Common/group";
import G6, { Util } from '@antv/g6';
import G6 from '@antv/g6';
import Pointer from "./RegisteredShape/pointer";
import LinkListNode from "./RegisteredShape/linkListNode";
import BinaryTreeNode from "./RegisteredShape/binaryTreeNode";
@ -11,8 +11,11 @@ import ArrayNode from "./RegisteredShape/arrayNode";
import Cursor from "./RegisteredShape/cursor";
import { Vector } from "./Common/vector";
import { EngineOptions, LayoutCreator } from "./options";
import { SVNode } from "./Model/SVNode";
import { SourceNode } from "./sources";
import { Util } from "./Common/util";
import { SVModel } from "./Model/SVModel";
export interface StructV {
@ -45,7 +48,7 @@ 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 = {};
@ -59,7 +62,7 @@ SV.registeredShape = [
CLenQueuePointer,
];
SV.registerShape = G6.registerNode;
SV.registerShape = Util.registerShape;
SV.registerLayout = function(name: string, layoutCreator: LayoutCreator) {
if(typeof layoutCreator.sourcesPreprocess !== 'function') {
@ -69,8 +72,8 @@ SV.registerLayout = function(name: string, layoutCreator: LayoutCreator) {
}
if(typeof layoutCreator.defineLeakRule !== 'function') {
layoutCreator.defineLeakRule = function(nodes: SVNode[]): SVNode[] {
return nodes;
layoutCreator.defineLeakRule = function(models: SVModel[]): SVModel[] {
return models;
}
}
@ -81,4 +84,3 @@ SV.registerLayout = function(name: string, layoutCreator: LayoutCreator) {
SV.registeredLayout[name] = layoutCreator;
};

View File

@ -266,6 +266,8 @@ export class Reconcile {
timingFunction
});
}
item.G6Item.enableCapture(false);
});
EventBus.emit('onLeak', leakModels);

View File

@ -1,8 +1,8 @@
import { Engine } from '../engine';
import { SVModel } from '../Model/SVModel';
import { Util } from '../Common/util';
import { Tooltip, Graph, GraphData } from '@antv/g6';
import { InitViewBehaviors } from '../BehaviorHelper/initViewBehaviors';
import { Tooltip, Graph, GraphData, Modes } from '@antv/g6';
import { InitG6Behaviors } from '../BehaviorHelper/initG6Behaviors';
@ -20,7 +20,7 @@ export class Renderer {
private g6Instance: Graph; // g6 实例
private shadowG6Instance: Graph;
constructor(engine: Engine, DOMContainer: HTMLElement) {
constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes) {
this.engine = engine;
const enable: boolean = this.engine.animationOptions.enable,
@ -53,9 +53,7 @@ export class Renderer {
easing: timingFunction
},
fitView: false,
modes: {
default: InitViewBehaviors()
},
modes: behaviorsModes,
plugins: [tooltip]
});
}
@ -69,7 +67,7 @@ export class Renderer {
private getTooltipContent(model: SVModel, items: { [key: string]: string }): HTMLDivElement {
const wrapper = document.createElement('div');
if(model === null || model === undefined) {
if (model === null || model === undefined) {
return wrapper;
}

View File

@ -5,12 +5,12 @@ 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/dragCanvasWithLeak";
import { EventBus } from "../Common/eventBus";
import { InitZoomCanvasWithLeak } from "../BehaviorHelper/zoomCanvasWithLeak";
import { Group } from "../Common/group";
import { Graph } from "_@antv_g6-pc@0.5.0@@antv/g6-pc";
import { Graph, Modes } from "@antv/g6-pc";
import { InitG6Behaviors } from "../BehaviorHelper/initG6Behaviors";
import { SVNode } from "../Model/SVNode";
import { SolveBrushSelectDrag, SolveDragCanvasWithLeak, SolveNodeAppendagesDrag, SolveZoomCanvasWithLeak } from "../BehaviorHelper/behaviorIssueHelper";
@ -26,33 +26,35 @@ export class ViewContainer {
public hasLeak: boolean;
public leakAreaY: number;
public brushSelectedModels: SVModel[]; // 保存框选过程中被选中的节点
public clickSelectNode: SVNode; // 点击选中的节点
constructor(engine: Engine, DOMContainer: HTMLElement) {
const behaviorsModes: Modes = InitG6Behaviors(engine, this);
this.engine = engine;
this.layoutProvider = new LayoutProvider(engine, this);
this.renderer = new Renderer(engine, DOMContainer);
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes);
this.reconcile = new Reconcile(engine, this.renderer);
this.prevLayoutGroupTable = new Map();
this.prevModelList = [];
this.accumulateLeakModels = [];
this.hasLeak = false; // 判断是否已经发生过泄漏
this.brushSelectedModels = [];
this.clickSelectNode = null;
const g6Instance = this.renderer.getG6Instance(),
leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
height = this.getG6Instance().getHeight(),
{ drag, zoom } = this.engine.interactionOptions;
{ drag, zoom } = this.engine.behaviorOptions;
this.leakAreaY = height - leakAreaHeight;
if (drag) {
InitDragCanvasWithLeak(this);
}
if (zoom) {
// InitZoomCanvasWithLeak(this);
}
FixNodeMarkerDrag(g6Instance);
SolveNodeAppendagesDrag(this);
SolveBrushSelectDrag(this);
drag && SolveDragCanvasWithLeak(this);
zoom && SolveZoomCanvasWithLeak(this);
}
@ -93,6 +95,13 @@ export class ViewContainer {
return this.accumulateLeakModels;
}
/**
*
*/
getLayoutGroupTable(): LayoutGroupTable {
return this.prevLayoutGroupTable;
}
/**
*
*/

View File

@ -1,6 +1,6 @@
import { Sources } from "./sources";
import { ModelConstructor } from "./Model/modelConstructor";
import { AnimationOptions, EngineOptions, InteractionOptions, LayoutGroupOptions, LayoutOptions, ViewOptions } from "./options";
import { LayoutGroupTable, ModelConstructor } from "./Model/modelConstructor";
import { AnimationOptions, BehaviorOptions, EngineOptions, LayoutGroupOptions, ViewOptions } from "./options";
import { EventBus } from "./Common/eventBus";
import { ViewContainer } from "./View/viewContainer";
import { SVNode } from "./Model/SVNode";
@ -11,13 +11,12 @@ import { SVModel } from "./Model/SVModel";
export class Engine {
private modelConstructor: ModelConstructor;
private viewContainer: ViewContainer;
private prevSource: Sources;
private prevStringSource: string;
public engineOptions: EngineOptions;
public viewOptions: ViewOptions;
public animationOptions: AnimationOptions;
public interactionOptions: InteractionOptions;
public behaviorOptions: BehaviorOptions;
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions) {
this.engineOptions = Object.assign({}, engineOptions);
@ -36,12 +35,12 @@ export class Engine {
timingFunction: 'easePolyOut'
}, engineOptions.animation);
this.interactionOptions = Object.assign({
this.behaviorOptions = Object.assign({
drag: true,
zoom: true,
dragNode: true,
selectNode: true
}, engineOptions.interaction);
}, engineOptions.behavior);
this.modelConstructor = new ModelConstructor(this);
this.viewContainer = new ViewContainer(this, DOMContainer);
@ -62,7 +61,6 @@ export class Engine {
return;
}
this.prevSource = source;
this.prevStringSource = stringSource;
// 1 转换模型data => model
@ -199,6 +197,15 @@ export class Engine {
});
}
/**
* /
* @param enable
*/
public switchBrushSelect(enable: boolean) {
const g6Instance = this.viewContainer.getG6Instance();
enable ? g6Instance.setMode('brush') : g6Instance.setMode('default');
}
/**
*
*/

View File

@ -1,3 +1,4 @@
import { SVModel } from "./Model/SVModel";
import { SVNode } from "./Model/SVNode";
import { SourceNode } from "./sources";
@ -86,6 +87,9 @@ export interface LayoutGroupOptions {
addressLabel?: AddressLabelOption;
indexLabel?: { [key: string]: IndexLabelOption };
layout?: LayoutOptions;
behavior?: {
dragNode: boolean | string[];
}
};
@ -110,7 +114,7 @@ export interface AnimationOptions {
};
export interface InteractionOptions {
export interface BehaviorOptions {
drag: boolean;
zoom: boolean;
}
@ -118,14 +122,14 @@ export interface InteractionOptions {
export interface EngineOptions {
view?: ViewOptions;
animation?: AnimationOptions;
interaction?: InteractionOptions;
behavior?: BehaviorOptions;
};
export interface LayoutCreator {
defineOptions(sourceData: SourceNode[]): LayoutGroupOptions;
sourcesPreprocess?(sourceData: SourceNode[], options: LayoutGroupOptions): SourceNode[];
defineLeakRule?(nodes: SVNode[]): SVNode[];
defineLeakRule?(models: SVModel[]): SVModel[];
layout(nodes: SVNode[], layoutOptions: LayoutOptions);
[key: string]: any;
}

View File

@ -17,5 +17,6 @@ module.exports = {
loader: 'ts-loader'
}
]
}
},
// devtool: 'eval-source-map'
};