feat: 增加框选功能
This commit is contained in:
@@ -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) {
|
||||
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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]
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user