342 lines
13 KiB
JavaScript
342 lines
13 KiB
JavaScript
import { Point, Line, Rectangle, Polyline, Ellipse, Path, } from '@antv/x6-geometry';
|
|
import { Dom } from '@antv/x6-common';
|
|
import { normalize } from '../registry/marker/util';
|
|
export var Util;
|
|
(function (Util) {
|
|
Util.normalizeMarker = normalize;
|
|
/**
|
|
* Transforms point by an SVG transformation represented by `matrix`.
|
|
*/
|
|
function transformPoint(point, matrix) {
|
|
const ret = Dom.createSVGPoint(point.x, point.y).matrixTransform(matrix);
|
|
return new Point(ret.x, ret.y);
|
|
}
|
|
Util.transformPoint = transformPoint;
|
|
/**
|
|
* Transforms line by an SVG transformation represented by `matrix`.
|
|
*/
|
|
function transformLine(line, matrix) {
|
|
return new Line(transformPoint(line.start, matrix), transformPoint(line.end, matrix));
|
|
}
|
|
Util.transformLine = transformLine;
|
|
/**
|
|
* Transforms polyline by an SVG transformation represented by `matrix`.
|
|
*/
|
|
function transformPolyline(polyline, matrix) {
|
|
let points = polyline instanceof Polyline ? polyline.points : polyline;
|
|
if (!Array.isArray(points)) {
|
|
points = [];
|
|
}
|
|
return new Polyline(points.map((p) => transformPoint(p, matrix)));
|
|
}
|
|
Util.transformPolyline = transformPolyline;
|
|
function transformRectangle(rect, matrix) {
|
|
const svgDocument = Dom.createSvgElement('svg');
|
|
const p = svgDocument.createSVGPoint();
|
|
p.x = rect.x;
|
|
p.y = rect.y;
|
|
const corner1 = p.matrixTransform(matrix);
|
|
p.x = rect.x + rect.width;
|
|
p.y = rect.y;
|
|
const corner2 = p.matrixTransform(matrix);
|
|
p.x = rect.x + rect.width;
|
|
p.y = rect.y + rect.height;
|
|
const corner3 = p.matrixTransform(matrix);
|
|
p.x = rect.x;
|
|
p.y = rect.y + rect.height;
|
|
const corner4 = p.matrixTransform(matrix);
|
|
const minX = Math.min(corner1.x, corner2.x, corner3.x, corner4.x);
|
|
const maxX = Math.max(corner1.x, corner2.x, corner3.x, corner4.x);
|
|
const minY = Math.min(corner1.y, corner2.y, corner3.y, corner4.y);
|
|
const maxY = Math.max(corner1.y, corner2.y, corner3.y, corner4.y);
|
|
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
|
}
|
|
Util.transformRectangle = transformRectangle;
|
|
/**
|
|
* Returns the bounding box of the element after transformations are
|
|
* applied. If `withoutTransformations` is `true`, transformations of
|
|
* the element will not be considered when computing the bounding box.
|
|
* If `target` is specified, bounding box will be computed relatively
|
|
* to the `target` element.
|
|
*/
|
|
function bbox(elem, withoutTransformations, target) {
|
|
let box;
|
|
const ownerSVGElement = elem.ownerSVGElement;
|
|
// If the element is not in the live DOM, it does not have a bounding
|
|
// box defined and so fall back to 'zero' dimension element.
|
|
if (!ownerSVGElement) {
|
|
return new Rectangle(0, 0, 0, 0);
|
|
}
|
|
try {
|
|
box = elem.getBBox();
|
|
}
|
|
catch (e) {
|
|
// Fallback for IE.
|
|
box = {
|
|
x: elem.clientLeft,
|
|
y: elem.clientTop,
|
|
width: elem.clientWidth,
|
|
height: elem.clientHeight,
|
|
};
|
|
}
|
|
if (withoutTransformations) {
|
|
return Rectangle.create(box);
|
|
}
|
|
const matrix = Dom.getTransformToElement(elem, target || ownerSVGElement);
|
|
return transformRectangle(box, matrix);
|
|
}
|
|
Util.bbox = bbox;
|
|
/**
|
|
* Returns the bounding box of the element after transformations are
|
|
* applied. Unlike `bbox()`, this function fixes a browser implementation
|
|
* bug to return the correct bounding box if this elemenent is a group of
|
|
* svg elements (if `options.recursive` is specified).
|
|
*/
|
|
function getBBox(elem, options = {}) {
|
|
let outputBBox;
|
|
const ownerSVGElement = elem.ownerSVGElement;
|
|
// If the element is not in the live DOM, it does not have a bounding box
|
|
// defined and so fall back to 'zero' dimension element.
|
|
// If the element is not an SVGGraphicsElement, we could not measure the
|
|
// bounding box either
|
|
if (!ownerSVGElement || !Dom.isSVGGraphicsElement(elem)) {
|
|
if (Dom.isHTMLElement(elem)) {
|
|
// If the element is a HTMLElement, return the position relative to the body
|
|
const { left, top, width, height } = getBoundingOffsetRect(elem);
|
|
return new Rectangle(left, top, width, height);
|
|
}
|
|
return new Rectangle(0, 0, 0, 0);
|
|
}
|
|
let target = options.target;
|
|
const recursive = options.recursive;
|
|
if (!recursive) {
|
|
try {
|
|
outputBBox = elem.getBBox();
|
|
}
|
|
catch (e) {
|
|
outputBBox = {
|
|
x: elem.clientLeft,
|
|
y: elem.clientTop,
|
|
width: elem.clientWidth,
|
|
height: elem.clientHeight,
|
|
};
|
|
}
|
|
if (!target) {
|
|
return Rectangle.create(outputBBox);
|
|
}
|
|
// transform like target
|
|
const matrix = Dom.getTransformToElement(elem, target);
|
|
return transformRectangle(outputBBox, matrix);
|
|
}
|
|
// recursive
|
|
{
|
|
const children = elem.childNodes;
|
|
const n = children.length;
|
|
if (n === 0) {
|
|
return getBBox(elem, {
|
|
target,
|
|
});
|
|
}
|
|
if (!target) {
|
|
target = elem; // eslint-disable-line
|
|
}
|
|
for (let i = 0; i < n; i += 1) {
|
|
const child = children[i];
|
|
let childBBox;
|
|
if (child.childNodes.length === 0) {
|
|
childBBox = getBBox(child, {
|
|
target,
|
|
});
|
|
}
|
|
else {
|
|
// if child is a group element, enter it with a recursive call
|
|
childBBox = getBBox(child, {
|
|
target,
|
|
recursive: true,
|
|
});
|
|
}
|
|
if (!outputBBox) {
|
|
outputBBox = childBBox;
|
|
}
|
|
else {
|
|
outputBBox = outputBBox.union(childBBox);
|
|
}
|
|
}
|
|
return outputBBox;
|
|
}
|
|
}
|
|
Util.getBBox = getBBox;
|
|
function getBoundingOffsetRect(elem) {
|
|
let left = 0;
|
|
let top = 0;
|
|
let width = 0;
|
|
let height = 0;
|
|
if (elem) {
|
|
let current = elem;
|
|
while (current) {
|
|
left += current.offsetLeft;
|
|
top += current.offsetTop;
|
|
current = current.offsetParent;
|
|
if (current) {
|
|
left += parseInt(Dom.getComputedStyle(current, 'borderLeft'), 10);
|
|
top += parseInt(Dom.getComputedStyle(current, 'borderTop'), 10);
|
|
}
|
|
}
|
|
width = elem.offsetWidth;
|
|
height = elem.offsetHeight;
|
|
}
|
|
return {
|
|
left,
|
|
top,
|
|
width,
|
|
height,
|
|
};
|
|
}
|
|
Util.getBoundingOffsetRect = getBoundingOffsetRect;
|
|
/**
|
|
* Convert the SVGElement to an equivalent geometric shape. The element's
|
|
* transformations are not taken into account.
|
|
*
|
|
* SVGRectElement => Rectangle
|
|
*
|
|
* SVGLineElement => Line
|
|
*
|
|
* SVGCircleElement => Ellipse
|
|
*
|
|
* SVGEllipseElement => Ellipse
|
|
*
|
|
* SVGPolygonElement => Polyline
|
|
*
|
|
* SVGPolylineElement => Polyline
|
|
*
|
|
* SVGPathElement => Path
|
|
*
|
|
* others => Rectangle
|
|
*/
|
|
function toGeometryShape(elem) {
|
|
const attr = (name) => {
|
|
const s = elem.getAttribute(name);
|
|
const v = s ? parseFloat(s) : 0;
|
|
return Number.isNaN(v) ? 0 : v;
|
|
};
|
|
switch (elem instanceof SVGElement && elem.nodeName.toLowerCase()) {
|
|
case 'rect':
|
|
return new Rectangle(attr('x'), attr('y'), attr('width'), attr('height'));
|
|
case 'circle':
|
|
return new Ellipse(attr('cx'), attr('cy'), attr('r'), attr('r'));
|
|
case 'ellipse':
|
|
return new Ellipse(attr('cx'), attr('cy'), attr('rx'), attr('ry'));
|
|
case 'polyline': {
|
|
const points = Dom.getPointsFromSvgElement(elem);
|
|
return new Polyline(points);
|
|
}
|
|
case 'polygon': {
|
|
const points = Dom.getPointsFromSvgElement(elem);
|
|
if (points.length > 1) {
|
|
points.push(points[0]);
|
|
}
|
|
return new Polyline(points);
|
|
}
|
|
case 'path': {
|
|
let d = elem.getAttribute('d');
|
|
if (!Path.isValid(d)) {
|
|
d = Path.normalize(d);
|
|
}
|
|
return Path.parse(d);
|
|
}
|
|
case 'line': {
|
|
return new Line(attr('x1'), attr('y1'), attr('x2'), attr('y2'));
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
// Anything else is a rectangle
|
|
return getBBox(elem);
|
|
}
|
|
Util.toGeometryShape = toGeometryShape;
|
|
function translateAndAutoOrient(elem, position, reference, target) {
|
|
const pos = Point.create(position);
|
|
const ref = Point.create(reference);
|
|
if (!target) {
|
|
const svg = elem instanceof SVGSVGElement ? elem : elem.ownerSVGElement;
|
|
target = svg; // eslint-disable-line
|
|
}
|
|
// Clean-up previously set transformations except the scale.
|
|
// If we didn't clean up the previous transformations then they'd
|
|
// add up with the old ones. Scale is an exception as it doesn't
|
|
// add up, consider: `this.scale(2).scale(2).scale(2)`. The result
|
|
// is that the element is scaled by the factor 2, not 8.
|
|
const s = Dom.scale(elem);
|
|
elem.setAttribute('transform', '');
|
|
const bbox = getBBox(elem, {
|
|
target,
|
|
}).scale(s.sx, s.sy);
|
|
// 1. Translate to origin.
|
|
const translateToOrigin = Dom.createSVGTransform();
|
|
translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2);
|
|
// 2. Rotate around origin.
|
|
const rotateAroundOrigin = Dom.createSVGTransform();
|
|
const angle = pos.angleBetween(ref, pos.clone().translate(1, 0));
|
|
if (angle)
|
|
rotateAroundOrigin.setRotate(angle, 0, 0);
|
|
// 3. Translate to the `position` + the offset (half my width)
|
|
// towards the `reference` point.
|
|
const translateFromOrigin = Dom.createSVGTransform();
|
|
const finalPosition = pos.clone().move(ref, bbox.width / 2);
|
|
translateFromOrigin.setTranslate(2 * pos.x - finalPosition.x, 2 * pos.y - finalPosition.y);
|
|
// 4. Get the current transformation matrix of this node
|
|
const ctm = Dom.getTransformToElement(elem, target);
|
|
// 5. Apply transformations and the scale
|
|
const transform = Dom.createSVGTransform();
|
|
transform.setMatrix(translateFromOrigin.matrix.multiply(rotateAroundOrigin.matrix.multiply(translateToOrigin.matrix.multiply(ctm.scale(s.sx, s.sy)))));
|
|
elem.setAttribute('transform', Dom.matrixToTransformString(transform.matrix));
|
|
}
|
|
Util.translateAndAutoOrient = translateAndAutoOrient;
|
|
function findShapeNode(magnet) {
|
|
if (magnet == null) {
|
|
return null;
|
|
}
|
|
let node = magnet;
|
|
do {
|
|
let tagName = node.tagName;
|
|
if (typeof tagName !== 'string')
|
|
return null;
|
|
tagName = tagName.toUpperCase();
|
|
if (Dom.hasClass(node, 'x6-port')) {
|
|
node = node.nextElementSibling;
|
|
}
|
|
else if (tagName === 'G') {
|
|
node = node.firstElementChild;
|
|
}
|
|
else if (tagName === 'TITLE') {
|
|
node = node.nextElementSibling;
|
|
}
|
|
else
|
|
break;
|
|
} while (node);
|
|
return node;
|
|
}
|
|
Util.findShapeNode = findShapeNode;
|
|
// BBox is calculated by the attribute and shape of the node.
|
|
// Because of the reduction in DOM API calls, there is a significant performance improvement.
|
|
function getBBoxV2(elem) {
|
|
const node = findShapeNode(elem);
|
|
if (!Dom.isSVGGraphicsElement(node)) {
|
|
if (Dom.isHTMLElement(elem)) {
|
|
const { left, top, width, height } = getBoundingOffsetRect(elem);
|
|
return new Rectangle(left, top, width, height);
|
|
}
|
|
return new Rectangle(0, 0, 0, 0);
|
|
}
|
|
const shape = toGeometryShape(node);
|
|
const bbox = shape.bbox() || Rectangle.create();
|
|
// const transform = node.getAttribute('transform')
|
|
// if (transform) {
|
|
// const nodeMatrix = Dom.transformStringToMatrix(transform)
|
|
// return transformRectangle(bbox, nodeMatrix)
|
|
// }
|
|
return bbox;
|
|
}
|
|
Util.getBBoxV2 = getBBoxV2;
|
|
})(Util || (Util = {}));
|
|
//# sourceMappingURL=index.js.map
|