@joint/core 4.2.0-alpha.1 → 4.2.0-beta.1
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/dist/geometry.js +1 -1
- package/dist/geometry.min.js +2 -3
- package/dist/joint.d.ts +321 -55
- package/dist/joint.js +2492 -853
- package/dist/joint.min.js +2 -3
- package/dist/joint.nowrap.js +2492 -853
- package/dist/joint.nowrap.min.js +2 -3
- package/dist/vectorizer.js +1 -1
- package/dist/vectorizer.min.js +2 -3
- package/dist/version.mjs +1 -1
- package/package.json +7 -7
- package/src/config/index.mjs +3 -1
- package/src/dia/Cell.mjs +77 -80
- package/src/dia/CellCollection.mjs +136 -0
- package/src/dia/CellView.mjs +1 -2
- package/src/dia/Element.mjs +2 -3
- package/src/dia/Graph.mjs +610 -317
- package/src/dia/GraphLayer.mjs +53 -0
- package/src/dia/GraphLayerCollection.mjs +313 -0
- package/src/dia/GraphLayerView.mjs +128 -0
- package/src/dia/GraphLayersController.mjs +166 -0
- package/src/dia/GraphTopologyIndex.mjs +222 -0
- package/src/dia/{layers/GridLayer.mjs → GridLayerView.mjs} +23 -16
- package/src/dia/{PaperLayer.mjs → LayerView.mjs} +52 -17
- package/src/dia/LegacyGraphLayerView.mjs +14 -0
- package/src/dia/Paper.mjs +756 -423
- package/src/dia/ToolsView.mjs +3 -3
- package/src/dia/index.mjs +6 -1
- package/src/dia/ports.mjs +11 -2
- package/src/dia/symbols.mjs +24 -0
- package/src/mvc/Collection.mjs +19 -19
- package/src/mvc/Model.mjs +13 -10
- package/types/joint.d.ts +320 -54
package/src/dia/Graph.mjs
CHANGED
|
@@ -2,330 +2,194 @@ import * as util from '../util/index.mjs';
|
|
|
2
2
|
import * as g from '../g/index.mjs';
|
|
3
3
|
|
|
4
4
|
import { Model } from '../mvc/Model.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import { Listener } from '../mvc/Listener.mjs';
|
|
6
6
|
import { wrappers, wrapWith } from '../util/wrappers.mjs';
|
|
7
7
|
import { cloneCells } from '../util/index.mjs';
|
|
8
|
+
import { GraphLayersController } from './GraphLayersController.mjs';
|
|
9
|
+
import { GraphLayerCollection } from './GraphLayerCollection.mjs';
|
|
10
|
+
import { config } from '../config/index.mjs';
|
|
11
|
+
import { CELL_MARKER } from './symbols.mjs';
|
|
12
|
+
import { GraphTopologyIndex } from './GraphTopologyIndex.mjs';
|
|
8
13
|
|
|
9
|
-
|
|
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
|
-
return new ModelClass(attrs, opt);
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
_addReference: function(model, options) {
|
|
42
|
-
Collection.prototype._addReference.apply(this, arguments);
|
|
43
|
-
// If not in `dry` mode and the model does not have a graph reference yet,
|
|
44
|
-
// set the reference.
|
|
45
|
-
if (!options.dry && !model.graph) {
|
|
46
|
-
model.graph = this.graph;
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
_removeReference: function(model, options) {
|
|
51
|
-
Collection.prototype._removeReference.apply(this, arguments);
|
|
52
|
-
// If not in `dry` mode and the model has a reference to this exact graph,
|
|
53
|
-
// remove the reference.
|
|
54
|
-
if (!options.dry && model.graph === this.graph) {
|
|
55
|
-
model.graph = null;
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
// `comparator` makes it easy to sort cells based on their `z` index.
|
|
60
|
-
comparator: function(model) {
|
|
61
|
-
|
|
62
|
-
return model.get('z') || 0;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
14
|
+
// The ID of the default graph layer.
|
|
15
|
+
const DEFAULT_LAYER_ID = 'cells';
|
|
66
16
|
|
|
67
17
|
export const Graph = Model.extend({
|
|
68
18
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @todo Remove in v5.0.0
|
|
21
|
+
* @description In legacy mode, the information about layers is not
|
|
22
|
+
* exported into JSON.
|
|
23
|
+
*/
|
|
24
|
+
legacyMode: true,
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @protected
|
|
28
|
+
* @description The ID of the default layer.
|
|
29
|
+
*/
|
|
30
|
+
defaultLayerId: DEFAULT_LAYER_ID,
|
|
31
|
+
|
|
32
|
+
initialize: function(attrs, options = {}) {
|
|
33
|
+
|
|
34
|
+
const layerCollection = this.layerCollection = new GraphLayerCollection([], {
|
|
35
|
+
layerNamespace: options.layerNamespace,
|
|
36
|
+
cellNamespace: options.cellNamespace,
|
|
37
|
+
graph: this,
|
|
38
|
+
/** @deprecated use cellNamespace instead */
|
|
39
|
+
model: options.cellModel,
|
|
80
40
|
});
|
|
81
|
-
Model.prototype.set.call(this, 'cells', cells);
|
|
82
41
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
42
|
+
// The default setup includes a single default layer.
|
|
43
|
+
layerCollection.add({ id: DEFAULT_LAYER_ID }, { graph: this.cid });
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @todo Remove in v5.0.0
|
|
47
|
+
* @description Retain legacy 'cells' collection in attributes for backward compatibility.
|
|
48
|
+
* Applicable only when the default layer setup is used.
|
|
49
|
+
*/
|
|
50
|
+
this.attributes.cells = this.getLayer(DEFAULT_LAYER_ID).cellCollection;
|
|
86
51
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
this.on('change:z', this._sortOnChangeZ, this);
|
|
52
|
+
// Controller that manages communication between the graph and its layers.
|
|
53
|
+
this.layersController = new GraphLayersController({ graph: this });
|
|
90
54
|
|
|
91
|
-
// `
|
|
55
|
+
// `Graph` keeps an internal data structure (an adjacency list)
|
|
92
56
|
// for fast graph queries. All changes that affect the structure of the graph
|
|
93
57
|
// must be reflected in the `al` object. This object provides fast answers to
|
|
94
|
-
// questions such as "what are the
|
|
58
|
+
// questions such as "what are the neighbors of this node" or "what
|
|
95
59
|
// are the sibling links of this link".
|
|
96
|
-
|
|
97
|
-
// Outgoing edges per node. Note that we use a hash-table for the list
|
|
98
|
-
// of outgoing edges for a faster lookup.
|
|
99
|
-
// [nodeId] -> Object [edgeId] -> true
|
|
100
|
-
this._out = {};
|
|
101
|
-
// Ingoing edges per node.
|
|
102
|
-
// [nodeId] -> Object [edgeId] -> true
|
|
103
|
-
this._in = {};
|
|
104
|
-
// `_nodes` is useful for quick lookup of all the elements in the graph, without
|
|
105
|
-
// having to go through the whole cells array.
|
|
106
|
-
// [node ID] -> true
|
|
107
|
-
this._nodes = {};
|
|
108
|
-
// `_edges` is useful for quick lookup of all the links in the graph, without
|
|
109
|
-
// having to go through the whole cells array.
|
|
110
|
-
// [edgeId] -> true
|
|
111
|
-
this._edges = {};
|
|
60
|
+
this.topologyIndex = new GraphTopologyIndex({ layerCollection });
|
|
112
61
|
|
|
113
62
|
this._batches = {};
|
|
114
|
-
|
|
115
|
-
cells.on('add', this._restructureOnAdd, this);
|
|
116
|
-
cells.on('remove', this._restructureOnRemove, this);
|
|
117
|
-
cells.on('reset', this._restructureOnReset, this);
|
|
118
|
-
cells.on('change:source', this._restructureOnChangeSource, this);
|
|
119
|
-
cells.on('change:target', this._restructureOnChangeTarget, this);
|
|
120
|
-
cells.on('remove', this._removeCell, this);
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
_sortOnChangeZ: function() {
|
|
124
|
-
|
|
125
|
-
this.get('cells').sort();
|
|
126
63
|
},
|
|
127
64
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (cell.isLink()) {
|
|
131
|
-
this._edges[cell.id] = true;
|
|
132
|
-
var { source, target } = cell.attributes;
|
|
133
|
-
if (source.id) {
|
|
134
|
-
(this._out[source.id] || (this._out[source.id] = {}))[cell.id] = true;
|
|
135
|
-
}
|
|
136
|
-
if (target.id) {
|
|
137
|
-
(this._in[target.id] || (this._in[target.id] = {}))[cell.id] = true;
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
this._nodes[cell.id] = true;
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
_restructureOnRemove: function(cell) {
|
|
145
|
-
|
|
146
|
-
if (cell.isLink()) {
|
|
147
|
-
delete this._edges[cell.id];
|
|
148
|
-
var { source, target } = cell.attributes;
|
|
149
|
-
if (source.id && this._out[source.id] && this._out[source.id][cell.id]) {
|
|
150
|
-
delete this._out[source.id][cell.id];
|
|
151
|
-
}
|
|
152
|
-
if (target.id && this._in[target.id] && this._in[target.id][cell.id]) {
|
|
153
|
-
delete this._in[target.id][cell.id];
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
delete this._nodes[cell.id];
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
_restructureOnReset: function(cells) {
|
|
161
|
-
|
|
162
|
-
// Normalize into an array of cells. The original `cells` is GraphCells mvc collection.
|
|
163
|
-
cells = cells.models;
|
|
164
|
-
|
|
165
|
-
this._out = {};
|
|
166
|
-
this._in = {};
|
|
167
|
-
this._nodes = {};
|
|
168
|
-
this._edges = {};
|
|
169
|
-
|
|
170
|
-
cells.forEach(this._restructureOnAdd, this);
|
|
171
|
-
},
|
|
65
|
+
toJSON: function(opt = {}) {
|
|
172
66
|
|
|
173
|
-
|
|
67
|
+
const { layerCollection } = this;
|
|
68
|
+
// Get the graph model attributes as a base JSON.
|
|
69
|
+
const json = Model.prototype.toJSON.apply(this, arguments);
|
|
174
70
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
delete this._out[prevSource.id][link.id];
|
|
178
|
-
}
|
|
179
|
-
var source = link.attributes.source;
|
|
180
|
-
if (source.id) {
|
|
181
|
-
(this._out[source.id] || (this._out[source.id] = {}))[link.id] = true;
|
|
182
|
-
}
|
|
183
|
-
},
|
|
71
|
+
// Add `cells` array holding all the cells in the graph.
|
|
72
|
+
json.cells = this.getCells().map(cell => cell.toJSON(opt.cellAttributes));
|
|
184
73
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
var target = link.get('target');
|
|
192
|
-
if (target.id) {
|
|
193
|
-
(this._in[target.id] || (this._in[target.id] = {}))[link.id] = true;
|
|
74
|
+
if (this.legacyMode) {
|
|
75
|
+
// Backwards compatibility for legacy setup
|
|
76
|
+
// with single default layer 'cells'.
|
|
77
|
+
// In this case, we do not need to export layers.
|
|
78
|
+
return json;
|
|
194
79
|
}
|
|
195
|
-
},
|
|
196
80
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
getOutboundEdges: function(node) {
|
|
81
|
+
// Add `layers` array holding all the layers in the graph.
|
|
82
|
+
json.layers = layerCollection.toJSON();
|
|
200
83
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Return all inbound edges for the node. Return value is an object
|
|
205
|
-
// of the form: [edgeId] -> true
|
|
206
|
-
getInboundEdges: function(node) {
|
|
207
|
-
|
|
208
|
-
return (this._in && this._in[node]) || {};
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
toJSON: function(opt = {}) {
|
|
84
|
+
// Add `defaultLayer` property indicating the default layer ID.
|
|
85
|
+
json.defaultLayer = this.defaultLayerId;
|
|
212
86
|
|
|
213
|
-
// JointJS does not recursively call `toJSON()` on attributes that are themselves models/collections.
|
|
214
|
-
// It just clones the attributes. Therefore, we must call `toJSON()` on the cells collection explicitly.
|
|
215
|
-
var json = Model.prototype.toJSON.apply(this, arguments);
|
|
216
|
-
json.cells = this.get('cells').toJSON(opt.cellAttributes);
|
|
217
87
|
return json;
|
|
218
88
|
},
|
|
219
89
|
|
|
220
90
|
fromJSON: function(json, opt) {
|
|
91
|
+
const { cells, layers, defaultLayer, ...attributes } = json;
|
|
221
92
|
|
|
222
|
-
if (!
|
|
223
|
-
|
|
93
|
+
if (!cells) {
|
|
224
94
|
throw new Error('Graph JSON must contain cells array.');
|
|
225
95
|
}
|
|
226
96
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
set: function(key, val, opt) {
|
|
97
|
+
// The `fromJSON` should trigger a single 'reset' event at the end.
|
|
98
|
+
// Set all attributes silently for now.
|
|
99
|
+
this.set(attributes, { silent: true });
|
|
231
100
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
attrs = key;
|
|
237
|
-
opt = val;
|
|
238
|
-
} else {
|
|
239
|
-
(attrs = {})[key] = val;
|
|
101
|
+
if (layers) {
|
|
102
|
+
// Reset the layers collection
|
|
103
|
+
// (`layers:reset` is not forwarded to the graph).
|
|
104
|
+
this._resetLayers(layers, defaultLayer, opt);
|
|
240
105
|
}
|
|
241
106
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this.resetCells(
|
|
245
|
-
attrs = util.omit(attrs, 'cells');
|
|
107
|
+
if (cells) {
|
|
108
|
+
// Reset the cells collection and trigger the 'reset' event.
|
|
109
|
+
this.resetCells(cells, opt);
|
|
246
110
|
}
|
|
247
111
|
|
|
248
|
-
|
|
249
|
-
return Model.prototype.set.call(this, attrs, opt);
|
|
112
|
+
return this;
|
|
250
113
|
},
|
|
251
114
|
|
|
115
|
+
/** @deprecated */
|
|
252
116
|
clear: function(opt) {
|
|
253
|
-
|
|
254
117
|
opt = util.assign({}, opt, { clear: true });
|
|
255
118
|
|
|
256
|
-
|
|
119
|
+
const cells = this.getCells();
|
|
257
120
|
|
|
258
|
-
if (
|
|
121
|
+
if (cells.length === 0) return this;
|
|
259
122
|
|
|
260
123
|
this.startBatch('clear', opt);
|
|
261
124
|
|
|
262
|
-
|
|
263
|
-
var cells = collection.sortBy(function(cell) {
|
|
125
|
+
const sortedCells = util.sortBy(cells, (cell) => {
|
|
264
126
|
return cell.isLink() ? 1 : 2;
|
|
265
127
|
});
|
|
266
128
|
|
|
267
129
|
do {
|
|
268
|
-
|
|
269
130
|
// Remove all the cells one by one.
|
|
270
131
|
// Note that all the links are removed first, so it's
|
|
271
132
|
// safe to remove the elements without removing the connected
|
|
272
133
|
// links first.
|
|
273
|
-
|
|
134
|
+
this.layerCollection.removeCell(sortedCells.shift(), opt);
|
|
274
135
|
|
|
275
|
-
} while (
|
|
136
|
+
} while (sortedCells.length > 0);
|
|
276
137
|
|
|
277
138
|
this.stopBatch('clear');
|
|
278
139
|
|
|
279
140
|
return this;
|
|
280
141
|
},
|
|
281
142
|
|
|
282
|
-
_prepareCell: function(
|
|
143
|
+
_prepareCell: function(cellInit, opt) {
|
|
283
144
|
|
|
284
|
-
let
|
|
285
|
-
if (
|
|
286
|
-
|
|
145
|
+
let cellAttributes;
|
|
146
|
+
if (cellInit[CELL_MARKER]) {
|
|
147
|
+
cellAttributes = cellInit.attributes;
|
|
287
148
|
} else {
|
|
288
|
-
|
|
149
|
+
cellAttributes = cellInit;
|
|
289
150
|
}
|
|
290
151
|
|
|
291
|
-
if (!util.isString(
|
|
152
|
+
if (!util.isString(cellAttributes.type)) {
|
|
292
153
|
throw new TypeError('dia.Graph: cell type must be a string.');
|
|
293
154
|
}
|
|
294
155
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
156
|
+
// Backward compatibility: prior v4.2, z-index was not set during reset.
|
|
157
|
+
if (opt && opt.ensureZIndex) {
|
|
158
|
+
if (cellAttributes.z === undefined) {
|
|
159
|
+
const layerId = cellAttributes[config.layerAttribute] || this.defaultLayerId;
|
|
160
|
+
const zIndex = this.maxZIndex(layerId) + 1;
|
|
161
|
+
if (cellInit[CELL_MARKER]) {
|
|
162
|
+
// Set with event in case there is a listener
|
|
163
|
+
// directly on the cell instance
|
|
164
|
+
// (the cell is not part of graph yet)
|
|
165
|
+
cellInit.set('z', zIndex, opt);
|
|
166
|
+
} else {
|
|
167
|
+
cellAttributes.z = zIndex;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
299
171
|
|
|
300
|
-
|
|
301
|
-
return firstCell ? (firstCell.get('z') || 0) : 0;
|
|
172
|
+
return cellInit;
|
|
302
173
|
},
|
|
303
174
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return lastCell ? (lastCell.get('z') || 0) : 0;
|
|
175
|
+
minZIndex: function(layerId = this.defaultLayerId) {
|
|
176
|
+
const layer = this.getLayer(layerId);
|
|
177
|
+
return layer.cellCollection.minZIndex();
|
|
308
178
|
},
|
|
309
179
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return this.addCells(cell, opt);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (cell instanceof Model) {
|
|
318
|
-
|
|
319
|
-
if (!cell.has('z')) {
|
|
320
|
-
cell.set('z', this.maxZIndex() + 1);
|
|
321
|
-
}
|
|
180
|
+
maxZIndex: function(layerId = this.defaultLayerId) {
|
|
181
|
+
const layer = this.getLayer(layerId);
|
|
182
|
+
return layer.cellCollection.maxZIndex();
|
|
183
|
+
},
|
|
322
184
|
|
|
323
|
-
|
|
185
|
+
addCell: function(cellInit, options) {
|
|
324
186
|
|
|
325
|
-
|
|
187
|
+
if (Array.isArray(cellInit)) {
|
|
188
|
+
return this.addCells(cellInit, options);
|
|
326
189
|
}
|
|
327
190
|
|
|
328
|
-
this.
|
|
191
|
+
this._prepareCell(cellInit, { ...options, ensureZIndex: true });
|
|
192
|
+
this.layerCollection.addCellToLayer(cellInit, this.getCellLayerId(cellInit), options);
|
|
329
193
|
|
|
330
194
|
return this;
|
|
331
195
|
},
|
|
@@ -338,62 +202,290 @@ export const Graph = Model.extend({
|
|
|
338
202
|
opt.maxPosition = opt.position = cells.length - 1;
|
|
339
203
|
|
|
340
204
|
this.startBatch('add', opt);
|
|
341
|
-
cells.forEach(
|
|
205
|
+
cells.forEach((cell) => {
|
|
342
206
|
this.addCell(cell, opt);
|
|
343
207
|
opt.position--;
|
|
344
|
-
}
|
|
208
|
+
});
|
|
345
209
|
this.stopBatch('add', opt);
|
|
346
210
|
|
|
347
211
|
return this;
|
|
348
212
|
},
|
|
349
213
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
214
|
+
/**
|
|
215
|
+
* @public
|
|
216
|
+
* @description Reset the cells in the graph.
|
|
217
|
+
* Useful for bulk operations and optimizations.
|
|
218
|
+
*/
|
|
219
|
+
resetCells: function(cellInits, options) {
|
|
220
|
+
const { layerCollection } = this;
|
|
221
|
+
// Note: `cellInits` is always an array and `options` is always an object.
|
|
222
|
+
// See `wrappers.cells` at the end of this file.
|
|
223
|
+
|
|
224
|
+
// When resetting cells, do not set z-index if not provided.
|
|
225
|
+
const prepareOptions = { ...options, ensureZIndex: false };
|
|
226
|
+
|
|
227
|
+
// Initialize a map of layer IDs to arrays of cells
|
|
228
|
+
const layerCellsMap = layerCollection.reduce((map, layer) => {
|
|
229
|
+
map[layer.id] = [];
|
|
230
|
+
return map;
|
|
231
|
+
}, {});
|
|
232
|
+
|
|
233
|
+
// Distribute cells into their respective layers
|
|
234
|
+
for (let i = 0; i < cellInits.length; i++) {
|
|
235
|
+
const cellInit = cellInits[i];
|
|
236
|
+
const layerId = this.getCellLayerId(cellInit);
|
|
237
|
+
if (layerId in layerCellsMap) {
|
|
238
|
+
this._prepareCell(cellInit, prepareOptions);
|
|
239
|
+
layerCellsMap[layerId].push(cellInit);
|
|
240
|
+
} else {
|
|
241
|
+
throw new Error(`dia.Graph: Layer "${layerId}" does not exist.`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
354
244
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
245
|
+
// Reset each layer's cell collection with the corresponding cells.
|
|
246
|
+
layerCollection.each(layer => {
|
|
247
|
+
layer.cellCollection.reset(layerCellsMap[layer.id], options);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Trigger a single `reset` event on the graph
|
|
251
|
+
// (while multiple `reset` events are triggered on layers).
|
|
252
|
+
// Backwards compatibility: use default layer collection
|
|
253
|
+
// The `collection` parameter is retained for backwards compatibility,
|
|
254
|
+
// and it is subject to removal in future releases.
|
|
255
|
+
this.trigger('reset', this.getDefaultLayer().cellCollection, options);
|
|
359
256
|
|
|
360
257
|
return this;
|
|
361
258
|
},
|
|
362
259
|
|
|
363
|
-
|
|
260
|
+
/**
|
|
261
|
+
* @public
|
|
262
|
+
* @description Get the layer ID in which the cell resides.
|
|
263
|
+
* Cells without an explicit layer are assigned to the default layer.
|
|
264
|
+
* @param {dia.Cell | Object} cellInit - Cell model or attributes.
|
|
265
|
+
* @returns {string} - The layer ID.
|
|
266
|
+
*/
|
|
267
|
+
getCellLayerId: function(cellInit) {
|
|
268
|
+
if (!cellInit) {
|
|
269
|
+
throw new Error('dia.Graph: No cell provided.');
|
|
270
|
+
}
|
|
271
|
+
const cellAttributes = cellInit[CELL_MARKER]
|
|
272
|
+
? cellInit.attributes
|
|
273
|
+
: cellInit;
|
|
274
|
+
return cellAttributes[config.layerAttribute] || this.defaultLayerId;
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @protected
|
|
279
|
+
* @description Reset the layers in the graph.
|
|
280
|
+
* It assumes the existing cells have been removed beforehand
|
|
281
|
+
* or can be discarded.
|
|
282
|
+
*/
|
|
283
|
+
_resetLayers: function(layers, defaultLayerId, options = {}) {
|
|
284
|
+
if (!Array.isArray(layers) || layers.length === 0) {
|
|
285
|
+
throw new Error('dia.Graph: At least one layer must be defined.');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Resetting layers disables legacy mode
|
|
289
|
+
this.legacyMode = false;
|
|
364
290
|
|
|
365
|
-
|
|
291
|
+
this.layerCollection.reset(layers, { ...options, graph: this.cid });
|
|
366
292
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
293
|
+
// If no default layer is specified, use the first layer as default
|
|
294
|
+
if (defaultLayerId) {
|
|
295
|
+
// The default layer must be one of the defined layers
|
|
296
|
+
if (!this.hasLayer(defaultLayerId)) {
|
|
297
|
+
throw new Error(`dia.Graph: default layer "${defaultLayerId}" does not exist.`);
|
|
298
|
+
}
|
|
299
|
+
this.defaultLayerId = defaultLayerId;
|
|
300
|
+
} else {
|
|
301
|
+
this.defaultLayerId = this.layerCollection.at(0).id;
|
|
370
302
|
}
|
|
371
303
|
|
|
372
304
|
return this;
|
|
373
305
|
},
|
|
374
306
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
307
|
+
/**
|
|
308
|
+
* @public
|
|
309
|
+
* @description Remove multiple cells from the graph.
|
|
310
|
+
* @param {Array<dia.Cell | dia.Cell.ID>} cellRefs - Array of cell references (models or IDs) to remove.
|
|
311
|
+
* @param {Object} [options] - Removal options. See {@link dia.Graph#removeCell}.
|
|
312
|
+
*/
|
|
313
|
+
removeCells: function(cellRefs, options) {
|
|
314
|
+
if (!cellRefs.length) return this;
|
|
315
|
+
// Remove multiple cells in a single batch
|
|
316
|
+
this.startBatch('remove');
|
|
317
|
+
for (const cellRef of cellRefs) {
|
|
318
|
+
if (!cellRef) continue;
|
|
319
|
+
let cell;
|
|
320
|
+
if (cellRef[CELL_MARKER]) {
|
|
321
|
+
cell = cellRef;
|
|
322
|
+
} else {
|
|
323
|
+
cell = this.getCell(cellRef);
|
|
324
|
+
if (!cell) {
|
|
325
|
+
// The cell might have been already removed (embedded cell, connected link, etc.)
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
this.layerCollection.removeCell(cell, options);
|
|
330
|
+
}
|
|
331
|
+
this.stopBatch('remove');
|
|
332
|
+
return this;
|
|
333
|
+
},
|
|
378
334
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
335
|
+
/**
|
|
336
|
+
* @protected
|
|
337
|
+
* @description Replace an existing cell with a new cell.
|
|
338
|
+
*/
|
|
339
|
+
_replaceCell: function(currentCell, newCellInit, opt = {}) {
|
|
340
|
+
const batchName = 'replace-cell';
|
|
341
|
+
const replaceOptions = { ...opt, replace: true };
|
|
342
|
+
this.startBatch(batchName, opt);
|
|
343
|
+
// 1. Remove the cell without removing connected links or embedded cells.
|
|
344
|
+
this.layerCollection.removeCell(currentCell, replaceOptions);
|
|
345
|
+
|
|
346
|
+
const newCellInitAttributes = (newCellInit[CELL_MARKER])
|
|
347
|
+
? newCellInit.attributes
|
|
348
|
+
: newCellInit;
|
|
349
|
+
// 2. Combine the current cell attributes with the new cell attributes
|
|
350
|
+
const replacementCellAttributes = Object.assign({}, currentCell.attributes, newCellInitAttributes);
|
|
351
|
+
let replacement;
|
|
352
|
+
|
|
353
|
+
if (newCellInit[CELL_MARKER]) {
|
|
354
|
+
// If the new cell is a model, set the merged attributes on the model
|
|
355
|
+
newCellInit.set(replacementCellAttributes, replaceOptions);
|
|
356
|
+
replacement = newCellInit;
|
|
357
|
+
} else {
|
|
358
|
+
replacement = replacementCellAttributes;
|
|
359
|
+
}
|
|
384
360
|
|
|
385
|
-
|
|
361
|
+
// 3. Add the replacement cell
|
|
362
|
+
this.addCell(replacement, replaceOptions);
|
|
363
|
+
this.stopBatch(batchName);
|
|
364
|
+
},
|
|
386
365
|
|
|
366
|
+
/**
|
|
367
|
+
* @protected
|
|
368
|
+
* @description Synchronize a single graph cell with the provided cell (model or attributes).
|
|
369
|
+
* If the cell with the same `id` exists, it is updated. If the cell does not exist, it is added.
|
|
370
|
+
* If the existing cell type is different from the incoming cell type, the existing cell is replaced.
|
|
371
|
+
*/
|
|
372
|
+
_syncCell: function(cellInit, opt = {}) {
|
|
373
|
+
const cellAttributes = (cellInit[CELL_MARKER])
|
|
374
|
+
? cellInit.attributes
|
|
375
|
+
: cellInit;
|
|
376
|
+
const currentCell = this.getCell(cellInit.id);
|
|
377
|
+
if (currentCell) {
|
|
378
|
+
// `cellInit` is either a model or attributes object
|
|
379
|
+
if ('type' in cellAttributes && currentCell.get('type') !== cellAttributes.type) {
|
|
380
|
+
// Replace the cell if the type has changed
|
|
381
|
+
this._replaceCell(currentCell, cellInit, opt);
|
|
387
382
|
} else {
|
|
383
|
+
// Update existing cell
|
|
384
|
+
// Note: the existing cell attributes are not removed,
|
|
385
|
+
// if they're missing in `cellAttributes`.
|
|
386
|
+
currentCell.set(cellAttributes, opt);
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
// The cell does not exist yet, add it
|
|
390
|
+
this.addCell(cellInit, opt);
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @public
|
|
396
|
+
* @description Synchronize the graph cells with the provided array of cells (models or attributes).
|
|
397
|
+
*/
|
|
398
|
+
syncCells: function(cellInits, opt = {}) {
|
|
388
399
|
|
|
389
|
-
|
|
400
|
+
const batchName = 'sync-cells';
|
|
401
|
+
const { remove = false, ...setOpt } = opt;
|
|
402
|
+
|
|
403
|
+
let currentCells, newCellsMap;
|
|
404
|
+
if (remove) {
|
|
405
|
+
// We need to track existing cells to remove the missing ones later
|
|
406
|
+
currentCells = this.getCells();
|
|
407
|
+
newCellsMap = new Map();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Observe changes to the graph cells
|
|
411
|
+
let changeObserver, changedLayers;
|
|
412
|
+
const shouldSort = opt.sort !== false;
|
|
413
|
+
if (shouldSort) {
|
|
414
|
+
changeObserver = new Listener();
|
|
415
|
+
changedLayers = new Set();
|
|
416
|
+
changeObserver.listenTo(this, {
|
|
417
|
+
'add': (cell) => {
|
|
418
|
+
changedLayers.add(this.getCellLayerId(cell));
|
|
419
|
+
},
|
|
420
|
+
'change': (cell) => {
|
|
421
|
+
if (cell.hasChanged(config.layerAttribute) || cell.hasChanged('z')) {
|
|
422
|
+
changedLayers.add(this.getCellLayerId(cell));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
this.startBatch(batchName, opt);
|
|
429
|
+
|
|
430
|
+
// Prevent multiple sorts during sync
|
|
431
|
+
setOpt.sort = false;
|
|
432
|
+
|
|
433
|
+
// Add or update incoming cells
|
|
434
|
+
for (const cellInit of cellInits) {
|
|
435
|
+
if (remove) {
|
|
436
|
+
// only track existence
|
|
437
|
+
newCellsMap.set(cellInit.id, true);
|
|
390
438
|
}
|
|
439
|
+
this._syncCell(cellInit, setOpt);
|
|
391
440
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
441
|
+
|
|
442
|
+
if (remove) {
|
|
443
|
+
// Remove cells not present in the incoming array
|
|
444
|
+
for (const cell of currentCells) {
|
|
445
|
+
if (!newCellsMap.has(cell.id)) {
|
|
446
|
+
this.layerCollection.removeCell(cell, setOpt);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (shouldSort) {
|
|
452
|
+
// Sort layers that had changes affecting z-index or layer
|
|
453
|
+
changeObserver.stopListening();
|
|
454
|
+
for (const layerId of changedLayers) {
|
|
455
|
+
this.getLayer(layerId).cellCollection.sort(opt);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
this.stopBatch(batchName);
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* @public
|
|
464
|
+
* @description Remove a cell from the graph.
|
|
465
|
+
* @param {dia.Cell} cell
|
|
466
|
+
* @param {Object} [options]
|
|
467
|
+
* @param {boolean} [options.disconnectLinks=false] - If `true`, the connected links are
|
|
468
|
+
* disconnected instead of removed.
|
|
469
|
+
* @param {boolean} [options.clear=false] - If `true`, the connected links
|
|
470
|
+
* are kept. @internal
|
|
471
|
+
* @param {boolean} [options.replace=false] - If `true`, the connected links and
|
|
472
|
+
* embedded cells are kept. @internal
|
|
473
|
+
* @throws Will throw an error if no cell is provided
|
|
474
|
+
* @throws Will throw an error if the ID of the cell to remove
|
|
475
|
+
* does not exist in the graph
|
|
476
|
+
**/
|
|
477
|
+
removeCell: function(cellRef, options) {
|
|
478
|
+
if (!cellRef) {
|
|
479
|
+
throw new Error('dia.Graph: no cell provided.');
|
|
480
|
+
}
|
|
481
|
+
const cell = cellRef[CELL_MARKER] ? cellRef : this.getCell(cellRef);
|
|
482
|
+
if (!cell) {
|
|
483
|
+
throw new Error('dia.Graph: cell to remove does not exist in the graph.');
|
|
484
|
+
}
|
|
485
|
+
if (cell.graph !== this) return;
|
|
486
|
+
this.startBatch('remove');
|
|
487
|
+
cell.collection.remove(cell, options);
|
|
488
|
+
this.stopBatch('remove');
|
|
397
489
|
},
|
|
398
490
|
|
|
399
491
|
transferCellEmbeds: function(sourceCell, targetCell, opt = {}) {
|
|
@@ -429,35 +521,249 @@ export const Graph = Model.extend({
|
|
|
429
521
|
this.stopBatch(batchName);
|
|
430
522
|
},
|
|
431
523
|
|
|
432
|
-
|
|
433
|
-
|
|
524
|
+
/**
|
|
525
|
+
* @private
|
|
526
|
+
* Helper method for addLayer and moveLayer methods
|
|
527
|
+
*/
|
|
528
|
+
_getBeforeLayerIdFromOptions(options, layer = null) {
|
|
529
|
+
let { before = null, index } = options;
|
|
530
|
+
|
|
531
|
+
if (before && index !== undefined) {
|
|
532
|
+
throw new Error('dia.Graph: Options "before" and "index" are mutually exclusive.');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let computedBefore;
|
|
536
|
+
if (index !== undefined) {
|
|
537
|
+
const layersArray = this.getLayers();
|
|
538
|
+
if (index >= layersArray.length) {
|
|
539
|
+
// If index is greater than the number of layers,
|
|
540
|
+
// return before as null (move to the end).
|
|
541
|
+
computedBefore = null;
|
|
542
|
+
} else if (index < 0) {
|
|
543
|
+
// If index is negative, move to the beginning.
|
|
544
|
+
computedBefore = layersArray[0].id;
|
|
545
|
+
} else {
|
|
546
|
+
const originalIndex = layersArray.indexOf(layer);
|
|
547
|
+
if (originalIndex !== -1 && index > originalIndex) {
|
|
548
|
+
// If moving a layer upwards in the stack, we need to adjust the index
|
|
549
|
+
// to account for the layer being removed from its original position.
|
|
550
|
+
index += 1;
|
|
551
|
+
}
|
|
552
|
+
// Otherwise, get the layer ID at the specified index.
|
|
553
|
+
computedBefore = layersArray[index]?.id || null;
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
computedBefore = before;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return computedBefore;
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* @public
|
|
564
|
+
* Adds a new layer to the graph.
|
|
565
|
+
* @param {GraphLayer | GraphLayerJSON} layerInit
|
|
566
|
+
* @param {*} options
|
|
567
|
+
* @param {string | null} [options.before] - ID of the layer
|
|
568
|
+
* before which to insert the new layer. If `null`, the layer is added at the end.
|
|
569
|
+
* @param {number} [options.index] - Zero-based index to which to add the layer.
|
|
570
|
+
* @throws Will throw an error if the layer to add is invalid
|
|
571
|
+
* @throws Will throw an error if a layer with the same ID already exists
|
|
572
|
+
* @throws Will throw if `before` reference is invalid
|
|
573
|
+
*/
|
|
574
|
+
addLayer(layerInit, options = {}) {
|
|
575
|
+
if (!layerInit || !layerInit.id) {
|
|
576
|
+
throw new Error('dia.Graph: Layer to add is invalid.');
|
|
577
|
+
}
|
|
578
|
+
if (this.hasLayer(layerInit.id)) {
|
|
579
|
+
throw new Error(`dia.Graph: Layer "${layerInit.id}" already exists.`);
|
|
580
|
+
}
|
|
581
|
+
const { before = null, index, ...insertOptions } = options;
|
|
582
|
+
insertOptions.graph = this.cid;
|
|
583
|
+
|
|
584
|
+
// Adding a new layer disables legacy mode
|
|
585
|
+
this.legacyMode = false;
|
|
586
|
+
|
|
587
|
+
const beforeId = this._getBeforeLayerIdFromOptions({ before, index });
|
|
588
|
+
this.layerCollection.insert(layerInit, beforeId, insertOptions);
|
|
589
|
+
},
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* @public
|
|
593
|
+
* Moves an existing layer to a new position in the layer stack.
|
|
594
|
+
* @param {string | GraphLayer} layerRef - ID or reference of the layer to move.
|
|
595
|
+
* @param {*} options
|
|
596
|
+
* @param {string | null} [options.before] - ID of the layer
|
|
597
|
+
* before which to insert the moved layer. If `null`, the layer is moved to the end.
|
|
598
|
+
* @param {number} [options.index] - Zero-based index to which to move the layer.
|
|
599
|
+
* @throws Will throw an error if the layer to move does not exist
|
|
600
|
+
* @throws Will throw an error if `before` reference is invalid
|
|
601
|
+
* @throws Will throw an error if both `before` and `index` options are provided
|
|
602
|
+
*/
|
|
603
|
+
moveLayer(layerRef, options = {}) {
|
|
604
|
+
if (!layerRef || !this.hasLayer(layerRef)) {
|
|
605
|
+
throw new Error('dia.Graph: Layer to move does not exist.');
|
|
606
|
+
}
|
|
607
|
+
const layer = this.getLayer(layerRef);
|
|
608
|
+
const { before = null, index, ...insertOptions } = options;
|
|
609
|
+
insertOptions.graph = this.cid;
|
|
610
|
+
|
|
611
|
+
// Moving a layer disables legacy mode
|
|
612
|
+
this.legacyMode = false;
|
|
613
|
+
|
|
614
|
+
const beforeId = this._getBeforeLayerIdFromOptions({ before, index }, layer);
|
|
615
|
+
this.layerCollection.insert(layer, beforeId, insertOptions);
|
|
616
|
+
},
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* @public
|
|
620
|
+
* Removes an existing layer from the graph.
|
|
621
|
+
* @param {string | GraphLayer} layerRef - ID or reference of the layer to remove.
|
|
622
|
+
* @param {*} options
|
|
623
|
+
* @throws Will throw an error if no layer is provided
|
|
624
|
+
* @throws Will throw an error if the layer to remove does not exist
|
|
625
|
+
*/
|
|
626
|
+
removeLayer(layerRef, options = {}) {
|
|
627
|
+
if (!layerRef) {
|
|
628
|
+
throw new Error('dia.Graph: No layer provided.');
|
|
629
|
+
}
|
|
434
630
|
|
|
435
|
-
|
|
631
|
+
// The layer must exist
|
|
632
|
+
const layerId = layerRef.id ? layerRef.id : layerRef;
|
|
633
|
+
const layer = this.getLayer(layerId);
|
|
634
|
+
|
|
635
|
+
// Prevent removing the default layer
|
|
636
|
+
// Note: if there is only one layer, it is also the default layer.
|
|
637
|
+
const { id: defaultLayerId } = this.getDefaultLayer();
|
|
638
|
+
if (layerId === defaultLayerId) {
|
|
639
|
+
throw new Error('dia.Graph: default layer cannot be removed.');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// A layer with cells cannot be removed
|
|
643
|
+
if (layer.cellCollection.length > 0) {
|
|
644
|
+
throw new Error(`dia.Graph: Layer "${layerId}" cannot be removed because it is not empty.`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
this.layerCollection.remove(layerId, { ...options, graph: this.cid });
|
|
436
648
|
},
|
|
437
649
|
|
|
438
|
-
|
|
650
|
+
getDefaultLayer() {
|
|
651
|
+
return this.layerCollection.get(this.defaultLayerId);
|
|
652
|
+
},
|
|
653
|
+
|
|
654
|
+
setDefaultLayer(layerRef, options = {}) {
|
|
655
|
+
if (!layerRef) {
|
|
656
|
+
throw new Error('dia.Graph: No default layer ID provided.');
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Make sure the layer exists
|
|
660
|
+
const defaultLayerId = layerRef.id ? layerRef.id : layerRef;
|
|
661
|
+
const defaultLayer = this.getLayer(defaultLayerId);
|
|
662
|
+
|
|
663
|
+
// If the default layer is not changing, do nothing
|
|
664
|
+
const currentDefaultLayerId = this.defaultLayerId;
|
|
665
|
+
if (defaultLayerId === currentDefaultLayerId) {
|
|
666
|
+
// The default layer stays the same
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Get all cells that belong to the current default layer implicitly
|
|
671
|
+
const implicitLayerCells = this.getImplicitLayerCells();
|
|
439
672
|
|
|
440
|
-
|
|
673
|
+
// Set the new default layer ID
|
|
674
|
+
this.defaultLayerId = defaultLayerId;
|
|
675
|
+
|
|
676
|
+
const batchName = 'default-layer-change';
|
|
677
|
+
this.startBatch(batchName, options);
|
|
678
|
+
|
|
679
|
+
if (implicitLayerCells.length > 0) {
|
|
680
|
+
// Reassign any cells lacking an explicit layer to the new default layer.
|
|
681
|
+
// Do not sort yet, wait until all cells are moved.
|
|
682
|
+
const moveOptions = { ...options, sort: false };
|
|
683
|
+
for (const cell of implicitLayerCells) {
|
|
684
|
+
this.layerCollection.moveCellBetweenLayers(cell, defaultLayerId, moveOptions);
|
|
685
|
+
}
|
|
686
|
+
// Now sort the new default layer
|
|
687
|
+
if (options.sort !== false) {
|
|
688
|
+
defaultLayer.cellCollection.sort(options);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Pretend to trigger the event on the layer itself.
|
|
693
|
+
// It will bubble up as `layer:default` event on the graph.
|
|
694
|
+
defaultLayer.trigger(defaultLayer.eventPrefix + 'default', defaultLayer, {
|
|
695
|
+
...options,
|
|
696
|
+
previousDefaultLayerId: currentDefaultLayerId
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
this.stopBatch(batchName, options);
|
|
441
700
|
},
|
|
442
701
|
|
|
443
|
-
|
|
702
|
+
/**
|
|
703
|
+
* @protected
|
|
704
|
+
* @description Get all cells that do not have an explicit layer assigned.
|
|
705
|
+
* These cells belong to the default layer implicitly.
|
|
706
|
+
* @return {Array<dia.Cell>} Array of cells without an explicit layer.
|
|
707
|
+
*/
|
|
708
|
+
getImplicitLayerCells() {
|
|
709
|
+
return this.getDefaultLayer().cellCollection.filter(cell => {
|
|
710
|
+
return cell.get(config.layerAttribute) == null;
|
|
711
|
+
});
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
getLayer(layerId) {
|
|
715
|
+
if (!this.hasLayer(layerId)) {
|
|
716
|
+
throw new Error(`dia.Graph: Layer "${layerId}" does not exist.`);
|
|
717
|
+
}
|
|
718
|
+
return this.layerCollection.get(layerId);
|
|
719
|
+
},
|
|
444
720
|
|
|
445
|
-
|
|
721
|
+
hasLayer(layerRef) {
|
|
722
|
+
return this.layerCollection.has(layerRef);
|
|
446
723
|
},
|
|
447
724
|
|
|
448
|
-
|
|
725
|
+
getLayers() {
|
|
726
|
+
return this.layerCollection.toArray();
|
|
727
|
+
},
|
|
449
728
|
|
|
450
|
-
|
|
729
|
+
getCell: function(cellRef) {
|
|
730
|
+
return this.layerCollection.getCell(cellRef);
|
|
451
731
|
},
|
|
452
732
|
|
|
453
|
-
|
|
733
|
+
getCells: function() {
|
|
734
|
+
return this.layerCollection.getCells();
|
|
735
|
+
},
|
|
454
736
|
|
|
455
|
-
|
|
737
|
+
getElements: function() {
|
|
738
|
+
|
|
739
|
+
return this.getCells().filter(cell => cell.isElement());
|
|
456
740
|
},
|
|
457
741
|
|
|
458
|
-
|
|
742
|
+
getLinks: function() {
|
|
743
|
+
|
|
744
|
+
return this.getCells().filter(cell => cell.isLink());
|
|
745
|
+
},
|
|
459
746
|
|
|
460
|
-
|
|
747
|
+
getFirstCell: function(layerId) {
|
|
748
|
+
let layer;
|
|
749
|
+
if (!layerId) {
|
|
750
|
+
// Get the first cell from the bottom-most layer
|
|
751
|
+
layer = this.getLayers().at(0);
|
|
752
|
+
} else {
|
|
753
|
+
layer = this.getLayer(layerId);
|
|
754
|
+
}
|
|
755
|
+
return layer.cellCollection.models.at(0);
|
|
756
|
+
},
|
|
757
|
+
|
|
758
|
+
getLastCell: function(layerId) {
|
|
759
|
+
let layer;
|
|
760
|
+
if (!layerId) {
|
|
761
|
+
// Get the last cell from the top-most layer
|
|
762
|
+
layer = this.getLayers().at(-1);
|
|
763
|
+
} else {
|
|
764
|
+
layer = this.getLayer(layerId);
|
|
765
|
+
}
|
|
766
|
+
return layer.cellCollection.models.at(-1);
|
|
461
767
|
},
|
|
462
768
|
|
|
463
769
|
// Get all inbound and outbound links connected to the cell `model`.
|
|
@@ -486,12 +792,13 @@ export const Graph = Model.extend({
|
|
|
486
792
|
}
|
|
487
793
|
|
|
488
794
|
function addOutbounds(graph, model) {
|
|
489
|
-
util.forIn(graph.getOutboundEdges(model.id), function(_, edge) {
|
|
795
|
+
util.forIn(graph.topologyIndex.getOutboundEdges(model.id), function(_, edge) {
|
|
490
796
|
// skip links that were already added
|
|
491
797
|
// (those must be self-loop links)
|
|
492
798
|
// (because they are inbound and outbound edges of the same two elements)
|
|
493
799
|
if (edges[edge]) return;
|
|
494
800
|
var link = graph.getCell(edge);
|
|
801
|
+
if (!link) return;
|
|
495
802
|
links.push(link);
|
|
496
803
|
edges[edge] = true;
|
|
497
804
|
if (indirect) {
|
|
@@ -511,12 +818,13 @@ export const Graph = Model.extend({
|
|
|
511
818
|
}
|
|
512
819
|
|
|
513
820
|
function addInbounds(graph, model) {
|
|
514
|
-
util.forIn(graph.getInboundEdges(model.id), function(_, edge) {
|
|
821
|
+
util.forIn(graph.topologyIndex.getInboundEdges(model.id), function(_, edge) {
|
|
515
822
|
// skip links that were already added
|
|
516
823
|
// (those must be self-loop links)
|
|
517
824
|
// (because they are inbound and outbound edges of the same two elements)
|
|
518
825
|
if (edges[edge]) return;
|
|
519
826
|
var link = graph.getCell(edge);
|
|
827
|
+
if (!link) return;
|
|
520
828
|
links.push(link);
|
|
521
829
|
edges[edge] = true;
|
|
522
830
|
if (indirect) {
|
|
@@ -551,7 +859,7 @@ export const Graph = Model.extend({
|
|
|
551
859
|
embeddedCells.forEach(function(cell) {
|
|
552
860
|
if (cell.isLink()) return;
|
|
553
861
|
if (outbound) {
|
|
554
|
-
util.forIn(this.getOutboundEdges(cell.id), function(exists, edge) {
|
|
862
|
+
util.forIn(this.topologyIndex.getOutboundEdges(cell.id), function(exists, edge) {
|
|
555
863
|
if (!edges[edge]) {
|
|
556
864
|
var edgeCell = this.getCell(edge);
|
|
557
865
|
var { source, target } = edgeCell.attributes;
|
|
@@ -571,7 +879,7 @@ export const Graph = Model.extend({
|
|
|
571
879
|
}.bind(this));
|
|
572
880
|
}
|
|
573
881
|
if (inbound) {
|
|
574
|
-
util.forIn(this.getInboundEdges(cell.id), function(exists, edge) {
|
|
882
|
+
util.forIn(this.topologyIndex.getInboundEdges(cell.id), function(exists, edge) {
|
|
575
883
|
if (!edges[edge]) {
|
|
576
884
|
var edgeCell = this.getCell(edge);
|
|
577
885
|
var { source, target } = edgeCell.attributes;
|
|
@@ -880,38 +1188,22 @@ export const Graph = Model.extend({
|
|
|
880
1188
|
|
|
881
1189
|
// Get all the roots of the graph. Time complexity: O(|V|).
|
|
882
1190
|
getSources: function() {
|
|
883
|
-
|
|
884
|
-
var sources = [];
|
|
885
|
-
util.forIn(this._nodes, function(exists, node) {
|
|
886
|
-
if (!this._in[node] || util.isEmpty(this._in[node])) {
|
|
887
|
-
sources.push(this.getCell(node));
|
|
888
|
-
}
|
|
889
|
-
}.bind(this));
|
|
890
|
-
return sources;
|
|
1191
|
+
return this.topologyIndex.getSourceNodes().map(nodeId => this.getCell(nodeId));
|
|
891
1192
|
},
|
|
892
1193
|
|
|
893
1194
|
// Get all the leafs of the graph. Time complexity: O(|V|).
|
|
894
1195
|
getSinks: function() {
|
|
895
|
-
|
|
896
|
-
var sinks = [];
|
|
897
|
-
util.forIn(this._nodes, function(exists, node) {
|
|
898
|
-
if (!this._out[node] || util.isEmpty(this._out[node])) {
|
|
899
|
-
sinks.push(this.getCell(node));
|
|
900
|
-
}
|
|
901
|
-
}.bind(this));
|
|
902
|
-
return sinks;
|
|
1196
|
+
return this.topologyIndex.getSinkNodes().map(nodeId => this.getCell(nodeId));
|
|
903
1197
|
},
|
|
904
1198
|
|
|
905
1199
|
// Return `true` if `element` is a root. Time complexity: O(1).
|
|
906
1200
|
isSource: function(element) {
|
|
907
|
-
|
|
908
|
-
return !this._in[element.id] || util.isEmpty(this._in[element.id]);
|
|
1201
|
+
return this.topologyIndex.isSourceNode(element.id);
|
|
909
1202
|
},
|
|
910
1203
|
|
|
911
1204
|
// Return `true` if `element` is a leaf. Time complexity: O(1).
|
|
912
1205
|
isSink: function(element) {
|
|
913
|
-
|
|
914
|
-
return !this._out[element.id] || util.isEmpty(this._out[element.id]);
|
|
1206
|
+
return this.topologyIndex.isSinkNode(element.id);
|
|
915
1207
|
},
|
|
916
1208
|
|
|
917
1209
|
// Return `true` is `elementB` is a successor of `elementA`. Return `false` otherwise.
|
|
@@ -987,9 +1279,10 @@ export const Graph = Model.extend({
|
|
|
987
1279
|
},
|
|
988
1280
|
|
|
989
1281
|
// Remove links connected to the cell `model` completely.
|
|
990
|
-
removeLinks: function(
|
|
991
|
-
|
|
992
|
-
|
|
1282
|
+
removeLinks: function(cell, opt) {
|
|
1283
|
+
this.getConnectedLinks(cell).forEach(link => {
|
|
1284
|
+
this.layerCollection.removeCell(link, opt);
|
|
1285
|
+
});
|
|
993
1286
|
},
|
|
994
1287
|
|
|
995
1288
|
// Find all cells at given point
|