@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,945 @@
1
+ import {
2
+ uniqueId,
3
+ union,
4
+ result,
5
+ merge,
6
+ forIn,
7
+ isObject,
8
+ isEqual,
9
+ isString,
10
+ cloneDeep,
11
+ omit,
12
+ uuid,
13
+ isEmpty,
14
+ assign,
15
+ uniq,
16
+ toArray,
17
+ setByPath,
18
+ unsetByPath,
19
+ getByPath,
20
+ timing,
21
+ interpolate,
22
+ nextFrame,
23
+ without,
24
+ cancelFrame,
25
+ defaultsDeep,
26
+ has,
27
+ sortBy,
28
+ defaults
29
+ } from '../util/util.mjs';
30
+ import { Model } from '../mvc/Model.mjs';
31
+ import { cloneCells } from '../util/cloneCells.mjs';
32
+ import { attributes } from './attributes/index.mjs';
33
+ import * as g from '../g/index.mjs';
34
+
35
+
36
+ // Cell base model.
37
+ // --------------------------
38
+
39
+ const attributesMerger = function(a, b) {
40
+ if (Array.isArray(a)) {
41
+ return b;
42
+ }
43
+ };
44
+
45
+ export const Cell = Model.extend({
46
+
47
+ // This is the same as mvc.Model with the only difference that is uses util.merge
48
+ // instead of just _.extend. The reason is that we want to mixin attributes set in upper classes.
49
+ constructor: function(attributes, options) {
50
+
51
+ var defaults;
52
+ var attrs = attributes || {};
53
+ if (typeof this.preinitialize === 'function') {
54
+ // Check to support an older version
55
+ this.preinitialize.apply(this, arguments);
56
+ }
57
+ this.cid = uniqueId('c');
58
+ this.attributes = {};
59
+ if (options && options.collection) this.collection = options.collection;
60
+ if (options && options.parse) attrs = this.parse(attrs, options) || {};
61
+ if ((defaults = result(this, 'defaults'))) {
62
+ //<custom code>
63
+ // Replaced the call to _.defaults with util.merge.
64
+ const customizer = (options && options.mergeArrays === true) ? false : attributesMerger;
65
+ attrs = merge({}, defaults, attrs, customizer);
66
+ //</custom code>
67
+ }
68
+ this.set(attrs, options);
69
+ this.changed = {};
70
+ this.initialize.apply(this, arguments);
71
+ },
72
+
73
+ translate: function(dx, dy, opt) {
74
+
75
+ throw new Error('Must define a translate() method.');
76
+ },
77
+
78
+ toJSON: function() {
79
+
80
+ const defaults = result(this.constructor.prototype, 'defaults');
81
+ const defaultAttrs = defaults.attrs || {};
82
+ const attrs = this.attributes.attrs;
83
+ const finalAttrs = {};
84
+
85
+ // Loop through all the attributes and
86
+ // omit the default attributes as they are implicitly reconstructible by the cell 'type'.
87
+ forIn(attrs, function(attr, selector) {
88
+
89
+ const defaultAttr = defaultAttrs[selector];
90
+
91
+ forIn(attr, function(value, name) {
92
+
93
+ // attr is mainly flat though it might have one more level (consider the `style` attribute).
94
+ // Check if the `value` is object and if yes, go one level deep.
95
+ if (isObject(value) && !Array.isArray(value)) {
96
+
97
+ forIn(value, function(value2, name2) {
98
+
99
+ if (!defaultAttr || !defaultAttr[name] || !isEqual(defaultAttr[name][name2], value2)) {
100
+
101
+ finalAttrs[selector] = finalAttrs[selector] || {};
102
+ (finalAttrs[selector][name] || (finalAttrs[selector][name] = {}))[name2] = value2;
103
+ }
104
+ });
105
+
106
+ } else if (!defaultAttr || !isEqual(defaultAttr[name], value)) {
107
+ // `value` is not an object, default attribute for such a selector does not exist
108
+ // or it is different than the attribute value set on the model.
109
+
110
+ finalAttrs[selector] = finalAttrs[selector] || {};
111
+ finalAttrs[selector][name] = value;
112
+ }
113
+ });
114
+ });
115
+
116
+ const attributes = cloneDeep(omit(this.attributes, 'attrs'));
117
+ attributes.attrs = finalAttrs;
118
+
119
+ return attributes;
120
+ },
121
+
122
+ initialize: function(options) {
123
+
124
+ const idAttribute = this.getIdAttribute();
125
+ if (!options || options[idAttribute] === undefined) {
126
+ this.set(idAttribute, this.generateId(), { silent: true });
127
+ }
128
+
129
+ this._transitionIds = {};
130
+ this._scheduledTransitionIds = {};
131
+
132
+ // Collect ports defined in `attrs` and keep collecting whenever `attrs` object changes.
133
+ this.processPorts();
134
+ this.on('change:attrs', this.processPorts, this);
135
+ },
136
+
137
+ getIdAttribute: function() {
138
+ return this.idAttribute || 'id';
139
+ },
140
+
141
+ generateId: function() {
142
+ return uuid();
143
+ },
144
+
145
+ /**
146
+ * @deprecated
147
+ */
148
+ processPorts: function() {
149
+
150
+ // Whenever `attrs` changes, we extract ports from the `attrs` object and store it
151
+ // in a more accessible way. Also, if any port got removed and there were links that had `target`/`source`
152
+ // set to that port, we remove those links as well (to follow the same behaviour as
153
+ // with a removed element).
154
+
155
+ var previousPorts = this.ports;
156
+
157
+ // Collect ports from the `attrs` object.
158
+ var ports = {};
159
+ forIn(this.get('attrs'), function(attrs, selector) {
160
+
161
+ if (attrs && attrs.port) {
162
+
163
+ // `port` can either be directly an `id` or an object containing an `id` (and potentially other data).
164
+ if (attrs.port.id !== undefined) {
165
+ ports[attrs.port.id] = attrs.port;
166
+ } else {
167
+ ports[attrs.port] = { id: attrs.port };
168
+ }
169
+ }
170
+ });
171
+
172
+ // Collect ports that have been removed (compared to the previous ports) - if any.
173
+ // Use hash table for quick lookup.
174
+ var removedPorts = {};
175
+ forIn(previousPorts, function(port, id) {
176
+
177
+ if (!ports[id]) removedPorts[id] = true;
178
+ });
179
+
180
+ // Remove all the incoming/outgoing links that have source/target port set to any of the removed ports.
181
+ if (this.graph && !isEmpty(removedPorts)) {
182
+
183
+ var inboundLinks = this.graph.getConnectedLinks(this, { inbound: true });
184
+ inboundLinks.forEach(function(link) {
185
+
186
+ if (removedPorts[link.get('target').port]) link.remove();
187
+ });
188
+
189
+ var outboundLinks = this.graph.getConnectedLinks(this, { outbound: true });
190
+ outboundLinks.forEach(function(link) {
191
+
192
+ if (removedPorts[link.get('source').port]) link.remove();
193
+ });
194
+ }
195
+
196
+ // Update the `ports` object.
197
+ this.ports = ports;
198
+ },
199
+
200
+ remove: function(opt = {}) {
201
+
202
+ // Store the graph in a variable because `this.graph` won't be accessible
203
+ // after `this.trigger('remove', ...)` down below.
204
+ const { graph, collection } = this;
205
+ if (!graph) {
206
+ // The collection is a common mvc collection (not the graph collection).
207
+ if (collection) collection.remove(this, opt);
208
+ return this;
209
+ }
210
+
211
+ graph.startBatch('remove');
212
+
213
+ // First, unembed this cell from its parent cell if there is one.
214
+ const parentCell = this.getParentCell();
215
+ if (parentCell) {
216
+ parentCell.unembed(this, opt);
217
+ }
218
+
219
+ // Remove also all the cells, which were embedded into this cell
220
+ const embeddedCells = this.getEmbeddedCells();
221
+ for (let i = 0, n = embeddedCells.length; i < n; i++) {
222
+ const embed = embeddedCells[i];
223
+ if (embed) {
224
+ embed.remove(opt);
225
+ }
226
+ }
227
+
228
+ this.trigger('remove', this, graph.attributes.cells, opt);
229
+
230
+ graph.stopBatch('remove');
231
+
232
+ return this;
233
+ },
234
+
235
+ toFront: function(opt) {
236
+ var graph = this.graph;
237
+ if (graph) {
238
+ opt = defaults(opt || {}, { foregroundEmbeds: true });
239
+
240
+ let cells;
241
+ if (opt.deep) {
242
+ cells = this.getEmbeddedCells({ deep: true, breadthFirst: opt.breadthFirst !== false, sortSiblings: opt.foregroundEmbeds });
243
+ cells.unshift(this);
244
+ } else {
245
+ cells = [this];
246
+ }
247
+
248
+ const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
249
+
250
+ const maxZ = graph.maxZIndex();
251
+ let z = maxZ - cells.length + 1;
252
+
253
+ const collection = graph.get('cells');
254
+
255
+ let shouldUpdate = (collection.toArray().indexOf(sortedCells[0]) !== (collection.length - cells.length));
256
+ if (!shouldUpdate) {
257
+ shouldUpdate = sortedCells.some(function(cell, index) {
258
+ return cell.z() !== z + index;
259
+ });
260
+ }
261
+
262
+ if (shouldUpdate) {
263
+ this.startBatch('to-front');
264
+
265
+ z = z + cells.length;
266
+
267
+ sortedCells.forEach(function(cell, index) {
268
+ cell.set('z', z + index, opt);
269
+ });
270
+
271
+ this.stopBatch('to-front');
272
+ }
273
+ }
274
+
275
+ return this;
276
+ },
277
+
278
+ toBack: function(opt) {
279
+ var graph = this.graph;
280
+ if (graph) {
281
+ opt = defaults(opt || {}, { foregroundEmbeds: true });
282
+
283
+ let cells;
284
+ if (opt.deep) {
285
+ cells = this.getEmbeddedCells({ deep: true, breadthFirst: opt.breadthFirst !== false, sortSiblings: opt.foregroundEmbeds });
286
+ cells.unshift(this);
287
+ } else {
288
+ cells = [this];
289
+ }
290
+
291
+ const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
292
+
293
+ let z = graph.minZIndex();
294
+
295
+ var collection = graph.get('cells');
296
+
297
+ let shouldUpdate = (collection.toArray().indexOf(sortedCells[0]) !== 0);
298
+ if (!shouldUpdate) {
299
+ shouldUpdate = sortedCells.some(function(cell, index) {
300
+ return cell.z() !== z + index;
301
+ });
302
+ }
303
+
304
+ if (shouldUpdate) {
305
+ this.startBatch('to-back');
306
+
307
+ z -= cells.length;
308
+
309
+ sortedCells.forEach(function(cell, index) {
310
+ cell.set('z', z + index, opt);
311
+ });
312
+
313
+ this.stopBatch('to-back');
314
+ }
315
+ }
316
+
317
+ return this;
318
+ },
319
+
320
+ parent: function(parent, opt) {
321
+
322
+ // getter
323
+ if (parent === undefined) return this.get('parent');
324
+ // setter
325
+ return this.set('parent', parent, opt);
326
+ },
327
+
328
+ embed: function(cell, opt) {
329
+ const cells = Array.isArray(cell) ? cell : [cell];
330
+ if (!this.canEmbed(cells)) {
331
+ throw new Error('Recursive embedding not allowed.');
332
+ }
333
+ if (cells.some(c => c.isEmbedded() && this.id !== c.parent())) {
334
+ throw new Error('Embedding of already embedded cells is not allowed.');
335
+ }
336
+ this._embedCells(cells, opt);
337
+ return this;
338
+ },
339
+
340
+ unembed: function(cell, opt) {
341
+ const cells = Array.isArray(cell) ? cell : [cell];
342
+ this._unembedCells(cells, opt);
343
+ return this;
344
+ },
345
+
346
+ canEmbed: function(cell) {
347
+ const cells = Array.isArray(cell) ? cell : [cell];
348
+ return cells.every(c => this !== c && !this.isEmbeddedIn(c));
349
+ },
350
+
351
+ _embedCells: function(cells, opt) {
352
+ const batchName = 'embed';
353
+ this.startBatch(batchName);
354
+ const embeds = assign([], this.get('embeds'));
355
+ cells.forEach(cell => {
356
+ // We keep all element ids after link ids.
357
+ embeds[cell.isLink() ? 'unshift' : 'push'](cell.id);
358
+ cell.parent(this.id, opt);
359
+ });
360
+ this.set('embeds', uniq(embeds), opt);
361
+ this.stopBatch(batchName);
362
+ },
363
+
364
+ _unembedCells: function(cells, opt) {
365
+ const batchName = 'unembed';
366
+ this.startBatch(batchName);
367
+ cells.forEach(cell => cell.unset('parent', opt));
368
+ this.set('embeds', without(this.get('embeds'), ...cells.map(cell => cell.id)), opt);
369
+ this.stopBatch(batchName);
370
+ },
371
+
372
+ getParentCell: function() {
373
+
374
+ // unlike link.source/target, cell.parent stores id directly as a string
375
+ var parentId = this.parent();
376
+ var graph = this.graph;
377
+
378
+ return (parentId && graph && graph.getCell(parentId)) || null;
379
+ },
380
+
381
+ // Return an array of ancestor cells.
382
+ // The array is ordered from the parent of the cell
383
+ // to the most distant ancestor.
384
+ getAncestors: function() {
385
+
386
+ var ancestors = [];
387
+
388
+ if (!this.graph) {
389
+ return ancestors;
390
+ }
391
+
392
+ var parentCell = this.getParentCell();
393
+ while (parentCell) {
394
+ ancestors.push(parentCell);
395
+ parentCell = parentCell.getParentCell();
396
+ }
397
+
398
+ return ancestors;
399
+ },
400
+
401
+ getEmbeddedCells: function(opt) {
402
+
403
+ opt = opt || {};
404
+
405
+ // Cell models can only be retrieved when this element is part of a collection.
406
+ // There is no way this element knows about other cells otherwise.
407
+ // This also means that calling e.g. `translate()` on an element with embeds before
408
+ // adding it to a graph does not translate its embeds.
409
+ if (!this.graph) {
410
+ return [];
411
+ }
412
+
413
+ if (opt.deep) {
414
+ if (opt.breadthFirst) {
415
+ return this._getEmbeddedCellsBfs(opt.sortSiblings);
416
+ } else {
417
+ return this._getEmbeddedCellsDfs(opt.sortSiblings);
418
+ }
419
+ }
420
+
421
+ const embeddedIds = this.get('embeds');
422
+ if (isEmpty(embeddedIds)) {
423
+ return [];
424
+ }
425
+
426
+ let cells = embeddedIds.map(this.graph.getCell, this.graph);
427
+ if (opt.sortSiblings) {
428
+ cells = sortBy(cells, cell => cell.z());
429
+ }
430
+
431
+ return cells;
432
+ },
433
+
434
+ _getEmbeddedCellsBfs: function(sortSiblings) {
435
+ const cells = [];
436
+
437
+ const queue = [];
438
+ queue.push(this);
439
+
440
+ while (queue.length > 0) {
441
+ const current = queue.shift();
442
+ cells.push(current);
443
+
444
+ const embeddedCells = current.getEmbeddedCells({ sortSiblings: sortSiblings });
445
+
446
+ queue.push(...embeddedCells);
447
+ }
448
+ cells.shift();
449
+
450
+ return cells;
451
+ },
452
+
453
+ _getEmbeddedCellsDfs: function(sortSiblings) {
454
+ const cells = [];
455
+
456
+ const stack = [];
457
+ stack.push(this);
458
+
459
+ while (stack.length > 0) {
460
+ const current = stack.pop();
461
+ cells.push(current);
462
+
463
+ const embeddedCells = current.getEmbeddedCells({ sortSiblings: sortSiblings });
464
+
465
+ // When using the stack, cells that are embedded last are processed first.
466
+ // To maintain the original order, we need to push the cells in reverse order
467
+ for (let i = embeddedCells.length - 1; i >= 0; --i) {
468
+ stack.push(embeddedCells[i]);
469
+ }
470
+ }
471
+ cells.shift();
472
+
473
+ return cells;
474
+ },
475
+
476
+ isEmbeddedIn: function(cell, opt) {
477
+
478
+ var cellId = isString(cell) ? cell : cell.id;
479
+ var parentId = this.parent();
480
+
481
+ opt = assign({ deep: true }, opt);
482
+
483
+ // See getEmbeddedCells().
484
+ if (this.graph && opt.deep) {
485
+
486
+ while (parentId) {
487
+ if (parentId === cellId) {
488
+ return true;
489
+ }
490
+ parentId = this.graph.getCell(parentId).parent();
491
+ }
492
+
493
+ return false;
494
+
495
+ } else {
496
+
497
+ // When this cell is not part of a collection check
498
+ // at least whether it's a direct child of given cell.
499
+ return parentId === cellId;
500
+ }
501
+ },
502
+
503
+ // Whether or not the cell is embedded in any other cell.
504
+ isEmbedded: function() {
505
+
506
+ return !!this.parent();
507
+ },
508
+
509
+ // Isolated cloning. Isolated cloning has two versions: shallow and deep (pass `{ deep: true }` in `opt`).
510
+ // Shallow cloning simply clones the cell and returns a new cell with different ID.
511
+ // Deep cloning clones the cell and all its embedded cells recursively.
512
+ clone: function(opt) {
513
+
514
+ opt = opt || {};
515
+
516
+ if (!opt.deep) {
517
+ // Shallow cloning.
518
+
519
+ var clone = Model.prototype.clone.apply(this, arguments);
520
+ // We don't want the clone to have the same ID as the original.
521
+ clone.set(this.getIdAttribute(), this.generateId());
522
+ // A shallow cloned element does not carry over the original embeds.
523
+ clone.unset('embeds');
524
+ // And can not be embedded in any cell
525
+ // as the clone is not part of the graph.
526
+ clone.unset('parent');
527
+
528
+ return clone;
529
+
530
+ } else {
531
+ // Deep cloning.
532
+
533
+ // For a deep clone, simply call `graph.cloneCells()` with the cell and all its embedded cells.
534
+ return toArray(cloneCells([this].concat(this.getEmbeddedCells({ deep: true }))));
535
+ }
536
+ },
537
+
538
+ // A convenient way to set nested properties.
539
+ // This method merges the properties you'd like to set with the ones
540
+ // stored in the cell and makes sure change events are properly triggered.
541
+ // You can either set a nested property with one object
542
+ // or use a property path.
543
+ // The most simple use case is:
544
+ // `cell.prop('name/first', 'John')` or
545
+ // `cell.prop({ name: { first: 'John' } })`.
546
+ // Nested arrays are supported too:
547
+ // `cell.prop('series/0/data/0/degree', 50)` or
548
+ // `cell.prop({ series: [ { data: [ { degree: 50 } ] } ] })`.
549
+ prop: function(props, value, opt) {
550
+
551
+ var delim = '/';
552
+ var _isString = isString(props);
553
+
554
+ if (_isString || Array.isArray(props)) {
555
+ // Get/set an attribute by a special path syntax that delimits
556
+ // nested objects by the colon character.
557
+
558
+ if (arguments.length > 1) {
559
+
560
+ var path;
561
+ var pathArray;
562
+
563
+ if (_isString) {
564
+ path = props;
565
+ pathArray = path.split('/');
566
+ } else {
567
+ path = props.join(delim);
568
+ pathArray = props.slice();
569
+ }
570
+
571
+ var property = pathArray[0];
572
+ var pathArrayLength = pathArray.length;
573
+
574
+ const options = opt || {};
575
+ options.propertyPath = path;
576
+ options.propertyValue = value;
577
+ options.propertyPathArray = pathArray;
578
+ if (!('rewrite' in options)) {
579
+ options.rewrite = false;
580
+ }
581
+
582
+ var update = {};
583
+ // Initialize the nested object. Sub-objects are either arrays or objects.
584
+ // An empty array is created if the sub-key is an integer. Otherwise, an empty object is created.
585
+ // Note that this imposes a limitation on object keys one can use with Inspector.
586
+ // Pure integer keys will cause issues and are therefore not allowed.
587
+ var initializer = update;
588
+ var prevProperty = property;
589
+
590
+ for (var i = 1; i < pathArrayLength; i++) {
591
+ var pathItem = pathArray[i];
592
+ var isArrayIndex = Number.isFinite(_isString ? Number(pathItem) : pathItem);
593
+ initializer = initializer[prevProperty] = isArrayIndex ? [] : {};
594
+ prevProperty = pathItem;
595
+ }
596
+
597
+ // Fill update with the `value` on `path`.
598
+ update = setByPath(update, pathArray, value, '/');
599
+
600
+ var baseAttributes = merge({}, this.attributes);
601
+ // if rewrite mode enabled, we replace value referenced by path with
602
+ // the new one (we don't merge).
603
+ options.rewrite && unsetByPath(baseAttributes, path, '/');
604
+
605
+ // Merge update with the model attributes.
606
+ var attributes = merge(baseAttributes, update);
607
+ // Finally, set the property to the updated attributes.
608
+ return this.set(property, attributes[property], options);
609
+
610
+ } else {
611
+
612
+ return getByPath(this.attributes, props, delim);
613
+ }
614
+ }
615
+
616
+ const options = value || {};
617
+ // Note: '' is not the path to the root. It's a path with an empty string i.e. { '': {}}.
618
+ options.propertyPath = null;
619
+ options.propertyValue = props;
620
+ options.propertyPathArray = [];
621
+ if (!('rewrite' in options)) {
622
+ options.rewrite = false;
623
+ }
624
+
625
+ // Create a new object containing only the changed attributes.
626
+ const changedAttributes = {};
627
+ for (const key in props) {
628
+ // Merging the values of changed attributes with the current ones.
629
+ const { changedValue } = merge({}, { changedValue: this.attributes[key] }, { changedValue: props[key] });
630
+ changedAttributes[key] = changedValue;
631
+ }
632
+
633
+ return this.set(changedAttributes, options);
634
+ },
635
+
636
+ // A convenient way to unset nested properties
637
+ removeProp: function(path, opt) {
638
+
639
+ opt = opt || {};
640
+
641
+ var pathArray = Array.isArray(path) ? path : path.split('/');
642
+
643
+ // Once a property is removed from the `attrs` attribute
644
+ // the cellView will recognize a `dirty` flag and re-render itself
645
+ // in order to remove the attribute from SVG element.
646
+ var property = pathArray[0];
647
+ if (property === 'attrs') opt.dirty = true;
648
+
649
+ if (pathArray.length === 1) {
650
+ // A top level property
651
+ return this.unset(path, opt);
652
+ }
653
+
654
+ // A nested property
655
+ var nestedPath = pathArray.slice(1);
656
+ var propertyValue = this.get(property);
657
+ if (propertyValue === undefined || propertyValue === null) return this;
658
+ propertyValue = cloneDeep(propertyValue);
659
+
660
+ unsetByPath(propertyValue, nestedPath, '/');
661
+
662
+ return this.set(property, propertyValue, opt);
663
+ },
664
+
665
+ // A convenient way to set nested attributes.
666
+ attr: function(attrs, value, opt) {
667
+
668
+ var args = Array.from(arguments);
669
+ if (args.length === 0) {
670
+ return this.get('attrs');
671
+ }
672
+
673
+ if (Array.isArray(attrs)) {
674
+ args[0] = ['attrs'].concat(attrs);
675
+ } else if (isString(attrs)) {
676
+ // Get/set an attribute by a special path syntax that delimits
677
+ // nested objects by the colon character.
678
+ args[0] = 'attrs/' + attrs;
679
+
680
+ } else {
681
+
682
+ args[0] = { 'attrs' : attrs };
683
+ }
684
+
685
+ return this.prop.apply(this, args);
686
+ },
687
+
688
+ // A convenient way to unset nested attributes
689
+ removeAttr: function(path, opt) {
690
+
691
+ if (Array.isArray(path)) {
692
+
693
+ return this.removeProp(['attrs'].concat(path));
694
+ }
695
+
696
+ return this.removeProp('attrs/' + path, opt);
697
+ },
698
+
699
+ transition: function(path, value, opt, delim) {
700
+
701
+ delim = delim || '/';
702
+
703
+ var defaults = {
704
+ duration: 100,
705
+ delay: 10,
706
+ timingFunction: timing.linear,
707
+ valueFunction: interpolate.number
708
+ };
709
+
710
+ opt = assign(defaults, opt);
711
+
712
+ var firstFrameTime = 0;
713
+ var interpolatingFunction;
714
+
715
+ var setter = function(runtime) {
716
+
717
+ var id, progress, propertyValue;
718
+
719
+ firstFrameTime = firstFrameTime || runtime;
720
+ runtime -= firstFrameTime;
721
+ progress = runtime / opt.duration;
722
+
723
+ if (progress < 1) {
724
+ this._transitionIds[path] = id = nextFrame(setter);
725
+ } else {
726
+ progress = 1;
727
+ delete this._transitionIds[path];
728
+ }
729
+
730
+ propertyValue = interpolatingFunction(opt.timingFunction(progress));
731
+
732
+ opt.transitionId = id;
733
+
734
+ this.prop(path, propertyValue, opt);
735
+
736
+ if (!id) this.trigger('transition:end', this, path);
737
+
738
+ }.bind(this);
739
+
740
+ const { _scheduledTransitionIds } = this;
741
+ let initialId;
742
+
743
+ var initiator = (callback) => {
744
+
745
+ if (_scheduledTransitionIds[path]) {
746
+ _scheduledTransitionIds[path] = without(_scheduledTransitionIds[path], initialId);
747
+ if (_scheduledTransitionIds[path].length === 0) {
748
+ delete _scheduledTransitionIds[path];
749
+ }
750
+ }
751
+
752
+ this.stopPendingTransitions(path, delim);
753
+
754
+ interpolatingFunction = opt.valueFunction(getByPath(this.attributes, path, delim), value);
755
+
756
+ this._transitionIds[path] = nextFrame(callback);
757
+
758
+ this.trigger('transition:start', this, path);
759
+
760
+ };
761
+
762
+ initialId = setTimeout(initiator, opt.delay, setter);
763
+
764
+ _scheduledTransitionIds[path] || (_scheduledTransitionIds[path] = []);
765
+ _scheduledTransitionIds[path].push(initialId);
766
+
767
+ return initialId;
768
+ },
769
+
770
+ getTransitions: function() {
771
+ return union(
772
+ Object.keys(this._transitionIds),
773
+ Object.keys(this._scheduledTransitionIds)
774
+ );
775
+ },
776
+
777
+ stopScheduledTransitions: function(path, delim = '/') {
778
+ const { _scheduledTransitionIds = {}} = this;
779
+ let transitions = Object.keys(_scheduledTransitionIds);
780
+ if (path) {
781
+ const pathArray = path.split(delim);
782
+ transitions = transitions.filter((key) => {
783
+ return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
784
+ });
785
+ }
786
+ transitions.forEach((key) => {
787
+ const transitionIds = _scheduledTransitionIds[key];
788
+ // stop the initiator
789
+ transitionIds.forEach(transitionId => clearTimeout(transitionId));
790
+ delete _scheduledTransitionIds[key];
791
+ // Note: we could trigger transition:cancel` event here
792
+ });
793
+ return this;
794
+ },
795
+
796
+ stopPendingTransitions(path, delim = '/') {
797
+ const { _transitionIds = {}} = this;
798
+ let transitions = Object.keys(_transitionIds);
799
+ if (path) {
800
+ const pathArray = path.split(delim);
801
+ transitions = transitions.filter((key) => {
802
+ return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
803
+ });
804
+ }
805
+ transitions.forEach((key) => {
806
+ const transitionId = _transitionIds[key];
807
+ // stop the setter
808
+ cancelFrame(transitionId);
809
+ delete _transitionIds[key];
810
+ this.trigger('transition:end', this, key);
811
+ });
812
+ },
813
+
814
+ stopTransitions: function(path, delim = '/') {
815
+ this.stopScheduledTransitions(path, delim);
816
+ this.stopPendingTransitions(path, delim);
817
+ return this;
818
+ },
819
+
820
+ // A shorcut making it easy to create constructs like the following:
821
+ // `var el = (new joint.shapes.standard.Rectangle()).addTo(graph)`.
822
+ addTo: function(graph, opt) {
823
+
824
+ graph.addCell(this, opt);
825
+ return this;
826
+ },
827
+
828
+ // A shortcut for an equivalent call: `paper.findViewByModel(cell)`
829
+ // making it easy to create constructs like the following:
830
+ // `cell.findView(paper).highlight()`
831
+ findView: function(paper) {
832
+
833
+ return paper.findViewByModel(this);
834
+ },
835
+
836
+ isElement: function() {
837
+
838
+ return false;
839
+ },
840
+
841
+ isLink: function() {
842
+
843
+ return false;
844
+ },
845
+
846
+ startBatch: function(name, opt) {
847
+
848
+ if (this.graph) { this.graph.startBatch(name, assign({}, opt, { cell: this })); }
849
+ return this;
850
+ },
851
+
852
+ stopBatch: function(name, opt) {
853
+
854
+ if (this.graph) { this.graph.stopBatch(name, assign({}, opt, { cell: this })); }
855
+ return this;
856
+ },
857
+
858
+ getChangeFlag: function(attributes) {
859
+
860
+ var flag = 0;
861
+ if (!attributes) return flag;
862
+ for (var key in attributes) {
863
+ if (!attributes.hasOwnProperty(key) || !this.hasChanged(key)) continue;
864
+ flag |= attributes[key];
865
+ }
866
+ return flag;
867
+ },
868
+
869
+ angle: function() {
870
+
871
+ // To be overridden.
872
+ return 0;
873
+ },
874
+
875
+ position: function() {
876
+
877
+ // To be overridden.
878
+ return new g.Point(0, 0);
879
+ },
880
+
881
+ z: function() {
882
+ return this.get('z') || 0;
883
+ },
884
+
885
+ getPointFromConnectedLink: function() {
886
+
887
+ // To be overridden
888
+ return new g.Point();
889
+ },
890
+
891
+ getBBox: function() {
892
+
893
+ // To be overridden
894
+ return new g.Rect(0, 0, 0, 0);
895
+ },
896
+
897
+ getPointRotatedAroundCenter(angle, x, y) {
898
+ const point = new g.Point(x, y);
899
+ if (angle) point.rotate(this.getBBox().center(), angle);
900
+ return point;
901
+ },
902
+
903
+ getAbsolutePointFromRelative(x, y) {
904
+ // Rotate the position to take the model angle into account
905
+ return this.getPointRotatedAroundCenter(
906
+ -this.angle(),
907
+ // Transform the relative position to absolute
908
+ this.position().offset(x, y)
909
+ );
910
+ },
911
+
912
+ getRelativePointFromAbsolute(x, y) {
913
+ return this
914
+ // Rotate the coordinates to mitigate the element's rotation.
915
+ .getPointRotatedAroundCenter(this.angle(), x, y)
916
+ // Transform the absolute position into relative
917
+ .difference(this.position());
918
+ }
919
+
920
+ }, {
921
+
922
+ getAttributeDefinition: function(attrName) {
923
+
924
+ var defNS = this.attributes;
925
+ var globalDefNS = attributes;
926
+ return (defNS && defNS[attrName]) || globalDefNS[attrName];
927
+ },
928
+
929
+ define: function(type, defaults, protoProps, staticProps) {
930
+
931
+ protoProps = assign({
932
+ defaults: defaultsDeep({ type: type }, defaults, this.prototype.defaults)
933
+ }, protoProps);
934
+
935
+ var Cell = this.extend(protoProps, staticProps);
936
+ // es5 backward compatibility
937
+ /* eslint-disable no-undef */
938
+ if (typeof joint !== 'undefined' && has(joint, 'shapes')) {
939
+ setByPath(joint.shapes, type, Cell, '.');
940
+ }
941
+ /* eslint-enable no-undef */
942
+ return Cell;
943
+ }
944
+ });
945
+