index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import { Point, Line, Rectangle, Polyline, Ellipse, Path, } from '@antv/x6-geometry';
  2. import { Dom } from '@antv/x6-common';
  3. import { normalize } from '../registry/marker/util';
  4. export var Util;
  5. (function (Util) {
  6. Util.normalizeMarker = normalize;
  7. /**
  8. * Transforms point by an SVG transformation represented by `matrix`.
  9. */
  10. function transformPoint(point, matrix) {
  11. const ret = Dom.createSVGPoint(point.x, point.y).matrixTransform(matrix);
  12. return new Point(ret.x, ret.y);
  13. }
  14. Util.transformPoint = transformPoint;
  15. /**
  16. * Transforms line by an SVG transformation represented by `matrix`.
  17. */
  18. function transformLine(line, matrix) {
  19. return new Line(transformPoint(line.start, matrix), transformPoint(line.end, matrix));
  20. }
  21. Util.transformLine = transformLine;
  22. /**
  23. * Transforms polyline by an SVG transformation represented by `matrix`.
  24. */
  25. function transformPolyline(polyline, matrix) {
  26. let points = polyline instanceof Polyline ? polyline.points : polyline;
  27. if (!Array.isArray(points)) {
  28. points = [];
  29. }
  30. return new Polyline(points.map((p) => transformPoint(p, matrix)));
  31. }
  32. Util.transformPolyline = transformPolyline;
  33. function transformRectangle(rect, matrix) {
  34. const svgDocument = Dom.createSvgElement('svg');
  35. const p = svgDocument.createSVGPoint();
  36. p.x = rect.x;
  37. p.y = rect.y;
  38. const corner1 = p.matrixTransform(matrix);
  39. p.x = rect.x + rect.width;
  40. p.y = rect.y;
  41. const corner2 = p.matrixTransform(matrix);
  42. p.x = rect.x + rect.width;
  43. p.y = rect.y + rect.height;
  44. const corner3 = p.matrixTransform(matrix);
  45. p.x = rect.x;
  46. p.y = rect.y + rect.height;
  47. const corner4 = p.matrixTransform(matrix);
  48. const minX = Math.min(corner1.x, corner2.x, corner3.x, corner4.x);
  49. const maxX = Math.max(corner1.x, corner2.x, corner3.x, corner4.x);
  50. const minY = Math.min(corner1.y, corner2.y, corner3.y, corner4.y);
  51. const maxY = Math.max(corner1.y, corner2.y, corner3.y, corner4.y);
  52. return new Rectangle(minX, minY, maxX - minX, maxY - minY);
  53. }
  54. Util.transformRectangle = transformRectangle;
  55. /**
  56. * Returns the bounding box of the element after transformations are
  57. * applied. If `withoutTransformations` is `true`, transformations of
  58. * the element will not be considered when computing the bounding box.
  59. * If `target` is specified, bounding box will be computed relatively
  60. * to the `target` element.
  61. */
  62. function bbox(elem, withoutTransformations, target) {
  63. let box;
  64. const ownerSVGElement = elem.ownerSVGElement;
  65. // If the element is not in the live DOM, it does not have a bounding
  66. // box defined and so fall back to 'zero' dimension element.
  67. if (!ownerSVGElement) {
  68. return new Rectangle(0, 0, 0, 0);
  69. }
  70. try {
  71. box = elem.getBBox();
  72. }
  73. catch (e) {
  74. // Fallback for IE.
  75. box = {
  76. x: elem.clientLeft,
  77. y: elem.clientTop,
  78. width: elem.clientWidth,
  79. height: elem.clientHeight,
  80. };
  81. }
  82. if (withoutTransformations) {
  83. return Rectangle.create(box);
  84. }
  85. const matrix = Dom.getTransformToElement(elem, target || ownerSVGElement);
  86. return transformRectangle(box, matrix);
  87. }
  88. Util.bbox = bbox;
  89. /**
  90. * Returns the bounding box of the element after transformations are
  91. * applied. Unlike `bbox()`, this function fixes a browser implementation
  92. * bug to return the correct bounding box if this elemenent is a group of
  93. * svg elements (if `options.recursive` is specified).
  94. */
  95. function getBBox(elem, options = {}) {
  96. let outputBBox;
  97. const ownerSVGElement = elem.ownerSVGElement;
  98. // If the element is not in the live DOM, it does not have a bounding box
  99. // defined and so fall back to 'zero' dimension element.
  100. // If the element is not an SVGGraphicsElement, we could not measure the
  101. // bounding box either
  102. if (!ownerSVGElement || !Dom.isSVGGraphicsElement(elem)) {
  103. if (Dom.isHTMLElement(elem)) {
  104. // If the element is a HTMLElement, return the position relative to the body
  105. const { left, top, width, height } = getBoundingOffsetRect(elem);
  106. return new Rectangle(left, top, width, height);
  107. }
  108. return new Rectangle(0, 0, 0, 0);
  109. }
  110. let target = options.target;
  111. const recursive = options.recursive;
  112. if (!recursive) {
  113. try {
  114. outputBBox = elem.getBBox();
  115. }
  116. catch (e) {
  117. outputBBox = {
  118. x: elem.clientLeft,
  119. y: elem.clientTop,
  120. width: elem.clientWidth,
  121. height: elem.clientHeight,
  122. };
  123. }
  124. if (!target) {
  125. return Rectangle.create(outputBBox);
  126. }
  127. // transform like target
  128. const matrix = Dom.getTransformToElement(elem, target);
  129. return transformRectangle(outputBBox, matrix);
  130. }
  131. // recursive
  132. {
  133. const children = elem.childNodes;
  134. const n = children.length;
  135. if (n === 0) {
  136. return getBBox(elem, {
  137. target,
  138. });
  139. }
  140. if (!target) {
  141. target = elem; // eslint-disable-line
  142. }
  143. for (let i = 0; i < n; i += 1) {
  144. const child = children[i];
  145. let childBBox;
  146. if (child.childNodes.length === 0) {
  147. childBBox = getBBox(child, {
  148. target,
  149. });
  150. }
  151. else {
  152. // if child is a group element, enter it with a recursive call
  153. childBBox = getBBox(child, {
  154. target,
  155. recursive: true,
  156. });
  157. }
  158. if (!outputBBox) {
  159. outputBBox = childBBox;
  160. }
  161. else {
  162. outputBBox = outputBBox.union(childBBox);
  163. }
  164. }
  165. return outputBBox;
  166. }
  167. }
  168. Util.getBBox = getBBox;
  169. function getBoundingOffsetRect(elem) {
  170. let left = 0;
  171. let top = 0;
  172. let width = 0;
  173. let height = 0;
  174. if (elem) {
  175. let current = elem;
  176. while (current) {
  177. left += current.offsetLeft;
  178. top += current.offsetTop;
  179. current = current.offsetParent;
  180. if (current) {
  181. left += parseInt(Dom.getComputedStyle(current, 'borderLeft'), 10);
  182. top += parseInt(Dom.getComputedStyle(current, 'borderTop'), 10);
  183. }
  184. }
  185. width = elem.offsetWidth;
  186. height = elem.offsetHeight;
  187. }
  188. return {
  189. left,
  190. top,
  191. width,
  192. height,
  193. };
  194. }
  195. Util.getBoundingOffsetRect = getBoundingOffsetRect;
  196. /**
  197. * Convert the SVGElement to an equivalent geometric shape. The element's
  198. * transformations are not taken into account.
  199. *
  200. * SVGRectElement => Rectangle
  201. *
  202. * SVGLineElement => Line
  203. *
  204. * SVGCircleElement => Ellipse
  205. *
  206. * SVGEllipseElement => Ellipse
  207. *
  208. * SVGPolygonElement => Polyline
  209. *
  210. * SVGPolylineElement => Polyline
  211. *
  212. * SVGPathElement => Path
  213. *
  214. * others => Rectangle
  215. */
  216. function toGeometryShape(elem) {
  217. const attr = (name) => {
  218. const s = elem.getAttribute(name);
  219. const v = s ? parseFloat(s) : 0;
  220. return Number.isNaN(v) ? 0 : v;
  221. };
  222. switch (elem instanceof SVGElement && elem.nodeName.toLowerCase()) {
  223. case 'rect':
  224. return new Rectangle(attr('x'), attr('y'), attr('width'), attr('height'));
  225. case 'circle':
  226. return new Ellipse(attr('cx'), attr('cy'), attr('r'), attr('r'));
  227. case 'ellipse':
  228. return new Ellipse(attr('cx'), attr('cy'), attr('rx'), attr('ry'));
  229. case 'polyline': {
  230. const points = Dom.getPointsFromSvgElement(elem);
  231. return new Polyline(points);
  232. }
  233. case 'polygon': {
  234. const points = Dom.getPointsFromSvgElement(elem);
  235. if (points.length > 1) {
  236. points.push(points[0]);
  237. }
  238. return new Polyline(points);
  239. }
  240. case 'path': {
  241. let d = elem.getAttribute('d');
  242. if (!Path.isValid(d)) {
  243. d = Path.normalize(d);
  244. }
  245. return Path.parse(d);
  246. }
  247. case 'line': {
  248. return new Line(attr('x1'), attr('y1'), attr('x2'), attr('y2'));
  249. }
  250. default:
  251. break;
  252. }
  253. // Anything else is a rectangle
  254. return getBBox(elem);
  255. }
  256. Util.toGeometryShape = toGeometryShape;
  257. function translateAndAutoOrient(elem, position, reference, target) {
  258. const pos = Point.create(position);
  259. const ref = Point.create(reference);
  260. if (!target) {
  261. const svg = elem instanceof SVGSVGElement ? elem : elem.ownerSVGElement;
  262. target = svg; // eslint-disable-line
  263. }
  264. // Clean-up previously set transformations except the scale.
  265. // If we didn't clean up the previous transformations then they'd
  266. // add up with the old ones. Scale is an exception as it doesn't
  267. // add up, consider: `this.scale(2).scale(2).scale(2)`. The result
  268. // is that the element is scaled by the factor 2, not 8.
  269. const s = Dom.scale(elem);
  270. elem.setAttribute('transform', '');
  271. const bbox = getBBox(elem, {
  272. target,
  273. }).scale(s.sx, s.sy);
  274. // 1. Translate to origin.
  275. const translateToOrigin = Dom.createSVGTransform();
  276. translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2);
  277. // 2. Rotate around origin.
  278. const rotateAroundOrigin = Dom.createSVGTransform();
  279. const angle = pos.angleBetween(ref, pos.clone().translate(1, 0));
  280. if (angle)
  281. rotateAroundOrigin.setRotate(angle, 0, 0);
  282. // 3. Translate to the `position` + the offset (half my width)
  283. // towards the `reference` point.
  284. const translateFromOrigin = Dom.createSVGTransform();
  285. const finalPosition = pos.clone().move(ref, bbox.width / 2);
  286. translateFromOrigin.setTranslate(2 * pos.x - finalPosition.x, 2 * pos.y - finalPosition.y);
  287. // 4. Get the current transformation matrix of this node
  288. const ctm = Dom.getTransformToElement(elem, target);
  289. // 5. Apply transformations and the scale
  290. const transform = Dom.createSVGTransform();
  291. transform.setMatrix(translateFromOrigin.matrix.multiply(rotateAroundOrigin.matrix.multiply(translateToOrigin.matrix.multiply(ctm.scale(s.sx, s.sy)))));
  292. elem.setAttribute('transform', Dom.matrixToTransformString(transform.matrix));
  293. }
  294. Util.translateAndAutoOrient = translateAndAutoOrient;
  295. function findShapeNode(magnet) {
  296. if (magnet == null) {
  297. return null;
  298. }
  299. let node = magnet;
  300. do {
  301. let tagName = node.tagName;
  302. if (typeof tagName !== 'string')
  303. return null;
  304. tagName = tagName.toUpperCase();
  305. if (Dom.hasClass(node, 'x6-port')) {
  306. node = node.nextElementSibling;
  307. }
  308. else if (tagName === 'G') {
  309. node = node.firstElementChild;
  310. }
  311. else if (tagName === 'TITLE') {
  312. node = node.nextElementSibling;
  313. }
  314. else
  315. break;
  316. } while (node);
  317. return node;
  318. }
  319. Util.findShapeNode = findShapeNode;
  320. // BBox is calculated by the attribute and shape of the node.
  321. // Because of the reduction in DOM API calls, there is a significant performance improvement.
  322. function getBBoxV2(elem) {
  323. const node = findShapeNode(elem);
  324. if (!Dom.isSVGGraphicsElement(node)) {
  325. if (Dom.isHTMLElement(elem)) {
  326. const { left, top, width, height } = getBoundingOffsetRect(elem);
  327. return new Rectangle(left, top, width, height);
  328. }
  329. return new Rectangle(0, 0, 0, 0);
  330. }
  331. const shape = toGeometryShape(node);
  332. const bbox = shape.bbox() || Rectangle.create();
  333. // const transform = node.getAttribute('transform')
  334. // if (transform) {
  335. // const nodeMatrix = Dom.transformStringToMatrix(transform)
  336. // return transformRectangle(bbox, nodeMatrix)
  337. // }
  338. return bbox;
  339. }
  340. Util.getBBoxV2 = getBBoxV2;
  341. })(Util || (Util = {}));
  342. //# sourceMappingURL=index.js.map