@joint/core 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +376 -0
  2. package/README.md +49 -0
  3. package/dist/geometry.js +6486 -0
  4. package/dist/geometry.min.js +8 -0
  5. package/dist/joint.d.ts +5536 -0
  6. package/dist/joint.js +39629 -0
  7. package/dist/joint.min.js +8 -0
  8. package/dist/joint.nowrap.js +39626 -0
  9. package/dist/joint.nowrap.min.js +8 -0
  10. package/dist/vectorizer.js +9135 -0
  11. package/dist/vectorizer.min.js +8 -0
  12. package/dist/version.mjs +3 -0
  13. package/index.js +3 -0
  14. package/joint.mjs +27 -0
  15. package/package.json +192 -0
  16. package/src/V/annotation.mjs +0 -0
  17. package/src/V/index.mjs +2642 -0
  18. package/src/anchors/index.mjs +123 -0
  19. package/src/config/index.mjs +12 -0
  20. package/src/connectionPoints/index.mjs +202 -0
  21. package/src/connectionStrategies/index.mjs +73 -0
  22. package/src/connectors/curve.mjs +553 -0
  23. package/src/connectors/index.mjs +6 -0
  24. package/src/connectors/jumpover.mjs +452 -0
  25. package/src/connectors/normal.mjs +12 -0
  26. package/src/connectors/rounded.mjs +17 -0
  27. package/src/connectors/smooth.mjs +44 -0
  28. package/src/connectors/straight.mjs +110 -0
  29. package/src/dia/Cell.mjs +945 -0
  30. package/src/dia/CellView.mjs +1316 -0
  31. package/src/dia/Element.mjs +519 -0
  32. package/src/dia/ElementView.mjs +859 -0
  33. package/src/dia/Graph.mjs +1112 -0
  34. package/src/dia/HighlighterView.mjs +319 -0
  35. package/src/dia/Link.mjs +565 -0
  36. package/src/dia/LinkView.mjs +2207 -0
  37. package/src/dia/Paper.mjs +3171 -0
  38. package/src/dia/PaperLayer.mjs +75 -0
  39. package/src/dia/ToolView.mjs +69 -0
  40. package/src/dia/ToolsView.mjs +128 -0
  41. package/src/dia/attributes/calc.mjs +128 -0
  42. package/src/dia/attributes/connection.mjs +75 -0
  43. package/src/dia/attributes/defs.mjs +76 -0
  44. package/src/dia/attributes/eval.mjs +64 -0
  45. package/src/dia/attributes/index.mjs +69 -0
  46. package/src/dia/attributes/legacy.mjs +148 -0
  47. package/src/dia/attributes/offset.mjs +53 -0
  48. package/src/dia/attributes/props.mjs +30 -0
  49. package/src/dia/attributes/shape.mjs +92 -0
  50. package/src/dia/attributes/text.mjs +180 -0
  51. package/src/dia/index.mjs +13 -0
  52. package/src/dia/layers/GridLayer.mjs +176 -0
  53. package/src/dia/ports.mjs +874 -0
  54. package/src/elementTools/Control.mjs +153 -0
  55. package/src/elementTools/HoverConnect.mjs +37 -0
  56. package/src/elementTools/index.mjs +5 -0
  57. package/src/env/index.mjs +43 -0
  58. package/src/g/bezier.mjs +175 -0
  59. package/src/g/curve.mjs +956 -0
  60. package/src/g/ellipse.mjs +245 -0
  61. package/src/g/extend.mjs +64 -0
  62. package/src/g/geometry.helpers.mjs +58 -0
  63. package/src/g/index.mjs +17 -0
  64. package/src/g/intersection.mjs +511 -0
  65. package/src/g/line.bearing.mjs +30 -0
  66. package/src/g/line.length.mjs +5 -0
  67. package/src/g/line.mjs +356 -0
  68. package/src/g/line.squaredLength.mjs +10 -0
  69. package/src/g/path.mjs +2260 -0
  70. package/src/g/point.mjs +375 -0
  71. package/src/g/points.mjs +247 -0
  72. package/src/g/polygon.mjs +51 -0
  73. package/src/g/polyline.mjs +523 -0
  74. package/src/g/rect.mjs +556 -0
  75. package/src/g/types.mjs +10 -0
  76. package/src/highlighters/addClass.mjs +27 -0
  77. package/src/highlighters/index.mjs +5 -0
  78. package/src/highlighters/list.mjs +111 -0
  79. package/src/highlighters/mask.mjs +220 -0
  80. package/src/highlighters/opacity.mjs +17 -0
  81. package/src/highlighters/stroke.mjs +100 -0
  82. package/src/layout/index.mjs +4 -0
  83. package/src/layout/ports/port.mjs +188 -0
  84. package/src/layout/ports/portLabel.mjs +224 -0
  85. package/src/linkAnchors/index.mjs +76 -0
  86. package/src/linkTools/Anchor.mjs +235 -0
  87. package/src/linkTools/Arrowhead.mjs +103 -0
  88. package/src/linkTools/Boundary.mjs +48 -0
  89. package/src/linkTools/Button.mjs +121 -0
  90. package/src/linkTools/Connect.mjs +85 -0
  91. package/src/linkTools/HoverConnect.mjs +161 -0
  92. package/src/linkTools/Segments.mjs +393 -0
  93. package/src/linkTools/Vertices.mjs +253 -0
  94. package/src/linkTools/helpers.mjs +33 -0
  95. package/src/linkTools/index.mjs +8 -0
  96. package/src/mvc/Collection.mjs +560 -0
  97. package/src/mvc/Data.mjs +46 -0
  98. package/src/mvc/Dom/Dom.mjs +587 -0
  99. package/src/mvc/Dom/Event.mjs +130 -0
  100. package/src/mvc/Dom/animations.mjs +122 -0
  101. package/src/mvc/Dom/events.mjs +69 -0
  102. package/src/mvc/Dom/index.mjs +13 -0
  103. package/src/mvc/Dom/methods.mjs +392 -0
  104. package/src/mvc/Dom/props.mjs +77 -0
  105. package/src/mvc/Dom/vars.mjs +5 -0
  106. package/src/mvc/Events.mjs +337 -0
  107. package/src/mvc/Listener.mjs +33 -0
  108. package/src/mvc/Model.mjs +239 -0
  109. package/src/mvc/View.mjs +323 -0
  110. package/src/mvc/ViewBase.mjs +182 -0
  111. package/src/mvc/index.mjs +9 -0
  112. package/src/mvc/mvcUtils.mjs +90 -0
  113. package/src/polyfills/array.js +4 -0
  114. package/src/polyfills/base64.js +68 -0
  115. package/src/polyfills/index.mjs +5 -0
  116. package/src/polyfills/number.js +3 -0
  117. package/src/polyfills/string.js +3 -0
  118. package/src/polyfills/typedArray.js +47 -0
  119. package/src/routers/index.mjs +6 -0
  120. package/src/routers/manhattan.mjs +856 -0
  121. package/src/routers/metro.mjs +91 -0
  122. package/src/routers/normal.mjs +6 -0
  123. package/src/routers/oneSide.mjs +60 -0
  124. package/src/routers/orthogonal.mjs +323 -0
  125. package/src/routers/rightAngle.mjs +1056 -0
  126. package/src/shapes/index.mjs +3 -0
  127. package/src/shapes/standard.mjs +755 -0
  128. package/src/util/cloneCells.mjs +67 -0
  129. package/src/util/getRectPoint.mjs +65 -0
  130. package/src/util/index.mjs +5 -0
  131. package/src/util/svgTagTemplate.mjs +110 -0
  132. package/src/util/util.mjs +1754 -0
  133. package/src/util/utilHelpers.mjs +2402 -0
  134. package/src/util/wrappers.mjs +56 -0
  135. package/types/geometry.d.ts +815 -0
  136. package/types/index.d.ts +53 -0
  137. package/types/joint.d.ts +4391 -0
  138. package/types/joint.head.d.ts +12 -0
  139. package/types/vectorizer.d.ts +327 -0
