mirror of
https://gitee.com/antv/g6.git
synced 2024-12-02 19:58:46 +08:00
feat: init mobile
This commit is contained in:
parent
ca64ce1e7b
commit
851b17b52b
231
packages/mobile/src/behavior/brush-select.ts
Normal file
231
packages/mobile/src/behavior/brush-select.ts
Normal file
@ -0,0 +1,231 @@
|
||||
import { G6Event, IG6GraphEvent } from '@antv/g6-core';
|
||||
|
||||
const { min, max, abs } = Math;
|
||||
|
||||
const DEFAULT_TRIGGER = 'shift';
|
||||
const ALLOW_EVENTS = ['drag', 'shift', 'ctrl', 'alt', 'control'];
|
||||
|
||||
export default {
|
||||
getDefaultCfg(): object {
|
||||
return {
|
||||
brushStyle: {
|
||||
fill: '#EEF6FF',
|
||||
fillOpacity: 0.4,
|
||||
stroke: '#DDEEFE',
|
||||
lineWidth: 1,
|
||||
},
|
||||
onSelect() {},
|
||||
onDeselect() {},
|
||||
selectedState: 'selected',
|
||||
trigger: DEFAULT_TRIGGER,
|
||||
includeEdges: true,
|
||||
selectedEdges: [],
|
||||
selectedNodes: [],
|
||||
};
|
||||
},
|
||||
getEvents(): { [key in G6Event]?: string } {
|
||||
// 检测输入是否合法
|
||||
if (!(ALLOW_EVENTS.indexOf(this.trigger.toLowerCase()) > -1)) {
|
||||
this.trigger = DEFAULT_TRIGGER;
|
||||
console.warn(
|
||||
"Behavior brush-select 的 trigger 参数不合法,请输入 'drag'、'shift'、'ctrl' 或 'alt'",
|
||||
);
|
||||
}
|
||||
if (this.trigger === 'drag') {
|
||||
return {
|
||||
dragstart: 'onMouseDown',
|
||||
drag: 'onMouseMove',
|
||||
dragend: 'onMouseUp',
|
||||
'canvas:click': 'clearStates',
|
||||
};
|
||||
}
|
||||
return {
|
||||
dragstart: 'onMouseDown',
|
||||
drag: 'onMouseMove',
|
||||
dragend: 'onMouseUp',
|
||||
'canvas:click': 'clearStates',
|
||||
keyup: 'onKeyUp',
|
||||
keydown: 'onKeyDown',
|
||||
};
|
||||
},
|
||||
onMouseDown(e: IG6GraphEvent) {
|
||||
// 按在node上面拖动时候不应该是框选
|
||||
const { item } = e;
|
||||
let { brush } = this;
|
||||
if (item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.trigger !== 'drag' && !this.keydown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedNodes && this.selectedNodes.length !== 0) {
|
||||
this.clearStates();
|
||||
}
|
||||
|
||||
if (!brush) {
|
||||
brush = this.createBrush();
|
||||
}
|
||||
this.originPoint = { x: e.canvasX, y: e.canvasY };
|
||||
brush.attr({ width: 0, height: 0 });
|
||||
brush.show();
|
||||
this.dragging = true;
|
||||
},
|
||||
onMouseMove(e: IG6GraphEvent) {
|
||||
if (!this.dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.trigger !== 'drag' && !this.keydown) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateBrush(e);
|
||||
},
|
||||
onMouseUp(e: IG6GraphEvent) {
|
||||
const { graph } = this;
|
||||
// TODO: 触发了 canvas:click 导致 clearStates
|
||||
if (!this.brush && !this.dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.trigger !== 'drag' && !this.keydown) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.brush.remove(true); // remove and destroy
|
||||
this.brush = null;
|
||||
this.getSelectedNodes(e);
|
||||
this.dragging = false;
|
||||
},
|
||||
clearStates() {
|
||||
const { graph, selectedState } = this;
|
||||
|
||||
const nodes = graph.findAllByState('node', selectedState);
|
||||
const edges = graph.findAllByState('edge', selectedState);
|
||||
nodes.forEach((node) => graph.setItemState(node, selectedState, false));
|
||||
edges.forEach((edge) => graph.setItemState(edge, selectedState, false));
|
||||
|
||||
this.selectedNodes = [];
|
||||
|
||||
this.selectedEdges = [];
|
||||
if (this.onDeselect) {
|
||||
this.onDeselect(this.selectedNodes, this.selectedEdges);
|
||||
}
|
||||
|
||||
graph.emit('nodeselectchange', {
|
||||
selectedItems: {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
},
|
||||
select: false,
|
||||
});
|
||||
},
|
||||
getSelectedNodes(e: IG6GraphEvent) {
|
||||
const { graph, originPoint, shouldUpdate } = this;
|
||||
const state = this.selectedState;
|
||||
const p1 = { x: e.x, y: e.y };
|
||||
const p2 = graph.getPointByCanvas(originPoint.x, originPoint.y);
|
||||
const left = min(p1.x, p2.x);
|
||||
const right = max(p1.x, p2.x);
|
||||
const top = min(p1.y, p2.y);
|
||||
const bottom = max(p1.y, p2.y);
|
||||
const selectedNodes = [];
|
||||
const selectedIds = [];
|
||||
graph.getNodes().forEach((node) => {
|
||||
const bbox = node.getBBox();
|
||||
if (
|
||||
bbox.centerX >= left &&
|
||||
bbox.centerX <= right &&
|
||||
bbox.centerY >= top &&
|
||||
bbox.centerY <= bottom
|
||||
) {
|
||||
if (shouldUpdate(node, 'select')) {
|
||||
selectedNodes.push(node);
|
||||
const model = node.getModel();
|
||||
selectedIds.push(model.id);
|
||||
graph.setItemState(node, state, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
const selectedEdges = [];
|
||||
if (this.includeEdges) {
|
||||
// 选中边,边的source和target都在选中的节点中时才选中
|
||||
selectedNodes.forEach((node) => {
|
||||
const edges = node.getOutEdges();
|
||||
edges.forEach((edge) => {
|
||||
const model = edge.getModel();
|
||||
const { source, target } = model;
|
||||
if (
|
||||
selectedIds.includes(source) &&
|
||||
selectedIds.includes(target) &&
|
||||
shouldUpdate(edge, 'select')
|
||||
) {
|
||||
selectedEdges.push(edge);
|
||||
graph.setItemState(edge, this.selectedState, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.selectedEdges = selectedEdges;
|
||||
this.selectedNodes = selectedNodes;
|
||||
if (this.onSelect) {
|
||||
this.onSelect(selectedNodes, selectedEdges);
|
||||
}
|
||||
graph.emit('nodeselectchange', {
|
||||
selectedItems: {
|
||||
nodes: selectedNodes,
|
||||
edges: selectedEdges,
|
||||
},
|
||||
select: true,
|
||||
});
|
||||
},
|
||||
createBrush() {
|
||||
const self = this;
|
||||
const brush = self.graph.get('canvas').addShape('rect', {
|
||||
attrs: self.brushStyle,
|
||||
capture: false,
|
||||
name: 'brush-shape',
|
||||
});
|
||||
this.brush = brush;
|
||||
return brush;
|
||||
},
|
||||
updateBrush(e: IG6GraphEvent) {
|
||||
const { originPoint } = this;
|
||||
this.brush.attr({
|
||||
width: abs(e.canvasX - originPoint.x),
|
||||
height: abs(e.canvasY - originPoint.y),
|
||||
x: min(e.canvasX, originPoint.x),
|
||||
y: min(e.canvasY, originPoint.y),
|
||||
});
|
||||
},
|
||||
onKeyDown(e: IG6GraphEvent) {
|
||||
const code = e.key;
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
const triggerLowerCase = this.trigger.toLowerCase();
|
||||
const codeLowerCase = code.toLowerCase();
|
||||
// 按住 control 键时,允许用户设置 trigger 为 ctrl
|
||||
if (
|
||||
codeLowerCase === triggerLowerCase ||
|
||||
(codeLowerCase === 'control' && triggerLowerCase === 'ctrl') ||
|
||||
(codeLowerCase === 'ctrl' && triggerLowerCase === 'control')
|
||||
) {
|
||||
this.keydown = true;
|
||||
} else {
|
||||
this.keydown = false;
|
||||
}
|
||||
},
|
||||
onKeyUp() {
|
||||
if (this.brush) {
|
||||
// 清除所有选中状态后,设置拖得动状态为false,并清除框选的brush
|
||||
this.brush.remove(true);
|
||||
this.brush = null;
|
||||
this.dragging = false;
|
||||
}
|
||||
this.keydown = false;
|
||||
},
|
||||
};
|
23
packages/mobile/src/behavior/edge-tooltip.ts
Normal file
23
packages/mobile/src/behavior/edge-tooltip.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import base from './tooltip-base';
|
||||
import { G6Event, EdgeConfig } from '@antv/g6-core';
|
||||
|
||||
export default {
|
||||
getDefaultCfg(): object {
|
||||
return {
|
||||
item: 'edge',
|
||||
offset: 12,
|
||||
formatText(model: EdgeConfig) {
|
||||
return `source: ${model.source} target: ${model.target}`;
|
||||
},
|
||||
};
|
||||
},
|
||||
getEvents(): { [key in G6Event | 'afterremoveitem']?: string } {
|
||||
return {
|
||||
'edge:mouseenter': 'onMouseEnter',
|
||||
'edge:mouseleave': 'onMouseLeave',
|
||||
'edge:mousemove': 'onMouseMove',
|
||||
afterremoveitem: 'onMouseLeave',
|
||||
};
|
||||
},
|
||||
...base,
|
||||
};
|
251
packages/mobile/src/behavior/lasso-select.ts
Normal file
251
packages/mobile/src/behavior/lasso-select.ts
Normal file
@ -0,0 +1,251 @@
|
||||
import { G6Event, IG6GraphEvent, IPoint, Item } from '@antv/g6-core';
|
||||
import Util from '../util';
|
||||
const { isPolygonsIntersect, pathToPoints } = Util;
|
||||
|
||||
const DEFAULT_TRIGGER = 'shift';
|
||||
const ALLOW_EVENTS = ['drag', 'shift', 'ctrl', 'alt', 'control'];
|
||||
|
||||
const isItemIntersecPolygon = (item: Item, polyPoints: number[][]) => {
|
||||
let shapePoints;
|
||||
const shape = item.getKeyShape();
|
||||
if (item.get('type') === 'path') {
|
||||
shapePoints = pathToPoints(shape.attr('path'));
|
||||
} else {
|
||||
const shapeBBox = shape.getCanvasBBox();
|
||||
shapePoints = [
|
||||
[shapeBBox.minX, shapeBBox.minY],
|
||||
[shapeBBox.maxX, shapeBBox.minY],
|
||||
[shapeBBox.maxX, shapeBBox.maxY],
|
||||
[shapeBBox.minX, shapeBBox.maxY],
|
||||
];
|
||||
}
|
||||
return isPolygonsIntersect(polyPoints, shapePoints);
|
||||
};
|
||||
|
||||
export default {
|
||||
getDefaultCfg(): object {
|
||||
return {
|
||||
delegateStyle: {
|
||||
fill: '#EEF6FF',
|
||||
fillOpacity: 0.4,
|
||||
stroke: '#DDEEFE',
|
||||
lineWidth: 1,
|
||||
},
|
||||
onSelect() {},
|
||||
onDeselect() {},
|
||||
selectedState: 'selected',
|
||||
trigger: DEFAULT_TRIGGER,
|
||||
includeEdges: true,
|
||||
selectedEdges: [],
|
||||
selectedNodes: [],
|
||||
// multiple: false,
|
||||
};
|
||||
},
|
||||
getEvents(): { [key in G6Event]?: string } {
|
||||
// 检测输入是否合法
|
||||
if (!(ALLOW_EVENTS.indexOf(this.trigger.toLowerCase()) > -1)) {
|
||||
this.trigger = DEFAULT_TRIGGER;
|
||||
console.warn(
|
||||
"Behavior lasso-select 的 trigger 参数不合法,请输入 'drag'、'shift'、'ctrl' 或 'alt'",
|
||||
);
|
||||
}
|
||||
if (this.trigger === 'drag') {
|
||||
return {
|
||||
dragstart: 'onDragStart',
|
||||
drag: 'onDragMove',
|
||||
dragend: 'onDragEnd',
|
||||
'canvas:click': 'clearStates',
|
||||
};
|
||||
}
|
||||
return {
|
||||
dragstart: 'onDragStart',
|
||||
drag: 'onDragMove',
|
||||
dragend: 'onDragEnd',
|
||||
keyup: 'onKeyUp',
|
||||
keydown: 'onKeyDown',
|
||||
'canvas:click': 'clearStates',
|
||||
};
|
||||
},
|
||||
onDragStart(e: IG6GraphEvent) {
|
||||
let { lasso } = this;
|
||||
const { item } = e;
|
||||
|
||||
// 排除在节点上拖动
|
||||
if (item) {
|
||||
return;
|
||||
}
|
||||
if (this.trigger !== 'drag' && !this.keydown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedNodes && this.selectedNodes.length !== 0) {
|
||||
this.clearStates();
|
||||
}
|
||||
|
||||
if (!lasso) {
|
||||
lasso = this.createLasso();
|
||||
}
|
||||
this.dragging = true;
|
||||
this.originPoint = { x: e.x, y: e.y };
|
||||
this.points.push(this.originPoint);
|
||||
lasso.show();
|
||||
},
|
||||
onDragMove(e: IG6GraphEvent) {
|
||||
if (!this.dragging) {
|
||||
return;
|
||||
}
|
||||
if (this.trigger !== 'drag' && !this.keydown) {
|
||||
return;
|
||||
}
|
||||
this.points.push({ x: e.x, y: e.y });
|
||||
this.updateLasso(e);
|
||||
},
|
||||
onDragEnd(e: IG6GraphEvent) {
|
||||
if (!this.lasso && !this.dragging) {
|
||||
return;
|
||||
}
|
||||
if (this.trigger !== 'drag' && !this.keydown) {
|
||||
return;
|
||||
}
|
||||
this.points.push(this.originPoint);
|
||||
this.getSelectedItems();
|
||||
|
||||
this.lasso.remove(true);
|
||||
this.lasso = null;
|
||||
this.points = [];
|
||||
this.dragging = false;
|
||||
},
|
||||
getLassoPath() {
|
||||
const points: IPoint[] = this.points;
|
||||
const path = [];
|
||||
if (points.length) {
|
||||
points.forEach((point, index) => {
|
||||
if (index === 0) {
|
||||
path.push(['M', point.x, point.y]);
|
||||
} else {
|
||||
path.push(['L', point.x, point.y]);
|
||||
}
|
||||
});
|
||||
path.push(['L', points[0].x, points[0].y]);
|
||||
}
|
||||
return path;
|
||||
},
|
||||
clearStates() {
|
||||
const { graph, selectedState } = this;
|
||||
const nodes = graph.findAllByState('node', selectedState);
|
||||
const edges = graph.findAllByState('edge', selectedState);
|
||||
nodes.forEach((node) => graph.setItemState(node, selectedState, false));
|
||||
edges.forEach((edge) => graph.setItemState(edge, selectedState, false));
|
||||
|
||||
if (this.onDeselect) {
|
||||
this.onDeselect(this.selectedNodes, this.selectedEdges);
|
||||
}
|
||||
|
||||
this.selectedNodes = [];
|
||||
this.selectedEdges = [];
|
||||
|
||||
graph.emit('nodeselectchange', {
|
||||
selectedItems: {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
},
|
||||
select: false,
|
||||
});
|
||||
},
|
||||
getSelectedItems() {
|
||||
const { graph, shouldUpdate } = this;
|
||||
const lassoContour = this.points.map((point) => [
|
||||
graph.getCanvasByPoint(point.x, point.y).x,
|
||||
graph.getCanvasByPoint(point.x, point.y).y,
|
||||
]);
|
||||
const state = this.selectedState;
|
||||
const selectedNodes = [];
|
||||
const selectedIds = [];
|
||||
graph.getNodes().forEach((node) => {
|
||||
if (isItemIntersecPolygon(node, lassoContour)) {
|
||||
if (shouldUpdate(node, 'select')) {
|
||||
selectedNodes.push(node);
|
||||
const model = node.getModel();
|
||||
selectedIds.push(model.id);
|
||||
graph.setItemState(node, state, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
const selectedEdges = [];
|
||||
if (this.includeEdges) {
|
||||
// 选中边,边的source和target都在选中的节点中时才选中
|
||||
selectedNodes.forEach((node) => {
|
||||
const edges = node.getOutEdges();
|
||||
edges.forEach((edge) => {
|
||||
const model = edge.getModel();
|
||||
const { source, target } = model;
|
||||
if (
|
||||
selectedIds.includes(source) &&
|
||||
selectedIds.includes(target) &&
|
||||
shouldUpdate(edge, 'select')
|
||||
) {
|
||||
selectedEdges.push(edge);
|
||||
graph.setItemState(edge, this.selectedState, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.selectedEdges = selectedEdges;
|
||||
this.selectedNodes = selectedNodes;
|
||||
if (this.onSelect) {
|
||||
this.onSelect(selectedNodes, selectedEdges);
|
||||
}
|
||||
graph.emit('nodeselectchange', {
|
||||
selectedItems: {
|
||||
nodes: selectedNodes,
|
||||
edges: selectedEdges,
|
||||
},
|
||||
select: true,
|
||||
});
|
||||
},
|
||||
createLasso() {
|
||||
const self = this;
|
||||
const lasso = self.graph.get('delegateGroup').addShape('path', {
|
||||
attrs: {
|
||||
path: [],
|
||||
...self.delegateStyle,
|
||||
},
|
||||
capture: false,
|
||||
name: 'lasso-shape',
|
||||
});
|
||||
this.lasso = lasso;
|
||||
this.points = [];
|
||||
return lasso;
|
||||
},
|
||||
updateLasso(e: IG6GraphEvent) {
|
||||
const self = this;
|
||||
this.lasso.attr({
|
||||
path: self.getLassoPath(),
|
||||
});
|
||||
},
|
||||
onKeyDown(e: IG6GraphEvent) {
|
||||
const code = e.key;
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
// if (this.selectedNodes && this.selectedNodes.length !== 0) {
|
||||
// this.clearStates();
|
||||
// }
|
||||
if (code.toLowerCase() === this.trigger.toLowerCase()) {
|
||||
this.keydown = true;
|
||||
} else {
|
||||
this.keydown = false;
|
||||
}
|
||||
},
|
||||
onKeyUp() {
|
||||
if (this.lasso) {
|
||||
// 清除所有选中状态后,设置拖得动状态为false,并清除框选的lasso
|
||||
this.lasso.remove(true);
|
||||
this.lasso = null;
|
||||
this.points = [];
|
||||
this.dragging = false;
|
||||
}
|
||||
this.keydown = false;
|
||||
},
|
||||
};
|
90
packages/mobile/src/behavior/tooltip-base.ts
Normal file
90
packages/mobile/src/behavior/tooltip-base.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { modifyCSS, createDom } from '@antv/dom-util';
|
||||
import { IG6GraphEvent } from '@antv/g6-core';
|
||||
|
||||
export default {
|
||||
onMouseEnter(e: IG6GraphEvent) {
|
||||
const { item } = e;
|
||||
this.currentTarget = item;
|
||||
this.showTooltip(e);
|
||||
this.graph.emit('tooltipchange', { item: e.item, action: 'show' });
|
||||
},
|
||||
onMouseMove(e: IG6GraphEvent) {
|
||||
if (!this.shouldUpdate(e)) {
|
||||
this.hideTooltip();
|
||||
return;
|
||||
}
|
||||
if (!this.currentTarget || e.item !== this.currentTarget) {
|
||||
return;
|
||||
}
|
||||
this.updatePosition(e);
|
||||
},
|
||||
onMouseLeave(e: IG6GraphEvent) {
|
||||
if (!this.shouldEnd(e)) {
|
||||
return;
|
||||
}
|
||||
this.hideTooltip();
|
||||
this.graph.emit('tooltipchange', { item: this.currentTarget, action: 'hide' });
|
||||
this.currentTarget = null;
|
||||
},
|
||||
showTooltip(e: IG6GraphEvent) {
|
||||
let { container } = this;
|
||||
if (!e.item || e.item.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!container) {
|
||||
container = this.createTooltip(this.graph.get('canvas'));
|
||||
this.container = container;
|
||||
}
|
||||
const text = this.formatText(e.item.get('model'), e);
|
||||
container.innerHTML = text;
|
||||
modifyCSS(this.container, { visibility: 'visible' });
|
||||
this.updatePosition(e);
|
||||
},
|
||||
hideTooltip() {
|
||||
modifyCSS(this.container, {
|
||||
visibility: 'hidden',
|
||||
});
|
||||
},
|
||||
updatePosition(e: IG6GraphEvent) {
|
||||
const shouldBegin = this.get('shouldBegin');
|
||||
const { width, height, container, graph } = this;
|
||||
if (!shouldBegin(e)) {
|
||||
modifyCSS(container, {
|
||||
visibility: 'hidden',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const point = graph.getPointByClient(e.clientX, e.clientY);
|
||||
let { x, y } = graph.getCanvasByPoint(point.x, point.y);
|
||||
const bbox = container.getBoundingClientRect();
|
||||
if (x > width / 2) {
|
||||
x -= bbox.width;
|
||||
} else {
|
||||
x += this.offset;
|
||||
}
|
||||
if (y > height / 2) {
|
||||
y -= bbox.height;
|
||||
} else {
|
||||
y += this.offset;
|
||||
}
|
||||
const left = `${x}px`;
|
||||
const top = `${y}px`;
|
||||
modifyCSS(this.container, { left, top, visibility: 'visible' });
|
||||
},
|
||||
createTooltip(canvas): HTMLElement {
|
||||
const el = canvas.get('el');
|
||||
el.style.position = 'relative';
|
||||
const container = createDom(`<div class="g6-tooltip g6-${this.item}-tooltip"></div>`);
|
||||
el.parentNode.appendChild(container);
|
||||
modifyCSS(container, {
|
||||
position: 'absolute',
|
||||
visibility: 'visible',
|
||||
});
|
||||
this.width = canvas.get('width');
|
||||
this.height = canvas.get('height');
|
||||
this.container = container;
|
||||
this.graph.get('tooltips').push(container);
|
||||
return container;
|
||||
},
|
||||
};
|
23
packages/mobile/src/behavior/tooltip.ts
Normal file
23
packages/mobile/src/behavior/tooltip.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { G6Event } from '@antv/g6-core';
|
||||
import base from './tooltip-base';
|
||||
|
||||
export default {
|
||||
getDefaultCfg(): object {
|
||||
return {
|
||||
item: 'node',
|
||||
offset: 12,
|
||||
formatText(model) {
|
||||
return model.label;
|
||||
},
|
||||
};
|
||||
},
|
||||
getEvents(): { [key in G6Event | 'afterremoveitem']?: string } {
|
||||
return {
|
||||
'node:mouseenter': 'onMouseEnter',
|
||||
'node:mouseleave': 'onMouseLeave',
|
||||
'node:mousemove': 'onMouseMove',
|
||||
afterremoveitem: 'onMouseLeave',
|
||||
};
|
||||
},
|
||||
...base,
|
||||
};
|
Loading…
Reference in New Issue
Block a user