@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,1112 @@
1
+ import * as util from '../util/index.mjs';
2
+ import * as g from '../g/index.mjs';
3
+
4
+ import { Model } from '../mvc/Model.mjs';
5
+ import { Collection } from '../mvc/Collection.mjs';
6
+ import { wrappers, wrapWith } from '../util/wrappers.mjs';
7
+ import { cloneCells } from '../util/index.mjs';
8
+
9
+ const GraphCells = Collection.extend({
10
+
11
+ initialize: function(models, opt) {
12
+
13
+ // Set the optional namespace where all model classes are defined.
14
+ if (opt.cellNamespace) {
15
+ this.cellNamespace = opt.cellNamespace;
16
+ } else {
17
+ /* eslint-disable no-undef */
18
+ this.cellNamespace = typeof joint !== 'undefined' && util.has(joint, 'shapes') ? joint.shapes : null;
19
+ /* eslint-enable no-undef */
20
+ }
21
+
22
+
23
+ this.graph = opt.graph;
24
+ },
25
+
26
+ model: function(attrs, opt) {
27
+
28
+ const collection = opt.collection;
29
+ const namespace = collection.cellNamespace;
30
+ const { type } = attrs;
31
+
32
+ // Find the model class based on the `type` attribute in the cell namespace
33
+ const ModelClass = util.getByPath(namespace, type, '.');
34
+ if (!ModelClass) {
35
+ throw new Error(`dia.Graph: Could not find cell constructor for type: '${type}'. Make sure to add the constructor to 'cellNamespace'.`);
36
+ }
37
+
38
+ const cell = new ModelClass(attrs, opt);
39
+ // Add a reference to the graph. It is necessary to do this here because this is the earliest place
40
+ // where a new model is created from a plain JS object. For other objects, see `joint.dia.Graph>>_prepareCell()`.
41
+ if (!opt.dry) {
42
+ cell.graph = collection.graph;
43
+ }
44
+
45
+ return cell;
46
+ },
47
+
48
+ // `comparator` makes it easy to sort cells based on their `z` index.
49
+ comparator: function(model) {
50
+
51
+ return model.get('z') || 0;
52
+ }
53
+ });
54
+
55
+
56
+ export const Graph = Model.extend({
57
+
58
+ initialize: function(attrs, opt) {
59
+
60
+ opt = opt || {};
61
+
62
+ // Passing `cellModel` function in the options object to graph allows for
63
+ // setting models based on attribute objects. This is especially handy
64
+ // when processing JSON graphs that are in a different than JointJS format.
65
+ var cells = new GraphCells([], {
66
+ model: opt.cellModel,
67
+ cellNamespace: opt.cellNamespace,
68
+ graph: this
69
+ });
70
+ Model.prototype.set.call(this, 'cells', cells);
71
+
72
+ // Make all the events fired in the `cells` collection available.
73
+ // to the outside world.
74
+ cells.on('all', this.trigger, this);
75
+
76
+ // JointJS automatically doesn't trigger re-sort if models attributes are changed later when
77
+ // they're already in the collection. Therefore, we're triggering sort manually here.
78
+ this.on('change:z', this._sortOnChangeZ, this);
79
+
80
+ // `joint.dia.Graph` keeps an internal data structure (an adjacency list)
81
+ // for fast graph queries. All changes that affect the structure of the graph
82
+ // must be reflected in the `al` object. This object provides fast answers to
83
+ // questions such as "what are the neighbours of this node" or "what
84
+ // are the sibling links of this link".
85
+
86
+ // Outgoing edges per node. Note that we use a hash-table for the list
87
+ // of outgoing edges for a faster lookup.
88
+ // [nodeId] -> Object [edgeId] -> true
89
+ this._out = {};
90
+ // Ingoing edges per node.
91
+ // [nodeId] -> Object [edgeId] -> true
92
+ this._in = {};
93
+ // `_nodes` is useful for quick lookup of all the elements in the graph, without
94
+ // having to go through the whole cells array.
95
+ // [node ID] -> true
96
+ this._nodes = {};
97
+ // `_edges` is useful for quick lookup of all the links in the graph, without
98
+ // having to go through the whole cells array.
99
+ // [edgeId] -> true
100
+ this._edges = {};
101
+
102
+ this._batches = {};
103
+
104
+ cells.on('add', this._restructureOnAdd, this);
105
+ cells.on('remove', this._restructureOnRemove, this);
106
+ cells.on('reset', this._restructureOnReset, this);
107
+ cells.on('change:source', this._restructureOnChangeSource, this);
108
+ cells.on('change:target', this._restructureOnChangeTarget, this);
109
+ cells.on('remove', this._removeCell, this);
110
+ },
111
+
112
+ _sortOnChangeZ: function() {
113
+
114
+ this.get('cells').sort();
115
+ },
116
+
117
+ _restructureOnAdd: function(cell) {
118
+
119
+ if (cell.isLink()) {
120
+ this._edges[cell.id] = true;
121
+ var { source, target } = cell.attributes;
122
+ if (source.id) {
123
+ (this._out[source.id] || (this._out[source.id] = {}))[cell.id] = true;
124
+ }
125
+ if (target.id) {
126
+ (this._in[target.id] || (this._in[target.id] = {}))[cell.id] = true;
127
+ }
128
+ } else {
129
+ this._nodes[cell.id] = true;
130
+ }
131
+ },
132
+
133
+ _restructureOnRemove: function(cell) {
134
+
135
+ if (cell.isLink()) {
136
+ delete this._edges[cell.id];
137
+ var { source, target } = cell.attributes;
138
+ if (source.id && this._out[source.id] && this._out[source.id][cell.id]) {
139
+ delete this._out[source.id][cell.id];
140
+ }
141
+ if (target.id && this._in[target.id] && this._in[target.id][cell.id]) {
142
+ delete this._in[target.id][cell.id];
143
+ }
144
+ } else {
145
+ delete this._nodes[cell.id];
146
+ }
147
+ },
148
+
149
+ _restructureOnReset: function(cells) {
150
+
151
+ // Normalize into an array of cells. The original `cells` is GraphCells mvc collection.
152
+ cells = cells.models;
153
+
154
+ this._out = {};
155
+ this._in = {};
156
+ this._nodes = {};
157
+ this._edges = {};
158
+
159
+ cells.forEach(this._restructureOnAdd, this);
160
+ },
161
+
162
+ _restructureOnChangeSource: function(link) {
163
+
164
+ var prevSource = link.previous('source');
165
+ if (prevSource.id && this._out[prevSource.id]) {
166
+ delete this._out[prevSource.id][link.id];
167
+ }
168
+ var source = link.attributes.source;
169
+ if (source.id) {
170
+ (this._out[source.id] || (this._out[source.id] = {}))[link.id] = true;
171
+ }
172
+ },
173
+
174
+ _restructureOnChangeTarget: function(link) {
175
+
176
+ var prevTarget = link.previous('target');
177
+ if (prevTarget.id && this._in[prevTarget.id]) {
178
+ delete this._in[prevTarget.id][link.id];
179
+ }
180
+ var target = link.get('target');
181
+ if (target.id) {
182
+ (this._in[target.id] || (this._in[target.id] = {}))[link.id] = true;
183
+ }
184
+ },
185
+
186
+ // Return all outbound edges for the node. Return value is an object
187
+ // of the form: [edgeId] -> true
188
+ getOutboundEdges: function(node) {
189
+
190
+ return (this._out && this._out[node]) || {};
191
+ },
192
+
193
+ // Return all inbound edges for the node. Return value is an object
194
+ // of the form: [edgeId] -> true
195
+ getInboundEdges: function(node) {
196
+
197
+ return (this._in && this._in[node]) || {};
198
+ },
199
+
200
+ toJSON: function() {
201
+
202
+ // JointJS does not recursively call `toJSON()` on attributes that are themselves models/collections.
203
+ // It just clones the attributes. Therefore, we must call `toJSON()` on the cells collection explicitly.
204
+ var json = Model.prototype.toJSON.apply(this, arguments);
205
+ json.cells = this.get('cells').toJSON();
206
+ return json;
207
+ },
208
+
209
+ fromJSON: function(json, opt) {
210
+
211
+ if (!json.cells) {
212
+
213
+ throw new Error('Graph JSON must contain cells array.');
214
+ }
215
+
216
+ return this.set(json, opt);
217
+ },
218
+
219
+ set: function(key, val, opt) {
220
+
221
+ var attrs;
222
+
223
+ // Handle both `key`, value and {key: value} style arguments.
224
+ if (typeof key === 'object') {
225
+ attrs = key;
226
+ opt = val;
227
+ } else {
228
+ (attrs = {})[key] = val;
229
+ }
230
+
231
+ // Make sure that `cells` attribute is handled separately via resetCells().
232
+ if (attrs.hasOwnProperty('cells')) {
233
+ this.resetCells(attrs.cells, opt);
234
+ attrs = util.omit(attrs, 'cells');
235
+ }
236
+
237
+ // The rest of the attributes are applied via original set method.
238
+ return Model.prototype.set.call(this, attrs, opt);
239
+ },
240
+
241
+ clear: function(opt) {
242
+
243
+ opt = util.assign({}, opt, { clear: true });
244
+
245
+ var collection = this.get('cells');
246
+
247
+ if (collection.length === 0) return this;
248
+
249
+ this.startBatch('clear', opt);
250
+
251
+ // The elements come after the links.
252
+ var cells = collection.sortBy(function(cell) {
253
+ return cell.isLink() ? 1 : 2;
254
+ });
255
+
256
+ do {
257
+
258
+ // Remove all the cells one by one.
259
+ // Note that all the links are removed first, so it's
260
+ // safe to remove the elements without removing the connected
261
+ // links first.
262
+ cells.shift().remove(opt);
263
+
264
+ } while (cells.length > 0);
265
+
266
+ this.stopBatch('clear');
267
+
268
+ return this;
269
+ },
270
+
271
+ _prepareCell: function(cell, opt) {
272
+
273
+ var attrs;
274
+ if (cell instanceof Model) {
275
+ attrs = cell.attributes;
276
+ if (!cell.graph && (!opt || !opt.dry)) {
277
+ // An element can not be member of more than one graph.
278
+ // A cell stops being the member of the graph after it's explicitly removed.
279
+ cell.graph = this;
280
+ }
281
+ } else {
282
+ // In case we're dealing with a plain JS object, we have to set the reference
283
+ // to the `graph` right after the actual model is created. This happens in the `model()` function
284
+ // of `joint.dia.GraphCells`.
285
+ attrs = cell;
286
+ }
287
+
288
+ if (!util.isString(attrs.type)) {
289
+ throw new TypeError('dia.Graph: cell type must be a string.');
290
+ }
291
+
292
+ return cell;
293
+ },
294
+
295
+ minZIndex: function() {
296
+
297
+ var firstCell = this.get('cells').first();
298
+ return firstCell ? (firstCell.get('z') || 0) : 0;
299
+ },
300
+
301
+ maxZIndex: function() {
302
+
303
+ var lastCell = this.get('cells').last();
304
+ return lastCell ? (lastCell.get('z') || 0) : 0;
305
+ },
306
+
307
+ addCell: function(cell, opt) {
308
+
309
+ if (Array.isArray(cell)) {
310
+
311
+ return this.addCells(cell, opt);
312
+ }
313
+
314
+ if (cell instanceof Model) {
315
+
316
+ if (!cell.has('z')) {
317
+ cell.set('z', this.maxZIndex() + 1);
318
+ }
319
+
320
+ } else if (cell.z === undefined) {
321
+
322
+ cell.z = this.maxZIndex() + 1;
323
+ }
324
+
325
+ this.get('cells').add(this._prepareCell(cell, opt), opt || {});
326
+
327
+ return this;
328
+ },
329
+
330
+ addCells: function(cells, opt) {
331
+
332
+ if (cells.length === 0) return this;
333
+
334
+ cells = util.flattenDeep(cells);
335
+ opt.maxPosition = opt.position = cells.length - 1;
336
+
337
+ this.startBatch('add', opt);
338
+ cells.forEach(function(cell) {
339
+ this.addCell(cell, opt);
340
+ opt.position--;
341
+ }, this);
342
+ this.stopBatch('add', opt);
343
+
344
+ return this;
345
+ },
346
+
347
+ // When adding a lot of cells, it is much more efficient to
348
+ // reset the entire cells collection in one go.
349
+ // Useful for bulk operations and optimizations.
350
+ resetCells: function(cells, opt) {
351
+
352
+ var preparedCells = util.toArray(cells).map(function(cell) {
353
+ return this._prepareCell(cell, opt);
354
+ }, this);
355
+ this.get('cells').reset(preparedCells, opt);
356
+
357
+ return this;
358
+ },
359
+
360
+ removeCells: function(cells, opt) {
361
+
362
+ if (cells.length) {
363
+
364
+ this.startBatch('remove');
365
+ util.invoke(cells, 'remove', opt);
366
+ this.stopBatch('remove');
367
+ }
368
+
369
+ return this;
370
+ },
371
+
372
+ _removeCell: function(cell, collection, options) {
373
+
374
+ options = options || {};
375
+
376
+ if (!options.clear) {
377
+ // Applications might provide a `disconnectLinks` option set to `true` in order to
378
+ // disconnect links when a cell is removed rather then removing them. The default
379
+ // is to remove all the associated links.
380
+ if (options.disconnectLinks) {
381
+
382
+ this.disconnectLinks(cell, options);
383
+
384
+ } else {
385
+
386
+ this.removeLinks(cell, options);
387
+ }
388
+ }
389
+ // Silently remove the cell from the cells collection. Silently, because
390
+ // `joint.dia.Cell.prototype.remove` already triggers the `remove` event which is
391
+ // then propagated to the graph model. If we didn't remove the cell silently, two `remove` events
392
+ // would be triggered on the graph model.
393
+ this.get('cells').remove(cell, { silent: true });
394
+
395
+ if (cell.graph === this) {
396
+ // Remove the element graph reference only if the cell is the member of this graph.
397
+ cell.graph = null;
398
+ }
399
+ },
400
+
401
+ // Get a cell by `id`.
402
+ getCell: function(id) {
403
+
404
+ return this.get('cells').get(id);
405
+ },
406
+
407
+ getCells: function() {
408
+
409
+ return this.get('cells').toArray();
410
+ },
411
+
412
+ getElements: function() {
413
+
414
+ return this.get('cells').toArray().filter(cell => cell.isElement());
415
+ },
416
+
417
+ getLinks: function() {
418
+
419
+ return this.get('cells').toArray().filter(cell => cell.isLink());
420
+ },
421
+
422
+ getFirstCell: function() {
423
+
424
+ return this.get('cells').first();
425
+ },
426
+
427
+ getLastCell: function() {
428
+
429
+ return this.get('cells').last();
430
+ },
431
+
432
+ // Get all inbound and outbound links connected to the cell `model`.
433
+ getConnectedLinks: function(model, opt) {
434
+
435
+ opt = opt || {};
436
+
437
+ var indirect = opt.indirect;
438
+ var inbound = opt.inbound;
439
+ var outbound = opt.outbound;
440
+ if ((inbound === undefined) && (outbound === undefined)) {
441
+ inbound = outbound = true;
442
+ }
443
+
444
+ // the final array of connected link models
445
+ var links = [];
446
+ // a hash table of connected edges of the form: [edgeId] -> true
447
+ // used for quick lookups to check if we already added a link
448
+ var edges = {};
449
+
450
+ if (outbound) {
451
+ addOutbounds(this, model);
452
+ }
453
+ if (inbound) {
454
+ addInbounds(this, model);
455
+ }
456
+
457
+ function addOutbounds(graph, model) {
458
+ util.forIn(graph.getOutboundEdges(model.id), function(_, edge) {
459
+ // skip links that were already added
460
+ // (those must be self-loop links)
461
+ // (because they are inbound and outbound edges of the same two elements)
462
+ if (edges[edge]) return;
463
+ var link = graph.getCell(edge);
464
+ links.push(link);
465
+ edges[edge] = true;
466
+ if (indirect) {
467
+ if (inbound) addInbounds(graph, link);
468
+ if (outbound) addOutbounds(graph, link);
469
+ }
470
+ }.bind(graph));
471
+ if (indirect && model.isLink()) {
472
+ var outCell = model.getTargetCell();
473
+ if (outCell && outCell.isLink()) {
474
+ if (!edges[outCell.id]) {
475
+ links.push(outCell);
476
+ addOutbounds(graph, outCell);
477
+ }
478
+ }
479
+ }
480
+ }
481
+
482
+ function addInbounds(graph, model) {
483
+ util.forIn(graph.getInboundEdges(model.id), function(_, edge) {
484
+ // skip links that were already added
485
+ // (those must be self-loop links)
486
+ // (because they are inbound and outbound edges of the same two elements)
487
+ if (edges[edge]) return;
488
+ var link = graph.getCell(edge);
489
+ links.push(link);
490
+ edges[edge] = true;
491
+ if (indirect) {
492
+ if (inbound) addInbounds(graph, link);
493
+ if (outbound) addOutbounds(graph, link);
494
+ }
495
+ }.bind(graph));
496
+ if (indirect && model.isLink()) {
497
+ var inCell = model.getSourceCell();
498
+ if (inCell && inCell.isLink()) {
499
+ if (!edges[inCell.id]) {
500
+ links.push(inCell);
501
+ addInbounds(graph, inCell);
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ // if `deep` option is `true`, check also all the links that are connected to any of the descendant cells
508
+ if (opt.deep) {
509
+
510
+ var embeddedCells = model.getEmbeddedCells({ deep: true });
511
+
512
+ // in the first round, we collect all the embedded elements
513
+ var embeddedElements = {};
514
+ embeddedCells.forEach(function(cell) {
515
+ if (cell.isElement()) {
516
+ embeddedElements[cell.id] = true;
517
+ }
518
+ });
519
+
520
+ embeddedCells.forEach(function(cell) {
521
+ if (cell.isLink()) return;
522
+ if (outbound) {
523
+ util.forIn(this.getOutboundEdges(cell.id), function(exists, edge) {
524
+ if (!edges[edge]) {
525
+ var edgeCell = this.getCell(edge);
526
+ var { source, target } = edgeCell.attributes;
527
+ var sourceId = source.id;
528
+ var targetId = target.id;
529
+
530
+ // if `includeEnclosed` option is falsy, skip enclosed links
531
+ if (!opt.includeEnclosed
532
+ && (sourceId && embeddedElements[sourceId])
533
+ && (targetId && embeddedElements[targetId])) {
534
+ return;
535
+ }
536
+
537
+ links.push(this.getCell(edge));
538
+ edges[edge] = true;
539
+ }
540
+ }.bind(this));
541
+ }
542
+ if (inbound) {
543
+ util.forIn(this.getInboundEdges(cell.id), function(exists, edge) {
544
+ if (!edges[edge]) {
545
+ var edgeCell = this.getCell(edge);
546
+ var { source, target } = edgeCell.attributes;
547
+ var sourceId = source.id;
548
+ var targetId = target.id;
549
+
550
+ // if `includeEnclosed` option is falsy, skip enclosed links
551
+ if (!opt.includeEnclosed
552
+ && (sourceId && embeddedElements[sourceId])
553
+ && (targetId && embeddedElements[targetId])) {
554
+ return;
555
+ }
556
+
557
+ links.push(this.getCell(edge));
558
+ edges[edge] = true;
559
+ }
560
+ }.bind(this));
561
+ }
562
+ }, this);
563
+ }
564
+
565
+ return links;
566
+ },
567
+
568
+ getNeighbors: function(model, opt) {
569
+
570
+ opt || (opt = {});
571
+
572
+ var inbound = opt.inbound;
573
+ var outbound = opt.outbound;
574
+ if (inbound === undefined && outbound === undefined) {
575
+ inbound = outbound = true;
576
+ }
577
+
578
+ var neighbors = this.getConnectedLinks(model, opt).reduce(function(res, link) {
579
+
580
+ var { source, target } = link.attributes;
581
+ var loop = link.hasLoop(opt);
582
+
583
+ // Discard if it is a point, or if the neighbor was already added.
584
+ if (inbound && util.has(source, 'id') && !res[source.id]) {
585
+
586
+ var sourceElement = this.getCell(source.id);
587
+ if (sourceElement.isElement()) {
588
+ if (loop || (sourceElement && sourceElement !== model && (!opt.deep || !sourceElement.isEmbeddedIn(model)))) {
589
+ res[source.id] = sourceElement;
590
+ }
591
+ }
592
+ }
593
+
594
+ // Discard if it is a point, or if the neighbor was already added.
595
+ if (outbound && util.has(target, 'id') && !res[target.id]) {
596
+
597
+ var targetElement = this.getCell(target.id);
598
+ if (targetElement.isElement()) {
599
+ if (loop || (targetElement && targetElement !== model && (!opt.deep || !targetElement.isEmbeddedIn(model)))) {
600
+ res[target.id] = targetElement;
601
+ }
602
+ }
603
+ }
604
+
605
+ return res;
606
+ }.bind(this), {});
607
+
608
+ if (model.isLink()) {
609
+ if (inbound) {
610
+ var sourceCell = model.getSourceCell();
611
+ if (sourceCell && sourceCell.isElement() && !neighbors[sourceCell.id]) {
612
+ neighbors[sourceCell.id] = sourceCell;
613
+ }
614
+ }
615
+ if (outbound) {
616
+ var targetCell = model.getTargetCell();
617
+ if (targetCell && targetCell.isElement() && !neighbors[targetCell.id]) {
618
+ neighbors[targetCell.id] = targetCell;
619
+ }
620
+ }
621
+ }
622
+
623
+ return util.toArray(neighbors);
624
+ },
625
+
626
+ getCommonAncestor: function(/* cells */) {
627
+
628
+ var cellsAncestors = Array.from(arguments).map(function(cell) {
629
+
630
+ var ancestors = [];
631
+ var parentId = cell.get('parent');
632
+
633
+ while (parentId) {
634
+
635
+ ancestors.push(parentId);
636
+ parentId = this.getCell(parentId).get('parent');
637
+ }
638
+
639
+ return ancestors;
640
+
641
+ }, this);
642
+
643
+ cellsAncestors = cellsAncestors.sort(function(a, b) {
644
+ return a.length - b.length;
645
+ });
646
+
647
+ var commonAncestor = util.toArray(cellsAncestors.shift()).find(function(ancestor) {
648
+ return cellsAncestors.every(function(cellAncestors) {
649
+ return cellAncestors.includes(ancestor);
650
+ });
651
+ });
652
+
653
+ return this.getCell(commonAncestor);
654
+ },
655
+
656
+ // Find the whole branch starting at `element`.
657
+ // If `opt.deep` is `true`, take into account embedded elements too.
658
+ // If `opt.breadthFirst` is `true`, use the Breadth-first search algorithm, otherwise use Depth-first search.
659
+ getSuccessors: function(element, opt) {
660
+
661
+ opt = opt || {};
662
+ var res = [];
663
+ // Modify the options so that it includes the `outbound` neighbors only. In other words, search forwards.
664
+ this.search(element, function(el) {
665
+ if (el !== element) {
666
+ res.push(el);
667
+ }
668
+ }, util.assign({}, opt, { outbound: true }));
669
+ return res;
670
+ },
671
+
672
+ cloneCells: cloneCells,
673
+ // Clone the whole subgraph (including all the connected links whose source/target is in the subgraph).
674
+ // If `opt.deep` is `true`, also take into account all the embedded cells of all the subgraph cells.
675
+ // Return a map of the form: [original cell ID] -> [clone].
676
+ cloneSubgraph: function(cells, opt) {
677
+
678
+ var subgraph = this.getSubgraph(cells, opt);
679
+ return this.cloneCells(subgraph);
680
+ },
681
+
682
+ // Return `cells` and all the connected links that connect cells in the `cells` array.
683
+ // If `opt.deep` is `true`, return all the cells including all their embedded cells
684
+ // and all the links that connect any of the returned cells.
685
+ // For example, for a single shallow element, the result is that very same element.
686
+ // For two elements connected with a link: `A --- L ---> B`, the result for
687
+ // `getSubgraph([A, B])` is `[A, L, B]`. The same goes for `getSubgraph([L])`, the result is again `[A, L, B]`.
688
+ getSubgraph: function(cells, opt) {
689
+
690
+ opt = opt || {};
691
+
692
+ var subgraph = [];
693
+ // `cellMap` is used for a quick lookup of existence of a cell in the `cells` array.
694
+ var cellMap = {};
695
+ var elements = [];
696
+ var links = [];
697
+
698
+ util.toArray(cells).forEach(function(cell) {
699
+ if (!cellMap[cell.id]) {
700
+ subgraph.push(cell);
701
+ cellMap[cell.id] = cell;
702
+ if (cell.isLink()) {
703
+ links.push(cell);
704
+ } else {
705
+ elements.push(cell);
706
+ }
707
+ }
708
+
709
+ if (opt.deep) {
710
+ var embeds = cell.getEmbeddedCells({ deep: true });
711
+ embeds.forEach(function(embed) {
712
+ if (!cellMap[embed.id]) {
713
+ subgraph.push(embed);
714
+ cellMap[embed.id] = embed;
715
+ if (embed.isLink()) {
716
+ links.push(embed);
717
+ } else {
718
+ elements.push(embed);
719
+ }
720
+ }
721
+ });
722
+ }
723
+ });
724
+
725
+ links.forEach(function(link) {
726
+ // For links, return their source & target (if they are elements - not points).
727
+ var { source, target } = link.attributes;
728
+ if (source.id && !cellMap[source.id]) {
729
+ var sourceElement = this.getCell(source.id);
730
+ subgraph.push(sourceElement);
731
+ cellMap[sourceElement.id] = sourceElement;
732
+ elements.push(sourceElement);
733
+ }
734
+ if (target.id && !cellMap[target.id]) {
735
+ var targetElement = this.getCell(target.id);
736
+ subgraph.push(this.getCell(target.id));
737
+ cellMap[targetElement.id] = targetElement;
738
+ elements.push(targetElement);
739
+ }
740
+ }, this);
741
+
742
+ elements.forEach(function(element) {
743
+ // For elements, include their connected links if their source/target is in the subgraph;
744
+ var links = this.getConnectedLinks(element, opt);
745
+ links.forEach(function(link) {
746
+ var { source, target } = link.attributes;
747
+ if (!cellMap[link.id] && source.id && cellMap[source.id] && target.id && cellMap[target.id]) {
748
+ subgraph.push(link);
749
+ cellMap[link.id] = link;
750
+ }
751
+ });
752
+ }, this);
753
+
754
+ return subgraph;
755
+ },
756
+
757
+ // Find all the predecessors of `element`. This is a reverse operation of `getSuccessors()`.
758
+ // If `opt.deep` is `true`, take into account embedded elements too.
759
+ // If `opt.breadthFirst` is `true`, use the Breadth-first search algorithm, otherwise use Depth-first search.
760
+ getPredecessors: function(element, opt) {
761
+
762
+ opt = opt || {};
763
+ var res = [];
764
+ // Modify the options so that it includes the `inbound` neighbors only. In other words, search backwards.
765
+ this.search(element, function(el) {
766
+ if (el !== element) {
767
+ res.push(el);
768
+ }
769
+ }, util.assign({}, opt, { inbound: true }));
770
+ return res;
771
+ },
772
+
773
+ // Perform search on the graph.
774
+ // If `opt.breadthFirst` is `true`, use the Breadth-first Search algorithm, otherwise use Depth-first search.
775
+ // By setting `opt.inbound` to `true`, you can reverse the direction of the search.
776
+ // If `opt.deep` is `true`, take into account embedded elements too.
777
+ // `iteratee` is a function of the form `function(element) {}`.
778
+ // If `iteratee` explicitly returns `false`, the searching stops.
779
+ search: function(element, iteratee, opt) {
780
+
781
+ opt = opt || {};
782
+ if (opt.breadthFirst) {
783
+ this.bfs(element, iteratee, opt);
784
+ } else {
785
+ this.dfs(element, iteratee, opt);
786
+ }
787
+ },
788
+
789
+ // Breadth-first search.
790
+ // If `opt.deep` is `true`, take into account embedded elements too.
791
+ // If `opt.inbound` is `true`, reverse the search direction (it's like reversing all the link directions).
792
+ // `iteratee` is a function of the form `function(element, distance) {}`.
793
+ // where `element` is the currently visited element and `distance` is the distance of that element
794
+ // from the root `element` passed the `bfs()`, i.e. the element we started the search from.
795
+ // Note that the `distance` is not the shortest or longest distance, it is simply the number of levels
796
+ // crossed till we visited the `element` for the first time. It is especially useful for tree graphs.
797
+ // If `iteratee` explicitly returns `false`, the searching stops.
798
+ bfs: function(element, iteratee, opt = {}) {
799
+
800
+ const visited = {};
801
+ const distance = {};
802
+ const queue = [];
803
+
804
+ queue.push(element);
805
+ distance[element.id] = 0;
806
+
807
+ while (queue.length > 0) {
808
+ var next = queue.shift();
809
+ if (visited[next.id]) continue;
810
+ visited[next.id] = true;
811
+ if (iteratee.call(this, next, distance[next.id]) === false) continue;
812
+ const neighbors = this.getNeighbors(next, opt);
813
+ for (let i = 0, n = neighbors.length; i < n; i++) {
814
+ const neighbor = neighbors[i];
815
+ distance[neighbor.id] = distance[next.id] + 1;
816
+ queue.push(neighbor);
817
+ }
818
+ }
819
+ },
820
+
821
+ // Depth-first search.
822
+ // If `opt.deep` is `true`, take into account embedded elements too.
823
+ // If `opt.inbound` is `true`, reverse the search direction (it's like reversing all the link directions).
824
+ // `iteratee` is a function of the form `function(element, distance) {}`.
825
+ // If `iteratee` explicitly returns `false`, the search stops.
826
+ dfs: function(element, iteratee, opt = {}) {
827
+
828
+ const visited = {};
829
+ const distance = {};
830
+ const queue = [];
831
+
832
+ queue.push(element);
833
+ distance[element.id] = 0;
834
+
835
+ while (queue.length > 0) {
836
+ const next = queue.pop();
837
+ if (visited[next.id]) continue;
838
+ visited[next.id] = true;
839
+ if (iteratee.call(this, next, distance[next.id]) === false) continue;
840
+ const neighbors = this.getNeighbors(next, opt);
841
+ const lastIndex = queue.length;
842
+ for (let i = 0, n = neighbors.length; i < n; i++) {
843
+ const neighbor = neighbors[i];
844
+ distance[neighbor.id] = distance[next.id] + 1;
845
+ queue.splice(lastIndex, 0, neighbor);
846
+ }
847
+ }
848
+ },
849
+
850
+ // Get all the roots of the graph. Time complexity: O(|V|).
851
+ getSources: function() {
852
+
853
+ var sources = [];
854
+ util.forIn(this._nodes, function(exists, node) {
855
+ if (!this._in[node] || util.isEmpty(this._in[node])) {
856
+ sources.push(this.getCell(node));
857
+ }
858
+ }.bind(this));
859
+ return sources;
860
+ },
861
+
862
+ // Get all the leafs of the graph. Time complexity: O(|V|).
863
+ getSinks: function() {
864
+
865
+ var sinks = [];
866
+ util.forIn(this._nodes, function(exists, node) {
867
+ if (!this._out[node] || util.isEmpty(this._out[node])) {
868
+ sinks.push(this.getCell(node));
869
+ }
870
+ }.bind(this));
871
+ return sinks;
872
+ },
873
+
874
+ // Return `true` if `element` is a root. Time complexity: O(1).
875
+ isSource: function(element) {
876
+
877
+ return !this._in[element.id] || util.isEmpty(this._in[element.id]);
878
+ },
879
+
880
+ // Return `true` if `element` is a leaf. Time complexity: O(1).
881
+ isSink: function(element) {
882
+
883
+ return !this._out[element.id] || util.isEmpty(this._out[element.id]);
884
+ },
885
+
886
+ // Return `true` is `elementB` is a successor of `elementA`. Return `false` otherwise.
887
+ isSuccessor: function(elementA, elementB) {
888
+
889
+ var isSuccessor = false;
890
+ this.search(elementA, function(element) {
891
+ if (element === elementB && element !== elementA) {
892
+ isSuccessor = true;
893
+ return false;
894
+ }
895
+ }, { outbound: true });
896
+ return isSuccessor;
897
+ },
898
+
899
+ // Return `true` is `elementB` is a predecessor of `elementA`. Return `false` otherwise.
900
+ isPredecessor: function(elementA, elementB) {
901
+
902
+ var isPredecessor = false;
903
+ this.search(elementA, function(element) {
904
+ if (element === elementB && element !== elementA) {
905
+ isPredecessor = true;
906
+ return false;
907
+ }
908
+ }, { inbound: true });
909
+ return isPredecessor;
910
+ },
911
+
912
+ // Return `true` is `elementB` is a neighbor of `elementA`. Return `false` otherwise.
913
+ // `opt.deep` controls whether to take into account embedded elements as well. See `getNeighbors()`
914
+ // for more details.
915
+ // If `opt.outbound` is set to `true`, return `true` only if `elementB` is a successor neighbor.
916
+ // Similarly, if `opt.inbound` is set to `true`, return `true` only if `elementB` is a predecessor neighbor.
917
+ isNeighbor: function(elementA, elementB, opt) {
918
+
919
+ opt = opt || {};
920
+
921
+ var inbound = opt.inbound;
922
+ var outbound = opt.outbound;
923
+ if ((inbound === undefined) && (outbound === undefined)) {
924
+ inbound = outbound = true;
925
+ }
926
+
927
+ var isNeighbor = false;
928
+
929
+ this.getConnectedLinks(elementA, opt).forEach(function(link) {
930
+
931
+ var { source, target } = link.attributes;
932
+
933
+ // Discard if it is a point.
934
+ if (inbound && util.has(source, 'id') && (source.id === elementB.id)) {
935
+ isNeighbor = true;
936
+ return false;
937
+ }
938
+
939
+ // Discard if it is a point, or if the neighbor was already added.
940
+ if (outbound && util.has(target, 'id') && (target.id === elementB.id)) {
941
+ isNeighbor = true;
942
+ return false;
943
+ }
944
+ });
945
+
946
+ return isNeighbor;
947
+ },
948
+
949
+ // Disconnect links connected to the cell `model`.
950
+ disconnectLinks: function(model, opt) {
951
+
952
+ this.getConnectedLinks(model).forEach(function(link) {
953
+
954
+ link.set((link.attributes.source.id === model.id ? 'source' : 'target'), { x: 0, y: 0 }, opt);
955
+ });
956
+ },
957
+
958
+ // Remove links connected to the cell `model` completely.
959
+ removeLinks: function(model, opt) {
960
+
961
+ util.invoke(this.getConnectedLinks(model), 'remove', opt);
962
+ },
963
+
964
+ // Find all elements at given point
965
+ findModelsFromPoint: function(p) {
966
+ return this.getElements().filter(el => el.getBBox({ rotate: true }).containsPoint(p));
967
+ },
968
+
969
+ // Find all elements in given area
970
+ findModelsInArea: function(rect, opt = {}) {
971
+ const r = new g.Rect(rect);
972
+ const { strict = false } = opt;
973
+ const method = strict ? 'containsRect' : 'intersect';
974
+ return this.getElements().filter(el => r[method](el.getBBox({ rotate: true })));
975
+ },
976
+
977
+ // Find all elements under the given element.
978
+ findModelsUnderElement: function(element, opt = {}) {
979
+ const { searchBy = 'bbox' } = opt;
980
+ const bbox = element.getBBox().rotateAroundCenter(element.angle());
981
+ const elements = (searchBy === 'bbox')
982
+ ? this.findModelsInArea(bbox)
983
+ : this.findModelsFromPoint(util.getRectPoint(bbox, searchBy));
984
+ // don't account element itself or any of its descendants
985
+ return elements.filter(el => element.id !== el.id && !el.isEmbeddedIn(element));
986
+ },
987
+
988
+ // Return bounding box of all elements.
989
+ getBBox: function() {
990
+
991
+ return this.getCellsBBox(this.getCells());
992
+ },
993
+
994
+ // Return the bounding box of all cells in array provided.
995
+ getCellsBBox: function(cells, opt = {}) {
996
+ const { rotate = true } = opt;
997
+ return util.toArray(cells).reduce(function(memo, cell) {
998
+ const rect = cell.getBBox({ rotate });
999
+ if (!rect) return memo;
1000
+ if (memo) {
1001
+ return memo.union(rect);
1002
+ }
1003
+ return rect;
1004
+ }, null);
1005
+ },
1006
+
1007
+ translate: function(dx, dy, opt) {
1008
+
1009
+ // Don't translate cells that are embedded in any other cell.
1010
+ var cells = this.getCells().filter(function(cell) {
1011
+ return !cell.isEmbedded();
1012
+ });
1013
+
1014
+ util.invoke(cells, 'translate', dx, dy, opt);
1015
+
1016
+ return this;
1017
+ },
1018
+
1019
+ resize: function(width, height, opt) {
1020
+
1021
+ return this.resizeCells(width, height, this.getCells(), opt);
1022
+ },
1023
+
1024
+ resizeCells: function(width, height, cells, opt) {
1025
+
1026
+ // `getBBox` method returns `null` if no elements provided.
1027
+ // i.e. cells can be an array of links
1028
+ var bbox = this.getCellsBBox(cells);
1029
+ if (bbox) {
1030
+ var sx = Math.max(width / bbox.width, 0);
1031
+ var sy = Math.max(height / bbox.height, 0);
1032
+ util.invoke(cells, 'scale', sx, sy, bbox.origin(), opt);
1033
+ }
1034
+
1035
+ return this;
1036
+ },
1037
+
1038
+ startBatch: function(name, data) {
1039
+
1040
+ data = data || {};
1041
+ this._batches[name] = (this._batches[name] || 0) + 1;
1042
+
1043
+ return this.trigger('batch:start', util.assign({}, data, { batchName: name }));
1044
+ },
1045
+
1046
+ stopBatch: function(name, data) {
1047
+
1048
+ data = data || {};
1049
+ this._batches[name] = (this._batches[name] || 0) - 1;
1050
+
1051
+ return this.trigger('batch:stop', util.assign({}, data, { batchName: name }));
1052
+ },
1053
+
1054
+ hasActiveBatch: function(name) {
1055
+
1056
+ const batches = this._batches;
1057
+ let names;
1058
+
1059
+ if (arguments.length === 0) {
1060
+ names = Object.keys(batches);
1061
+ } else if (Array.isArray(name)) {
1062
+ names = name;
1063
+ } else {
1064
+ names = [name];
1065
+ }
1066
+
1067
+ return names.some((batch) => batches[batch] > 0);
1068
+ }
1069
+
1070
+ }, {
1071
+
1072
+ validations: {
1073
+
1074
+ multiLinks: function(graph, link) {
1075
+
1076
+ // Do not allow multiple links to have the same source and target.
1077
+ var { source, target } = link.attributes;
1078
+
1079
+ if (source.id && target.id) {
1080
+
1081
+ var sourceModel = link.getSourceCell();
1082
+ if (sourceModel) {
1083
+
1084
+ var connectedLinks = graph.getConnectedLinks(sourceModel, { outbound: true });
1085
+ var sameLinks = connectedLinks.filter(function(_link) {
1086
+
1087
+ var { source: _source, target: _target } = _link.attributes;
1088
+ return _source && _source.id === source.id &&
1089
+ (!_source.port || (_source.port === source.port)) &&
1090
+ _target && _target.id === target.id &&
1091
+ (!_target.port || (_target.port === target.port));
1092
+
1093
+ });
1094
+
1095
+ if (sameLinks.length > 1) {
1096
+ return false;
1097
+ }
1098
+ }
1099
+ }
1100
+
1101
+ return true;
1102
+ },
1103
+
1104
+ linkPinning: function(_graph, link) {
1105
+ var { source, target } = link.attributes;
1106
+ return source.id && target.id;
1107
+ }
1108
+ }
1109
+
1110
+ });
1111
+
1112
+ wrapWith(Graph.prototype, ['resetCells', 'addCells', 'removeCells'], wrappers.cells);