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
+199
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) {
}
-49
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
});
}
});
}
-71
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
});
});
});
}
+113
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]
};
}
-54
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;
}
-67
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
});
}
});
}