@antv/g6
Version:
A Graph Visualization Framework in JavaScript
289 lines • 12.9 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.Snapline = void 0;
const g_1 = require("@antv/g");
const util_1 = require("@antv/util");
const constants_1 = require("../../constants");
const element_1 = require("../../utils/element");
const vector_1 = require("../../utils/vector");
const base_plugin_1 = require("../base-plugin");
const defaultLineStyle = { x1: 0, y1: 0, x2: 0, y2: 0, visibility: 'hidden' };
/**
* <zh/> 对齐线插件
*
* <en/> Snapline plugin
*/
class Snapline extends base_plugin_1.BasePlugin {
constructor(context, options) {
super(context, Object.assign({}, Snapline.defaultOptions, options));
this.initSnapline = () => {
const canvas = this.context.canvas.getLayer('transient');
if (!this.horizontalLine) {
this.horizontalLine = canvas.appendChild(new g_1.Line({ style: Object.assign(Object.assign({}, defaultLineStyle), this.options.horizontalLineStyle) }));
}
if (!this.verticalLine) {
this.verticalLine = canvas.appendChild(new g_1.Line({ style: Object.assign(Object.assign({}, defaultLineStyle), this.options.verticalLineStyle) }));
}
};
this.isHorizontalSticking = false;
this.isVerticalSticking = false;
this.enableStick = true;
this.autoSnapToLine = (nodeId, bbox, metadata) => __awaiter(this, void 0, void 0, function* () {
const { verticalX, horizontalY } = metadata;
const { tolerance } = this.options;
const { min: [nodeMinX, nodeMinY], max: [nodeMaxX, nodeMaxY], center: [nodeCenterX, nodeCenterY], } = bbox;
let dx = 0;
let dy = 0;
if (verticalX !== null) {
if (distance(nodeMaxX, verticalX) < tolerance)
dx = verticalX - nodeMaxX;
if (distance(nodeMinX, verticalX) < tolerance)
dx = verticalX - nodeMinX;
if (distance(nodeCenterX, verticalX) < tolerance)
dx = verticalX - nodeCenterX;
if (dx !== 0)
this.isVerticalSticking = true;
}
if (horizontalY !== null) {
if (distance(nodeMaxY, horizontalY) < tolerance)
dy = horizontalY - nodeMaxY;
if (distance(nodeMinY, horizontalY) < tolerance)
dy = horizontalY - nodeMinY;
if (distance(nodeCenterY, horizontalY) < tolerance)
dy = horizontalY - nodeCenterY;
if (dy !== 0)
this.isHorizontalSticking = true;
}
if (dx !== 0 || dy !== 0) {
// Stick to the line
yield this.context.graph.translateElementBy({ [nodeId]: [dx, dy] }, false);
}
});
this.enableSnap = (event) => {
const { target } = event;
const threshold = 0.5;
if (this.isHorizontalSticking || this.isVerticalSticking) {
const [dx, dy] = this.getDelta(event);
if (this.isHorizontalSticking &&
this.isVerticalSticking &&
Math.abs(dx) <= threshold &&
Math.abs(dy) <= threshold) {
this.context.graph.translateElementBy({ [target.id]: [-dx, -dy] }, false);
return false;
}
else if (this.isHorizontalSticking && Math.abs(dy) <= threshold) {
this.context.graph.translateElementBy({ [target.id]: [0, -dy] }, false);
return false;
}
else if (this.isVerticalSticking && Math.abs(dx) <= threshold) {
this.context.graph.translateElementBy({ [target.id]: [-dx, 0] }, false);
return false;
}
else {
this.isHorizontalSticking = false;
this.isVerticalSticking = false;
this.enableStick = false;
setTimeout(() => {
this.enableStick = true;
}, 200);
}
}
return this.enableStick;
};
this.calcSnaplineMetadata = (target, nodeBBox) => {
const { tolerance, shape } = this.options;
const { min: [nodeMinX, nodeMinY], max: [nodeMaxX, nodeMaxY], center: [nodeCenterX, nodeCenterY], } = nodeBBox;
let verticalX = null;
let verticalMinY = null;
let verticalMaxY = null;
let horizontalY = null;
let horizontalMinX = null;
let horizontalMaxX = null;
this.getNodes().some((snapNode) => {
if ((0, util_1.isEqual)(target.id, snapNode.id))
return false;
const snapBBox = getShape(snapNode, shape).getRenderBounds();
const { min: [snapMinX, snapMinY], max: [snapMaxX, snapMaxY], center: [snapCenterX, snapCenterY], } = snapBBox;
if (verticalX === null) {
if (distance(snapCenterX, nodeCenterX) < tolerance) {
verticalX = snapCenterX;
}
else if (distance(snapMinX, nodeMinX) < tolerance) {
verticalX = snapMinX;
}
else if (distance(snapMinX, nodeMaxX) < tolerance) {
verticalX = snapMinX;
}
else if (distance(snapMaxX, nodeMaxX) < tolerance) {
verticalX = snapMaxX;
}
else if (distance(snapMaxX, nodeMinX) < tolerance) {
verticalX = snapMaxX;
}
if (verticalX !== null) {
verticalMinY = Math.min(snapMinY, nodeMinY);
verticalMaxY = Math.max(snapMaxY, nodeMaxY);
}
}
if (horizontalY === null) {
if (distance(snapCenterY, nodeCenterY) < tolerance) {
horizontalY = snapCenterY;
}
else if (distance(snapMinY, nodeMinY) < tolerance) {
horizontalY = snapMinY;
}
else if (distance(snapMinY, nodeMaxY) < tolerance) {
horizontalY = snapMinY;
}
else if (distance(snapMaxY, nodeMaxY) < tolerance) {
horizontalY = snapMaxY;
}
else if (distance(snapMaxY, nodeMinY) < tolerance) {
horizontalY = snapMaxY;
}
if (horizontalY !== null) {
horizontalMinX = Math.min(snapMinX, nodeMinX);
horizontalMaxX = Math.max(snapMaxX, nodeMaxX);
}
}
return verticalX !== null && horizontalY !== null;
});
return { verticalX, verticalMinY, verticalMaxY, horizontalY, horizontalMinX, horizontalMaxX };
};
this.onDragStart = () => {
this.initSnapline();
};
this.onDrag = (event) => __awaiter(this, void 0, void 0, function* () {
const { target } = event;
if (this.options.autoSnap) {
const enable = this.enableSnap(event);
if (!enable)
return;
}
const nodeBBox = getShape(target, this.options.shape).getRenderBounds();
const metadata = this.calcSnaplineMetadata(target, nodeBBox);
this.hideSnapline();
if (metadata.verticalX !== null || metadata.horizontalY !== null) {
this.updateSnapline(metadata);
}
if (this.options.autoSnap) {
yield this.autoSnapToLine(target.id, nodeBBox, metadata);
}
});
this.onDragEnd = () => {
this.hideSnapline();
};
this.bindEvents();
}
getNodes() {
var _a;
const { filter } = this.options;
const allNodes = ((_a = this.context.element) === null || _a === void 0 ? void 0 : _a.getNodes()) || [];
// 不考虑超出画布视口范围、不可见的节点
// Nodes that are out of the canvas viewport range, invisible are not considered
const nodes = allNodes.filter((node) => {
var _a;
return (0, element_1.isVisible)(node) && ((_a = this.context.viewport) === null || _a === void 0 ? void 0 : _a.isInViewport(node.getRenderBounds()));
});
if (!filter)
return nodes;
return nodes.filter((node) => filter(node));
}
hideSnapline() {
this.horizontalLine.style.visibility = 'hidden';
this.verticalLine.style.visibility = 'hidden';
}
getLineWidth(direction) {
const { lineWidth } = this.options[`${direction}LineStyle`];
return +(lineWidth || defaultLineStyle.lineWidth || 1) / this.context.graph.getZoom();
}
updateSnapline(metadata) {
const { verticalX, verticalMinY, verticalMaxY, horizontalY, horizontalMinX, horizontalMaxX } = metadata;
const [canvasWidth, canvasHeight] = this.context.canvas.getSize();
const { offset } = this.options;
if (horizontalY !== null) {
Object.assign(this.horizontalLine.style, {
x1: offset === Infinity ? 0 : horizontalMinX - offset,
y1: horizontalY,
x2: offset === Infinity ? canvasWidth : horizontalMaxX + offset,
y2: horizontalY,
visibility: 'visible',
lineWidth: this.getLineWidth('horizontal'),
});
}
else {
this.horizontalLine.style.visibility = 'hidden';
}
if (verticalX !== null) {
Object.assign(this.verticalLine.style, {
x1: verticalX,
y1: offset === Infinity ? 0 : verticalMinY - offset,
x2: verticalX,
y2: offset === Infinity ? canvasHeight : verticalMaxY + offset,
visibility: 'visible',
lineWidth: this.getLineWidth('vertical'),
});
}
else {
this.verticalLine.style.visibility = 'hidden';
}
}
/**
* 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);
}
bindEvents() {
return __awaiter(this, void 0, void 0, function* () {
const { graph } = this.context;
graph.on(constants_1.NodeEvent.DRAG_START, this.onDragStart);
graph.on(constants_1.NodeEvent.DRAG, this.onDrag);
graph.on(constants_1.NodeEvent.DRAG_END, this.onDragEnd);
});
}
unbindEvents() {
const { graph } = this.context;
graph.off(constants_1.NodeEvent.DRAG_START, this.onDragStart);
graph.off(constants_1.NodeEvent.DRAG, this.onDrag);
graph.off(constants_1.NodeEvent.DRAG_END, this.onDragEnd);
}
destroyElements() {
var _a, _b;
(_a = this.horizontalLine) === null || _a === void 0 ? void 0 : _a.destroy();
(_b = this.verticalLine) === null || _b === void 0 ? void 0 : _b.destroy();
}
destroy() {
this.destroyElements();
this.unbindEvents();
super.destroy();
}
}
exports.Snapline = Snapline;
Snapline.defaultOptions = {
tolerance: 5,
offset: 20,
autoSnap: true,
shape: 'key',
verticalLineStyle: { stroke: '#1783FF' },
horizontalLineStyle: { stroke: '#1783FF' },
filter: () => true,
};
const distance = (a, b) => Math.abs(a - b);
const getShape = (node, shapeFilter) => {
return typeof shapeFilter === 'function' ? shapeFilter(node) : node.getShape(shapeFilter);
};
//# sourceMappingURL=index.js.map