@antv/g6
Version:
A Graph Visualization Framework in JavaScript
334 lines • 13.6 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DragElement = void 0;
const g_1 = require("@antv/g");
const util_1 = require("@antv/util");
const constants_1 = require("../constants");
const bbox_1 = require("../utils/bbox");
const element_1 = require("../utils/element");
const id_1 = require("../utils/id");
const prefix_1 = require("../utils/prefix");
const vector_1 = require("../utils/vector");
const base_behavior_1 = require("./base-behavior");
/**
* <zh/> 拖拽元素交互
*
* <en/> Drag element behavior
*/
class DragElement extends base_behavior_1.BaseBehavior {
constructor(context, options) {
super(context, Object.assign({}, DragElement.defaultOptions, options));
this.enable = false;
this.enableElements = ['node', 'combo'];
this.target = [];
this.shadowOrigin = [0, 0];
this.hiddenEdges = [];
this.isDragging = false;
/**
* <zh/> 拖拽放下的回调
*
* <en/> Callback when dragging is released
* @param event - <zh/> 拖拽事件对象 | <en/> drag event object
*/
this.onDrop = (event) => __awaiter(this, void 0, void 0, function* () {
var _a;
if (this.options.dropEffect !== 'link')
return;
const { model, element } = this.context;
const modifiedParentId = event.target.id;
this.target.forEach((id) => {
const originalParent = model.getParentData(id, constants_1.COMBO_KEY);
// 如果是在原父 combo 内部拖拽,需要刷新 combo 数据
// If it is a drag and drop within the original parent combo, you need to refresh the combo data
if (originalParent && (0, id_1.idOf)(originalParent) === modifiedParentId) {
model.refreshComboData(modifiedParentId);
}
model.setParent(id, modifiedParentId, constants_1.COMBO_KEY);
});
yield ((_a = element === null || element === void 0 ? void 0 : element.draw({ animation: true })) === null || _a === void 0 ? void 0 : _a.finished);
});
this.setCursor = (event) => {
if (this.isDragging)
return;
const { type } = event;
const { canvas } = this.context;
const { cursor } = this.options;
if (type === constants_1.CommonEvent.POINTER_ENTER)
canvas.setCursor((cursor === null || cursor === void 0 ? void 0 : cursor.grab) || 'grab');
else
canvas.setCursor((cursor === null || cursor === void 0 ? void 0 : cursor.default) || 'default');
};
this.onDragStart = this.onDragStart.bind(this);
this.onDrag = this.onDrag.bind(this);
this.onDragEnd = this.onDragEnd.bind(this);
this.onDrop = this.onDrop.bind(this);
this.bindEvents();
}
/**
* <zh/> 更新元素拖拽配置
*
* <en/> Update the element dragging configuration
* @param options - <zh/> 配置项 | <en/> options
* @internal
*/
update(options) {
this.unbindEvents();
super.update(options);
this.bindEvents();
}
bindEvents() {
const { graph, canvas } = this.context;
// @ts-expect-error internal property
const $canvas = canvas.getLayer().getContextService().$canvas;
if ($canvas) {
$canvas.addEventListener('blur', this.onDragEnd);
$canvas.addEventListener('contextmenu', this.onDragEnd);
}
this.enableElements.forEach((type) => {
graph.on(`${type}:${constants_1.CommonEvent.DRAG_START}`, this.onDragStart);
graph.on(`${type}:${constants_1.CommonEvent.DRAG}`, this.onDrag);
graph.on(`${type}:${constants_1.CommonEvent.DRAG_END}`, this.onDragEnd);
graph.on(`${type}:${constants_1.CommonEvent.POINTER_ENTER}`, this.setCursor);
graph.on(`${type}:${constants_1.CommonEvent.POINTER_LEAVE}`, this.setCursor);
});
if (['link'].includes(this.options.dropEffect)) {
graph.on(constants_1.ComboEvent.DROP, this.onDrop);
graph.on(constants_1.CanvasEvent.DROP, this.onDrop);
}
}
/**
* <zh/> 获取当前选中的节点 id 集合
*
* <en/> Get the id collection of the currently selected node
* @param currTarget - <zh/> 当前拖拽目标元素 id 集合 | <en/> The id collection of the current drag target element
* @returns <zh/> 当前选中的节点 id 集合 | <en/> The id collection of the currently selected node
* @internal
*/
getSelectedNodeIDs(currTarget) {
return Array.from(new Set(this.context.graph
.getElementDataByState('node', this.options.state)
.map((node) => node.id)
.concat(currTarget)));
}
/**
* Get the delta of the drag
* @param event - drag event object
* @returns delta
* @internal
*/
getDelta(event) {
const zoom = this.context.graph.getZoom();
return (0, vector_1.divide)([event.dx, event.dy], zoom);
}
/**
* <zh/> 拖拽开始时的回调
*
* <en/> Callback when dragging starts
* @param event - <zh/> 拖拽事件对象 | <en/> drag event object
* @internal
*/
onDragStart(event) {
var _a;
this.enable = this.validate(event);
if (!this.enable)
return;
const { batch, canvas, graph } = this.context;
canvas.setCursor(((_a = this.options.cursor) === null || _a === void 0 ? void 0 : _a.grabbing) || 'grabbing');
this.isDragging = true;
batch.startBatch();
// 如果当前节点是选中状态,则查询出画布中所有选中的节点,否则只拖拽当前节点
// If the current node is selected, query all selected nodes in the canvas, otherwise only drag the current node
const id = event.target.id;
const states = graph.getElementState(id);
if (states.includes(this.options.state))
this.target = this.getSelectedNodeIDs([id]);
else
this.target = [id];
this.hideEdge();
this.context.graph.frontElement(this.target);
if (this.options.shadow)
this.createShadow(this.target);
}
/**
* <zh/> 拖拽过程中的回调
*
* <en/> Callback when dragging
* @param event - <zh/> 拖拽事件对象 | <en/> drag event object
* @internal
*/
onDrag(event) {
if (!this.enable)
return;
const delta = this.getDelta(event);
if (this.options.shadow)
this.moveShadow(delta);
else
this.moveElement(this.target, delta);
}
/**
* <zh/> 元素拖拽结束的回调
*
* <en/> Callback when dragging ends
* @internal
*/
onDragEnd() {
var _a, _b, _c;
if (!this.enable)
return; // It can be called multiple times
this.enable = false;
if (this.options.shadow) {
if (!this.shadow)
return;
this.shadow.style.visibility = 'hidden';
const { x = 0, y = 0 } = this.shadow.attributes;
const [dx, dy] = (0, vector_1.subtract)([+x, +y], this.shadowOrigin);
this.moveElement(this.target, [dx, dy]);
}
this.showEdges();
(_b = (_a = this.options).onFinish) === null || _b === void 0 ? void 0 : _b.call(_a, this.target);
const { batch, canvas } = this.context;
batch.endBatch();
canvas.setCursor(((_c = this.options.cursor) === null || _c === void 0 ? void 0 : _c.grab) || 'grab');
this.isDragging = false;
this.target = [];
}
/**
* <zh/> 验证元素是否允许拖拽
*
* <en/> Verify if the element is allowed to be dragged
* @param event - <zh/> 拖拽事件对象 | <en/> drag event object
* @returns <zh/> 是否允许拖拽 | <en/> Whether to allow dragging
* @internal
*/
validate(event) {
if (this.destroyed ||
(0, element_1.isToBeDestroyed)(event.target) ||
// @ts-expect-error private property
// 避免动画冲突,在combo/node折叠展开过程中不触发
this.context.graph.isCollapsingExpanding)
return false;
const { enable } = this.options;
if ((0, util_1.isFunction)(enable))
return enable(event);
return !!enable;
}
/**
* <zh/> 移动元素
*
* <en/> Move the element
* @param ids - <zh/> 元素 id 集合 | <en/> element id collection
* @param offset <zh/> 偏移量 | <en/> offset
* @internal
*/
moveElement(ids, offset) {
return __awaiter(this, void 0, void 0, function* () {
const { graph, model } = this.context;
const { dropEffect } = this.options;
if (dropEffect === 'move')
ids.forEach((id) => model.refreshComboData(id));
graph.translateElementBy(Object.fromEntries(ids.map((id) => [id, offset])), false);
});
}
moveShadow(offset) {
if (!this.shadow)
return;
const { x = 0, y = 0 } = this.shadow.attributes;
const [dx, dy] = offset;
this.shadow.attr({ x: +x + dx, y: +y + dy });
}
createShadow(target) {
const shadowStyle = (0, prefix_1.subStyleProps)(this.options, 'shadow');
const bbox = (0, bbox_1.getCombinedBBox)(target.map((id) => this.context.element.getElement(id).getBounds()));
const [x, y] = bbox.min;
this.shadowOrigin = [x, y];
const [width, height] = (0, bbox_1.getBBoxSize)(bbox);
const positionStyle = { width, height, x, y };
if (this.shadow) {
this.shadow.attr(Object.assign(Object.assign(Object.assign({}, shadowStyle), positionStyle), { visibility: 'visible' }));
}
else {
this.shadow = new g_1.Rect({
style: Object.assign(Object.assign(Object.assign({
// @ts-ignore $layer is not in the type definition
$layer: 'transient' }, shadowStyle), positionStyle), { pointerEvents: 'none' }),
});
this.context.canvas.appendChild(this.shadow);
}
}
showEdges() {
if (this.options.shadow || this.hiddenEdges.length === 0)
return;
this.context.graph.showElement(this.hiddenEdges);
this.hiddenEdges = [];
}
/**
* Hide the edge
* @internal
*/
hideEdge() {
const { hideEdge, shadow } = this.options;
if (hideEdge === 'none' || shadow)
return;
const { graph } = this.context;
if (hideEdge === 'all')
this.hiddenEdges = graph.getEdgeData().map(id_1.idOf);
else {
this.hiddenEdges = Array.from(new Set(this.target.map((id) => graph.getRelatedEdgesData(id, hideEdge).map(id_1.idOf)).flat()));
}
graph.hideElement(this.hiddenEdges);
}
unbindEvents() {
const { graph, canvas } = this.context;
// @ts-expect-error internal property
const $canvas = canvas.getLayer().getContextService().$canvas;
if ($canvas) {
$canvas.removeEventListener('blur', this.onDragEnd);
$canvas.removeEventListener('contextmenu', this.onDragEnd);
}
this.enableElements.forEach((type) => {
graph.off(`${type}:${constants_1.CommonEvent.DRAG_START}`, this.onDragStart);
graph.off(`${type}:${constants_1.CommonEvent.DRAG}`, this.onDrag);
graph.off(`${type}:${constants_1.CommonEvent.DRAG_END}`, this.onDragEnd);
graph.off(`${type}:${constants_1.CommonEvent.POINTER_ENTER}`, this.setCursor);
graph.off(`${type}:${constants_1.CommonEvent.POINTER_LEAVE}`, this.setCursor);
});
graph.off(`combo:${constants_1.CommonEvent.DROP}`, this.onDrop);
graph.off(`canvas:${constants_1.CommonEvent.DROP}`, this.onDrop);
}
destroy() {
var _a;
this.unbindEvents();
(_a = this.shadow) === null || _a === void 0 ? void 0 : _a.destroy();
super.destroy();
}
}
exports.DragElement = DragElement;
DragElement.defaultOptions = {
animation: true,
enable: (event) => ['node', 'combo'].includes(event.targetType),
dropEffect: 'move',
state: 'selected',
hideEdge: 'none',
shadow: false,
shadowZIndex: 100,
shadowFill: '#F3F9FF',
shadowFillOpacity: 0.5,
shadowStroke: '#1890FF',
shadowStrokeOpacity: 0.9,
shadowLineDash: [5, 5],
cursor: {
default: 'default',
grab: 'grab',
grabbing: 'grabbing',
},
};
//# sourceMappingURL=drag-element.js.map