feat: init mobile

This commit is contained in:
chenpeng-yiyu 2020-12-23 19:31:14 +08:00 committed by Yanyan Wang
parent ca64ce1e7b
commit 851b17b52b
5 changed files with 618 additions and 0 deletions

View 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;
},
};

View 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,
};

View 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;
},
};

View 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;
},
};

View 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,
};