@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.
- package/LICENSE +376 -0
- package/README.md +49 -0
- package/dist/geometry.js +6486 -0
- package/dist/geometry.min.js +8 -0
- package/dist/joint.d.ts +5536 -0
- package/dist/joint.js +39629 -0
- package/dist/joint.min.js +8 -0
- package/dist/joint.nowrap.js +39626 -0
- package/dist/joint.nowrap.min.js +8 -0
- package/dist/vectorizer.js +9135 -0
- package/dist/vectorizer.min.js +8 -0
- package/dist/version.mjs +3 -0
- package/index.js +3 -0
- package/joint.mjs +27 -0
- package/package.json +192 -0
- package/src/V/annotation.mjs +0 -0
- package/src/V/index.mjs +2642 -0
- package/src/anchors/index.mjs +123 -0
- package/src/config/index.mjs +12 -0
- package/src/connectionPoints/index.mjs +202 -0
- package/src/connectionStrategies/index.mjs +73 -0
- package/src/connectors/curve.mjs +553 -0
- package/src/connectors/index.mjs +6 -0
- package/src/connectors/jumpover.mjs +452 -0
- package/src/connectors/normal.mjs +12 -0
- package/src/connectors/rounded.mjs +17 -0
- package/src/connectors/smooth.mjs +44 -0
- package/src/connectors/straight.mjs +110 -0
- package/src/dia/Cell.mjs +945 -0
- package/src/dia/CellView.mjs +1316 -0
- package/src/dia/Element.mjs +519 -0
- package/src/dia/ElementView.mjs +859 -0
- package/src/dia/Graph.mjs +1112 -0
- package/src/dia/HighlighterView.mjs +319 -0
- package/src/dia/Link.mjs +565 -0
- package/src/dia/LinkView.mjs +2207 -0
- package/src/dia/Paper.mjs +3171 -0
- package/src/dia/PaperLayer.mjs +75 -0
- package/src/dia/ToolView.mjs +69 -0
- package/src/dia/ToolsView.mjs +128 -0
- package/src/dia/attributes/calc.mjs +128 -0
- package/src/dia/attributes/connection.mjs +75 -0
- package/src/dia/attributes/defs.mjs +76 -0
- package/src/dia/attributes/eval.mjs +64 -0
- package/src/dia/attributes/index.mjs +69 -0
- package/src/dia/attributes/legacy.mjs +148 -0
- package/src/dia/attributes/offset.mjs +53 -0
- package/src/dia/attributes/props.mjs +30 -0
- package/src/dia/attributes/shape.mjs +92 -0
- package/src/dia/attributes/text.mjs +180 -0
- package/src/dia/index.mjs +13 -0
- package/src/dia/layers/GridLayer.mjs +176 -0
- package/src/dia/ports.mjs +874 -0
- package/src/elementTools/Control.mjs +153 -0
- package/src/elementTools/HoverConnect.mjs +37 -0
- package/src/elementTools/index.mjs +5 -0
- package/src/env/index.mjs +43 -0
- package/src/g/bezier.mjs +175 -0
- package/src/g/curve.mjs +956 -0
- package/src/g/ellipse.mjs +245 -0
- package/src/g/extend.mjs +64 -0
- package/src/g/geometry.helpers.mjs +58 -0
- package/src/g/index.mjs +17 -0
- package/src/g/intersection.mjs +511 -0
- package/src/g/line.bearing.mjs +30 -0
- package/src/g/line.length.mjs +5 -0
- package/src/g/line.mjs +356 -0
- package/src/g/line.squaredLength.mjs +10 -0
- package/src/g/path.mjs +2260 -0
- package/src/g/point.mjs +375 -0
- package/src/g/points.mjs +247 -0
- package/src/g/polygon.mjs +51 -0
- package/src/g/polyline.mjs +523 -0
- package/src/g/rect.mjs +556 -0
- package/src/g/types.mjs +10 -0
- package/src/highlighters/addClass.mjs +27 -0
- package/src/highlighters/index.mjs +5 -0
- package/src/highlighters/list.mjs +111 -0
- package/src/highlighters/mask.mjs +220 -0
- package/src/highlighters/opacity.mjs +17 -0
- package/src/highlighters/stroke.mjs +100 -0
- package/src/layout/index.mjs +4 -0
- package/src/layout/ports/port.mjs +188 -0
- package/src/layout/ports/portLabel.mjs +224 -0
- package/src/linkAnchors/index.mjs +76 -0
- package/src/linkTools/Anchor.mjs +235 -0
- package/src/linkTools/Arrowhead.mjs +103 -0
- package/src/linkTools/Boundary.mjs +48 -0
- package/src/linkTools/Button.mjs +121 -0
- package/src/linkTools/Connect.mjs +85 -0
- package/src/linkTools/HoverConnect.mjs +161 -0
- package/src/linkTools/Segments.mjs +393 -0
- package/src/linkTools/Vertices.mjs +253 -0
- package/src/linkTools/helpers.mjs +33 -0
- package/src/linkTools/index.mjs +8 -0
- package/src/mvc/Collection.mjs +560 -0
- package/src/mvc/Data.mjs +46 -0
- package/src/mvc/Dom/Dom.mjs +587 -0
- package/src/mvc/Dom/Event.mjs +130 -0
- package/src/mvc/Dom/animations.mjs +122 -0
- package/src/mvc/Dom/events.mjs +69 -0
- package/src/mvc/Dom/index.mjs +13 -0
- package/src/mvc/Dom/methods.mjs +392 -0
- package/src/mvc/Dom/props.mjs +77 -0
- package/src/mvc/Dom/vars.mjs +5 -0
- package/src/mvc/Events.mjs +337 -0
- package/src/mvc/Listener.mjs +33 -0
- package/src/mvc/Model.mjs +239 -0
- package/src/mvc/View.mjs +323 -0
- package/src/mvc/ViewBase.mjs +182 -0
- package/src/mvc/index.mjs +9 -0
- package/src/mvc/mvcUtils.mjs +90 -0
- package/src/polyfills/array.js +4 -0
- package/src/polyfills/base64.js +68 -0
- package/src/polyfills/index.mjs +5 -0
- package/src/polyfills/number.js +3 -0
- package/src/polyfills/string.js +3 -0
- package/src/polyfills/typedArray.js +47 -0
- package/src/routers/index.mjs +6 -0
- package/src/routers/manhattan.mjs +856 -0
- package/src/routers/metro.mjs +91 -0
- package/src/routers/normal.mjs +6 -0
- package/src/routers/oneSide.mjs +60 -0
- package/src/routers/orthogonal.mjs +323 -0
- package/src/routers/rightAngle.mjs +1056 -0
- package/src/shapes/index.mjs +3 -0
- package/src/shapes/standard.mjs +755 -0
- package/src/util/cloneCells.mjs +67 -0
- package/src/util/getRectPoint.mjs +65 -0
- package/src/util/index.mjs +5 -0
- package/src/util/svgTagTemplate.mjs +110 -0
- package/src/util/util.mjs +1754 -0
- package/src/util/utilHelpers.mjs +2402 -0
- package/src/util/wrappers.mjs +56 -0
- package/types/geometry.d.ts +815 -0
- package/types/index.d.ts +53 -0
- package/types/joint.d.ts +4391 -0
- package/types/joint.head.d.ts +12 -0
- 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
|
+
|