@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering
347 lines • 12.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeMarker = void 0;
exports.transformPoint = transformPoint;
exports.transformLine = transformLine;
exports.transformPolyline = transformPolyline;
exports.transformRectangle = transformRectangle;
exports.bbox = bbox;
exports.getBBox = getBBox;
exports.getBoundingOffsetRect = getBoundingOffsetRect;
exports.toGeometryShape = toGeometryShape;
exports.translateAndAutoOrient = translateAndAutoOrient;
exports.findShapeNode = findShapeNode;
exports.getBBoxV2 = getBBoxV2;
const geometry_1 = require("../../geometry");
const path_1 = require("../../geometry/path");
const util_1 = require("../../geometry/path/util");
const util_2 = require("../../registry/marker/util");
const dom_1 = require("../dom");
exports.normalizeMarker = util_2.normalize;
/**
* Transforms point by an SVG transformation represented by `matrix`.
*/
function transformPoint(point, matrix) {
const ret = dom_1.Dom.createSVGPoint(point.x, point.y).matrixTransform(matrix);
return new geometry_1.Point(ret.x, ret.y);
}
/**
* Transforms line by an SVG transformation represented by `matrix`.
*/
function transformLine(line, matrix) {
return new geometry_1.Line(transformPoint(line.start, matrix), transformPoint(line.end, matrix));
}
/**
* Transforms polyline by an SVG transformation represented by `matrix`.
*/
function transformPolyline(polyline, matrix) {
let points = polyline instanceof geometry_1.Polyline ? polyline.points : polyline;
if (!Array.isArray(points)) {
points = [];
}
return new geometry_1.Polyline(points.map((p) => transformPoint(p, matrix)));
}
function transformRectangle(rect, matrix) {
const svgDocument = dom_1.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 geometry_1.Rectangle(minX, minY, maxX - minX, maxY - minY);
}
/**
* 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 = null;
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 geometry_1.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 geometry_1.Rectangle.create(box);
}
const matrix = dom_1.Dom.getTransformToElement(elem, target || ownerSVGElement);
return transformRectangle(box, matrix);
}
/**
* 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 box = null;
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_1.Dom.isSVGGraphicsElement(elem)) {
if (dom_1.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 geometry_1.Rectangle(left, top, width, height);
}
return new geometry_1.Rectangle(0, 0, 0, 0);
}
let target = options.target;
const recursive = options.recursive;
if (!recursive) {
try {
box = elem.getBBox();
}
catch (_e) {
box = {
x: elem.clientLeft,
y: elem.clientTop,
width: elem.clientWidth,
height: elem.clientHeight,
};
}
if (!target) {
return geometry_1.Rectangle.create(box);
}
// transform like target
const matrix = dom_1.Dom.getTransformToElement(elem, target);
return transformRectangle(box, matrix);
}
// recursive
{
const children = elem.childNodes;
const n = children.length;
if (n === 0) {
return getBBox(elem, {
target,
});
}
if (!target) {
target = elem; // eslint-disable-line
}
let aggregate = null;
for (let i = 0; i < n; i += 1) {
const child = children[i];
let childBBox = null;
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 (!aggregate) {
aggregate = geometry_1.Rectangle.create(childBBox);
}
else {
aggregate = aggregate.union(childBBox);
}
}
return aggregate || new geometry_1.Rectangle(0, 0, 0, 0);
}
}
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_1.Dom.getComputedStyle(current, 'borderLeft'), 10);
top += parseInt(dom_1.Dom.getComputedStyle(current, 'borderTop'), 10);
}
}
width = elem.offsetWidth;
height = elem.offsetHeight;
}
return {
left,
top,
width,
height,
};
}
/**
* 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 geometry_1.Rectangle(attr('x'), attr('y'), attr('width'), attr('height'));
case 'circle':
return new geometry_1.Ellipse(attr('cx'), attr('cy'), attr('r'), attr('r'));
case 'ellipse':
return new geometry_1.Ellipse(attr('cx'), attr('cy'), attr('rx'), attr('ry'));
case 'polyline': {
const points = dom_1.Dom.getPointsFromSvgElement(elem);
return new geometry_1.Polyline(points);
}
case 'polygon': {
const points = dom_1.Dom.getPointsFromSvgElement(elem);
if (points.length > 1) {
points.push(points[0]);
}
return new geometry_1.Polyline(points);
}
case 'path': {
let d = elem.getAttribute('d');
if (!(0, util_1.isValid)(d)) {
d = (0, path_1.normalizePathData)(d);
}
return geometry_1.Path.parse(d);
}
case 'line': {
return new geometry_1.Line(attr('x1'), attr('y1'), attr('x2'), attr('y2'));
}
default:
break;
}
// Anything else is a rectangle
return getBBox(elem);
}
function translateAndAutoOrient(elem, position, reference, target) {
const pos = geometry_1.Point.create(position);
const ref = geometry_1.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_1.Dom.scale(elem);
elem.setAttribute('transform', '');
const bbox = getBBox(elem, {
target,
}).scale(s.sx, s.sy);
// 1. Translate to origin.
const translateToOrigin = dom_1.Dom.createSVGTransform();
translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2);
// 2. Rotate around origin.
const rotateAroundOrigin = dom_1.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_1.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_1.Dom.getTransformToElement(elem, target);
// 5. Apply transformations and the scale
const transform = dom_1.Dom.createSVGTransform();
transform.setMatrix(translateFromOrigin.matrix.multiply(rotateAroundOrigin.matrix.multiply(translateToOrigin.matrix.multiply(ctm.scale(s.sx, s.sy)))));
elem.setAttribute('transform', dom_1.Dom.matrixToTransformString(transform.matrix));
}
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_1.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;
}
// 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_1.Dom.isSVGGraphicsElement(node)) {
if (dom_1.Dom.isHTMLElement(elem)) {
const { left, top, width, height } = getBoundingOffsetRect(elem);
return new geometry_1.Rectangle(left, top, width, height);
}
return new geometry_1.Rectangle(0, 0, 0, 0);
}
const shape = toGeometryShape(node);
const bbox = shape.bbox() || geometry_1.Rectangle.create();
// const transform = node.getAttribute('transform')
// if (transform) {
// const nodeMatrix = Dom.transformStringToMatrix(transform)
// return transformRectangle(bbox, nodeMatrix)
// }
return bbox;
}
//# sourceMappingURL=index.js.map