@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,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);
|