@@ -0,0 +1,519 @@
1
+ import { Cell } from './Cell.mjs';
2
+ import { Point, toRad, normalizeAngle, Rect } from '../g/index.mjs';
3
+ import { isNumber, isObject, interpolate, assign, invoke, normalizeSides } from '../util/index.mjs';
4
+ import { elementPortPrototype } from './ports.mjs';
5
+
6
+ // Element base model.
7
+ // -----------------------------
8
+
9
+ export const Element = Cell.extend({
10
+
11
+ defaults: {
12
+ position: { x: 0, y: 0 },
13
+ size: { width: 1, height: 1 },
14
+ angle: 0
15
+ },
16
+
17
+ initialize: function() {
18
+
19
+ this._initializePorts();
20
+ Cell.prototype.initialize.apply(this, arguments);
21
+ },
22
+
23
+ /**
24
+ * @abstract
25
+ */
26
+ _initializePorts: function() {
27
+ // implemented in ports.js
28
+ },
29
+
30
+ _refreshPorts: function() {
31
+ // implemented in ports.js
32
+ },
33
+
34
+ isElement: function() {
35
+
36
+ return true;
37
+ },
38
+
39
+ position: function(x, y, opt) {
40
+
41
+ const isSetter = isNumber(y);
42
+ opt = (isSetter ? opt : x) || {};
43
+ const { parentRelative, deep, restrictedArea } = opt;
44
+
45
+
46
+ // option `parentRelative` for setting the position relative to the element's parent.
47
+ let parentPosition;
48
+ if (parentRelative) {
49
+
50
+ // Getting the parent's position requires the collection.
51
+ // Cell.parent() holds cell id only.
52
+ if (!this.graph) throw new Error('Element must be part of a graph.');
53
+
54
+ const parent = this.getParentCell();
55
+ if (parent && !parent.isLink()) {
56
+ parentPosition = parent.get('position');
57
+ }
58
+ }
59
+
60
+ if (isSetter) {
61
+
62
+ if (parentPosition) {
63
+ x += parentPosition.x;
64
+ y += parentPosition.y;
65
+ }
66
+
67
+ if (deep || restrictedArea) {
68
+ const { x: x0, y: y0 } = this.get('position');
69
+ this.translate(x - x0, y - y0, opt);
70
+ } else {
71
+ this.set('position', { x, y }, opt);
72
+ }
73
+
74
+ return this;
75
+
76
+ } else { // Getter returns a geometry point.
77
+
78
+ const elementPosition = Point(this.get('position'));
79
+ return parentRelative
80
+ ? elementPosition.difference(parentPosition)
81
+ : elementPosition;
82
+ }
83
+ },
84
+
85
+ translate: function(tx, ty, opt) {
86
+
87
+ tx = tx || 0;
88
+ ty = ty || 0;
89
+
90
+ if (tx === 0 && ty === 0) {
91
+ // Like nothing has happened.
92
+ return this;
93
+ }
94
+
95
+ opt = opt || {};
96
+ // Pass the initiator of the translation.
97
+ opt.translateBy = opt.translateBy || this.id;
98
+
99
+ var position = this.get('position') || { x: 0, y: 0 };
100
+ var ra = opt.restrictedArea;
101
+ if (ra && opt.translateBy === this.id) {
102
+
103
+ if (typeof ra === 'function') {
104
+
105
+ var newPosition = ra.call(this, position.x + tx, position.y + ty, opt);
106
+
107
+ tx = newPosition.x - position.x;
108
+ ty = newPosition.y - position.y;
109
+
110
+ } else {
111
+ // We are restricting the translation for the element itself only. We get
112
+ // the bounding box of the element including all its embeds.
113
+ // All embeds have to be translated the exact same way as the element.
114
+ var bbox = this.getBBox({ deep: true });
115
+ //- - - - - - - - - - - - -> ra.x + ra.width
116
+ // - - - -> position.x |
117
+ // -> bbox.x
118
+ // ▓▓▓▓▓▓▓ |
119
+ // ░░░░░░░▓▓▓▓▓▓▓
120
+ // ░░░░░░░░░ |
121
+ // ▓▓▓▓▓▓▓▓░░░░░░░
122
+ // ▓▓▓▓▓▓▓▓ |
123
+ // <-dx-> | restricted area right border
124
+ // <-width-> | ░ translated element
125
+ // <- - bbox.width - -> ▓ embedded element
126
+ var dx = position.x - bbox.x;
127
+ var dy = position.y - bbox.y;
128
+ // Find the maximal/minimal coordinates that the element can be translated
129
+ // while complies the restrictions.
130
+ var x = Math.max(ra.x + dx, Math.min(ra.x + ra.width + dx - bbox.width, position.x + tx));
131
+ var y = Math.max(ra.y + dy, Math.min(ra.y + ra.height + dy - bbox.height, position.y + ty));
132
+ // recalculate the translation taking the restrictions into account.
133
+ tx = x - position.x;
134
+ ty = y - position.y;
135
+ }
136
+ }
137
+
138
+ var translatedPosition = {
139
+ x: position.x + tx,
140
+ y: position.y + ty
141
+ };
142
+
143
+ // To find out by how much an element was translated in event 'change:position' handlers.
144
+ opt.tx = tx;
145
+ opt.ty = ty;
146
+
147
+ if (opt.transition) {
148
+
149
+ if (!isObject(opt.transition)) opt.transition = {};
150
+
151
+ this.transition('position', translatedPosition, assign({}, opt.transition, {
152
+ valueFunction: interpolate.object
153
+ }));
154
+
155
+ // Recursively call `translate()` on all the embeds cells.
156
+ invoke(this.getEmbeddedCells(), 'translate', tx, ty, opt);
157
+
158
+ } else {
159
+
160
+ this.startBatch('translate', opt);
161
+ this.set('position', translatedPosition, opt);
162
+ invoke(this.getEmbeddedCells(), 'translate', tx, ty, opt);
163
+ this.stopBatch('translate', opt);
164
+ }
165
+
166
+ return this;
167
+ },
168
+
169
+ size: function(width, height, opt) {
170
+
171
+ var currentSize = this.get('size');
172
+ // Getter
173
+ // () signature
174
+ if (width === undefined) {
175
+ return {
176
+ width: currentSize.width,
177
+ height: currentSize.height
178
+ };
179
+ }
180
+ // Setter
181
+ // (size, opt) signature
182
+ if (isObject(width)) {
183
+ opt = height;
184
+ height = isNumber(width.height) ? width.height : currentSize.height;
185
+ width = isNumber(width.width) ? width.width : currentSize.width;
186
+ }
187
+
188
+ return this.resize(width, height, opt);
189
+ },
190
+
191
+ resize: function(width, height, opt) {
192
+
193
+ opt = opt || {};
194
+
195
+ this.startBatch('resize', opt);
196
+
197
+ if (opt.direction) {
198
+
199
+ var currentSize = this.get('size');
200
+
201
+ switch (opt.direction) {
202
+
203
+ case 'left':
204
+ case 'right':
205
+ // Don't change height when resizing horizontally.
206
+ height = currentSize.height;
207
+ break;
208
+
209
+ case 'top':
210
+ case 'bottom':
211
+ // Don't change width when resizing vertically.
212
+ width = currentSize.width;
213
+ break;
214
+ }
215
+
216
+ // Get the angle and clamp its value between 0 and 360 degrees.
217
+ var angle = normalizeAngle(this.get('angle') || 0);
218
+
219
+ // This is a rectangle in size of the un-rotated element.
220
+ var bbox = this.getBBox();
221
+
222
+ var origin;
223
+
224
+ if (angle) {
225
+
226
+ var quadrant = {
227
+ 'top-right': 0,
228
+ 'right': 0,
229
+ 'top-left': 1,
230
+ 'top': 1,
231
+ 'bottom-left': 2,
232
+ 'left': 2,
233
+ 'bottom-right': 3,
234
+ 'bottom': 3
235
+ }[opt.direction];
236
+
237
+ if (opt.absolute) {
238
+
239
+ // We are taking the element's rotation into account
240
+ quadrant += Math.floor((angle + 45) / 90);
241
+ quadrant %= 4;
242
+ }
243
+
244
+ // Pick the corner point on the element, which meant to stay on its place before and
245
+ // after the rotation.
246
+ var fixedPoint = bbox[['bottomLeft', 'corner', 'topRight', 'origin'][quadrant]]();
247
+
248
+ // Find an image of the previous indent point. This is the position, where is the
249
+ // point actually located on the screen.
250
+ var imageFixedPoint = Point(fixedPoint).rotate(bbox.center(), -angle);
251
+
252
+ // Every point on the element rotates around a circle with the centre of rotation
253
+ // in the middle of the element while the whole element is being rotated. That means
254
+ // that the distance from a point in the corner of the element (supposed its always rect) to
255
+ // the center of the element doesn't change during the rotation and therefore it equals
256
+ // to a distance on un-rotated element.
257
+ // We can find the distance as DISTANCE = (ELEMENTWIDTH/2)^2 + (ELEMENTHEIGHT/2)^2)^0.5.
258
+ var radius = Math.sqrt((width * width) + (height * height)) / 2;
259
+
260
+ // Now we are looking for an angle between x-axis and the line starting at image of fixed point
261
+ // and ending at the center of the element. We call this angle `alpha`.
262
+
263
+ // The image of a fixed point is located in n-th quadrant. For each quadrant passed
264
+ // going anti-clockwise we have to add 90 degrees. Note that the first quadrant has index 0.
265
+ //
266
+ // 3 | 2
267
+ // --c-- Quadrant positions around the element's center `c`
268
+ // 0 | 1
269
+ //
270
+ var alpha = quadrant * Math.PI / 2;
271
+
272
+ // Add an angle between the beginning of the current quadrant (line parallel with x-axis or y-axis
273
+ // going through the center of the element) and line crossing the indent of the fixed point and the center
274
+ // of the element. This is the angle we need but on the un-rotated element.
275
+ alpha += Math.atan(quadrant % 2 == 0 ? height / width : width / height);
276
+
277
+ // Lastly we have to deduct the original angle the element was rotated by and that's it.
278
+ alpha -= toRad(angle);
279
+
280
+ // With this angle and distance we can easily calculate the centre of the un-rotated element.
281
+ // Note that fromPolar constructor accepts an angle in radians.
282
+ var center = Point.fromPolar(radius, alpha, imageFixedPoint);
283
+
284
+ // The top left corner on the un-rotated element has to be half a width on the left
285
+ // and half a height to the top from the center. This will be the origin of rectangle
286
+ // we were looking for.
287
+ origin = Point(center).offset(width / -2, height / -2);
288
+
289
+ } else {
290
+ // calculation for the origin Point when there is no rotation of the element
291
+ origin = bbox.topLeft();
292
+
293
+ switch (opt.direction) {
294
+ case 'top':
295
+ case 'top-right':
296
+ origin.offset(0, bbox.height - height);
297
+ break;
298
+ case 'left':
299
+ case 'bottom-left':
300
+ origin.offset(bbox.width -width, 0);
301
+ break;
302
+ case 'top-left':
303
+ origin.offset(bbox.width - width, bbox.height - height);
304
+ break;
305
+ }
306
+ }
307
+
308
+ // Resize the element (before re-positioning it).
309
+ this.set('size', { width: width, height: height }, opt);
310
+
311
+ // Finally, re-position the element.
312
+ this.position(origin.x, origin.y, opt);
313
+
314
+ } else {
315
+
316
+ // Resize the element.
317
+ this.set('size', { width: width, height: height }, opt);
318
+ }
319
+
320
+ this.stopBatch('resize', opt);
321
+
322
+ return this;
323
+ },
324
+
325
+ scale: function(sx, sy, origin, opt) {
326
+
327
+ var scaledBBox = this.getBBox().scale(sx, sy, origin);
328
+ this.startBatch('scale', opt);
329
+ this.position(scaledBBox.x, scaledBBox.y, opt);
330
+ this.resize(scaledBBox.width, scaledBBox.height, opt);
331
+ this.stopBatch('scale');
332
+ return this;
333
+ },
334
+
335
+ fitEmbeds: function(opt) {
336
+
337
+ return this.fitToChildren(opt);
338
+ },
339
+
340
+ fitToChildren: function(opt = {}) {
341
+
342
+ // Getting the children's size and position requires the collection.
343
+ // Cell.get('embeds') holds an array of cell ids only.
344
+ const { graph } = this;
345
+ if (!graph) throw new Error('Element must be part of a graph.');
346
+
347
+ const childElements = this.getEmbeddedCells().filter(cell => cell.isElement());
348
+ if (childElements.length === 0) return this;
349
+
350
+ this.startBatch('fit-embeds', opt);
351
+
352
+ if (opt.deep) {
353
+ // `opt.deep = true` means "fit to all descendants".
354
+ // As the first action of the fitting algorithm, recursively apply `fitToChildren()` on all descendants.
355
+ // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant, then go up (= this element).
356
+ invoke(childElements, 'fitToChildren', opt);
357
+ }
358
+
359
+ // Set new size and position of this element, based on:
360
+ // - union of bboxes of all children
361
+ // - inflated by given `opt.padding`
362
+ this._fitToElements(Object.assign({ elements: childElements }, opt));
363
+
364
+ this.stopBatch('fit-embeds');
365
+
366
+ return this;
367
+ },
368
+
369
+ fitParent: function(opt = {}) {
370
+
371
+ const { graph } = this;
372
+ if (!graph) throw new Error('Element must be part of a graph.');
373
+
374
+ // When `opt.deep = true`, we want `opt.terminator` to be the last ancestor processed.
375
+ // If the current element is `opt.terminator`, it means that this element has already been processed as parent so we can exit now.
376
+ if (opt.deep && opt.terminator && ((opt.terminator === this) || (opt.terminator === this.id))) return this;
377
+
378
+ const parentElement = this.getParentCell();
379
+ if (!parentElement || !parentElement.isElement()) return this;
380
+
381
+ // Get all children of parent element (i.e. this element + any sibling elements).
382
+ const siblingElements = parentElement.getEmbeddedCells().filter(cell => cell.isElement());
383
+ if (siblingElements.length === 0) return this;
384
+
385
+ this.startBatch('fit-parent', opt);
386
+
387
+ // Set new size and position of parent element, based on:
388
+ // - union of bboxes of all children of parent element (i.e. this element + any sibling elements)
389
+ // - inflated by given `opt.padding`
390
+ parentElement._fitToElements(Object.assign({ elements: siblingElements }, opt));
391
+
392
+ if (opt.deep) {
393
+ // `opt.deep = true` means "fit all ancestors to their respective children".
394
+ // As the last action of the fitting algorithm, recursively apply `fitParent()` on all ancestors.
395
+ // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant (= this element), then go up.
396
+ parentElement.fitParent(opt);
397
+ }
398
+
399
+ this.stopBatch('fit-parent');
400
+
401
+ return this;
402
+ },
403
+
404
+ // Assumption: This element is part of a graph.
405
+ _fitToElements: function(opt = {}) {
406
+
407
+ const elementsBBox = this.graph.getCellsBBox(opt.elements);
408
+ // If no `opt.elements` were provided, do nothing.
409
+ if (!elementsBBox) return;
410
+
411
+ const { expandOnly, shrinkOnly } = opt;
412
+ // This combination is meaningless, do nothing.
413
+ if (expandOnly && shrinkOnly) return;
414
+
415
+ // Calculate new size and position of this element based on:
416
+ // - union of bboxes of `opt.elements`
417
+ // - inflated by `opt.padding` (if not provided, all four properties = 0)
418
+ let { x, y, width, height } = elementsBBox;
419
+ const { left, right, top, bottom } = normalizeSides(opt.padding);
420
+ x -= left;
421
+ y -= top;
422
+ width += left + right;
423
+ height += bottom + top;
424
+ let resultBBox = new Rect(x, y, width, height);
425
+
426
+ if (expandOnly) {
427
+ // Non-shrinking is enforced by taking union of this element's current bbox with bbox calculated from `opt.elements`.
428
+ resultBBox = this.getBBox().union(resultBBox);
429
+
430
+ } else if (shrinkOnly) {
431
+ // Non-expansion is enforced by taking intersection of this element's current bbox with bbox calculated from `opt.elements`.
432
+ const intersectionBBox = this.getBBox().intersect(resultBBox);
433
+ // If all children are outside this element's current bbox, then `intersectionBBox` is `null` - does not make sense, do nothing.
434
+ if (!intersectionBBox) return;
435
+
436
+ resultBBox = intersectionBBox;
437
+ }
438
+
439
+ // Set the new size and position of this element.
440
+ this.set({
441
+ position: { x: resultBBox.x, y: resultBBox.y },
442
+ size: { width: resultBBox.width, height: resultBBox.height }
443
+ }, opt);
444
+ },
445
+
446
+ // Rotate element by `angle` degrees, optionally around `origin` point.
447
+ // If `origin` is not provided, it is considered to be the center of the element.
448
+ // If `absolute` is `true`, the `angle` is considered is absolute, i.e. it is not
449
+ // the difference from the previous angle.
450
+ rotate: function(angle, absolute, origin, opt) {
451
+
452
+ if (origin) {
453
+
454
+ var center = this.getBBox().center();
455
+ var size = this.get('size');
456
+ var position = this.get('position');
457
+ center.rotate(origin, this.get('angle') - angle);
458
+ var dx = center.x - size.width / 2 - position.x;
459
+ var dy = center.y - size.height / 2 - position.y;
460
+ this.startBatch('rotate', { angle: angle, absolute: absolute, origin: origin });
461
+ this.position(position.x + dx, position.y + dy, opt);
462
+ this.rotate(angle, absolute, null, opt);
463
+ this.stopBatch('rotate');
464
+
465
+ } else {
466
+
467
+ this.set('angle', absolute ? angle : (this.get('angle') + angle) % 360, opt);
468
+ }
469
+
470
+ return this;
471
+ },
472
+
473
+ angle: function() {
474
+ return normalizeAngle(this.get('angle') || 0);
475
+ },
476
+
477
+ getBBox: function(opt = {}) {
478
+
479
+ const { graph, attributes } = this;
480
+ const { deep, rotate } = opt;
481
+
482
+ if (deep && graph) {
483
+ // Get all the embedded elements using breadth first algorithm.
484
+ const elements = this.getEmbeddedCells({ deep: true, breadthFirst: true });
485
+ // Add the model itself.
486
+ elements.push(this);
487
+ // Note: the default of getCellsBBox() is rotate=true and can't be
488
+ // changed without a breaking change
489
+ return graph.getCellsBBox(elements, opt);
490
+ }
491
+
492
+ const { angle = 0, position: { x, y }, size: { width, height }} = attributes;
493
+ const bbox = new Rect(x, y, width, height);
494
+ if (rotate) {
495
+ bbox.rotateAroundCenter(angle);
496
+ }
497
+ return bbox;
498
+ },
499
+
500
+ getPointFromConnectedLink: function(link, endType) {
501
+ // Center of the model
502
+ var bbox = this.getBBox();
503
+ var center = bbox.center();
504
+ // Center of a port
505
+ var endDef = link.get(endType);
506
+ if (!endDef) return center;
507
+ var portId = endDef.port;
508
+ if (!portId || !this.hasPort(portId)) return center;
509
+ var portGroup = this.portProp(portId, ['group']);
510
+ var portsPositions = this.getPortsPositions(portGroup);
511
+ var portCenter = new Point(portsPositions[portId]).offset(bbox.origin());
512
+ var angle = this.angle();
513
+ if (angle) portCenter.rotate(center, -angle);
514
+ return portCenter;
515
+ }
516
+ });
517
+
518
+ assign(Element.prototype, elementPortPrototype);
519
+