@joint/core 4.2.0-alpha.1 → 4.2.0-beta.2

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.
@@ -1,4 +1,4 @@
1
- /*! JointJS v4.2.0-alpha.1 (2025-09-25) - JavaScript diagramming library
1
+ /*! JointJS v4.2.0-beta.2 (2025-11-04) - JavaScript diagramming library
2
2
 
3
3
  This Source Code Form is subject to the terms of the Mozilla Public
4
4
  License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12517,7 +12517,9 @@ var joint = (function (exports) {
12517
12517
  defaultTheme: 'default',
12518
12518
  // The maximum delay required for two consecutive touchend events to be interpreted
12519
12519
  // as a double-tap.
12520
- doubleTapInterval: 300
12520
+ doubleTapInterval: 300,
12521
+ // Name of the attribute used to store the layer id on the cell model.
12522
+ layerAttribute: 'layer'
12521
12523
  };
12522
12524
 
12523
12525
  // TODO: should not read config outside the mvc package
@@ -14447,16 +14449,12 @@ var joint = (function (exports) {
14447
14449
  var Model = function (attributes, options) {
14448
14450
  var attrs = attributes || {};
14449
14451
  options || (options = {});
14452
+ this.eventPrefix = options.eventPrefix || '';
14450
14453
  this.preinitialize.apply(this, arguments);
14451
14454
  this.cid = uniqueId(this.cidPrefix);
14452
14455
  this.attributes = {};
14453
14456
  if (options.collection) this.collection = options.collection;
14454
- var attributeDefaults = result(this, 'defaults');
14455
-
14456
- // Just _.defaults would work fine, but the additional _.extends
14457
- // is in there for historical reasons. See #3843.
14458
- attrs = defaults(assign({}, attributeDefaults, attrs), attributeDefaults);
14459
- this.set(attrs, options);
14457
+ this._setDefaults(attrs, options);
14460
14458
  this.changed = {};
14461
14459
  this.initialize.apply(this, arguments);
14462
14460
  };
@@ -14545,14 +14543,14 @@ var joint = (function (exports) {
14545
14543
  if (this.idAttribute in attrs) {
14546
14544
  var prevId = this.id;
14547
14545
  this.id = this.get(this.idAttribute);
14548
- this.trigger('changeId', this, prevId, options);
14546
+ this.trigger(this.eventPrefix + 'changeId', this, prevId, options);
14549
14547
  }
14550
14548
 
14551
14549
  // Trigger all relevant attribute changes.
14552
14550
  if (!silent) {
14553
14551
  if (changes.length) this._pending = options;
14554
14552
  for (var i = 0; i < changes.length; i++) {
14555
- this.trigger('change:' + changes[i], this, current[changes[i]], options);
14553
+ this.trigger(this.eventPrefix + 'change:' + changes[i], this, current[changes[i]], options);
14556
14554
  }
14557
14555
  }
14558
14556
 
@@ -14563,7 +14561,7 @@ var joint = (function (exports) {
14563
14561
  while (this._pending) {
14564
14562
  options = this._pending;
14565
14563
  this._pending = false;
14566
- this.trigger('change', this, options);
14564
+ this.trigger(this.eventPrefix + 'change', this, options);
14567
14565
  }
14568
14566
  }
14569
14567
  this._pending = false;
@@ -14642,6 +14640,13 @@ var joint = (function (exports) {
14642
14640
  validationError: error
14643
14641
  }));
14644
14642
  return false;
14643
+ },
14644
+ _setDefaults: function (ctorAttributes, options) {
14645
+ const attributeDefaults = result(this, 'defaults');
14646
+ // Just _.defaults would work fine, but the additional _.extends
14647
+ // is in there for historical reasons. See #3843.
14648
+ const attributes = defaults(assign({}, attributeDefaults, ctorAttributes), attributeDefaults);
14649
+ this.set(attributes, options);
14645
14650
  }
14646
14651
  });
14647
14652
 
@@ -15561,6 +15566,30 @@ var joint = (function (exports) {
15561
15566
  assign(attributesNS, offsetAttributesNS);
15562
15567
  const attributes = attributesNS;
15563
15568
 
15569
+ // Internal tags to identify objects as specific JointJS types.
15570
+ // Used instead of `instanceof` for performance and cross-frame safety.
15571
+
15572
+ // dia.Cell
15573
+ const CELL_MARKER = Symbol('joint.cellMarker');
15574
+
15575
+ // dia.CellCollection
15576
+ const CELL_COLLECTION_MARKER = Symbol('joint.cellCollectionMarker');
15577
+
15578
+ // dia.GraphLayer
15579
+ const GRAPH_LAYER_MARKER = Symbol('joint.graphLayerMarker');
15580
+
15581
+ // dia.GraphLayerCollection
15582
+ const GRAPH_LAYER_COLLECTION_MARKER = Symbol('joint.graphLayerCollectionMarker');
15583
+
15584
+ // dia.CellView
15585
+ const CELL_VIEW_MARKER = Symbol('joint.cellViewMarker');
15586
+
15587
+ // dia.LayerView
15588
+ const LAYER_VIEW_MARKER = Symbol('joint.layerViewMarker');
15589
+
15590
+ // dia.GraphLayerView
15591
+ const GRAPH_LAYER_VIEW_MARKER = Symbol('joint.graphLayerViewMarker');
15592
+
15564
15593
  // Cell base model.
15565
15594
  // --------------------------
15566
15595
 
@@ -15581,35 +15610,18 @@ var joint = (function (exports) {
15581
15610
  }
15582
15611
  }
15583
15612
  const Cell = Model.extend({
15584
- // This is the same as mvc.Model with the only difference that is uses util.merge
15585
- // instead of just _.extend. The reason is that we want to mixin attributes set in upper classes.
15586
- constructor: function (attributes, options) {
15587
- var defaults;
15588
- var attrs = attributes || {};
15589
- if (typeof this.preinitialize === 'function') {
15590
- // Check to support an older version
15591
- this.preinitialize.apply(this, arguments);
15592
- }
15593
- this.cid = uniqueId('c');
15594
- this.attributes = {};
15595
- if (options && options.collection) this.collection = options.collection;
15596
- if (options && options.parse) attrs = this.parse(attrs, options) || {};
15597
- if (defaults = result(this, 'defaults')) {
15598
- //<custom code>
15599
- // Replaced the call to _.defaults with util.merge.
15613
+ cidPrefix: 'c',
15614
+ // Default attributes are merged deeply instead of shallowly.
15615
+ _setDefaults: function (ctorAttributes, options) {
15616
+ let attributes;
15617
+ const attributeDefaults = result(this, 'defaults');
15618
+ if (attributeDefaults) {
15600
15619
  const customizer = options && options.mergeArrays === true ? false : config$3.cellDefaultsMergeStrategy || attributesMerger;
15601
- attrs = merge({}, defaults, attrs, customizer);
15602
- //</custom code>
15603
- }
15604
- this.set(attrs, options);
15605
- this.changed = {};
15606
- if (options && options.portLayoutNamespace) {
15607
- this.portLayoutNamespace = options.portLayoutNamespace;
15608
- }
15609
- if (options && options.portLabelLayoutNamespace) {
15610
- this.portLabelLayoutNamespace = options.portLabelLayoutNamespace;
15620
+ attributes = merge({}, attributeDefaults, ctorAttributes, customizer);
15621
+ } else {
15622
+ attributes = ctorAttributes;
15611
15623
  }
15612
- this.initialize.apply(this, arguments);
15624
+ this.set(attributes, options);
15613
15625
  },
15614
15626
  translate: function (dx, dy, opt) {
15615
15627
  throw new Error('Must define a translate() method.');
@@ -15649,9 +15661,9 @@ var joint = (function (exports) {
15649
15661
  }
15650
15662
  return finalAttributes;
15651
15663
  },
15652
- initialize: function (options) {
15664
+ initialize: function (attributes) {
15653
15665
  const idAttribute = this.getIdAttribute();
15654
- if (!options || options[idAttribute] === undefined) {
15666
+ if (!attributes || attributes[idAttribute] === undefined) {
15655
15667
  this.set(idAttribute, this.generateId(), {
15656
15668
  silent: true
15657
15669
  });
@@ -15722,39 +15734,24 @@ var joint = (function (exports) {
15722
15734
  this.ports = ports;
15723
15735
  },
15724
15736
  remove: function (opt = {}) {
15725
- // Store the graph in a variable because `this.graph` won't be accessible
15726
- // after `this.trigger('remove', ...)` down below.
15727
15737
  const {
15728
15738
  graph,
15729
15739
  collection
15730
15740
  } = this;
15731
- if (!graph) {
15741
+ // If the cell is part of a graph, remove it using the graph API.
15742
+ // To make sure the cell is removed in a batch operation.
15743
+ if (graph) {
15744
+ graph.removeCell(this, opt);
15745
+ } else {
15732
15746
  // The collection is a common mvc collection (not the graph collection).
15733
15747
  if (collection) collection.remove(this, opt);
15734
- return this;
15735
- }
15736
- graph.startBatch('remove');
15737
-
15738
- // First, unembed this cell from its parent cell if there is one.
15739
- const parentCell = this.getParentCell();
15740
- if (parentCell) {
15741
- parentCell.unembed(this, opt);
15742
15748
  }
15743
-
15744
- // Remove also all the cells, which were embedded into this cell
15745
- const embeddedCells = this.getEmbeddedCells();
15746
- for (let i = 0, n = embeddedCells.length; i < n; i++) {
15747
- const embed = embeddedCells[i];
15748
- if (embed) {
15749
- embed.remove(opt);
15750
- }
15751
- }
15752
- this.trigger('remove', this, graph.attributes.cells, opt);
15753
- graph.stopBatch('remove');
15754
15749
  return this;
15755
15750
  },
15756
15751
  toFront: function (opt) {
15757
- var graph = this.graph;
15752
+ const {
15753
+ graph
15754
+ } = this;
15758
15755
  if (graph) {
15759
15756
  opt = defaults(opt || {}, {
15760
15757
  foregroundEmbeds: true
@@ -15771,10 +15768,11 @@ var joint = (function (exports) {
15771
15768
  cells = [this];
15772
15769
  }
15773
15770
  const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
15774
- const maxZ = graph.maxZIndex();
15771
+ const layerId = graph.getCellLayerId(this);
15772
+ const maxZ = graph.maxZIndex(layerId);
15775
15773
  let z = maxZ - cells.length + 1;
15776
- const collection = graph.get('cells');
15777
- let shouldUpdate = collection.toArray().indexOf(sortedCells[0]) !== collection.length - cells.length;
15774
+ const layerCells = graph.getLayer(layerId).cellCollection.toArray();
15775
+ let shouldUpdate = layerCells.indexOf(sortedCells[0]) !== layerCells.length - cells.length;
15778
15776
  if (!shouldUpdate) {
15779
15777
  shouldUpdate = sortedCells.some(function (cell, index) {
15780
15778
  return cell.z() !== z + index;
@@ -15792,7 +15790,9 @@ var joint = (function (exports) {
15792
15790
  return this;
15793
15791
  },
15794
15792
  toBack: function (opt) {
15795
- var graph = this.graph;
15793
+ const {
15794
+ graph
15795
+ } = this;
15796
15796
  if (graph) {
15797
15797
  opt = defaults(opt || {}, {
15798
15798
  foregroundEmbeds: true
@@ -15809,9 +15809,10 @@ var joint = (function (exports) {
15809
15809
  cells = [this];
15810
15810
  }
15811
15811
  const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
15812
- let z = graph.minZIndex();
15813
- var collection = graph.get('cells');
15814
- let shouldUpdate = collection.toArray().indexOf(sortedCells[0]) !== 0;
15812
+ const layerId = graph.getCellLayerId(this);
15813
+ let z = graph.minZIndex(layerId);
15814
+ const layerCells = graph.getLayer(layerId).cellCollection.toArray();
15815
+ let shouldUpdate = layerCells.indexOf(sortedCells[0]) !== 0;
15815
15816
  if (!shouldUpdate) {
15816
15817
  shouldUpdate = sortedCells.some(function (cell, index) {
15817
15818
  return cell.z() !== z + index;
@@ -16174,41 +16175,42 @@ var joint = (function (exports) {
16174
16175
  opt = assign(defaults, opt);
16175
16176
  var firstFrameTime = 0;
16176
16177
  var interpolatingFunction;
16178
+ const transitionKey = Array.isArray(path) ? path.join(delim) : path;
16177
16179
  var setter = function (runtime) {
16178
16180
  var id, progress, propertyValue;
16179
16181
  firstFrameTime = firstFrameTime || runtime;
16180
16182
  runtime -= firstFrameTime;
16181
16183
  progress = runtime / opt.duration;
16182
16184
  if (progress < 1) {
16183
- this._transitionIds[path] = id = nextFrame(setter);
16185
+ this._transitionIds[transitionKey] = id = nextFrame(setter);
16184
16186
  } else {
16185
16187
  progress = 1;
16186
- delete this._transitionIds[path];
16188
+ delete this._transitionIds[transitionKey];
16187
16189
  }
16188
16190
  propertyValue = interpolatingFunction(opt.timingFunction(progress));
16189
16191
  opt.transitionId = id;
16190
16192
  this.prop(path, propertyValue, opt);
16191
- if (!id) this.trigger('transition:end', this, path);
16193
+ if (!id) this.trigger('transition:end', this, transitionKey);
16192
16194
  }.bind(this);
16193
16195
  const {
16194
16196
  _scheduledTransitionIds
16195
16197
  } = this;
16196
16198
  let initialId;
16197
16199
  var initiator = callback => {
16198
- if (_scheduledTransitionIds[path]) {
16199
- _scheduledTransitionIds[path] = without(_scheduledTransitionIds[path], initialId);
16200
- if (_scheduledTransitionIds[path].length === 0) {
16201
- delete _scheduledTransitionIds[path];
16200
+ if (_scheduledTransitionIds[transitionKey]) {
16201
+ _scheduledTransitionIds[transitionKey] = without(_scheduledTransitionIds[transitionKey], initialId);
16202
+ if (_scheduledTransitionIds[transitionKey].length === 0) {
16203
+ delete _scheduledTransitionIds[transitionKey];
16202
16204
  }
16203
16205
  }
16204
16206
  this.stopPendingTransitions(path, delim);
16205
16207
  interpolatingFunction = opt.valueFunction(getByPath(this.attributes, path, delim), value);
16206
- this._transitionIds[path] = nextFrame(callback);
16207
- this.trigger('transition:start', this, path);
16208
+ this._transitionIds[transitionKey] = nextFrame(callback);
16209
+ this.trigger('transition:start', this, transitionKey);
16208
16210
  };
16209
16211
  initialId = setTimeout(initiator, opt.delay, setter);
16210
- _scheduledTransitionIds[path] || (_scheduledTransitionIds[path] = []);
16211
- _scheduledTransitionIds[path].push(initialId);
16212
+ _scheduledTransitionIds[transitionKey] || (_scheduledTransitionIds[transitionKey] = []);
16213
+ _scheduledTransitionIds[transitionKey].push(initialId);
16212
16214
  return initialId;
16213
16215
  },
16214
16216
  getTransitions: function () {
@@ -16220,7 +16222,8 @@ var joint = (function (exports) {
16220
16222
  } = this;
16221
16223
  let transitions = Object.keys(_scheduledTransitionIds);
16222
16224
  if (path) {
16223
- const pathArray = path.split(delim);
16225
+ // Ensure all path segments are strings for `isEqual` comparison, since it strictly compares values
16226
+ const pathArray = Array.isArray(path) ? path.map(item => String(item)) : path.split(delim);
16224
16227
  transitions = transitions.filter(key => {
16225
16228
  return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
16226
16229
  });
@@ -16240,7 +16243,8 @@ var joint = (function (exports) {
16240
16243
  } = this;
16241
16244
  let transitions = Object.keys(_transitionIds);
16242
16245
  if (path) {
16243
- const pathArray = path.split(delim);
16246
+ // Ensure all path segments are strings for `isEqual` comparison, since it strictly compares values
16247
+ const pathArray = Array.isArray(path) ? path.map(item => String(item)) : path.split(delim);
16244
16248
  transitions = transitions.filter(key => {
16245
16249
  return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
16246
16250
  });
@@ -16258,7 +16262,7 @@ var joint = (function (exports) {
16258
16262
  this.stopPendingTransitions(path, delim);
16259
16263
  return this;
16260
16264
  },
16261
- // A shorcut making it easy to create constructs like the following:
16265
+ // A shortcut making it easy to create constructs like the following:
16262
16266
  // `var el = (new joint.shapes.standard.Rectangle()).addTo(graph)`.
16263
16267
  addTo: function (graph, opt) {
16264
16268
  graph.addCell(this, opt);
@@ -16340,6 +16344,29 @@ var joint = (function (exports) {
16340
16344
  .getPointRotatedAroundCenter(this.angle(), x, y)
16341
16345
  // Transform the absolute position into relative
16342
16346
  .difference(this.position());
16347
+ },
16348
+ layer: function (layerId, opt) {
16349
+ const layerAttribute = config$3.layerAttribute;
16350
+
16351
+ // Getter:
16352
+
16353
+ // If `undefined` return the current layer ID
16354
+ if (layerId === undefined) {
16355
+ return this.get(layerAttribute) || null;
16356
+ }
16357
+
16358
+ // Setter:
16359
+
16360
+ // If strictly `null` unset the layer
16361
+ if (layerId === null) {
16362
+ return this.unset(layerAttribute, opt);
16363
+ }
16364
+
16365
+ // Otherwise set the layer ID
16366
+ if (!isString(layerId)) {
16367
+ throw new Error('dia.Cell: Layer id must be a string.');
16368
+ }
16369
+ return this.set(layerAttribute, layerId, opt);
16343
16370
  }
16344
16371
  }, {
16345
16372
  getAttributeDefinition: function (attrName) {
@@ -16364,6 +16391,9 @@ var joint = (function (exports) {
16364
16391
  return Cell;
16365
16392
  }
16366
16393
  });
16394
+ Object.defineProperty(Cell.prototype, CELL_MARKER, {
16395
+ value: true
16396
+ });
16367
16397
 
16368
16398
  const wrapWith = function (object, methods, wrapper) {
16369
16399
  if (isString(wrapper)) {
@@ -17394,7 +17424,17 @@ var joint = (function (exports) {
17394
17424
  }
17395
17425
  };
17396
17426
  const elementPortPrototype = {
17397
- _initializePorts: function () {
17427
+ _initializePorts: function (options) {
17428
+ if (options) {
17429
+ // Override port layout namespaces if provided in options
17430
+ if (options.portLayoutNamespace) {
17431
+ this.portLayoutNamespace = options.portLayoutNamespace;
17432
+ }
17433
+ // Override port label layout namespaces if provided in options
17434
+ if (options.portLabelLayoutNamespace) {
17435
+ this.portLabelLayoutNamespace = options.portLabelLayoutNamespace;
17436
+ }
17437
+ }
17398
17438
  this._createPortData();
17399
17439
  this.on('change:ports', function () {
17400
17440
  this._processRemovedPort();
@@ -18076,8 +18116,8 @@ var joint = (function (exports) {
18076
18116
  },
18077
18117
  angle: 0
18078
18118
  },
18079
- initialize: function () {
18080
- this._initializePorts();
18119
+ initialize: function (attributes, options) {
18120
+ this._initializePorts(options);
18081
18121
  Cell.prototype.initialize.apply(this, arguments);
18082
18122
  },
18083
18123
  /**
@@ -21618,7 +21658,7 @@ var joint = (function (exports) {
21618
21658
  for (i = 0; i < toAdd.length; i++) {
21619
21659
  if (at != null) options.index = at + i;
21620
21660
  model = toAdd[i];
21621
- model.trigger('add', model, this, options);
21661
+ model.trigger(model.eventPrefix + 'add', model, this, options);
21622
21662
  }
21623
21663
  if (sort || orderChanged) this.trigger('sort', this, options);
21624
21664
  if (toAdd.length || toRemove.length || toMerge.length) {
@@ -21681,7 +21721,7 @@ var joint = (function (exports) {
21681
21721
  // properties, or an attributes object that is transformed through modelId.
21682
21722
  get: function (obj) {
21683
21723
  if (obj == null) return void 0;
21684
- return this._byId[obj] || this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)] || obj.cid && this._byId[obj.cid];
21724
+ return this._byId.get(obj) || this._byId.get(this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)) || obj.cid && this._byId.get(obj.cid);
21685
21725
  },
21686
21726
  // Returns `true` if the model is in the collection.
21687
21727
  has: function (obj) {
@@ -21720,7 +21760,8 @@ var joint = (function (exports) {
21720
21760
  },
21721
21761
  // Define how to uniquely identify models in the collection.
21722
21762
  modelId: function (attrs, idAttribute) {
21723
- return attrs[idAttribute || this.model.prototype.idAttribute || 'id'];
21763
+ var _this$model$prototype;
21764
+ return attrs[idAttribute || ((_this$model$prototype = this.model.prototype) === null || _this$model$prototype === void 0 ? void 0 : _this$model$prototype.idAttribute) || 'id'];
21724
21765
  },
21725
21766
  // Get an iterator of all models in this collection.
21726
21767
  values: function () {
@@ -21777,17 +21818,17 @@ var joint = (function (exports) {
21777
21818
  _reset: function () {
21778
21819
  this.length = 0;
21779
21820
  this.models = [];
21780
- this._byId = {};
21821
+ this._byId = new Map();
21781
21822
  },
21782
21823
  // Prepare a hash of attributes (or other model) to be added to this
21783
21824
  // collection.
21784
21825
  _prepareModel: function (attrs, options) {
21785
21826
  if (this._isModel(attrs)) {
21786
- if (!attrs.collection) attrs.collection = this;
21827
+ if (!options.dry && !attrs.collection) attrs.collection = this;
21787
21828
  return attrs;
21788
21829
  }
21789
21830
  options = options ? clone$1(options) : {};
21790
- options.collection = this;
21831
+ if (!options.dry) options.collection = this;
21791
21832
  var model;
21792
21833
  if (this.model.prototype) {
21793
21834
  model = new this.model(attrs, options);
@@ -21811,12 +21852,12 @@ var joint = (function (exports) {
21811
21852
 
21812
21853
  // Remove references before triggering 'remove' event to prevent an
21813
21854
  // infinite loop. #3693
21814
- delete this._byId[model.cid];
21855
+ this._byId.delete(model.cid);
21815
21856
  var id = this.modelId(model.attributes, model.idAttribute);
21816
- if (id != null) delete this._byId[id];
21857
+ if (id != null) this._byId.delete(id);
21817
21858
  if (!options.silent) {
21818
21859
  options.index = index;
21819
- model.trigger('remove', model, this, options);
21860
+ model.trigger(model.eventPrefix + 'remove', model, this, options);
21820
21861
  }
21821
21862
  removed.push(model);
21822
21863
  this._removeReference(model, options);
@@ -21831,17 +21872,17 @@ var joint = (function (exports) {
21831
21872
  },
21832
21873
  // Internal method to create a model's ties to a collection.
21833
21874
  _addReference: function (model, options) {
21834
- this._byId[model.cid] = model;
21875
+ this._byId.set(model.cid, model);
21835
21876
  var id = this.modelId(model.attributes, model.idAttribute);
21836
- if (id != null) this._byId[id] = model;
21877
+ if (id != null) this._byId.set(id, model);
21837
21878
  model.on('all', this._onModelEvent, this);
21838
21879
  },
21839
21880
  // Internal method to sever a model's ties to a collection.
21840
21881
  _removeReference: function (model, options) {
21841
- delete this._byId[model.cid];
21882
+ this._byId.delete(model.cid);
21842
21883
  var id = this.modelId(model.attributes, model.idAttribute);
21843
- if (id != null) delete this._byId[id];
21844
- if (this === model.collection) delete model.collection;
21884
+ if (id != null) this._byId.delete(id);
21885
+ if (!options.dry && this === model.collection) delete model.collection;
21845
21886
  model.off('all', this._onModelEvent, this);
21846
21887
  },
21847
21888
  // Internal method called every time a model in the set fires an event.
@@ -21850,12 +21891,12 @@ var joint = (function (exports) {
21850
21891
  // in other collections are ignored.
21851
21892
  _onModelEvent: function (event, model, collection, options) {
21852
21893
  if (model) {
21853
- if ((event === 'add' || event === 'remove') && collection !== this) return;
21894
+ if ((event === model.eventPrefix + 'add' || event === model.eventPrefix + 'remove') && collection !== this) return;
21854
21895
  if (event === 'changeId') {
21855
21896
  var prevId = this.modelId(model.previousAttributes(), model.idAttribute);
21856
21897
  var id = this.modelId(model.attributes, model.idAttribute);
21857
- if (prevId != null) delete this._byId[prevId];
21858
- if (id != null) this._byId[id] = model;
21898
+ if (prevId != null) this._byId.delete(prevId);
21899
+ if (id != null) this._byId.set(id, model);
21859
21900
  }
21860
21901
  }
21861
21902
  this.trigger.apply(this, arguments);
@@ -26681,21 +26722,181 @@ var joint = (function (exports) {
26681
26722
  topRight: topRight
26682
26723
  };
26683
26724
 
26684
- const GraphCells = Collection.extend({
26685
- initialize: function (models, opt) {
26686
- // Set the optional namespace where all model classes are defined.
26687
- if (opt.cellNamespace) {
26688
- this.cellNamespace = opt.cellNamespace;
26689
- } else {
26690
- /* eslint-disable no-undef */
26691
- this.cellNamespace = typeof joint !== 'undefined' && has(joint, 'shapes') ? joint.shapes : null;
26692
- /* eslint-enable no-undef */
26725
+ /**
26726
+ * @class GraphLayersController
26727
+ * @description Coordinates interactions between the graph and its layers.
26728
+ * Automatically moves cells between layers when the layer attribute changes.
26729
+ */
26730
+ class GraphLayersController extends Listener {
26731
+ constructor(options) {
26732
+ super(options);
26733
+
26734
+ // Make sure there are no arguments passed to the callbacks.
26735
+ // See the `mvc.Listener` documentation for more details.
26736
+ this.callbackArguments = [];
26737
+ const graph = options.graph;
26738
+ if (!graph) {
26739
+ throw new Error('GraphLayersController: "graph" option is required.');
26740
+ }
26741
+ this.graph = graph;
26742
+ this.layerCollection = graph.layerCollection;
26743
+ this.startListening();
26744
+ }
26745
+ startListening() {
26746
+ // Handle all events from the layer collection and its inner cell collections.
26747
+ this.listenTo(this.layerCollection, 'all', this.onLayerCollectionEvent);
26748
+ }
26749
+
26750
+ /**
26751
+ * @description When a cell changes its layer attribute,
26752
+ * move the cell to the target layer.
26753
+ */
26754
+ onCellChange(cell, options) {
26755
+ if (!cell.hasChanged(config$3.layerAttribute)) return;
26756
+ // Move the cell to the appropriate layer
26757
+ const targetLayerId = this.graph.getCellLayerId(cell);
26758
+ this.layerCollection.moveCellBetweenLayers(cell, targetLayerId, options);
26759
+ }
26760
+
26761
+ /**
26762
+ * @description When a cell is removed from a layer,
26763
+ * also remove its embeds and connected links from the graph.
26764
+ * Note: an embedded cell might come from a different layer,
26765
+ * so we can not use the layer's cell collection to remove it.
26766
+ */
26767
+ onCellRemove(cell, options) {
26768
+ // If the cell is being moved from one layer to another,
26769
+ // no further action is needed.
26770
+ if (options.fromLayer) return;
26771
+
26772
+ // When replacing a cell, we do not want to remove its embeds or
26773
+ // unembed it from its parent.
26774
+ if (options.replace) return;
26775
+
26776
+ // First, unembed this cell from its parent cell if there is one.
26777
+ const parentCell = cell.getParentCell();
26778
+ if (parentCell) {
26779
+ parentCell.unembed(cell, options);
26780
+ }
26781
+
26782
+ // Remove also all the cells, which were embedded into this cell
26783
+ const embeddedCells = cell.getEmbeddedCells();
26784
+ for (let i = 0, n = embeddedCells.length; i < n; i++) {
26785
+ const embed = embeddedCells[i];
26786
+ if (embed) {
26787
+ this.layerCollection.removeCell(embed, options);
26788
+ }
26789
+ }
26790
+
26791
+ // When not clearing the whole graph or replacing the cell,
26792
+ // we don't want to remove the connected links.
26793
+ if (!options.clear) {
26794
+ // Applications might provide a `disconnectLinks` option set to `true` in order to
26795
+ // disconnect links when a cell is removed rather then removing them. The default
26796
+ // is to remove all the associated links.
26797
+ if (options.disconnectLinks) {
26798
+ this.graph.disconnectLinks(cell, options);
26799
+ } else {
26800
+ this.graph.removeLinks(cell, options);
26801
+ }
26802
+ }
26803
+ }
26804
+ onLayerCollectionEvent(eventName, model) {
26805
+ if (!model) return;
26806
+ if (model[CELL_MARKER]) {
26807
+ // First handle cell-specific cases that require custom processing,
26808
+ // then forward the event to the graph.
26809
+ // For example, when a cell is removed from a layer, its embeds and
26810
+ // connected links must be removed as well. Listeners on the graph
26811
+ // should receive removal notifications in the following order:
26812
+ // embeds → links → cell.
26813
+ switch (eventName) {
26814
+ case 'change':
26815
+ /* ('change', cell, options) */
26816
+ this.onCellChange.call(this, model, arguments[2]);
26817
+ break;
26818
+ case 'remove':
26819
+ /* ('remove', cell, collection, options) */
26820
+ // When a cell is removed from a layer,
26821
+ // ensure it is also removed from the graph.
26822
+ this.onCellRemove.call(this, model, arguments[3]);
26823
+ break;
26824
+ }
26825
+ // Notify the graph about cell events.
26826
+ this.forwardCellEvent.apply(this, arguments);
26827
+ return;
26828
+ }
26829
+ if (model[CELL_COLLECTION_MARKER]) {
26830
+ this.forwardCellCollectionEvent.apply(this, arguments);
26831
+ return;
26832
+ }
26833
+ if (model[GRAPH_LAYER_MARKER]) {
26834
+ this.forwardLayerEvent.apply(this, arguments);
26835
+ return;
26836
+ }
26837
+ if (model[GRAPH_LAYER_COLLECTION_MARKER]) {
26838
+ this.forwardLayerCollectionEvent.apply(this, arguments);
26839
+ return;
26840
+ }
26841
+ }
26842
+ forwardLayerEvent() {
26843
+ // Note: the layer event prefix is `layer:`
26844
+ this.graph.trigger.apply(this.graph, arguments);
26845
+ }
26846
+ forwardCellEvent(eventName, cell) {
26847
+ var _arguments$;
26848
+ // Moving a cell from one layer to another is an internal operation
26849
+ // that should not be exposed at the graph level.
26850
+ // The single `move` event is triggered instead.
26851
+ if ((eventName === 'remove' || eventName === 'add') && (_arguments$ = arguments[3]) !== null && _arguments$ !== void 0 && _arguments$.fromLayer) return;
26852
+ this.graph.trigger.apply(this.graph, arguments);
26853
+ }
26854
+ forwardCellCollectionEvent(eventName) {
26855
+ // Do not forward `layer:remove` or `layer:sort` events to the graph
26856
+ if (eventName !== 'sort') return;
26857
+ // Backwards compatibility:
26858
+ // Trigger 'sort' event for cell collection 'sort' events
26859
+ this.graph.trigger.apply(this.graph, arguments);
26860
+ }
26861
+ forwardLayerCollectionEvent(eventName) {
26862
+ if (eventName === 'reset') {
26863
+ // Currently, there is no need to forward `layers:reset` event.
26864
+ // The graph `fromJSON()` triggers a single `reset` event after
26865
+ // resetting cells, layers and attributes.
26866
+ return;
26867
+ }
26868
+ // Forward layer collection events with `layers:` prefix.
26869
+ // For example `layers:reset` event when the layer collection is reset
26870
+ arguments[0] = 'layers:' + arguments[0];
26871
+ this.graph.trigger.apply(this.graph, arguments);
26872
+ }
26873
+ }
26874
+
26875
+ /**
26876
+ * @class CellCollection
26877
+ * @description A CellCollection is a collection of cells which supports z-index management.
26878
+ * Additionally, it facilitates creating cell models from JSON using cellNamespace
26879
+ * and stores a reference to the graph when the cell model has been added.
26880
+ */
26881
+ class CellCollection extends Collection {
26882
+ [CELL_COLLECTION_MARKER] = true;
26883
+ initialize(_models, opt) {
26884
+ this.layer = opt.layer;
26885
+ }
26886
+
26887
+ // Method for checking whether an object should be considered a model for
26888
+ // the purposes of adding to the collection.
26889
+ _isModel(model) {
26890
+ return Boolean(model[CELL_MARKER]);
26891
+ }
26892
+
26893
+ // Overriding the default `model` method to create cell models
26894
+ // based on their `type` attribute and the `cellNamespace` option.
26895
+ model(attrs, opt) {
26896
+ const namespace = this.cellNamespace;
26897
+ if (!namespace) {
26898
+ throw new Error('dia.CellCollection: cellNamespace is required to instantiate a Cell from JSON.');
26693
26899
  }
26694
- this.graph = opt.graph;
26695
- },
26696
- model: function (attrs, opt) {
26697
- const collection = opt.collection;
26698
- const namespace = collection.cellNamespace;
26699
26900
  const {
26700
26901
  type
26701
26902
  } = attrs;
@@ -26706,56 +26907,474 @@ var joint = (function (exports) {
26706
26907
  throw new Error(`dia.Graph: Could not find cell constructor for type: '${type}'. Make sure to add the constructor to 'cellNamespace'.`);
26707
26908
  }
26708
26909
  return new ModelClass(attrs, opt);
26709
- },
26710
- _addReference: function (model, options) {
26711
- Collection.prototype._addReference.apply(this, arguments);
26910
+ }
26911
+
26912
+ // Override to set graph reference
26913
+ _addReference(model, options) {
26914
+ super._addReference(model, options);
26915
+
26712
26916
  // If not in `dry` mode and the model does not have a graph reference yet,
26713
26917
  // set the reference.
26714
26918
  if (!options.dry && !model.graph) {
26715
- model.graph = this.graph;
26919
+ model.graph = this.layer.graph;
26716
26920
  }
26717
- },
26718
- _removeReference: function (model, options) {
26719
- Collection.prototype._removeReference.apply(this, arguments);
26921
+ }
26922
+
26923
+ // Override to remove graph reference
26924
+ _removeReference(model, options) {
26925
+ super._removeReference(model, options);
26926
+
26720
26927
  // If not in `dry` mode and the model has a reference to this exact graph,
26721
26928
  // remove the reference.
26722
- if (!options.dry && model.graph === this.graph) {
26929
+ // Note: graph reference is removed from the layer after the `remove` event is fired.
26930
+ // Due to this, event handlers can still access the graph during the `remove` event.
26931
+ if (!options.dry && model.graph === this.layer.graph) {
26723
26932
  model.graph = null;
26724
26933
  }
26725
- },
26934
+ }
26935
+
26936
+ // remove graph reference additionally
26937
+ _removeReferenceFast(model, options) {
26938
+ model.off('all', this._onModelEvent, this);
26939
+ if (!options.dry) {
26940
+ // If not in `dry` mode and the model has a reference
26941
+ // to this exact graph/collection, remove the reference.
26942
+ if (this === model.collection) {
26943
+ delete model.collection;
26944
+ }
26945
+ if (model.graph === this.layer.graph) {
26946
+ model.graph = null;
26947
+ }
26948
+ }
26949
+ }
26950
+
26726
26951
  // `comparator` makes it easy to sort cells based on their `z` index.
26727
- comparator: function (model) {
26952
+ comparator(model) {
26728
26953
  return model.get('z') || 0;
26729
26954
  }
26730
- });
26731
- const Graph = Model.extend({
26732
- initialize: function (attrs, opt) {
26733
- opt = opt || {};
26734
26955
 
26735
- // Passing `cellModel` function in the options object to graph allows for
26736
- // setting models based on attribute objects. This is especially handy
26737
- // when processing JSON graphs that are in a different than JointJS format.
26738
- var cells = new GraphCells([], {
26739
- model: opt.cellModel,
26740
- cellNamespace: opt.cellNamespace,
26741
- graph: this
26956
+ // This method overrides base mvc.Collection implementation
26957
+ // in a way that improves performance of resetting large collections.
26958
+ // For layers specifically, there is an option where we put references
26959
+ // from the main collection in order to improve performance when
26960
+ // there is only one layer
26961
+ reset(models, options) {
26962
+ options = assign({}, {
26963
+ add: true,
26964
+ remove: false,
26965
+ merge: false
26966
+ }, options);
26967
+ for (let i = 0; i < this.models.length; i++) {
26968
+ this._removeReferenceFast(this.models[i], options);
26969
+ }
26970
+ options.previousModels = this.models;
26971
+ this._reset();
26972
+ for (let i = 0; i < models.length; i++) {
26973
+ const model = this._prepareModel(models[i], options);
26974
+ if (model) {
26975
+ this.models.push(model);
26976
+ this._addReference(model, options);
26977
+ }
26978
+ }
26979
+ this.length = this.models.length;
26980
+ const sort = this.comparator && options.sort !== false;
26981
+ if (sort) {
26982
+ this.sort({
26983
+ silent: true
26984
+ });
26985
+ }
26986
+ if (!options.silent) {
26987
+ this.trigger('reset', this, options);
26988
+ }
26989
+ return this.models;
26990
+ }
26991
+ minZIndex() {
26992
+ var _this$first;
26993
+ return ((_this$first = this.first()) === null || _this$first === void 0 ? void 0 : _this$first.get('z')) || 0;
26994
+ }
26995
+ maxZIndex() {
26996
+ var _this$last;
26997
+ return ((_this$last = this.last()) === null || _this$last === void 0 ? void 0 : _this$last.get('z')) || 0;
26998
+ }
26999
+ }
27000
+
27001
+ const DEFAULT_GRAPH_LAYER_TYPE = 'GraphLayer';
27002
+
27003
+ /**
27004
+ * @class GraphLayer
27005
+ * @description A GraphLayer is a model representing a single layer in a dia.Graph.
27006
+ */
27007
+ class GraphLayer extends Model {
27008
+ [GRAPH_LAYER_MARKER] = true;
27009
+ preinitialize() {
27010
+ // This allows for propagating events from the inner `cellCollection` collection
27011
+ // without any prefix and therefore distinguish them from the events
27012
+ // fired by the GraphLayer model itself.
27013
+ this.eventPrefix = 'layer:';
27014
+ }
27015
+ defaults() {
27016
+ return {
27017
+ type: DEFAULT_GRAPH_LAYER_TYPE
27018
+ };
27019
+ }
27020
+ initialize(attrs, options = {}) {
27021
+ super.initialize(attrs, options);
27022
+ this.cellCollection = new CellCollection([], {
27023
+ layer: this
26742
27024
  });
26743
- Model.prototype.set.call(this, 'cells', cells);
26744
27025
 
26745
- // Make all the events fired in the `cells` collection available.
26746
- // to the outside world.
26747
- cells.on('all', this.trigger, this);
27026
+ // Forward all events from the inner `cellCollection` collection
27027
+ this.cellCollection.on('all', this.trigger, this);
27028
+ // Listen to cell changes to manage z-index sorting
27029
+ this.cellCollection.on('change', this.onCellChange, this);
27030
+ }
27031
+ onCellChange(cell, opt) {
27032
+ if (opt.sort === false || !cell.hasChanged('z')) return;
27033
+ this.cellCollection.sort();
27034
+ }
27035
+
27036
+ /**
27037
+ * @public
27038
+ * @description Returns all cells in this layer.
27039
+ */
27040
+ getCells() {
27041
+ return this.cellCollection.toArray();
27042
+ }
27043
+ }
26748
27044
 
26749
- // JointJS automatically doesn't trigger re-sort if models attributes are changed later when
26750
- // they're already in the collection. Therefore, we're triggering sort manually here.
26751
- this.on('change:z', this._sortOnChangeZ, this);
27045
+ /**
27046
+ * @class GraphLayerCollection
27047
+ * @description A collection of layers used in dia.Graph. It facilitates creating layers from JSON using layerNamespace.
27048
+ */
27049
+ const GraphLayerCollection = Collection.extend({
27050
+ defaultLayerNamespace: {
27051
+ GraphLayer
27052
+ },
27053
+ /**
27054
+ * @override
27055
+ * @description Initializes the collection and sets up the layer and cell namespaces.
27056
+ */
27057
+ initialize: function (_models, options = {}) {
27058
+ const {
27059
+ layerNamespace,
27060
+ cellNamespace,
27061
+ graph
27062
+ } = options;
26752
27063
 
26753
- // `joint.dia.Graph` keeps an internal data structure (an adjacency list)
26754
- // for fast graph queries. All changes that affect the structure of the graph
26755
- // must be reflected in the `al` object. This object provides fast answers to
26756
- // questions such as "what are the neighbours of this node" or "what
26757
- // are the sibling links of this link".
27064
+ // Initialize the namespace that holds all available layer classes.
27065
+ // Custom namespaces are merged with the default ones.
27066
+ this.layerNamespace = assign({}, this.defaultLayerNamespace, layerNamespace);
27067
+
27068
+ // Initialize the namespace for all cell model classes, if provided.
27069
+ if (cellNamespace) {
27070
+ this.cellNamespace = cellNamespace;
27071
+ } else {
27072
+ /* eslint-disable no-undef */
27073
+ this.cellNamespace = typeof joint !== 'undefined' && has(joint, 'shapes') ? joint.shapes : null;
27074
+ /* eslint-enable no-undef */
27075
+ }
27076
+ this.graph = graph;
27077
+ },
27078
+ /**
27079
+ * @override
27080
+ * @description Overrides the default `model` method
27081
+ * to create layer models based on their `type` attribute.
27082
+ */
27083
+ model: function (attrs, opt) {
27084
+ const collection = opt.collection;
27085
+ const namespace = collection.layerNamespace;
27086
+ const {
27087
+ type
27088
+ } = attrs;
27089
+
27090
+ // Find the model class based on the `type` attribute in the cell namespace
27091
+ const GraphLayerClass = getByPath(namespace, type, '.');
27092
+ if (!GraphLayerClass) {
27093
+ throw new Error(`dia.Graph: Could not find layer constructor for type: '${type}'. Make sure to add the constructor to 'layerNamespace'.`);
27094
+ }
27095
+ return new GraphLayerClass(attrs, opt);
27096
+ },
27097
+ // Override to set graph reference
27098
+ _addReference(layer, options) {
27099
+ Collection.prototype._addReference.call(this, layer, options);
27100
+
27101
+ // assign graph and cellNamespace references
27102
+ // to the added layer
27103
+ layer.graph = this.graph;
27104
+ layer.cellCollection.cellNamespace = this.cellNamespace;
27105
+ },
27106
+ // Override to remove graph reference
27107
+ _removeReference(layer, options) {
27108
+ Collection.prototype._removeReference.call(this, layer, options);
27109
+
27110
+ // remove graph and cellNamespace references
27111
+ // from the removed layer
27112
+ layer.graph = null;
27113
+ layer.cellCollection.cellNamespace = null;
27114
+ },
27115
+ /**
27116
+ * @override
27117
+ * @description Overrides the default `_prepareModel` method
27118
+ * to set default layer type if missing.
27119
+ */
27120
+ _prepareModel: function (attrs, options) {
27121
+ if (!attrs[GRAPH_LAYER_MARKER]) {
27122
+ // Add a mandatory `type` attribute if missing
27123
+ if (!attrs.type) {
27124
+ const preparedAttributes = clone$1(attrs);
27125
+ preparedAttributes.type = DEFAULT_GRAPH_LAYER_TYPE;
27126
+ arguments[0] = preparedAttributes;
27127
+ }
27128
+ }
27129
+ return Collection.prototype._prepareModel.apply(this, arguments);
27130
+ },
27131
+ /**
27132
+ * @override
27133
+ * @description Add an assertion to prevent direct resetting of the collection.
27134
+ */
27135
+ reset(models, options) {
27136
+ this._assertInternalCall(options);
27137
+ return Collection.prototype.reset.apply(this, arguments);
27138
+ },
27139
+ /**
27140
+ * @override
27141
+ * @description Add an assertion to prevent direct addition of layers.
27142
+ */
27143
+ add(models, options) {
27144
+ this._assertInternalCall(options);
27145
+ return Collection.prototype.add.apply(this, arguments);
27146
+ },
27147
+ /**
27148
+ * @override
27149
+ * @description Add an assertion to prevent direct removal of layers.
27150
+ */
27151
+ remove(models, options) {
27152
+ this._assertInternalCall(options);
27153
+ return Collection.prototype.remove.apply(this, arguments);
27154
+ },
27155
+ /**
27156
+ * @override
27157
+ * @description Overrides the default `_onModelEvent` method
27158
+ * to distinguish between events coming from different model types.
27159
+ */
27160
+ _onModelEvent(_, model) {
27161
+ if (model && model[CELL_MARKER]) {
27162
+ // Do not filter cell `add` and `remove` events
27163
+ // See `mvc.Collection` for more details
27164
+ this.trigger.apply(this, arguments);
27165
+ return;
27166
+ }
26758
27167
 
27168
+ // For other events, use the default behavior
27169
+ Collection.prototype._onModelEvent.apply(this, arguments);
27170
+ },
27171
+ /**
27172
+ * @protected
27173
+ * @description Asserts that the collection manipulation
27174
+ * is done via internal graph methods. Otherwise, it throws an error.
27175
+ * This is a temporary measure until layers API is stabilized.
27176
+ */
27177
+ _assertInternalCall(options) {
27178
+ if (options && !options.graph && !options.silent) {
27179
+ throw new Error('dia.GraphLayerCollection: direct manipulation of the collection is not supported, use graph methods instead.');
27180
+ }
27181
+ },
27182
+ /**
27183
+ * @public
27184
+ * @description Inserts a layer before another layer or at the end if `beforeLayerId` is null.
27185
+ */
27186
+ insert(layerInit, beforeLayerId = null, options = {}) {
27187
+ const id = layerInit.id;
27188
+ if (id === beforeLayerId) {
27189
+ // Inserting before itself is a no-op
27190
+ return;
27191
+ }
27192
+ if (beforeLayerId && !this.has(beforeLayerId)) {
27193
+ throw new Error(`dia.GraphLayerCollection: Layer "${beforeLayerId}" does not exist`);
27194
+ }
27195
+
27196
+ // See if the layer is already in the collection
27197
+ let currentIndex = -1;
27198
+ if (this.has(id)) {
27199
+ currentIndex = this.findIndex(l => l.id === id);
27200
+ if (currentIndex === this.length - 1 && !beforeLayerId) {
27201
+ // The layer is already at the end
27202
+ return;
27203
+ }
27204
+ // Remove the layer from its current position
27205
+ this.remove(id, {
27206
+ silent: true
27207
+ });
27208
+ }
27209
+
27210
+ // At what index to insert the layer?
27211
+ let insertAt;
27212
+ if (!beforeLayerId) {
27213
+ insertAt = this.length;
27214
+ } else {
27215
+ insertAt = this.findIndex(l => l.id === beforeLayerId);
27216
+ }
27217
+ if (currentIndex !== -1) {
27218
+ // Re-insert the layer at the new position.
27219
+ this.add(layerInit, {
27220
+ at: insertAt,
27221
+ silent: true
27222
+ });
27223
+ // Trigger `sort` event manually
27224
+ // since we are not using collection sorting workflow
27225
+ this.trigger('sort', this, options);
27226
+ } else {
27227
+ // Add to the collection and trigger an event
27228
+ // when new layer has been added
27229
+ this.add(layerInit, {
27230
+ ...options,
27231
+ at: insertAt
27232
+ });
27233
+ }
27234
+ },
27235
+ /**
27236
+ * @public
27237
+ * @description Finds and returns a cell by its id from all layers.
27238
+ */
27239
+ getCell(cellRef) {
27240
+ // TODO: should we create a map of cells for faster lookup?
27241
+ for (const layer of this.models) {
27242
+ const cell = layer.cellCollection.get(cellRef);
27243
+ if (cell) {
27244
+ return cell;
27245
+ }
27246
+ }
27247
+ // Backward compatibility: return undefined if cell is not found
27248
+ return undefined;
27249
+ },
27250
+ /**
27251
+ * @public
27252
+ * @description Returns all cells in all layers in the correct order.
27253
+ */
27254
+ getCells() {
27255
+ const layers = this.models;
27256
+ if (layers.length === 1) {
27257
+ // Single layer:
27258
+ // Fast path, just return the copy of the only layer's cells
27259
+ return layers[0].getCells();
27260
+ }
27261
+ // Multiple layers:
27262
+ // Each layer has its models sorted already, so we can just concatenate
27263
+ // them in the order of layers.
27264
+ const cells = [];
27265
+ for (const layer of layers) {
27266
+ Array.prototype.push.apply(cells, layer.cellCollection.models);
27267
+ }
27268
+ return cells;
27269
+ },
27270
+ /**
27271
+ * @public
27272
+ * @description Removes a cell from its current layer.
27273
+ */
27274
+ removeCell(cell, options = {}) {
27275
+ var _cell$collection;
27276
+ const cellCollection = (_cell$collection = cell.collection) === null || _cell$collection === void 0 || (_cell$collection = _cell$collection.layer) === null || _cell$collection === void 0 ? void 0 : _cell$collection.cellCollection;
27277
+ if (!cellCollection) return;
27278
+ cellCollection.remove(cell, options);
27279
+ },
27280
+ /**
27281
+ * @public
27282
+ * @description Move a cell from its current layer to a target layer.
27283
+ */
27284
+ moveCellBetweenLayers(cell, targetLayerId, options = {}) {
27285
+ var _cell$collection2;
27286
+ const sourceLayer = (_cell$collection2 = cell.collection) === null || _cell$collection2 === void 0 ? void 0 : _cell$collection2.layer;
27287
+ if (!sourceLayer) {
27288
+ throw new Error('dia.GraphLayerCollection: cannot move a cell that is not part of any layer.');
27289
+ }
27290
+ const targetLayer = this.get(targetLayerId);
27291
+ if (!targetLayer) {
27292
+ throw new Error(`dia.GraphLayerCollection: cannot move cell to layer '${targetLayerId}' because such layer does not exist.`);
27293
+ }
27294
+ if (sourceLayer === targetLayer) {
27295
+ // 1. The provided cell is already in the target layer
27296
+ // 2. Implicit default layer vs. explicit default (or vice versa)
27297
+ // No follow-up action needed
27298
+ return;
27299
+ }
27300
+ const moveOptions = {
27301
+ ...options,
27302
+ fromLayer: sourceLayer.id,
27303
+ toLayer: targetLayer.id
27304
+ };
27305
+ // Move the cell between the two layer collections
27306
+ sourceLayer.cellCollection.remove(cell, moveOptions);
27307
+ targetLayer.cellCollection.add(cell, moveOptions);
27308
+ // Trigger a single `move` event to ease distinguishing layer moves
27309
+ // from add/remove operations
27310
+ cell.trigger('move', cell, moveOptions);
27311
+ },
27312
+ /**
27313
+ * @public
27314
+ * @description Adds a cell to the specified layer.
27315
+ */
27316
+ addCellToLayer(cell, layerId, options = {}) {
27317
+ const targetLayer = this.get(layerId);
27318
+ if (!targetLayer) {
27319
+ throw new Error(`dia.GraphLayerCollection: layer "${layerId}" does not exist.`);
27320
+ }
27321
+ const addOptions = {
27322
+ ...options,
27323
+ toLayer: targetLayer.id
27324
+ };
27325
+ // Add the cell to the target layer collection
27326
+ targetLayer.cellCollection.add(cell, addOptions);
27327
+ }
27328
+ });
27329
+ Object.defineProperty(GraphLayerCollection.prototype, GRAPH_LAYER_COLLECTION_MARKER, {
27330
+ value: true
27331
+ });
27332
+
27333
+ /**
27334
+ * @class GraphTopologyIndex
27335
+ * @description Maintains an index of the graph topology (adjacency list)
27336
+ * for fast graph queries.
27337
+ */
27338
+ class GraphTopologyIndex extends Listener {
27339
+ constructor(options) {
27340
+ super(options);
27341
+
27342
+ // Make sure there are no arguments passed to the callbacks.
27343
+ // See the `mvc.Listener` documentation for more details.
27344
+ this.callbackArguments = [];
27345
+ this.layerCollection = options.layerCollection;
27346
+ if (!this.layerCollection) {
27347
+ throw new Error('GraphTopologyIndex: "layerCollection" option is required.');
27348
+ }
27349
+ this.initializeIndex();
27350
+ this.startListening();
27351
+ }
27352
+
27353
+ /**
27354
+ * @public
27355
+ * @description Start listening to graph and layer collection events
27356
+ * to maintain the topology index.
27357
+ */
27358
+ startListening() {
27359
+ this.listenTo(this.layerCollection.graph, {
27360
+ 'add': this._restructureOnAdd,
27361
+ 'remove': this._restructureOnRemove,
27362
+ 'reset': this._restructureOnReset
27363
+ });
27364
+ // Listening to the collection instead of the graph
27365
+ // to avoid reacting to graph attribute change events
27366
+ // e.g. graph.set('source', ...);
27367
+ this.listenTo(this.layerCollection, {
27368
+ 'change:source': this._restructureOnChangeSource,
27369
+ 'change:target': this._restructureOnChangeTarget
27370
+ });
27371
+ }
27372
+
27373
+ /**
27374
+ * @protected
27375
+ * @description Initialize the internal data structures.
27376
+ */
27377
+ initializeIndex() {
26759
27378
  // Outgoing edges per node. Note that we use a hash-table for the list
26760
27379
  // of outgoing edges for a faster lookup.
26761
27380
  // [nodeId] -> Object [edgeId] -> true
@@ -26771,21 +27390,27 @@ var joint = (function (exports) {
26771
27390
  // having to go through the whole cells array.
26772
27391
  // [edgeId] -> true
26773
27392
  this._edges = {};
26774
- this._batches = {};
26775
- cells.on('add', this._restructureOnAdd, this);
26776
- cells.on('remove', this._restructureOnRemove, this);
26777
- cells.on('reset', this._restructureOnReset, this);
26778
- cells.on('change:source', this._restructureOnChangeSource, this);
26779
- cells.on('change:target', this._restructureOnChangeTarget, this);
26780
- cells.on('remove', this._removeCell, this);
26781
- },
26782
- _sortOnChangeZ: function () {
26783
- this.get('cells').sort();
26784
- },
26785
- _restructureOnAdd: function (cell) {
27393
+ }
27394
+
27395
+ /**
27396
+ * @protected
27397
+ * @description Restructure the topology index on graph reset.
27398
+ * E.g. when fromJSON or resetCells is called.
27399
+ */
27400
+ _restructureOnReset() {
27401
+ this.initializeIndex();
27402
+ this.layerCollection.getCells().forEach(this._restructureOnAdd, this);
27403
+ }
27404
+
27405
+ /**
27406
+ * @protected
27407
+ * @description Restructure the topology index on cell addition.
27408
+ * @param {dia.Cell} cell - The cell being added.
27409
+ */
27410
+ _restructureOnAdd(cell) {
26786
27411
  if (cell.isLink()) {
26787
27412
  this._edges[cell.id] = true;
26788
- var {
27413
+ const {
26789
27414
  source,
26790
27415
  target
26791
27416
  } = cell.attributes;
@@ -26798,11 +27423,17 @@ var joint = (function (exports) {
26798
27423
  } else {
26799
27424
  this._nodes[cell.id] = true;
26800
27425
  }
26801
- },
26802
- _restructureOnRemove: function (cell) {
27426
+ }
27427
+
27428
+ /**
27429
+ * @protected
27430
+ * @description Restructure the topology index on cell removal.
27431
+ * @param {dia.Cell} cell - The cell being removed.
27432
+ */
27433
+ _restructureOnRemove(cell) {
26803
27434
  if (cell.isLink()) {
26804
27435
  delete this._edges[cell.id];
26805
- var {
27436
+ const {
26806
27437
  source,
26807
27438
  target
26808
27439
  } = cell.attributes;
@@ -26815,89 +27446,222 @@ var joint = (function (exports) {
26815
27446
  } else {
26816
27447
  delete this._nodes[cell.id];
26817
27448
  }
26818
- },
26819
- _restructureOnReset: function (cells) {
26820
- // Normalize into an array of cells. The original `cells` is GraphCells mvc collection.
26821
- cells = cells.models;
26822
- this._out = {};
26823
- this._in = {};
26824
- this._nodes = {};
26825
- this._edges = {};
26826
- cells.forEach(this._restructureOnAdd, this);
26827
- },
26828
- _restructureOnChangeSource: function (link) {
26829
- var prevSource = link.previous('source');
27449
+ }
27450
+
27451
+ /**
27452
+ * @protected
27453
+ * @description Restructure the topology index on link source change.
27454
+ * @param {dia.Link} link - The link being changed.
27455
+ */
27456
+ _restructureOnChangeSource(link) {
27457
+ const prevSource = link.previous('source');
26830
27458
  if (prevSource.id && this._out[prevSource.id]) {
26831
27459
  delete this._out[prevSource.id][link.id];
26832
27460
  }
26833
- var source = link.attributes.source;
27461
+ const source = link.attributes.source;
26834
27462
  if (source.id) {
26835
27463
  (this._out[source.id] || (this._out[source.id] = {}))[link.id] = true;
26836
27464
  }
26837
- },
26838
- _restructureOnChangeTarget: function (link) {
26839
- var prevTarget = link.previous('target');
27465
+ }
27466
+
27467
+ /**
27468
+ * @protected
27469
+ * @description Restructure the topology index on link target change.
27470
+ * @param {dia.Link} link - The link being changed.
27471
+ */
27472
+ _restructureOnChangeTarget(link) {
27473
+ const prevTarget = link.previous('target');
26840
27474
  if (prevTarget.id && this._in[prevTarget.id]) {
26841
27475
  delete this._in[prevTarget.id][link.id];
26842
27476
  }
26843
- var target = link.get('target');
27477
+ const target = link.get('target');
26844
27478
  if (target.id) {
26845
27479
  (this._in[target.id] || (this._in[target.id] = {}))[link.id] = true;
26846
27480
  }
26847
- },
26848
- // Return all outbound edges for the node. Return value is an object
26849
- // of the form: [edgeId] -> true
26850
- getOutboundEdges: function (node) {
26851
- return this._out && this._out[node] || {};
26852
- },
26853
- // Return all inbound edges for the node. Return value is an object
26854
- // of the form: [edgeId] -> true
26855
- getInboundEdges: function (node) {
26856
- return this._in && this._in[node] || {};
27481
+ }
27482
+
27483
+ /**
27484
+ * @public
27485
+ * @description Get all outbound edges for the node. Time complexity: O(1).
27486
+ * @param {string} nodeId - The id of the node.
27487
+ * @returns {Object} - An object of the form: [edgeId] -> true.
27488
+ */
27489
+ getOutboundEdges(nodeId) {
27490
+ return this._out[nodeId] || {};
27491
+ }
27492
+
27493
+ /**
27494
+ * @public
27495
+ * @description Get all inbound edges for the node. Time complexity: O(1).
27496
+ * @param {string} nodeId - The id of the node.
27497
+ * @returns {Object} - An object of the form: [edgeId] -> true.
27498
+ */
27499
+ getInboundEdges(nodeId) {
27500
+ return this._in[nodeId] || {};
27501
+ }
27502
+
27503
+ /**
27504
+ * @public
27505
+ * @description Get all sink nodes (leafs) in the graph. Time complexity: O(|V|).
27506
+ * @returns {string[]} - Array of node ids.
27507
+ */
27508
+ getSinkNodes() {
27509
+ const sinks = [];
27510
+ for (const nodeId in this._nodes) {
27511
+ if (!this._out[nodeId] || isEmpty(this._out[nodeId])) {
27512
+ sinks.push(nodeId);
27513
+ }
27514
+ }
27515
+ return sinks;
27516
+ }
27517
+
27518
+ /**
27519
+ * @public
27520
+ * @description Get all source nodes (roots) in the graph. Time complexity: O(|V|).
27521
+ * @returns {string[]} - Array of node ids.
27522
+ */
27523
+ getSourceNodes() {
27524
+ const sources = [];
27525
+ for (const nodeId in this._nodes) {
27526
+ if (!this._in[nodeId] || isEmpty(this._in[nodeId])) {
27527
+ sources.push(nodeId);
27528
+ }
27529
+ }
27530
+ return sources;
27531
+ }
27532
+
27533
+ /**
27534
+ * @public
27535
+ * @description Return `true` if `nodeId` is a source node (root). Time complexity: O(1).
27536
+ * @param {string} nodeId - The id of the node to check.
27537
+ * @returns {boolean}
27538
+ */
27539
+ isSourceNode(nodeId) {
27540
+ return !this._in[nodeId] || isEmpty(this._in[nodeId]);
27541
+ }
27542
+
27543
+ /**
27544
+ * @public
27545
+ * @description Return `true` if `nodeId` is a sink node (leaf). Time complexity: O(1).
27546
+ * @param {string} nodeId - The id of the node to check.
27547
+ * @returns {boolean}
27548
+ */
27549
+ isSinkNode(nodeId) {
27550
+ return !this._out[nodeId] || isEmpty(this._out[nodeId]);
27551
+ }
27552
+ }
27553
+
27554
+ // The ID of the default graph layer.
27555
+ const DEFAULT_LAYER_ID = 'cells';
27556
+ const Graph = Model.extend({
27557
+ /**
27558
+ * @todo Remove in v5.0.0
27559
+ * @description In legacy mode, the information about layers is not
27560
+ * exported into JSON.
27561
+ */
27562
+ legacyMode: true,
27563
+ /**
27564
+ * @protected
27565
+ * @description The ID of the default layer.
27566
+ */
27567
+ defaultLayerId: DEFAULT_LAYER_ID,
27568
+ initialize: function (attrs, options = {}) {
27569
+ const layerCollection = this.layerCollection = new GraphLayerCollection([], {
27570
+ layerNamespace: options.layerNamespace,
27571
+ cellNamespace: options.cellNamespace,
27572
+ graph: this,
27573
+ /** @deprecated use cellNamespace instead */
27574
+ model: options.cellModel
27575
+ });
27576
+
27577
+ // The default setup includes a single default layer.
27578
+ layerCollection.add({
27579
+ id: DEFAULT_LAYER_ID
27580
+ }, {
27581
+ graph: this.cid
27582
+ });
27583
+
27584
+ /**
27585
+ * @todo Remove in v5.0.0
27586
+ * @description Retain legacy 'cells' collection in attributes for backward compatibility.
27587
+ * Applicable only when the default layer setup is used.
27588
+ */
27589
+ this.attributes.cells = this.getLayer(DEFAULT_LAYER_ID).cellCollection;
27590
+
27591
+ // Controller that manages communication between the graph and its layers.
27592
+ this.layersController = new GraphLayersController({
27593
+ graph: this
27594
+ });
27595
+
27596
+ // `Graph` keeps an internal data structure (an adjacency list)
27597
+ // for fast graph queries. All changes that affect the structure of the graph
27598
+ // must be reflected in the `al` object. This object provides fast answers to
27599
+ // questions such as "what are the neighbors of this node" or "what
27600
+ // are the sibling links of this link".
27601
+ this.topologyIndex = new GraphTopologyIndex({
27602
+ layerCollection
27603
+ });
27604
+ this._batches = {};
26857
27605
  },
26858
27606
  toJSON: function (opt = {}) {
26859
- // JointJS does not recursively call `toJSON()` on attributes that are themselves models/collections.
26860
- // It just clones the attributes. Therefore, we must call `toJSON()` on the cells collection explicitly.
26861
- var json = Model.prototype.toJSON.apply(this, arguments);
26862
- json.cells = this.get('cells').toJSON(opt.cellAttributes);
27607
+ const {
27608
+ layerCollection
27609
+ } = this;
27610
+ // Get the graph model attributes as a base JSON.
27611
+ const json = Model.prototype.toJSON.apply(this, arguments);
27612
+
27613
+ // Add `cells` array holding all the cells in the graph.
27614
+ json.cells = this.getCells().map(cell => cell.toJSON(opt.cellAttributes));
27615
+ if (this.legacyMode) {
27616
+ // Backwards compatibility for legacy setup
27617
+ // with single default layer 'cells'.
27618
+ // In this case, we do not need to export layers.
27619
+ return json;
27620
+ }
27621
+
27622
+ // Add `layers` array holding all the layers in the graph.
27623
+ json.layers = layerCollection.toJSON();
27624
+
27625
+ // Add `defaultLayer` property indicating the default layer ID.
27626
+ json.defaultLayer = this.defaultLayerId;
26863
27627
  return json;
26864
27628
  },
26865
27629
  fromJSON: function (json, opt) {
26866
- if (!json.cells) {
27630
+ const {
27631
+ cells,
27632
+ layers,
27633
+ defaultLayer,
27634
+ ...attributes
27635
+ } = json;
27636
+ if (!cells) {
26867
27637
  throw new Error('Graph JSON must contain cells array.');
26868
27638
  }
26869
- return this.set(json, opt);
26870
- },
26871
- set: function (key, val, opt) {
26872
- var attrs;
26873
27639
 
26874
- // Handle both `key`, value and {key: value} style arguments.
26875
- if (typeof key === 'object') {
26876
- attrs = key;
26877
- opt = val;
26878
- } else {
26879
- (attrs = {})[key] = val;
27640
+ // The `fromJSON` should trigger a single 'reset' event at the end.
27641
+ // Set all attributes silently for now.
27642
+ this.set(attributes, {
27643
+ silent: true
27644
+ });
27645
+ if (layers) {
27646
+ // Reset the layers collection
27647
+ // (`layers:reset` is not forwarded to the graph).
27648
+ this._resetLayers(layers, defaultLayer, opt);
26880
27649
  }
26881
-
26882
- // Make sure that `cells` attribute is handled separately via resetCells().
26883
- if (attrs.hasOwnProperty('cells')) {
26884
- this.resetCells(attrs.cells, opt);
26885
- attrs = omit(attrs, 'cells');
27650
+ if (cells) {
27651
+ // Reset the cells collection and trigger the 'reset' event.
27652
+ this.resetCells(cells, opt);
26886
27653
  }
26887
-
26888
- // The rest of the attributes are applied via original set method.
26889
- return Model.prototype.set.call(this, attrs, opt);
27654
+ return this;
26890
27655
  },
27656
+ /** @deprecated */
26891
27657
  clear: function (opt) {
26892
27658
  opt = assign({}, opt, {
26893
27659
  clear: true
26894
27660
  });
26895
- var collection = this.get('cells');
26896
- if (collection.length === 0) return this;
27661
+ const cells = this.getCells();
27662
+ if (cells.length === 0) return this;
26897
27663
  this.startBatch('clear', opt);
26898
-
26899
- // The elements come after the links.
26900
- var cells = collection.sortBy(function (cell) {
27664
+ const sortedCells = sortBy(cells, cell => {
26901
27665
  return cell.isLink() ? 1 : 2;
26902
27666
  });
26903
27667
  do {
@@ -26905,43 +27669,56 @@ var joint = (function (exports) {
26905
27669
  // Note that all the links are removed first, so it's
26906
27670
  // safe to remove the elements without removing the connected
26907
27671
  // links first.
26908
- cells.shift().remove(opt);
26909
- } while (cells.length > 0);
27672
+ this.layerCollection.removeCell(sortedCells.shift(), opt);
27673
+ } while (sortedCells.length > 0);
26910
27674
  this.stopBatch('clear');
26911
27675
  return this;
26912
27676
  },
26913
- _prepareCell: function (cell) {
26914
- let attrs;
26915
- if (cell instanceof Model) {
26916
- attrs = cell.attributes;
27677
+ _prepareCell: function (cellInit, opt) {
27678
+ let cellAttributes;
27679
+ if (cellInit[CELL_MARKER]) {
27680
+ cellAttributes = cellInit.attributes;
26917
27681
  } else {
26918
- attrs = cell;
27682
+ cellAttributes = cellInit;
26919
27683
  }
26920
- if (!isString(attrs.type)) {
27684
+ if (!isString(cellAttributes.type)) {
26921
27685
  throw new TypeError('dia.Graph: cell type must be a string.');
26922
27686
  }
26923
- return cell;
27687
+
27688
+ // Backward compatibility: prior v4.2, z-index was not set during reset.
27689
+ if (opt && opt.ensureZIndex) {
27690
+ if (cellAttributes.z === undefined) {
27691
+ const layerId = cellAttributes[config$3.layerAttribute] || this.defaultLayerId;
27692
+ const zIndex = this.maxZIndex(layerId) + 1;
27693
+ if (cellInit[CELL_MARKER]) {
27694
+ // Set with event in case there is a listener
27695
+ // directly on the cell instance
27696
+ // (the cell is not part of graph yet)
27697
+ cellInit.set('z', zIndex, opt);
27698
+ } else {
27699
+ cellAttributes.z = zIndex;
27700
+ }
27701
+ }
27702
+ }
27703
+ return cellInit;
26924
27704
  },
26925
- minZIndex: function () {
26926
- var firstCell = this.get('cells').first();
26927
- return firstCell ? firstCell.get('z') || 0 : 0;
27705
+ minZIndex: function (layerId = this.defaultLayerId) {
27706
+ const layer = this.getLayer(layerId);
27707
+ return layer.cellCollection.minZIndex();
26928
27708
  },
26929
- maxZIndex: function () {
26930
- var lastCell = this.get('cells').last();
26931
- return lastCell ? lastCell.get('z') || 0 : 0;
27709
+ maxZIndex: function (layerId = this.defaultLayerId) {
27710
+ const layer = this.getLayer(layerId);
27711
+ return layer.cellCollection.maxZIndex();
26932
27712
  },
26933
- addCell: function (cell, opt) {
26934
- if (Array.isArray(cell)) {
26935
- return this.addCells(cell, opt);
27713
+ addCell: function (cellInit, options) {
27714
+ if (Array.isArray(cellInit)) {
27715
+ return this.addCells(cellInit, options);
26936
27716
  }
26937
- if (cell instanceof Model) {
26938
- if (!cell.has('z')) {
26939
- cell.set('z', this.maxZIndex() + 1);
26940
- }
26941
- } else if (cell.z === undefined) {
26942
- cell.z = this.maxZIndex() + 1;
26943
- }
26944
- this.get('cells').add(this._prepareCell(cell, opt), opt || {});
27717
+ this._prepareCell(cellInit, {
27718
+ ...options,
27719
+ ensureZIndex: true
27720
+ });
27721
+ this.layerCollection.addCellToLayer(cellInit, this.getCellLayerId(cellInit), options);
26945
27722
  return this;
26946
27723
  },
26947
27724
  addCells: function (cells, opt) {
@@ -26949,50 +27726,278 @@ var joint = (function (exports) {
26949
27726
  cells = flattenDeep(cells);
26950
27727
  opt.maxPosition = opt.position = cells.length - 1;
26951
27728
  this.startBatch('add', opt);
26952
- cells.forEach(function (cell) {
27729
+ cells.forEach(cell => {
26953
27730
  this.addCell(cell, opt);
26954
27731
  opt.position--;
26955
- }, this);
27732
+ });
26956
27733
  this.stopBatch('add', opt);
26957
27734
  return this;
26958
27735
  },
26959
- // When adding a lot of cells, it is much more efficient to
26960
- // reset the entire cells collection in one go.
26961
- // Useful for bulk operations and optimizations.
26962
- resetCells: function (cells, opt) {
26963
- var preparedCells = toArray$1(cells).map(function (cell) {
26964
- return this._prepareCell(cell, opt);
26965
- }, this);
26966
- this.get('cells').reset(preparedCells, opt);
27736
+ /**
27737
+ * @public
27738
+ * @description Reset the cells in the graph.
27739
+ * Useful for bulk operations and optimizations.
27740
+ */
27741
+ resetCells: function (cellInits, options) {
27742
+ const {
27743
+ layerCollection
27744
+ } = this;
27745
+ // Note: `cellInits` is always an array and `options` is always an object.
27746
+ // See `wrappers.cells` at the end of this file.
27747
+
27748
+ // When resetting cells, do not set z-index if not provided.
27749
+ const prepareOptions = {
27750
+ ...options,
27751
+ ensureZIndex: false
27752
+ };
27753
+
27754
+ // Initialize a map of layer IDs to arrays of cells
27755
+ const layerCellsMap = layerCollection.reduce((map, layer) => {
27756
+ map[layer.id] = [];
27757
+ return map;
27758
+ }, {});
27759
+
27760
+ // Distribute cells into their respective layers
27761
+ for (let i = 0; i < cellInits.length; i++) {
27762
+ const cellInit = cellInits[i];
27763
+ const layerId = this.getCellLayerId(cellInit);
27764
+ if (layerId in layerCellsMap) {
27765
+ this._prepareCell(cellInit, prepareOptions);
27766
+ layerCellsMap[layerId].push(cellInit);
27767
+ } else {
27768
+ throw new Error(`dia.Graph: Layer "${layerId}" does not exist.`);
27769
+ }
27770
+ }
27771
+
27772
+ // Reset each layer's cell collection with the corresponding cells.
27773
+ layerCollection.each(layer => {
27774
+ layer.cellCollection.reset(layerCellsMap[layer.id], options);
27775
+ });
27776
+
27777
+ // Trigger a single `reset` event on the graph
27778
+ // (while multiple `reset` events are triggered on layers).
27779
+ // Backwards compatibility: use default layer collection
27780
+ // The `collection` parameter is retained for backwards compatibility,
27781
+ // and it is subject to removal in future releases.
27782
+ this.trigger('reset', this.getDefaultLayer().cellCollection, options);
26967
27783
  return this;
26968
27784
  },
26969
- removeCells: function (cells, opt) {
26970
- if (cells.length) {
26971
- this.startBatch('remove');
26972
- invoke(cells, 'remove', opt);
26973
- this.stopBatch('remove');
27785
+ /**
27786
+ * @public
27787
+ * @description Get the layer ID in which the cell resides.
27788
+ * Cells without an explicit layer are assigned to the default layer.
27789
+ * @param {dia.Cell | Object} cellInit - Cell model or attributes.
27790
+ * @returns {string} - The layer ID.
27791
+ */
27792
+ getCellLayerId: function (cellInit) {
27793
+ if (!cellInit) {
27794
+ throw new Error('dia.Graph: No cell provided.');
27795
+ }
27796
+ const cellAttributes = cellInit[CELL_MARKER] ? cellInit.attributes : cellInit;
27797
+ return cellAttributes[config$3.layerAttribute] || this.defaultLayerId;
27798
+ },
27799
+ /**
27800
+ * @protected
27801
+ * @description Reset the layers in the graph.
27802
+ * It assumes the existing cells have been removed beforehand
27803
+ * or can be discarded.
27804
+ */
27805
+ _resetLayers: function (layers, defaultLayerId, options = {}) {
27806
+ if (!Array.isArray(layers) || layers.length === 0) {
27807
+ throw new Error('dia.Graph: At least one layer must be defined.');
27808
+ }
27809
+
27810
+ // Resetting layers disables legacy mode
27811
+ this.legacyMode = false;
27812
+ this.layerCollection.reset(layers, {
27813
+ ...options,
27814
+ graph: this.cid
27815
+ });
27816
+
27817
+ // If no default layer is specified, use the first layer as default
27818
+ if (defaultLayerId) {
27819
+ // The default layer must be one of the defined layers
27820
+ if (!this.hasLayer(defaultLayerId)) {
27821
+ throw new Error(`dia.Graph: default layer "${defaultLayerId}" does not exist.`);
27822
+ }
27823
+ this.defaultLayerId = defaultLayerId;
27824
+ } else {
27825
+ this.defaultLayerId = this.layerCollection.at(0).id;
26974
27826
  }
26975
27827
  return this;
26976
27828
  },
26977
- _removeCell: function (cell, collection, options) {
26978
- options = options || {};
26979
- if (!options.clear) {
26980
- // Applications might provide a `disconnectLinks` option set to `true` in order to
26981
- // disconnect links when a cell is removed rather then removing them. The default
26982
- // is to remove all the associated links.
26983
- if (options.disconnectLinks) {
26984
- this.disconnectLinks(cell, options);
27829
+ /**
27830
+ * @public
27831
+ * @description Remove multiple cells from the graph.
27832
+ * @param {Array<dia.Cell | dia.Cell.ID>} cellRefs - Array of cell references (models or IDs) to remove.
27833
+ * @param {Object} [options] - Removal options. See {@link dia.Graph#removeCell}.
27834
+ */
27835
+ removeCells: function (cellRefs, options) {
27836
+ if (!cellRefs.length) return this;
27837
+ // Remove multiple cells in a single batch
27838
+ this.startBatch('remove');
27839
+ for (const cellRef of cellRefs) {
27840
+ if (!cellRef) continue;
27841
+ let cell;
27842
+ if (cellRef[CELL_MARKER]) {
27843
+ cell = cellRef;
26985
27844
  } else {
26986
- this.removeLinks(cell, options);
27845
+ cell = this.getCell(cellRef);
27846
+ if (!cell) {
27847
+ // The cell might have been already removed (embedded cell, connected link, etc.)
27848
+ continue;
27849
+ }
26987
27850
  }
27851
+ this.layerCollection.removeCell(cell, options);
26988
27852
  }
26989
- // Silently remove the cell from the cells collection. Silently, because
26990
- // `joint.dia.Cell.prototype.remove` already triggers the `remove` event which is
26991
- // then propagated to the graph model. If we didn't remove the cell silently, two `remove` events
26992
- // would be triggered on the graph model.
26993
- this.get('cells').remove(cell, {
26994
- silent: true
26995
- });
27853
+ this.stopBatch('remove');
27854
+ return this;
27855
+ },
27856
+ /**
27857
+ * @protected
27858
+ * @description Replace an existing cell with a new cell.
27859
+ */
27860
+ _replaceCell: function (currentCell, newCellInit, opt = {}) {
27861
+ const batchName = 'replace-cell';
27862
+ const replaceOptions = {
27863
+ ...opt,
27864
+ replace: true
27865
+ };
27866
+ this.startBatch(batchName, opt);
27867
+ // 1. Remove the cell without removing connected links or embedded cells.
27868
+ this.layerCollection.removeCell(currentCell, replaceOptions);
27869
+ const newCellInitAttributes = newCellInit[CELL_MARKER] ? newCellInit.attributes : newCellInit;
27870
+ // 2. Combine the current cell attributes with the new cell attributes
27871
+ const replacementCellAttributes = Object.assign({}, currentCell.attributes, newCellInitAttributes);
27872
+ let replacement;
27873
+ if (newCellInit[CELL_MARKER]) {
27874
+ // If the new cell is a model, set the merged attributes on the model
27875
+ newCellInit.set(replacementCellAttributes, replaceOptions);
27876
+ replacement = newCellInit;
27877
+ } else {
27878
+ replacement = replacementCellAttributes;
27879
+ }
27880
+
27881
+ // 3. Add the replacement cell
27882
+ this.addCell(replacement, replaceOptions);
27883
+ this.stopBatch(batchName);
27884
+ },
27885
+ /**
27886
+ * @protected
27887
+ * @description Synchronize a single graph cell with the provided cell (model or attributes).
27888
+ * If the cell with the same `id` exists, it is updated. If the cell does not exist, it is added.
27889
+ * If the existing cell type is different from the incoming cell type, the existing cell is replaced.
27890
+ */
27891
+ _syncCell: function (cellInit, opt = {}) {
27892
+ const cellAttributes = cellInit[CELL_MARKER] ? cellInit.attributes : cellInit;
27893
+ const currentCell = this.getCell(cellInit.id);
27894
+ if (currentCell) {
27895
+ // `cellInit` is either a model or attributes object
27896
+ if ('type' in cellAttributes && currentCell.get('type') !== cellAttributes.type) {
27897
+ // Replace the cell if the type has changed
27898
+ this._replaceCell(currentCell, cellInit, opt);
27899
+ } else {
27900
+ // Update existing cell
27901
+ // Note: the existing cell attributes are not removed,
27902
+ // if they're missing in `cellAttributes`.
27903
+ currentCell.set(cellAttributes, opt);
27904
+ }
27905
+ } else {
27906
+ // The cell does not exist yet, add it
27907
+ this.addCell(cellInit, opt);
27908
+ }
27909
+ },
27910
+ /**
27911
+ * @public
27912
+ * @description Synchronize the graph cells with the provided array of cells (models or attributes).
27913
+ */
27914
+ syncCells: function (cellInits, opt = {}) {
27915
+ const batchName = 'sync-cells';
27916
+ const {
27917
+ remove = false,
27918
+ ...setOpt
27919
+ } = opt;
27920
+ let currentCells, newCellsMap;
27921
+ if (remove) {
27922
+ // We need to track existing cells to remove the missing ones later
27923
+ currentCells = this.getCells();
27924
+ newCellsMap = new Map();
27925
+ }
27926
+
27927
+ // Observe changes to the graph cells
27928
+ let changeObserver, changedLayers;
27929
+ const shouldSort = opt.sort !== false;
27930
+ if (shouldSort) {
27931
+ changeObserver = new Listener();
27932
+ changedLayers = new Set();
27933
+ changeObserver.listenTo(this, {
27934
+ 'add': cell => {
27935
+ changedLayers.add(this.getCellLayerId(cell));
27936
+ },
27937
+ 'change': cell => {
27938
+ if (cell.hasChanged(config$3.layerAttribute) || cell.hasChanged('z')) {
27939
+ changedLayers.add(this.getCellLayerId(cell));
27940
+ }
27941
+ }
27942
+ });
27943
+ }
27944
+ this.startBatch(batchName, opt);
27945
+
27946
+ // Prevent multiple sorts during sync
27947
+ setOpt.sort = false;
27948
+
27949
+ // Add or update incoming cells
27950
+ for (const cellInit of cellInits) {
27951
+ if (remove) {
27952
+ // only track existence
27953
+ newCellsMap.set(cellInit.id, true);
27954
+ }
27955
+ this._syncCell(cellInit, setOpt);
27956
+ }
27957
+ if (remove) {
27958
+ // Remove cells not present in the incoming array
27959
+ for (const cell of currentCells) {
27960
+ if (!newCellsMap.has(cell.id)) {
27961
+ this.layerCollection.removeCell(cell, setOpt);
27962
+ }
27963
+ }
27964
+ }
27965
+ if (shouldSort) {
27966
+ // Sort layers that had changes affecting z-index or layer
27967
+ changeObserver.stopListening();
27968
+ for (const layerId of changedLayers) {
27969
+ this.getLayer(layerId).cellCollection.sort(opt);
27970
+ }
27971
+ }
27972
+ this.stopBatch(batchName);
27973
+ },
27974
+ /**
27975
+ * @public
27976
+ * @description Remove a cell from the graph.
27977
+ * @param {dia.Cell} cell
27978
+ * @param {Object} [options]
27979
+ * @param {boolean} [options.disconnectLinks=false] - If `true`, the connected links are
27980
+ * disconnected instead of removed.
27981
+ * @param {boolean} [options.clear=false] - If `true`, the connected links
27982
+ * are kept. @internal
27983
+ * @param {boolean} [options.replace=false] - If `true`, the connected links and
27984
+ * embedded cells are kept. @internal
27985
+ * @throws Will throw an error if no cell is provided
27986
+ * @throws Will throw an error if the ID of the cell to remove
27987
+ * does not exist in the graph
27988
+ **/
27989
+ removeCell: function (cellRef, options) {
27990
+ if (!cellRef) {
27991
+ throw new Error('dia.Graph: no cell provided.');
27992
+ }
27993
+ const cell = cellRef[CELL_MARKER] ? cellRef : this.getCell(cellRef);
27994
+ if (!cell) {
27995
+ throw new Error('dia.Graph: cell to remove does not exist in the graph.');
27996
+ }
27997
+ if (cell.graph !== this) return;
27998
+ this.startBatch('remove');
27999
+ cell.collection.remove(cell, options);
28000
+ this.stopBatch('remove');
26996
28001
  },
26997
28002
  transferCellEmbeds: function (sourceCell, targetCell, opt = {}) {
26998
28003
  const batchName = 'transfer-embeds';
@@ -27022,24 +28027,249 @@ var joint = (function (exports) {
27022
28027
  });
27023
28028
  this.stopBatch(batchName);
27024
28029
  },
27025
- // Get a cell by `id`.
27026
- getCell: function (id) {
27027
- return this.get('cells').get(id);
28030
+ /**
28031
+ * @private
28032
+ * Helper method for addLayer and moveLayer methods
28033
+ */
28034
+ _getBeforeLayerIdFromOptions(options, layer = null) {
28035
+ let {
28036
+ before = null,
28037
+ index
28038
+ } = options;
28039
+ if (before && index !== undefined) {
28040
+ throw new Error('dia.Graph: Options "before" and "index" are mutually exclusive.');
28041
+ }
28042
+ let computedBefore;
28043
+ if (index !== undefined) {
28044
+ const layersArray = this.getLayers();
28045
+ if (index >= layersArray.length) {
28046
+ // If index is greater than the number of layers,
28047
+ // return before as null (move to the end).
28048
+ computedBefore = null;
28049
+ } else if (index < 0) {
28050
+ // If index is negative, move to the beginning.
28051
+ computedBefore = layersArray[0].id;
28052
+ } else {
28053
+ var _layersArray$index;
28054
+ const originalIndex = layersArray.indexOf(layer);
28055
+ if (originalIndex !== -1 && index > originalIndex) {
28056
+ // If moving a layer upwards in the stack, we need to adjust the index
28057
+ // to account for the layer being removed from its original position.
28058
+ index += 1;
28059
+ }
28060
+ // Otherwise, get the layer ID at the specified index.
28061
+ computedBefore = ((_layersArray$index = layersArray[index]) === null || _layersArray$index === void 0 ? void 0 : _layersArray$index.id) || null;
28062
+ }
28063
+ } else {
28064
+ computedBefore = before;
28065
+ }
28066
+ return computedBefore;
28067
+ },
28068
+ /**
28069
+ * @public
28070
+ * Adds a new layer to the graph.
28071
+ * @param {GraphLayer | GraphLayerJSON} layerInit
28072
+ * @param {*} options
28073
+ * @param {string | null} [options.before] - ID of the layer
28074
+ * before which to insert the new layer. If `null`, the layer is added at the end.
28075
+ * @param {number} [options.index] - Zero-based index to which to add the layer.
28076
+ * @throws Will throw an error if the layer to add is invalid
28077
+ * @throws Will throw an error if a layer with the same ID already exists
28078
+ * @throws Will throw if `before` reference is invalid
28079
+ */
28080
+ addLayer(layerInit, options = {}) {
28081
+ if (!layerInit || !layerInit.id) {
28082
+ throw new Error('dia.Graph: Layer to add is invalid.');
28083
+ }
28084
+ if (this.hasLayer(layerInit.id)) {
28085
+ throw new Error(`dia.Graph: Layer "${layerInit.id}" already exists.`);
28086
+ }
28087
+ const {
28088
+ before = null,
28089
+ index,
28090
+ ...insertOptions
28091
+ } = options;
28092
+ insertOptions.graph = this.cid;
28093
+
28094
+ // Adding a new layer disables legacy mode
28095
+ this.legacyMode = false;
28096
+ const beforeId = this._getBeforeLayerIdFromOptions({
28097
+ before,
28098
+ index
28099
+ });
28100
+ this.layerCollection.insert(layerInit, beforeId, insertOptions);
28101
+ },
28102
+ /**
28103
+ * @public
28104
+ * Moves an existing layer to a new position in the layer stack.
28105
+ * @param {string | GraphLayer} layerRef - ID or reference of the layer to move.
28106
+ * @param {*} options
28107
+ * @param {string | null} [options.before] - ID of the layer
28108
+ * before which to insert the moved layer. If `null`, the layer is moved to the end.
28109
+ * @param {number} [options.index] - Zero-based index to which to move the layer.
28110
+ * @throws Will throw an error if the layer to move does not exist
28111
+ * @throws Will throw an error if `before` reference is invalid
28112
+ * @throws Will throw an error if both `before` and `index` options are provided
28113
+ */
28114
+ moveLayer(layerRef, options = {}) {
28115
+ if (!layerRef || !this.hasLayer(layerRef)) {
28116
+ throw new Error('dia.Graph: Layer to move does not exist.');
28117
+ }
28118
+ const layer = this.getLayer(layerRef);
28119
+ const {
28120
+ before = null,
28121
+ index,
28122
+ ...insertOptions
28123
+ } = options;
28124
+ insertOptions.graph = this.cid;
28125
+
28126
+ // Moving a layer disables legacy mode
28127
+ this.legacyMode = false;
28128
+ const beforeId = this._getBeforeLayerIdFromOptions({
28129
+ before,
28130
+ index
28131
+ }, layer);
28132
+ this.layerCollection.insert(layer, beforeId, insertOptions);
28133
+ },
28134
+ /**
28135
+ * @public
28136
+ * Removes an existing layer from the graph.
28137
+ * @param {string | GraphLayer} layerRef - ID or reference of the layer to remove.
28138
+ * @param {*} options
28139
+ * @throws Will throw an error if no layer is provided
28140
+ * @throws Will throw an error if the layer to remove does not exist
28141
+ */
28142
+ removeLayer(layerRef, options = {}) {
28143
+ if (!layerRef) {
28144
+ throw new Error('dia.Graph: No layer provided.');
28145
+ }
28146
+
28147
+ // The layer must exist
28148
+ const layerId = layerRef.id ? layerRef.id : layerRef;
28149
+ const layer = this.getLayer(layerId);
28150
+
28151
+ // Prevent removing the default layer
28152
+ // Note: if there is only one layer, it is also the default layer.
28153
+ const {
28154
+ id: defaultLayerId
28155
+ } = this.getDefaultLayer();
28156
+ if (layerId === defaultLayerId) {
28157
+ throw new Error('dia.Graph: default layer cannot be removed.');
28158
+ }
28159
+
28160
+ // A layer with cells cannot be removed
28161
+ if (layer.cellCollection.length > 0) {
28162
+ throw new Error(`dia.Graph: Layer "${layerId}" cannot be removed because it is not empty.`);
28163
+ }
28164
+ this.layerCollection.remove(layerId, {
28165
+ ...options,
28166
+ graph: this.cid
28167
+ });
28168
+ },
28169
+ getDefaultLayer() {
28170
+ return this.layerCollection.get(this.defaultLayerId);
28171
+ },
28172
+ setDefaultLayer(layerRef, options = {}) {
28173
+ if (!layerRef) {
28174
+ throw new Error('dia.Graph: No default layer ID provided.');
28175
+ }
28176
+
28177
+ // Make sure the layer exists
28178
+ const defaultLayerId = layerRef.id ? layerRef.id : layerRef;
28179
+ const defaultLayer = this.getLayer(defaultLayerId);
28180
+
28181
+ // If the default layer is not changing, do nothing
28182
+ const currentDefaultLayerId = this.defaultLayerId;
28183
+ if (defaultLayerId === currentDefaultLayerId) {
28184
+ // The default layer stays the same
28185
+ return;
28186
+ }
28187
+
28188
+ // Get all cells that belong to the current default layer implicitly
28189
+ const implicitLayerCells = this.getImplicitLayerCells();
28190
+
28191
+ // Set the new default layer ID
28192
+ this.defaultLayerId = defaultLayerId;
28193
+ const batchName = 'default-layer-change';
28194
+ this.startBatch(batchName, options);
28195
+ if (implicitLayerCells.length > 0) {
28196
+ // Reassign any cells lacking an explicit layer to the new default layer.
28197
+ // Do not sort yet, wait until all cells are moved.
28198
+ const moveOptions = {
28199
+ ...options,
28200
+ sort: false
28201
+ };
28202
+ for (const cell of implicitLayerCells) {
28203
+ this.layerCollection.moveCellBetweenLayers(cell, defaultLayerId, moveOptions);
28204
+ }
28205
+ // Now sort the new default layer
28206
+ if (options.sort !== false) {
28207
+ defaultLayer.cellCollection.sort(options);
28208
+ }
28209
+ }
28210
+
28211
+ // Pretend to trigger the event on the layer itself.
28212
+ // It will bubble up as `layer:default` event on the graph.
28213
+ defaultLayer.trigger(defaultLayer.eventPrefix + 'default', defaultLayer, {
28214
+ ...options,
28215
+ previousDefaultLayerId: currentDefaultLayerId
28216
+ });
28217
+ this.stopBatch(batchName, options);
28218
+ },
28219
+ /**
28220
+ * @protected
28221
+ * @description Get all cells that do not have an explicit layer assigned.
28222
+ * These cells belong to the default layer implicitly.
28223
+ * @return {Array<dia.Cell>} Array of cells without an explicit layer.
28224
+ */
28225
+ getImplicitLayerCells() {
28226
+ return this.getDefaultLayer().cellCollection.filter(cell => {
28227
+ return cell.get(config$3.layerAttribute) == null;
28228
+ });
28229
+ },
28230
+ getLayer(layerId) {
28231
+ if (!this.hasLayer(layerId)) {
28232
+ throw new Error(`dia.Graph: Layer "${layerId}" does not exist.`);
28233
+ }
28234
+ return this.layerCollection.get(layerId);
28235
+ },
28236
+ hasLayer(layerRef) {
28237
+ return this.layerCollection.has(layerRef);
28238
+ },
28239
+ getLayers() {
28240
+ return this.layerCollection.toArray();
28241
+ },
28242
+ getCell: function (cellRef) {
28243
+ return this.layerCollection.getCell(cellRef);
27028
28244
  },
27029
28245
  getCells: function () {
27030
- return this.get('cells').toArray();
28246
+ return this.layerCollection.getCells();
27031
28247
  },
27032
28248
  getElements: function () {
27033
- return this.get('cells').toArray().filter(cell => cell.isElement());
28249
+ return this.getCells().filter(cell => cell.isElement());
27034
28250
  },
27035
28251
  getLinks: function () {
27036
- return this.get('cells').toArray().filter(cell => cell.isLink());
28252
+ return this.getCells().filter(cell => cell.isLink());
27037
28253
  },
27038
- getFirstCell: function () {
27039
- return this.get('cells').first();
28254
+ getFirstCell: function (layerId) {
28255
+ let layer;
28256
+ if (!layerId) {
28257
+ // Get the first cell from the bottom-most layer
28258
+ layer = this.getLayers().at(0);
28259
+ } else {
28260
+ layer = this.getLayer(layerId);
28261
+ }
28262
+ return layer.cellCollection.models.at(0);
27040
28263
  },
27041
- getLastCell: function () {
27042
- return this.get('cells').last();
28264
+ getLastCell: function (layerId) {
28265
+ let layer;
28266
+ if (!layerId) {
28267
+ // Get the last cell from the top-most layer
28268
+ layer = this.getLayers().at(-1);
28269
+ } else {
28270
+ layer = this.getLayer(layerId);
28271
+ }
28272
+ return layer.cellCollection.models.at(-1);
27043
28273
  },
27044
28274
  // Get all inbound and outbound links connected to the cell `model`.
27045
28275
  getConnectedLinks: function (model, opt) {
@@ -27063,12 +28293,13 @@ var joint = (function (exports) {
27063
28293
  addInbounds(this, model);
27064
28294
  }
27065
28295
  function addOutbounds(graph, model) {
27066
- forIn(graph.getOutboundEdges(model.id), function (_, edge) {
28296
+ forIn(graph.topologyIndex.getOutboundEdges(model.id), function (_, edge) {
27067
28297
  // skip links that were already added
27068
28298
  // (those must be self-loop links)
27069
28299
  // (because they are inbound and outbound edges of the same two elements)
27070
28300
  if (edges[edge]) return;
27071
28301
  var link = graph.getCell(edge);
28302
+ if (!link) return;
27072
28303
  links.push(link);
27073
28304
  edges[edge] = true;
27074
28305
  if (indirect) {
@@ -27087,12 +28318,13 @@ var joint = (function (exports) {
27087
28318
  }
27088
28319
  }
27089
28320
  function addInbounds(graph, model) {
27090
- forIn(graph.getInboundEdges(model.id), function (_, edge) {
28321
+ forIn(graph.topologyIndex.getInboundEdges(model.id), function (_, edge) {
27091
28322
  // skip links that were already added
27092
28323
  // (those must be self-loop links)
27093
28324
  // (because they are inbound and outbound edges of the same two elements)
27094
28325
  if (edges[edge]) return;
27095
28326
  var link = graph.getCell(edge);
28327
+ if (!link) return;
27096
28328
  links.push(link);
27097
28329
  edges[edge] = true;
27098
28330
  if (indirect) {
@@ -27127,7 +28359,7 @@ var joint = (function (exports) {
27127
28359
  embeddedCells.forEach(function (cell) {
27128
28360
  if (cell.isLink()) return;
27129
28361
  if (outbound) {
27130
- forIn(this.getOutboundEdges(cell.id), function (exists, edge) {
28362
+ forIn(this.topologyIndex.getOutboundEdges(cell.id), function (exists, edge) {
27131
28363
  if (!edges[edge]) {
27132
28364
  var edgeCell = this.getCell(edge);
27133
28365
  var {
@@ -27147,7 +28379,7 @@ var joint = (function (exports) {
27147
28379
  }.bind(this));
27148
28380
  }
27149
28381
  if (inbound) {
27150
- forIn(this.getInboundEdges(cell.id), function (exists, edge) {
28382
+ forIn(this.topologyIndex.getInboundEdges(cell.id), function (exists, edge) {
27151
28383
  if (!edges[edge]) {
27152
28384
  var edgeCell = this.getCell(edge);
27153
28385
  var {
@@ -27426,31 +28658,19 @@ var joint = (function (exports) {
27426
28658
  },
27427
28659
  // Get all the roots of the graph. Time complexity: O(|V|).
27428
28660
  getSources: function () {
27429
- var sources = [];
27430
- forIn(this._nodes, function (exists, node) {
27431
- if (!this._in[node] || isEmpty(this._in[node])) {
27432
- sources.push(this.getCell(node));
27433
- }
27434
- }.bind(this));
27435
- return sources;
28661
+ return this.topologyIndex.getSourceNodes().map(nodeId => this.getCell(nodeId));
27436
28662
  },
27437
28663
  // Get all the leafs of the graph. Time complexity: O(|V|).
27438
28664
  getSinks: function () {
27439
- var sinks = [];
27440
- forIn(this._nodes, function (exists, node) {
27441
- if (!this._out[node] || isEmpty(this._out[node])) {
27442
- sinks.push(this.getCell(node));
27443
- }
27444
- }.bind(this));
27445
- return sinks;
28665
+ return this.topologyIndex.getSinkNodes().map(nodeId => this.getCell(nodeId));
27446
28666
  },
27447
28667
  // Return `true` if `element` is a root. Time complexity: O(1).
27448
28668
  isSource: function (element) {
27449
- return !this._in[element.id] || isEmpty(this._in[element.id]);
28669
+ return this.topologyIndex.isSourceNode(element.id);
27450
28670
  },
27451
28671
  // Return `true` if `element` is a leaf. Time complexity: O(1).
27452
28672
  isSink: function (element) {
27453
- return !this._out[element.id] || isEmpty(this._out[element.id]);
28673
+ return this.topologyIndex.isSinkNode(element.id);
27454
28674
  },
27455
28675
  // Return `true` is `elementB` is a successor of `elementA`. Return `false` otherwise.
27456
28676
  isSuccessor: function (elementA, elementB) {
@@ -27521,8 +28741,10 @@ var joint = (function (exports) {
27521
28741
  });
27522
28742
  },
27523
28743
  // Remove links connected to the cell `model` completely.
27524
- removeLinks: function (model, opt) {
27525
- invoke(this.getConnectedLinks(model), 'remove', opt);
28744
+ removeLinks: function (cell, opt) {
28745
+ this.getConnectedLinks(cell).forEach(link => {
28746
+ this.layerCollection.removeCell(link, opt);
28747
+ });
27526
28748
  },
27527
28749
  // Find all cells at given point
27528
28750
 
@@ -27713,85 +28935,6 @@ var joint = (function (exports) {
27713
28935
  });
27714
28936
  wrapWith(Graph.prototype, ['resetCells', 'addCells', 'removeCells'], wrappers.cells);
27715
28937
 
27716
- const LayersNames = {
27717
- GRID: 'grid',
27718
- CELLS: 'cells',
27719
- BACK: 'back',
27720
- FRONT: 'front',
27721
- TOOLS: 'tools',
27722
- LABELS: 'labels'
27723
- };
27724
- const PaperLayer = View.extend({
27725
- tagName: 'g',
27726
- svgElement: true,
27727
- pivotNodes: null,
27728
- defaultTheme: null,
27729
- options: {
27730
- name: ''
27731
- },
27732
- className: function () {
27733
- const {
27734
- name
27735
- } = this.options;
27736
- if (!name) return null;
27737
- return addClassNamePrefix(`${name}-layer`);
27738
- },
27739
- init: function () {
27740
- this.pivotNodes = {};
27741
- },
27742
- insertSortedNode: function (node, z) {
27743
- this.el.insertBefore(node, this.insertPivot(z));
27744
- },
27745
- insertNode: function (node) {
27746
- const {
27747
- el
27748
- } = this;
27749
- if (node.parentNode !== el) {
27750
- el.appendChild(node);
27751
- }
27752
- },
27753
- insertPivot: function (z) {
27754
- const {
27755
- el,
27756
- pivotNodes
27757
- } = this;
27758
- z = +z;
27759
- z || (z = 0);
27760
- let pivotNode = pivotNodes[z];
27761
- if (pivotNode) return pivotNode;
27762
- pivotNode = pivotNodes[z] = document.createComment('z-index:' + (z + 1));
27763
- let neighborZ = -Infinity;
27764
- for (let currentZ in pivotNodes) {
27765
- currentZ = +currentZ;
27766
- if (currentZ < z && currentZ > neighborZ) {
27767
- neighborZ = currentZ;
27768
- if (neighborZ === z - 1) continue;
27769
- }
27770
- }
27771
- if (neighborZ !== -Infinity) {
27772
- const neighborPivot = pivotNodes[neighborZ];
27773
- // Insert After
27774
- el.insertBefore(pivotNode, neighborPivot.nextSibling);
27775
- } else {
27776
- // First Child
27777
- el.insertBefore(pivotNode, el.firstChild);
27778
- }
27779
- return pivotNode;
27780
- },
27781
- removePivots: function () {
27782
- const {
27783
- el,
27784
- pivotNodes
27785
- } = this;
27786
- for (let z in pivotNodes) el.removeChild(pivotNodes[z]);
27787
- this.pivotNodes = {};
27788
- },
27789
- isEmpty: function () {
27790
- // Check if the layer has any child elements (pivot comments are not counted).
27791
- return this.el.children.length === 0;
27792
- }
27793
- });
27794
-
27795
28938
  const calcAttributesList = ['transform', 'x', 'y', 'cx', 'cy', 'dx', 'dy', 'x1', 'y1', 'x2', 'y2', 'points', 'd', 'r', 'rx', 'ry', 'width', 'height', 'stroke-width', 'font-size'];
27796
28939
  const positiveValueList = ['r', 'rx', 'ry', 'width', 'height', 'stroke-width', 'font-size'];
27797
28940
  const calcAttributes = calcAttributesList.reduce((acc, attrName) => {
@@ -29033,7 +30176,6 @@ var joint = (function (exports) {
29033
30176
  // Internal tag to identify this object as a cell view instance.
29034
30177
  // Used instead of `instanceof` for performance and cross-frame safety.
29035
30178
 
29036
- const CELL_VIEW_MARKER = Symbol('joint.cellViewMarker');
29037
30179
  Object.defineProperty(CellView.prototype, CELL_VIEW_MARKER, {
29038
30180
  value: true
29039
30181
  });
@@ -31841,6 +32983,231 @@ var joint = (function (exports) {
31841
32983
  }
31842
32984
  });
31843
32985
 
32986
+ const LayerView = View.extend({
32987
+ tagName: 'g',
32988
+ svgElement: true,
32989
+ pivotNodes: null,
32990
+ defaultTheme: null,
32991
+ UPDATE_PRIORITY: 4,
32992
+ options: {
32993
+ id: ''
32994
+ },
32995
+ paper: null,
32996
+ init: function () {
32997
+ this.pivotNodes = {};
32998
+ this.id = this.options.id || this.cid;
32999
+ },
33000
+ setPaperReference: function (paper) {
33001
+ this.paper = paper;
33002
+ this.afterPaperReferenceSet(paper);
33003
+ },
33004
+ unsetPaperReference: function () {
33005
+ this.beforePaperReferenceUnset();
33006
+ this.paper = null;
33007
+ },
33008
+ assertPaperReference() {
33009
+ if (!this.paper) {
33010
+ throw new Error('LayerView: paper reference is not set.');
33011
+ }
33012
+ },
33013
+ afterPaperReferenceSet: function () {
33014
+ // Can be overridden in subclasses.
33015
+ },
33016
+ beforePaperReferenceUnset: function () {
33017
+ // Can be overridden in subclasses.
33018
+ },
33019
+ // prevents id to be set on the DOM element
33020
+ _setAttributes: function (attrs) {
33021
+ const newAttrs = clone$1(attrs);
33022
+ delete newAttrs.id;
33023
+ View.prototype._setAttributes.call(this, newAttrs);
33024
+ },
33025
+ className: function () {
33026
+ const {
33027
+ id
33028
+ } = this.options;
33029
+ return addClassNamePrefix(`${id}-layer`);
33030
+ },
33031
+ insertSortedNode: function (node, z) {
33032
+ this.el.insertBefore(node, this.insertPivot(z));
33033
+ },
33034
+ insertNode: function (node) {
33035
+ const {
33036
+ el
33037
+ } = this;
33038
+ if (node.parentNode !== el) {
33039
+ el.appendChild(node);
33040
+ }
33041
+ },
33042
+ insertPivot: function (z) {
33043
+ const {
33044
+ el,
33045
+ pivotNodes
33046
+ } = this;
33047
+ z = +z;
33048
+ z || (z = 0);
33049
+ let pivotNode = pivotNodes[z];
33050
+ if (pivotNode) return pivotNode;
33051
+ pivotNode = pivotNodes[z] = document.createComment('z-index:' + (z + 1));
33052
+ let neighborZ = -Infinity;
33053
+ for (let currentZ in pivotNodes) {
33054
+ currentZ = +currentZ;
33055
+ if (currentZ < z && currentZ > neighborZ) {
33056
+ neighborZ = currentZ;
33057
+ if (neighborZ === z - 1) continue;
33058
+ }
33059
+ }
33060
+ if (neighborZ !== -Infinity) {
33061
+ const neighborPivot = pivotNodes[neighborZ];
33062
+ // Insert After
33063
+ el.insertBefore(pivotNode, neighborPivot.nextSibling);
33064
+ } else {
33065
+ // First Child
33066
+ el.insertBefore(pivotNode, el.firstChild);
33067
+ }
33068
+ return pivotNode;
33069
+ },
33070
+ removePivots: function () {
33071
+ const {
33072
+ el,
33073
+ pivotNodes
33074
+ } = this;
33075
+ for (let z in pivotNodes) el.removeChild(pivotNodes[z]);
33076
+ this.pivotNodes = {};
33077
+ },
33078
+ isEmpty: function () {
33079
+ // Check if the layer has any child elements (pivot comments are not counted).
33080
+ return this.el.children.length === 0;
33081
+ },
33082
+ reset: function () {
33083
+ this.removePivots();
33084
+ }
33085
+ });
33086
+ Object.defineProperty(LayerView.prototype, LAYER_VIEW_MARKER, {
33087
+ value: true
33088
+ });
33089
+
33090
+ /**
33091
+ * @class GraphLayerView
33092
+ * @description A GraphLayerView is responsible for managing the rendering of cell views inside a layer.
33093
+ * It listens to the corresponding GraphLayer model and updates the DOM accordingly.
33094
+ * It uses dia.Paper sorting options to sort cell views in the DOM based on their `z` attribute.
33095
+ */
33096
+ const GraphLayerView = LayerView.extend({
33097
+ SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'],
33098
+ style: {
33099
+ webkitUserSelect: 'none',
33100
+ userSelect: 'none'
33101
+ },
33102
+ graph: null,
33103
+ init() {
33104
+ LayerView.prototype.init.apply(this, arguments);
33105
+ this.graph = this.model.graph;
33106
+ },
33107
+ className: function () {
33108
+ const {
33109
+ id
33110
+ } = this.options;
33111
+ return [addClassNamePrefix(`${id}-layer`), addClassNamePrefix('cells')].join(' ');
33112
+ },
33113
+ afterPaperReferenceSet(paper) {
33114
+ this.listenTo(this.model, 'sort', this.onCellCollectionSort);
33115
+ this.listenTo(this.model, 'change', this.onCellChange);
33116
+ this.listenTo(this.model, 'move', this.onCellMove);
33117
+ this.listenTo(this.graph, 'batch:stop', this.onGraphBatchStop);
33118
+ },
33119
+ beforePaperReferenceUnset() {
33120
+ this.stopListening(this.model);
33121
+ this.stopListening(this.graph);
33122
+ },
33123
+ onCellCollectionSort() {
33124
+ if (this.graph.hasActiveBatch(this.SORT_DELAYING_BATCHES)) return;
33125
+ this.sort();
33126
+ },
33127
+ onCellMove(cell, opt = {}) {
33128
+ // When a cell is moved from one layer to another,
33129
+ // request insertion of its view in the new layer.
33130
+ this.paper.requestCellViewInsertion(cell, opt);
33131
+ },
33132
+ onCellChange(cell, opt) {
33133
+ if (!cell.hasChanged('z')) return;
33134
+ // Re-insert the cell view to maintain correct z-ordering
33135
+ if (this.paper.options.sorting === sortingTypes.APPROX) {
33136
+ this.paper.requestCellViewInsertion(cell, opt);
33137
+ }
33138
+ },
33139
+ onGraphBatchStop(data) {
33140
+ const name = data && data.batchName;
33141
+ const sortDelayingBatches = this.SORT_DELAYING_BATCHES;
33142
+ // After certain batches, sorting may be required
33143
+ if (sortDelayingBatches.includes(name) && !this.graph.hasActiveBatch(sortDelayingBatches)) {
33144
+ this.sort();
33145
+ }
33146
+ },
33147
+ sort() {
33148
+ this.assertPaperReference();
33149
+ const {
33150
+ paper
33151
+ } = this;
33152
+ if (!paper.isExactSorting()) {
33153
+ // noop
33154
+ return;
33155
+ }
33156
+ if (paper.isFrozen()) {
33157
+ // sort views once unfrozen
33158
+ paper._updates.sort = true;
33159
+ return;
33160
+ }
33161
+ this.sortExact();
33162
+ },
33163
+ sortExact() {
33164
+ // Run insertion sort algorithm in order to efficiently sort DOM elements according to their
33165
+ // associated model `z` attribute.
33166
+ const cellNodes = Array.from(this.el.children).filter(node => node.getAttribute('model-id'));
33167
+ const cellCollection = this.model.cellCollection;
33168
+ sortElements(cellNodes, function (a, b) {
33169
+ const cellA = cellCollection.get(a.getAttribute('model-id'));
33170
+ const cellB = cellCollection.get(b.getAttribute('model-id'));
33171
+ const zA = cellA.attributes.z || 0;
33172
+ const zB = cellB.attributes.z || 0;
33173
+ return zA === zB ? 0 : zA < zB ? -1 : 1;
33174
+ });
33175
+ },
33176
+ insertCellView(cellView) {
33177
+ this.assertPaperReference();
33178
+ const {
33179
+ paper
33180
+ } = this;
33181
+ const {
33182
+ el,
33183
+ model
33184
+ } = cellView;
33185
+ switch (paper.options.sorting) {
33186
+ case sortingTypes.APPROX:
33187
+ this.insertSortedNode(el, model.get('z'));
33188
+ break;
33189
+ case sortingTypes.EXACT:
33190
+ default:
33191
+ this.insertNode(el);
33192
+ break;
33193
+ }
33194
+ }
33195
+ });
33196
+ Object.defineProperty(GraphLayerView.prototype, GRAPH_LAYER_VIEW_MARKER, {
33197
+ value: true
33198
+ });
33199
+
33200
+ /**
33201
+ * @class LegacyGraphLayerView
33202
+ * @description A legacy GraphLayerView with an additional class name for backward compatibility.
33203
+ */
33204
+ const LegacyGraphLayerView = GraphLayerView.extend({
33205
+ className: function () {
33206
+ const className = GraphLayerView.prototype.className.apply(this, arguments);
33207
+ return className + ' ' + addClassNamePrefix('viewport');
33208
+ }
33209
+ });
33210
+
31844
33211
  /**
31845
33212
  * Deque implementation for managing a double-ended queue.
31846
33213
  * This implementation uses a doubly linked list for efficient operations.
@@ -31963,23 +33330,24 @@ var joint = (function (exports) {
31963
33330
  }
31964
33331
  }
31965
33332
 
31966
- const GridLayer = PaperLayer.extend({
33333
+ const GridLayerView = LayerView.extend({
31967
33334
  style: {
31968
33335
  'pointer-events': 'none'
31969
33336
  },
31970
33337
  _gridCache: null,
31971
33338
  _gridSettings: null,
31972
33339
  init() {
31973
- PaperLayer.prototype.init.apply(this, arguments);
31974
- const {
31975
- options: {
31976
- paper
31977
- }
31978
- } = this;
33340
+ LayerView.prototype.init.apply(this, arguments);
33341
+ this.paper = this.options.paper;
31979
33342
  this._gridCache = null;
31980
33343
  this._gridSettings = [];
33344
+ },
33345
+ afterPaperReferenceSet(paper) {
31981
33346
  this.listenTo(paper, 'transform resize', this.updateGrid);
31982
33347
  },
33348
+ beforePaperReferenceUnset(paper) {
33349
+ this.stopListening(paper);
33350
+ },
31983
33351
  setGrid(drawGrid) {
31984
33352
  this._gridSettings = this.getGridSettings(drawGrid);
31985
33353
  this.renderGrid();
@@ -32004,9 +33372,7 @@ var joint = (function (exports) {
32004
33372
  },
32005
33373
  renderGrid() {
32006
33374
  const {
32007
- options: {
32008
- paper
32009
- }
33375
+ paper
32010
33376
  } = this;
32011
33377
  const {
32012
33378
  _gridSettings: gridSettings
@@ -32051,9 +33417,7 @@ var joint = (function (exports) {
32051
33417
  const {
32052
33418
  _gridCache: grid,
32053
33419
  _gridSettings: gridSettings,
32054
- options: {
32055
- paper
32056
- }
33420
+ paper
32057
33421
  } = this;
32058
33422
  if (!grid) return;
32059
33423
  const {
@@ -32087,7 +33451,7 @@ var joint = (function (exports) {
32087
33451
  });
32088
33452
  },
32089
33453
  _getPatternId(index) {
32090
- return `pattern_${this.options.paper.cid}_${index}`;
33454
+ return `pattern_${this.paper.cid}_${index}`;
32091
33455
  },
32092
33456
  _getGridRefs() {
32093
33457
  let {
@@ -32122,27 +33486,27 @@ var joint = (function (exports) {
32122
33486
  return grid;
32123
33487
  },
32124
33488
  _resolveDrawGridOption(opt) {
32125
- var namespace = this.options.patterns;
33489
+ const namespace = this.options.patterns;
32126
33490
  if (isString(opt) && Array.isArray(namespace[opt])) {
32127
33491
  return namespace[opt].map(function (item) {
32128
33492
  return assign({}, item);
32129
33493
  });
32130
33494
  }
32131
- var options = opt || {
33495
+ const options = opt || {
32132
33496
  args: [{}]
32133
33497
  };
32134
- var isArray = Array.isArray(options);
32135
- var name = options.name;
33498
+ const isArray = Array.isArray(options);
33499
+ let name = options.name;
32136
33500
  if (!isArray && !name && !options.markup) {
32137
33501
  name = 'dot';
32138
33502
  }
32139
33503
  if (name && Array.isArray(namespace[name])) {
32140
- var pattern = namespace[name].map(function (item) {
33504
+ const pattern = namespace[name].map(function (item) {
32141
33505
  return assign({}, item);
32142
33506
  });
32143
- var args = Array.isArray(options.args) ? options.args : [options.args || {}];
33507
+ const args = Array.isArray(options.args) ? options.args : [options.args || {}];
32144
33508
  defaults(args[0], omit(opt, 'args'));
32145
- for (var i = 0; i < args.length; i++) {
33509
+ for (let i = 0; i < args.length; i++) {
32146
33510
  if (pattern[i]) {
32147
33511
  assign(pattern[i], args[i]);
32148
33512
  }
@@ -32159,6 +33523,15 @@ var joint = (function (exports) {
32159
33523
  }
32160
33524
  });
32161
33525
 
33526
+ const paperLayers = {
33527
+ GRID: 'grid',
33528
+ BACK: 'back',
33529
+ /** @deprecated */
33530
+ CELLS: 'cells',
33531
+ FRONT: 'front',
33532
+ TOOLS: 'tools',
33533
+ LABELS: 'labels'
33534
+ };
32162
33535
  const sortingTypes = {
32163
33536
  NONE: 'sorting-none',
32164
33537
  APPROX: 'sorting-approximate',
@@ -32191,18 +33564,205 @@ var joint = (function (exports) {
32191
33564
  }
32192
33565
  }
32193
33566
  };
32194
- const defaultLayers = [{
32195
- name: LayersNames.GRID
32196
- }, {
32197
- name: LayersNames.BACK
33567
+ const gridPatterns = {
33568
+ dot: [{
33569
+ color: '#AAAAAA',
33570
+ thickness: 1,
33571
+ markup: 'rect',
33572
+ render: function (el, opt) {
33573
+ V(el).attr({
33574
+ width: opt.thickness,
33575
+ height: opt.thickness,
33576
+ fill: opt.color
33577
+ });
33578
+ }
33579
+ }],
33580
+ fixedDot: [{
33581
+ color: '#AAAAAA',
33582
+ thickness: 1,
33583
+ markup: 'rect',
33584
+ render: function (el, opt) {
33585
+ V(el).attr({
33586
+ fill: opt.color
33587
+ });
33588
+ },
33589
+ update: function (el, opt, paper) {
33590
+ const {
33591
+ sx,
33592
+ sy
33593
+ } = paper.scale();
33594
+ const width = sx <= 1 ? opt.thickness : opt.thickness / sx;
33595
+ const height = sy <= 1 ? opt.thickness : opt.thickness / sy;
33596
+ V(el).attr({
33597
+ width,
33598
+ height
33599
+ });
33600
+ }
33601
+ }],
33602
+ mesh: [{
33603
+ color: '#AAAAAA',
33604
+ thickness: 1,
33605
+ markup: 'path',
33606
+ render: function (el, opt) {
33607
+ var d;
33608
+ var width = opt.width;
33609
+ var height = opt.height;
33610
+ var thickness = opt.thickness;
33611
+ if (width - thickness >= 0 && height - thickness >= 0) {
33612
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
33613
+ } else {
33614
+ d = 'M 0 0 0 0';
33615
+ }
33616
+ V(el).attr({
33617
+ 'd': d,
33618
+ stroke: opt.color,
33619
+ 'stroke-width': opt.thickness
33620
+ });
33621
+ }
33622
+ }],
33623
+ doubleMesh: [{
33624
+ color: '#AAAAAA',
33625
+ thickness: 1,
33626
+ markup: 'path',
33627
+ render: function (el, opt) {
33628
+ var d;
33629
+ var width = opt.width;
33630
+ var height = opt.height;
33631
+ var thickness = opt.thickness;
33632
+ if (width - thickness >= 0 && height - thickness >= 0) {
33633
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
33634
+ } else {
33635
+ d = 'M 0 0 0 0';
33636
+ }
33637
+ V(el).attr({
33638
+ 'd': d,
33639
+ stroke: opt.color,
33640
+ 'stroke-width': opt.thickness
33641
+ });
33642
+ }
33643
+ }, {
33644
+ color: '#000000',
33645
+ thickness: 3,
33646
+ scaleFactor: 4,
33647
+ markup: 'path',
33648
+ render: function (el, opt) {
33649
+ var d;
33650
+ var width = opt.width;
33651
+ var height = opt.height;
33652
+ var thickness = opt.thickness;
33653
+ if (width - thickness >= 0 && height - thickness >= 0) {
33654
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
33655
+ } else {
33656
+ d = 'M 0 0 0 0';
33657
+ }
33658
+ V(el).attr({
33659
+ 'd': d,
33660
+ stroke: opt.color,
33661
+ 'stroke-width': opt.thickness
33662
+ });
33663
+ }
33664
+ }]
33665
+ };
33666
+ const backgroundPatterns = {
33667
+ flipXy: function (img) {
33668
+ // d b
33669
+ // q p
33670
+
33671
+ var canvas = document.createElement('canvas');
33672
+ var imgWidth = img.width;
33673
+ var imgHeight = img.height;
33674
+ canvas.width = 2 * imgWidth;
33675
+ canvas.height = 2 * imgHeight;
33676
+ var ctx = canvas.getContext('2d');
33677
+ // top-left image
33678
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33679
+ // xy-flipped bottom-right image
33680
+ ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
33681
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33682
+ // x-flipped top-right image
33683
+ ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
33684
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33685
+ // y-flipped bottom-left image
33686
+ ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
33687
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33688
+ return canvas;
33689
+ },
33690
+ flipX: function (img) {
33691
+ // d b
33692
+ // d b
33693
+
33694
+ var canvas = document.createElement('canvas');
33695
+ var imgWidth = img.width;
33696
+ var imgHeight = img.height;
33697
+ canvas.width = imgWidth * 2;
33698
+ canvas.height = imgHeight;
33699
+ var ctx = canvas.getContext('2d');
33700
+ // left image
33701
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33702
+ // flipped right image
33703
+ ctx.translate(2 * imgWidth, 0);
33704
+ ctx.scale(-1, 1);
33705
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33706
+ return canvas;
33707
+ },
33708
+ flipY: function (img) {
33709
+ // d d
33710
+ // q q
33711
+
33712
+ var canvas = document.createElement('canvas');
33713
+ var imgWidth = img.width;
33714
+ var imgHeight = img.height;
33715
+ canvas.width = imgWidth;
33716
+ canvas.height = imgHeight * 2;
33717
+ var ctx = canvas.getContext('2d');
33718
+ // top image
33719
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33720
+ // flipped bottom image
33721
+ ctx.translate(0, 2 * imgHeight);
33722
+ ctx.scale(1, -1);
33723
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33724
+ return canvas;
33725
+ },
33726
+ watermark: function (img, opt) {
33727
+ // d
33728
+ // d
33729
+
33730
+ opt = opt || {};
33731
+ var imgWidth = img.width;
33732
+ var imgHeight = img.height;
33733
+ var canvas = document.createElement('canvas');
33734
+ canvas.width = imgWidth * 3;
33735
+ canvas.height = imgHeight * 3;
33736
+ var ctx = canvas.getContext('2d');
33737
+ var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
33738
+ var radians = toRad(angle);
33739
+ var stepX = canvas.width / 4;
33740
+ var stepY = canvas.height / 4;
33741
+ for (var i = 0; i < 4; i++) {
33742
+ for (var j = 0; j < 4; j++) {
33743
+ if ((i + j) % 2 > 0) {
33744
+ // reset the current transformations
33745
+ ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
33746
+ ctx.rotate(radians);
33747
+ ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
33748
+ }
33749
+ }
33750
+ }
33751
+ return canvas;
33752
+ }
33753
+ };
33754
+ const implicitLayers = [{
33755
+ id: paperLayers.GRID,
33756
+ type: 'GridLayerView',
33757
+ patterns: gridPatterns
32198
33758
  }, {
32199
- name: LayersNames.CELLS
33759
+ id: paperLayers.BACK
32200
33760
  }, {
32201
- name: LayersNames.LABELS
33761
+ id: paperLayers.LABELS
32202
33762
  }, {
32203
- name: LayersNames.FRONT
33763
+ id: paperLayers.FRONT
32204
33764
  }, {
32205
- name: LayersNames.TOOLS
33765
+ id: paperLayers.TOOLS
32206
33766
  }];
32207
33767
  const CELL_VIEW_PLACEHOLDER_MARKER = Symbol('joint.cellViewPlaceholderMarker');
32208
33768
  const Paper = View.extend({
@@ -32272,7 +33832,7 @@ var joint = (function (exports) {
32272
33832
  // Do not create hard dependency on the joint.shapes.standard namespace (by importing the standard.Link model directly)
32273
33833
  const {
32274
33834
  cellNamespace
32275
- } = this.model.get('cells');
33835
+ } = this.model.layerCollection;
32276
33836
  const ctor = getByPath(cellNamespace, ['standard', 'Link']);
32277
33837
  if (!ctor) throw new Error('dia.Paper: no default link model found. Use `options.defaultLink` to specify a default link model.');
32278
33838
  return new ctor();
@@ -32358,12 +33918,24 @@ var joint = (function (exports) {
32358
33918
  viewManagement: false,
32359
33919
  // no docs yet
32360
33920
  onViewUpdate: function (view, flag, priority, opt, paper) {
32361
- // Do not update connected links when:
32362
- // 1. the view was just inserted (added to the graph and rendered)
32363
- // 2. the view was just mounted (added back to the paper by viewport function)
32364
- // 3. the change was marked as `isolate`.
32365
- // 4. the view model was just removed from the graph
32366
- if (flag & (paper.FLAG_INSERT | paper.FLAG_REMOVE) || opt.mounting || opt.isolate) return;
33921
+ if (opt.mounting || opt.isolate) {
33922
+ // Do not update connected links when:
33923
+ // - the view was just mounted (added back to the paper by viewport function)
33924
+ // - the change was marked as `isolate`.
33925
+ return;
33926
+ }
33927
+ // Always update connected links when the view model was replaced with another model
33928
+ // with the same id.
33929
+ // Note: the removal is done in 2 steps: remove the old model, add the new model.
33930
+ // We update connected links on the add step.
33931
+ if (!(opt.replace && opt.add)) {
33932
+ if (flag & (paper.FLAG_INSERT | paper.FLAG_REMOVE)) {
33933
+ // Do not update connected links when:
33934
+ // - the view was just inserted (added to the graph and rendered)
33935
+ // - the view model was just removed from the graph
33936
+ return;
33937
+ }
33938
+ }
32367
33939
  paper.requestConnectedLinksUpdate(view, priority, opt);
32368
33940
  },
32369
33941
  // no docs yet
@@ -32380,6 +33952,7 @@ var joint = (function (exports) {
32380
33952
  // Default namespaces
32381
33953
 
32382
33954
  cellViewNamespace: null,
33955
+ layerViewNamespace: null,
32383
33956
  routerNamespace: null,
32384
33957
  connectorNamespace: null,
32385
33958
  highlighterNamespace: highlighters,
@@ -32427,10 +34000,11 @@ var joint = (function (exports) {
32427
34000
  }
32428
34001
  `,
32429
34002
  svg: null,
32430
- viewport: null,
32431
34003
  defs: null,
32432
34004
  tools: null,
32433
34005
  layers: null,
34006
+ // deprecated, use layers element instead
34007
+ viewport: null,
32434
34008
  // For storing the current transformation matrix (CTM) of the paper's viewport.
32435
34009
  _viewportMatrix: null,
32436
34010
  // For verifying whether the CTM is up-to-date. The viewport transform attribute
@@ -32440,7 +34014,6 @@ var joint = (function (exports) {
32440
34014
  _updates: null,
32441
34015
  // Paper Layers
32442
34016
  _layers: null,
32443
- SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'],
32444
34017
  UPDATE_DELAYING_BATCHES: ['translate'],
32445
34018
  // If you interact with these elements,
32446
34019
  // the default interaction such as `element move` is prevented.
@@ -32460,12 +34033,13 @@ var joint = (function (exports) {
32460
34033
  // The find buffer is used to extend the area of the search
32461
34034
  // to mitigate the differences between the model and view geometry.
32462
34035
  DEFAULT_FIND_BUFFER: 200,
32463
- // Default layer to insert the cell views into.
32464
- DEFAULT_CELL_LAYER: LayersNames.CELLS,
32465
- // Update flags
32466
34036
  FLAG_INSERT: 1 << 30,
32467
34037
  FLAG_REMOVE: 1 << 29,
32468
34038
  FLAG_INIT: 1 << 28,
34039
+ // Layers that are always present on the paper (e.g. grid, back, front, tools)
34040
+ implicitLayers,
34041
+ // Reference layer for inserting new graph layers.
34042
+ graphLayerRefId: paperLayers.LABELS,
32469
34043
  init: function () {
32470
34044
  const {
32471
34045
  options
@@ -32475,6 +34049,12 @@ var joint = (function (exports) {
32475
34049
  options.cellViewNamespace = typeof joint !== 'undefined' && has(joint, 'shapes') ? joint.shapes : null;
32476
34050
  /* eslint-enable no-undef */
32477
34051
  }
34052
+ const defaultLayerViewNamespace = {
34053
+ LayerView,
34054
+ GraphLayerView,
34055
+ GridLayerView
34056
+ };
34057
+ this.layerViewNamespace = defaultsDeep({}, options.layerViewNamespace || {}, defaultLayerViewNamespace);
32478
34058
  const model = this.model = options.model || new Graph();
32479
34059
 
32480
34060
  // This property tells us if we need to keep the compatibility
@@ -32484,18 +34064,17 @@ var joint = (function (exports) {
32484
34064
  // Layers (SVGGroups)
32485
34065
  this._layers = {
32486
34066
  viewsMap: {},
32487
- namesMap: {},
32488
34067
  order: []
32489
34068
  };
32490
- this.cloneOptions();
32491
- this.render();
32492
- this._setDimensions();
32493
- this.startListening();
32494
34069
 
32495
34070
  // Hash of all cell views.
32496
34071
  this._views = {};
32497
34072
  this._viewPlaceholders = {};
32498
34073
  this._idToCid = {};
34074
+ this.cloneOptions();
34075
+ this.render();
34076
+ this._setDimensions();
34077
+ this.startListening();
32499
34078
 
32500
34079
  // Mouse wheel events buffer
32501
34080
  this._mw_evt_buffer = {
@@ -32504,7 +34083,7 @@ var joint = (function (exports) {
32504
34083
  };
32505
34084
 
32506
34085
  // Render existing cells in the graph
32507
- this.resetViews(model.attributes.cells.models);
34086
+ this.resetViews(model.getCells());
32508
34087
  },
32509
34088
  _resetUpdates: function () {
32510
34089
  if (this._updates && this._updates.id) cancelFrame(this._updates.id);
@@ -32524,7 +34103,8 @@ var joint = (function (exports) {
32524
34103
  },
32525
34104
  startListening: function () {
32526
34105
  var model = this.model;
32527
- this.listenTo(model, 'add', this.onCellAdded).listenTo(model, 'remove', this.onCellRemoved).listenTo(model, 'change', this.onCellChange).listenTo(model, 'reset', this.onGraphReset).listenTo(model, 'sort', this.onGraphSort).listenTo(model, 'batch:stop', this.onGraphBatchStop);
34106
+ this.listenTo(model, 'add', this.onCellAdded).listenTo(model, 'remove', this.onCellRemoved).listenTo(model, 'reset', this.onGraphReset).listenTo(model, 'batch:stop', this.onGraphBatchStop);
34107
+ this.listenTo(model, 'layer:add', this.onGraphLayerAdd).listenTo(model, 'layer:remove', this.onGraphLayerRemove).listenTo(model, 'layers:sort', this.onGraphLayerCollectionSort);
32528
34108
  this.on('cell:highlight', this.onCellHighlight).on('cell:unhighlight', this.onCellUnhighlight).on('transform', this.update);
32529
34109
  },
32530
34110
  onCellAdded: function (cell, _, opt) {
@@ -32550,22 +34130,15 @@ var joint = (function (exports) {
32550
34130
  this.requestViewUpdate(viewLike, this.FLAG_REMOVE, viewLike.UPDATE_PRIORITY, opt);
32551
34131
  }
32552
34132
  },
32553
- onCellChange: function (cell, opt) {
32554
- if (cell === this.model.attributes.cells) return;
32555
- if (cell.hasChanged('layer') || cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX) {
32556
- const viewLike = this._getCellViewLike(cell);
32557
- if (viewLike) {
32558
- this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
32559
- }
32560
- }
32561
- },
32562
- onGraphReset: function (collection, opt) {
32563
- this.resetLayers();
32564
- this.resetViews(collection.models, opt);
32565
- },
32566
- onGraphSort: function () {
32567
- if (this.model.hasActiveBatch(this.SORT_DELAYING_BATCHES)) return;
32568
- this.sortViews();
34133
+ onGraphReset: function (_collection, opt) {
34134
+ // Re-render all graph layer views
34135
+ // but keep the implicit layer views.
34136
+ this.renderGraphLayerViews();
34137
+ this.resetLayerViews();
34138
+ // Backward compatibility: reassign the `cells` property
34139
+ // with the default layer view.
34140
+ this.assertLayerViews();
34141
+ this.resetViews(this.model.getCells(), opt);
32569
34142
  },
32570
34143
  onGraphBatchStop: function (data) {
32571
34144
  if (this.isFrozen() || this.isIdle()) return;
@@ -32577,10 +34150,92 @@ var joint = (function (exports) {
32577
34150
  this.updateViews(data);
32578
34151
  }
32579
34152
  }
32580
- var sortDelayingBatches = this.SORT_DELAYING_BATCHES;
32581
- if (sortDelayingBatches.includes(name) && !graph.hasActiveBatch(sortDelayingBatches)) {
32582
- this.sortViews();
34153
+ },
34154
+ /**
34155
+ * @protected
34156
+ * @description When a new layer is added to the graph, we create a new layer view
34157
+ **/
34158
+ onGraphLayerAdd: function (layer, _, opt) {
34159
+ if (this.hasLayerView(layer.id)) return;
34160
+ const layerView = this.createLayerView({
34161
+ id: layer.id,
34162
+ model: layer
34163
+ });
34164
+ const layers = this.model.getLayers();
34165
+ let before;
34166
+ // Note: There is always at least one graph layer.
34167
+ if (layers[layers.length - 1] === layer) {
34168
+ // This is the last layer, so insert before the labels layer
34169
+ before = paperLayers.LABELS;
34170
+ } else {
34171
+ // There is a layer after the current one, so insert before that one
34172
+ const index = layers.indexOf(layer);
34173
+ before = layers[index + 1].id;
32583
34174
  }
34175
+ this.addLayerView(layerView, {
34176
+ before
34177
+ });
34178
+ },
34179
+ /**
34180
+ * @protected
34181
+ * @description When a layer is removed from the graph, we remove the corresponding layer view
34182
+ **/
34183
+ onGraphLayerRemove: function (layer, _, opt) {
34184
+ if (!this.hasLayerView(layer)) return;
34185
+
34186
+ // Request layer removal. Since the UPDATE_PRIORITY is lower
34187
+ // than cells update priority, the cell views will be removed first.
34188
+ this.requestLayerViewRemoval(layer);
34189
+ },
34190
+ /**
34191
+ * @protected
34192
+ * @description When the graph layer collection is sorted,
34193
+ * we reorder all graph layer views.
34194
+ **/
34195
+ onGraphLayerCollectionSort: function (layerCollection) {
34196
+ layerCollection.each(layer => {
34197
+ if (!this.hasLayerView(layer)) return;
34198
+ this.moveLayerView(layer, {
34199
+ before: this.graphLayerRefId
34200
+ });
34201
+ });
34202
+ },
34203
+ /**
34204
+ * @protected
34205
+ * @description Resets all graph layer views.
34206
+ */
34207
+ renderGraphLayerViews: function () {
34208
+ // Remove all existing graph layer views
34209
+ // Note: we don't use `getGraphLayerViews()` here because
34210
+ // rendered graph layer views could be different from the ones
34211
+ // in the graph layer collection (`onResetGraphLayerCollectionReset`).
34212
+ this.getLayerViews().forEach(layerView => {
34213
+ if (!layerView[GRAPH_LAYER_VIEW_MARKER]) return;
34214
+ this._removeLayerView(layerView);
34215
+ });
34216
+ // Create and insert new graph layer views
34217
+ this.model.getLayers().forEach(layer => {
34218
+ const layerView = this.createLayerView({
34219
+ id: layer.id,
34220
+ model: layer
34221
+ });
34222
+ // Insert the layer view into the paper layers, just before the labels layer.
34223
+ // All cell layers are positioned between the "back" and "labels" layers,
34224
+ // with the default "cells" layer originally occupying this position.
34225
+ this.addLayerView(layerView, {
34226
+ before: this.graphLayerRefId
34227
+ });
34228
+ });
34229
+ },
34230
+ /**
34231
+ * @protected
34232
+ * @description Renders all implicit layer views.
34233
+ */
34234
+ renderImplicitLayerViews: function () {
34235
+ this.implicitLayers.forEach(layerInit => {
34236
+ const layerView = this.createLayerView(layerInit);
34237
+ this.addLayerView(layerView);
34238
+ });
32584
34239
  },
32585
34240
  cloneOptions: function () {
32586
34241
  const {
@@ -32675,136 +34330,295 @@ var joint = (function (exports) {
32675
34330
  }]
32676
34331
  }];
32677
34332
  },
32678
- hasLayerView(layerName) {
32679
- return layerName in this._layers.viewsMap;
34333
+ /**
34334
+ * @public
34335
+ * @description Checks whether the layer view exists by the given layer id or layer model.
34336
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
34337
+ * @return {boolean} True if the layer view exists, false otherwise.
34338
+ */
34339
+ hasLayerView(layerRef) {
34340
+ let layerId;
34341
+ if (isString(layerRef)) {
34342
+ layerId = layerRef;
34343
+ } else if (layerRef) {
34344
+ layerId = layerRef.id;
34345
+ } else {
34346
+ return false;
34347
+ }
34348
+ return layerId in this._layers.viewsMap;
32680
34349
  },
32681
- getLayerView(layerName) {
32682
- const {
32683
- _layers: {
32684
- viewsMap
32685
- }
32686
- } = this;
32687
- if (layerName in viewsMap) return viewsMap[layerName];
32688
- throw new Error(`dia.Paper: Unknown layer "${layerName}".`);
34350
+ /**
34351
+ * @public
34352
+ * @description Returns the layer view by the given layer id or layer model.
34353
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
34354
+ * @return {dia.LayerView} The layer view.
34355
+ * @throws {Error} if the layer view is not found
34356
+ */
34357
+ getLayerView(layerRef) {
34358
+ let layerId;
34359
+ if (isString(layerRef)) {
34360
+ layerId = layerRef;
34361
+ } else if (layerRef) {
34362
+ layerId = layerRef.id;
34363
+ } else {
34364
+ throw new Error('dia.Paper: No layer provided.');
34365
+ }
34366
+ const layerView = this._layers.viewsMap[layerId];
34367
+ if (!layerView) {
34368
+ throw new Error(`dia.Paper: Unknown layer view "${layerId}".`);
34369
+ }
34370
+ return layerView;
32689
34371
  },
32690
- getLayerNode(layerName) {
32691
- return this.getLayerView(layerName).el;
34372
+ /**
34373
+ * @deprecated use `getLayerView(layerId).el` instead
34374
+ */
34375
+ getLayerNode(layerId) {
34376
+ return this.getLayerView(layerId).el;
32692
34377
  },
32693
- _removeLayer(layerView) {
32694
- this._unregisterLayer(layerView);
34378
+ /**
34379
+ * @protected
34380
+ * @description Removes the given layer view from the paper.
34381
+ * It does not check whether the layer view is empty.
34382
+ * @param {dia.LayerView} layerView - The layer view to remove.
34383
+ */
34384
+ _removeLayerView(layerView) {
34385
+ this._unregisterLayerView(layerView);
32695
34386
  layerView.remove();
32696
34387
  },
32697
- _unregisterLayer(layerView) {
32698
- const {
32699
- _layers: {
32700
- viewsMap,
32701
- namesMap,
32702
- order
32703
- }
32704
- } = this;
32705
- const layerName = this._getLayerName(layerView);
32706
- order.splice(order.indexOf(layerName), 1);
32707
- delete namesMap[layerView.cid];
32708
- delete viewsMap[layerName];
34388
+ /**
34389
+ * @protected
34390
+ * @description Removes all layer views from the paper.
34391
+ * It does not check whether the layer views are empty.
34392
+ */
34393
+ _removeLayerViews: function () {
34394
+ Object.values(this._layers.viewsMap).forEach(layerView => {
34395
+ this._removeLayerView(layerView);
34396
+ });
32709
34397
  },
32710
- _registerLayer(layerName, layerView, beforeLayerView) {
34398
+ /**
34399
+ * @protected
34400
+ * @description Unregisters the given layer view from the paper.
34401
+ * @param {dia.LayerView} layerView - The layer view to unregister.
34402
+ */
34403
+ _unregisterLayerView(layerView) {
32711
34404
  const {
32712
34405
  _layers: {
32713
34406
  viewsMap,
32714
- namesMap,
32715
34407
  order
32716
34408
  }
32717
34409
  } = this;
32718
- if (beforeLayerView) {
32719
- const beforeLayerName = this._getLayerName(beforeLayerView);
32720
- order.splice(order.indexOf(beforeLayerName), 0, layerName);
32721
- } else {
32722
- order.push(layerName);
34410
+ const layerId = layerView.id;
34411
+ // Remove the layer id from the order list.
34412
+ const layerIndex = order.indexOf(layerId);
34413
+ if (layerIndex !== -1) {
34414
+ order.splice(layerIndex, 1);
32723
34415
  }
32724
- viewsMap[layerName] = layerView;
32725
- namesMap[layerView.cid] = layerName;
34416
+ // Unlink the layer view from the paper.
34417
+ layerView.unsetPaperReference();
34418
+ // Remove the layer view from the paper's registry.
34419
+ delete viewsMap[layerId];
32726
34420
  },
32727
- _getLayerView(layer) {
32728
- const {
32729
- _layers: {
32730
- namesMap,
32731
- viewsMap
32732
- }
32733
- } = this;
32734
- if (layer instanceof PaperLayer) {
32735
- if (layer.cid in namesMap) return layer;
32736
- return null;
34421
+ /**
34422
+ * @protected
34423
+ * @description Registers the given layer view in the paper.
34424
+ * @param {dia.LayerView} layerView - The layer view to register.
34425
+ * @throws {Error} if the layer view is not an instance of dia.LayerView
34426
+ * @throws {Error} if the layer view already exists in the paper
34427
+ */
34428
+ _registerLayerView(layerView) {
34429
+ if (!layerView || !layerView[LAYER_VIEW_MARKER]) {
34430
+ throw new Error('dia.Paper: The layer view must be an instance of dia.LayerView.');
32737
34431
  }
32738
- if (layer in viewsMap) return viewsMap[layer];
32739
- return null;
34432
+ if (this.hasLayerView(layerView.id)) {
34433
+ throw new Error(`dia.Paper: The layer view "${layerView.id}" already exists.`);
34434
+ }
34435
+ // Link the layer view back to the paper.
34436
+ layerView.setPaperReference(this);
34437
+ // Store the layer view in the paper's registry.
34438
+ this._layers.viewsMap[layerView.id] = layerView;
32740
34439
  },
32741
- _getLayerName(layerView) {
34440
+ /**
34441
+ * @public
34442
+ * @description Removes the layer view by the given layer id or layer model.
34443
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
34444
+ * @throws {Error} if the layer view is not empty
34445
+ */
34446
+ removeLayerView(layerRef) {
34447
+ const layerView = this.getLayerView(layerRef);
34448
+ if (!layerView.isEmpty()) {
34449
+ throw new Error('dia.Paper: The layer view is not empty.');
34450
+ }
34451
+ this._removeLayerView(layerView);
34452
+ },
34453
+ /**
34454
+ * @protected
34455
+ * @description Schedules the layer view removal by the given layer id or layer model.
34456
+ * The actual removal will be performed during the paper update cycle.
34457
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
34458
+ * @param {Object} [opt] - Update options.
34459
+ */
34460
+ requestLayerViewRemoval(layerRef, opt) {
34461
+ const layerView = this.getLayerView(layerRef);
32742
34462
  const {
32743
- _layers: {
32744
- namesMap
32745
- }
34463
+ FLAG_REMOVE
32746
34464
  } = this;
32747
- return namesMap[layerView.cid];
34465
+ const {
34466
+ UPDATE_PRIORITY
34467
+ } = layerView;
34468
+ this.requestViewUpdate(layerView, FLAG_REMOVE, UPDATE_PRIORITY, opt);
32748
34469
  },
32749
- _requireLayerView(layer) {
32750
- const layerView = this._getLayerView(layer);
32751
- if (!layerView) {
32752
- if (layer instanceof PaperLayer) {
32753
- throw new Error('dia.Paper: The layer is not registered.');
34470
+ /**
34471
+ * @public
34472
+ * @internal not documented
34473
+ * @description Schedules the cell view insertion into the appropriate layer view.
34474
+ * The actual insertion will be performed during the paper update cycle.
34475
+ * @param {dia.Cell} cell - The cell model whose view should be inserted.
34476
+ * @param {Object} [opt] - Update options.
34477
+ */
34478
+ requestCellViewInsertion(cell, opt) {
34479
+ const viewLike = this._getCellViewLike(cell);
34480
+ if (!viewLike) return;
34481
+ this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
34482
+ },
34483
+ /**
34484
+ * @private
34485
+ * Helper method for addLayerView and moveLayerView methods
34486
+ */
34487
+ _getBeforeLayerViewFromOptions(layerView, options) {
34488
+ let {
34489
+ before = null,
34490
+ index
34491
+ } = options;
34492
+ if (before && index !== undefined) {
34493
+ throw new Error('dia.Paper: Options "before" and "index" are mutually exclusive.');
34494
+ }
34495
+ let computedBefore;
34496
+ if (index !== undefined) {
34497
+ const {
34498
+ _layers: {
34499
+ order
34500
+ }
34501
+ } = this;
34502
+ if (index >= order.length) {
34503
+ // If index is greater than the number of layers,
34504
+ // return before as null (move to the end).
34505
+ computedBefore = null;
34506
+ } else if (index < 0) {
34507
+ // If index is negative, move to the beginning.
34508
+ computedBefore = order[0];
32754
34509
  } else {
32755
- throw new Error(`dia.Paper: Unknown layer "${layer}".`);
34510
+ const originalIndex = order.indexOf(layerView.id);
34511
+ if (originalIndex !== -1 && index > originalIndex) {
34512
+ // If moving a layer upwards in the stack, we need to adjust the index
34513
+ // to account for the layer being removed from its original position.
34514
+ index += 1;
34515
+ }
34516
+ // Otherwise, get the layer ID at the specified index.
34517
+ computedBefore = order[index] || null;
32756
34518
  }
34519
+ } else {
34520
+ computedBefore = before;
32757
34521
  }
32758
- return layerView;
34522
+ return computedBefore ? this.getLayerView(computedBefore) : null;
32759
34523
  },
32760
- hasLayer(layer) {
32761
- return this._getLayerView(layer) !== null;
34524
+ /**
34525
+ * @public
34526
+ * @description Adds the layer view to the paper.
34527
+ * @param {dia.LayerView} layerView - The layer view to add.
34528
+ * @param {Object} [options] - Adding options.
34529
+ * @param {string|dia.GraphLayer} [options.before] - Layer id or layer model before
34530
+ */
34531
+ addLayerView(layerView, options = {}) {
34532
+ this._registerLayerView(layerView);
34533
+ const beforeLayerView = this._getBeforeLayerViewFromOptions(layerView, options);
34534
+ this.insertLayerView(layerView, beforeLayerView);
32762
34535
  },
32763
- removeLayer(layer) {
32764
- const layerView = this._requireLayerView(layer);
32765
- if (!layerView.isEmpty()) {
32766
- throw new Error('dia.Paper: The layer is not empty.');
32767
- }
32768
- this._removeLayer(layerView);
34536
+ /**
34537
+ * @public
34538
+ * @description Moves the layer view.
34539
+ * @param {Paper.LayerRef} layerRef - The layer view reference to move.
34540
+ * @param {Object} [options] - Moving options.
34541
+ * @param {Paper.LayerRef} [options.before] - Layer id or layer model before
34542
+ * @param {number} [options.index] - Zero-based index to which to move the layer view.
34543
+ */
34544
+ moveLayerView(layerRef, options = {}) {
34545
+ const layerView = this.getLayerView(layerRef);
34546
+ const beforeLayerView = this._getBeforeLayerViewFromOptions(layerView, options);
34547
+ this.insertLayerView(layerView, beforeLayerView);
32769
34548
  },
32770
- addLayer(layerName, layerView, options = {}) {
32771
- if (!layerName || typeof layerName !== 'string') {
32772
- throw new Error('dia.Paper: The layer name must be provided.');
32773
- }
32774
- if (this._getLayerView(layerName)) {
32775
- throw new Error(`dia.Paper: The layer "${layerName}" already exists.`);
32776
- }
32777
- if (!(layerView instanceof PaperLayer)) {
32778
- throw new Error('dia.Paper: The layer view is not an instance of dia.PaperLayer.');
32779
- }
34549
+ /**
34550
+ * @protected
34551
+ * @description Inserts the layer view into the paper.
34552
+ * If the layer view already exists in the paper, it is moved to the new position.
34553
+ * @param {dia.LayerView} layerView - The layer view to insert.
34554
+ * @param {dia.LayerView} [before] - Layer view before
34555
+ * which the layer view should be inserted.
34556
+ */
34557
+ insertLayerView(layerView, beforeLayerView) {
34558
+ const layerId = layerView.id;
32780
34559
  const {
32781
- insertBefore
32782
- } = options;
32783
- if (!insertBefore) {
32784
- this._registerLayer(layerName, layerView, null);
32785
- this.layers.appendChild(layerView.el);
32786
- } else {
32787
- const beforeLayerView = this._requireLayerView(insertBefore);
32788
- this._registerLayer(layerName, layerView, beforeLayerView);
34560
+ _layers: {
34561
+ order
34562
+ }
34563
+ } = this;
34564
+ const currentLayerIndex = order.indexOf(layerId);
34565
+
34566
+ // Should the layer view be inserted before another layer view?
34567
+ if (beforeLayerView) {
34568
+ const beforeLayerViewId = beforeLayerView.id;
34569
+ if (layerId === beforeLayerViewId) {
34570
+ // The layer view is already in the right place.
34571
+ return;
34572
+ }
34573
+ let beforeLayerPosition = order.indexOf(beforeLayerViewId);
34574
+ // Remove from the `order` list if the layer view is already in the order.
34575
+ if (currentLayerIndex !== -1) {
34576
+ if (currentLayerIndex < beforeLayerPosition) {
34577
+ beforeLayerPosition -= 1;
34578
+ }
34579
+ order.splice(currentLayerIndex, 1);
34580
+ }
34581
+ order.splice(beforeLayerPosition, 0, layerId);
32789
34582
  this.layers.insertBefore(layerView.el, beforeLayerView.el);
34583
+ return;
32790
34584
  }
34585
+
34586
+ // Remove from the `order` list if the layer view is already in the order.
34587
+ // This is needed for the case when the layer view is inserted in the new position.
34588
+ if (currentLayerIndex !== -1) {
34589
+ order.splice(currentLayerIndex, 1);
34590
+ }
34591
+ order.push(layerId);
34592
+ this.layers.appendChild(layerView.el);
32791
34593
  },
32792
- moveLayer(layer, insertBefore) {
32793
- const layerView = this._requireLayerView(layer);
32794
- if (layerView === this._getLayerView(insertBefore)) return;
32795
- const layerName = this._getLayerName(layerView);
32796
- this._unregisterLayer(layerView);
32797
- this.addLayer(layerName, layerView, {
32798
- insertBefore
32799
- });
32800
- },
32801
- getLayerNames() {
32802
- // Returns a sorted array of layer names.
34594
+ /**
34595
+ * @protected
34596
+ * @description Returns an array of layer view ids in the order they are rendered.
34597
+ * @returns {string[]} An array of layer view ids.
34598
+ */
34599
+ getLayerViewOrder() {
32803
34600
  return this._layers.order.slice();
32804
34601
  },
32805
- getLayers() {
32806
- // Returns a sorted array of layer views.
32807
- return this.getLayerNames().map(name => this.getLayerView(name));
34602
+ /**
34603
+ * @public
34604
+ * @description Returns an array of layer views in the order they are rendered.
34605
+ * @returns {dia.LayerView[]} An array of layer views.
34606
+ */
34607
+ getLayerViews() {
34608
+ return this.getLayerViewOrder().map(id => this.getLayerView(id));
34609
+ },
34610
+ /**
34611
+ * @public
34612
+ * @description Returns an array of graph layer views in the order they are rendered.
34613
+ * @returns {dia.GraphLayerView[]} An array of graph layer views.
34614
+ */
34615
+ getGraphLayerViews() {
34616
+ const {
34617
+ _layers: {
34618
+ viewsMap
34619
+ }
34620
+ } = this;
34621
+ return this.model.getLayers().map(layer => viewsMap[layer.id]);
32808
34622
  },
32809
34623
  render: function () {
32810
34624
  this.renderChildren();
@@ -32824,7 +34638,7 @@ var joint = (function (exports) {
32824
34638
  this.svg = svg;
32825
34639
  this.defs = defs;
32826
34640
  this.layers = layers;
32827
- this.renderLayers();
34641
+ this.renderLayerViews();
32828
34642
  V.ensureId(svg);
32829
34643
  this.addStylesheet(stylesheet);
32830
34644
  if (options.background) {
@@ -32839,60 +34653,79 @@ var joint = (function (exports) {
32839
34653
  if (!css) return;
32840
34654
  V(this.svg).prepend(V.createSVGStyle(css));
32841
34655
  },
32842
- createLayer(name) {
32843
- switch (name) {
32844
- case LayersNames.GRID:
32845
- return new GridLayer({
32846
- name,
32847
- paper: this,
32848
- patterns: this.constructor.gridPatterns
32849
- });
32850
- default:
32851
- return new PaperLayer({
32852
- name
32853
- });
34656
+ /**
34657
+ * @protected
34658
+ * @description Creates a layer view instance based on the provided options.
34659
+ * It finds the appropriate layer view constructor from the paper's
34660
+ * `layerViewNamespace` and instantiates it.
34661
+ * @param {*} options See `dia.LayerView` options.
34662
+ * @returns {dia.LayerView}
34663
+ */
34664
+ createLayerView(options) {
34665
+ if (options == null) {
34666
+ throw new Error('dia.Paper: Layer view options are required.');
34667
+ }
34668
+ if (options.id == null) {
34669
+ throw new Error('dia.Paper: Layer view id is required.');
34670
+ }
34671
+ const viewOptions = clone$1(options);
34672
+ let viewConstructor;
34673
+ if (viewOptions.model) {
34674
+ const modelType = viewOptions.model.get('type') || viewOptions.model.constructor.name;
34675
+ const type = modelType + 'View';
34676
+
34677
+ // For backward compatibility we use the LegacyGraphLayerView for the default `cells` layer.
34678
+ if (this.model.layersController.legacyMode) {
34679
+ viewConstructor = LegacyGraphLayerView;
34680
+ } else {
34681
+ viewConstructor = this.layerViewNamespace[type] || LayerView;
34682
+ }
34683
+ } else {
34684
+ // Paper layers
34685
+ const type = viewOptions.type;
34686
+ viewConstructor = this.layerViewNamespace[type] || LayerView;
32854
34687
  }
34688
+ return new viewConstructor(viewOptions);
32855
34689
  },
32856
- renderLayer: function (name) {
32857
- const layerView = this.createLayer(name);
32858
- this.addLayer(name, layerView);
32859
- return layerView;
34690
+ /**
34691
+ * @protected
34692
+ * @description Renders all paper layer views and graph layer views.
34693
+ */
34694
+ renderLayerViews: function () {
34695
+ this._removeLayerViews();
34696
+ // Render the paper layers.
34697
+ this.renderImplicitLayerViews();
34698
+ // Render the layers.
34699
+ this.renderGraphLayerViews();
34700
+ // Ensure that essential layer views are present.
34701
+ this.assertLayerViews();
32860
34702
  },
32861
- renderLayers: function (layers = defaultLayers) {
32862
- this.removeLayers();
32863
- layers.forEach(({
32864
- name
32865
- }) => this.renderLayer(name));
32866
- // Throws an exception if doesn't exist
32867
- const cellsLayerView = this.getLayerView(LayersNames.CELLS);
32868
- const toolsLayerView = this.getLayerView(LayersNames.TOOLS);
32869
- const labelsLayerView = this.getLayerView(LayersNames.LABELS);
34703
+ /**
34704
+ * @protected
34705
+ * @description Ensures that essential layer views are present on the paper.
34706
+ * @throws {Error} if any of the essential layer views is missing
34707
+ */
34708
+ assertLayerViews: function () {
34709
+ // Throws an exception if essential layer views are missing.
34710
+ const cellsLayerView = this.getLayerView(this.model.getDefaultLayer().id);
34711
+ const toolsLayerView = this.getLayerView(paperLayers.TOOLS);
34712
+ const labelsLayerView = this.getLayerView(paperLayers.LABELS);
34713
+
32870
34714
  // backwards compatibility
32871
34715
  this.tools = toolsLayerView.el;
32872
34716
  this.cells = this.viewport = cellsLayerView.el;
32873
- // user-select: none;
32874
- cellsLayerView.vel.addClass(addClassNamePrefix('viewport'));
34717
+ // Backwards compatibility: same as `LegacyGraphLayerView` we keep
34718
+ // the `viewport` class on the labels layer.
32875
34719
  labelsLayerView.vel.addClass(addClassNamePrefix('viewport'));
32876
- cellsLayerView.el.style.webkitUserSelect = 'none';
32877
- cellsLayerView.el.style.userSelect = 'none';
32878
34720
  labelsLayerView.el.style.webkitUserSelect = 'none';
32879
34721
  labelsLayerView.el.style.userSelect = 'none';
32880
34722
  },
32881
- removeLayers: function () {
32882
- const {
32883
- _layers: {
32884
- viewsMap
32885
- }
32886
- } = this;
32887
- Object.values(viewsMap).forEach(layerView => this._removeLayer(layerView));
32888
- },
32889
- resetLayers: function () {
32890
- const {
32891
- _layers: {
32892
- viewsMap
32893
- }
32894
- } = this;
32895
- Object.values(viewsMap).forEach(layerView => layerView.removePivots());
34723
+ /**
34724
+ * @protected
34725
+ * @description Resets all layer views.
34726
+ */
34727
+ resetLayerViews: function () {
34728
+ this.getLayerViews().forEach(layerView => layerView.reset());
32896
34729
  },
32897
34730
  update: function () {
32898
34731
  if (this._background) {
@@ -33008,26 +34841,25 @@ var joint = (function (exports) {
33008
34841
  return this;
33009
34842
  },
33010
34843
  clientMatrix: function () {
33011
- return V.createSVGMatrix(this.cells.getScreenCTM());
34844
+ return V.createSVGMatrix(this.layers.getScreenCTM());
33012
34845
  },
33013
34846
  requestConnectedLinksUpdate: function (view, priority, opt) {
33014
- if (view instanceof CellView) {
33015
- var model = view.model;
33016
- var links = this.model.getConnectedLinks(model);
33017
- for (var j = 0, n = links.length; j < n; j++) {
33018
- var link = links[j];
33019
- var linkView = this._getCellViewLike(link);
33020
- if (!linkView) continue;
33021
- // We do not have to update placeholder views.
33022
- // They will be updated on initial render.
33023
- if (linkView[CELL_VIEW_PLACEHOLDER_MARKER]) continue;
33024
- var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
33025
- this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
33026
- }
34847
+ if (!view || !view[CELL_VIEW_MARKER]) return;
34848
+ var model = view.model;
34849
+ var links = this.model.getConnectedLinks(model);
34850
+ for (var j = 0, n = links.length; j < n; j++) {
34851
+ var link = links[j];
34852
+ var linkView = this._getCellViewLike(link);
34853
+ if (!linkView) continue;
34854
+ // We do not have to update placeholder views.
34855
+ // They will be updated on initial render.
34856
+ if (linkView[CELL_VIEW_PLACEHOLDER_MARKER]) continue;
34857
+ var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
34858
+ this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
33027
34859
  }
33028
34860
  },
33029
34861
  forcePostponedViewUpdate: function (view, flag) {
33030
- if (!view || !(view instanceof CellView)) return false;
34862
+ if (!view || !view[CELL_VIEW_MARKER]) return false;
33031
34863
  const model = view.model;
33032
34864
  if (model.isElement()) return false;
33033
34865
  const dumpOptions = {
@@ -33142,6 +34974,12 @@ var joint = (function (exports) {
33142
34974
  const {
33143
34975
  model
33144
34976
  } = view;
34977
+ if (view[GRAPH_LAYER_VIEW_MARKER]) {
34978
+ if (flag & FLAG_REMOVE) {
34979
+ this.removeLayerView(view);
34980
+ return 0;
34981
+ }
34982
+ }
33145
34983
  if (view[CELL_VIEW_MARKER]) {
33146
34984
  if (flag & FLAG_REMOVE) {
33147
34985
  this.removeView(model);
@@ -33741,7 +35579,7 @@ var joint = (function (exports) {
33741
35579
  }
33742
35580
  this.options.frozen = updates.keyFrozen = false;
33743
35581
  if (updates.sort) {
33744
- this.sortViews();
35582
+ this.sortLayerViews();
33745
35583
  updates.sort = false;
33746
35584
  }
33747
35585
  },
@@ -33769,8 +35607,8 @@ var joint = (function (exports) {
33769
35607
  this.freeze();
33770
35608
  this._updates.disabled = true;
33771
35609
  //clean up all DOM elements/views to prevent memory leaks
33772
- this.removeLayers();
33773
35610
  this.removeViews();
35611
+ this._removeLayerViews();
33774
35612
  },
33775
35613
  getComputedSize: function () {
33776
35614
  var options = this.options;
@@ -34008,7 +35846,17 @@ var joint = (function (exports) {
34008
35846
  if (opt && opt.useModelGeometry) {
34009
35847
  return this.model.getBBox() || new Rect();
34010
35848
  }
34011
- return V(this.cells).getBBox();
35849
+ const graphLayerViews = this.getGraphLayerViews();
35850
+ // Return an empty rectangle if there are no layers
35851
+ // should not happen in practice
35852
+ if (graphLayerViews.length === 0) {
35853
+ return new Rect();
35854
+ }
35855
+
35856
+ // Combine content area rectangles from all layers,
35857
+ // considering only graph layer views to exclude non-cell elements (e.g., grid, tools)
35858
+ const bbox = Rect.fromRectUnion(...graphLayerViews.map(view => view.vel.getBBox()));
35859
+ return bbox;
34012
35860
  },
34013
35861
  // Return the dimensions of the content bbox in the paper units (as it appears on screen).
34014
35862
  getContentBBox: function (opt) {
@@ -34084,7 +35932,7 @@ var joint = (function (exports) {
34084
35932
  cid,
34085
35933
  model: cell,
34086
35934
  interactive,
34087
- labelsLayer: labelsLayer === true ? LayersNames.LABELS : labelsLayer
35935
+ labelsLayer: labelsLayer === true ? paperLayers.LABELS : labelsLayer
34088
35936
  });
34089
35937
  },
34090
35938
  _resolveCellViewClass: function (cell) {
@@ -34245,7 +36093,7 @@ var joint = (function (exports) {
34245
36093
  this.unfreeze({
34246
36094
  key
34247
36095
  });
34248
- this.sortViews();
36096
+ this.sortLayerViews();
34249
36097
  },
34250
36098
  removeViews: function () {
34251
36099
  // Remove all views and their references from the paper.
@@ -34259,7 +36107,7 @@ var joint = (function (exports) {
34259
36107
  this._viewPlaceholders = {};
34260
36108
  this._idToCid = {};
34261
36109
  },
34262
- sortViews: function () {
36110
+ sortLayerViews: function () {
34263
36111
  if (!this.isExactSorting()) {
34264
36112
  // noop
34265
36113
  return;
@@ -34269,38 +36117,16 @@ var joint = (function (exports) {
34269
36117
  this._updates.sort = true;
34270
36118
  return;
34271
36119
  }
34272
- this.sortViewsExact();
36120
+ this.sortLayerViewsExact();
34273
36121
  },
34274
- sortViewsExact: function () {
34275
- // Run insertion sort algorithm in order to efficiently sort DOM elements according to their
34276
- // associated model `z` attribute.
34277
-
34278
- var cellNodes = Array.from(this.cells.childNodes).filter(node => node.getAttribute('model-id'));
34279
- var cells = this.model.get('cells');
34280
- sortElements(cellNodes, function (a, b) {
34281
- var cellA = cells.get(a.getAttribute('model-id'));
34282
- var cellB = cells.get(b.getAttribute('model-id'));
34283
- var zA = cellA.attributes.z || 0;
34284
- var zB = cellB.attributes.z || 0;
34285
- return zA === zB ? 0 : zA < zB ? -1 : 1;
34286
- });
36122
+ sortLayerViewsExact: function () {
36123
+ this.getGraphLayerViews().forEach(view => view.sortExact());
34287
36124
  },
34288
36125
  insertView: function (view, isInitialInsert) {
34289
- const {
34290
- el,
34291
- model
34292
- } = view;
34293
- const layerName = model.get('layer') || this.DEFAULT_CELL_LAYER;
34294
- const layerView = this.getLayerView(layerName);
34295
- switch (this.options.sorting) {
34296
- case sortingTypes.APPROX:
34297
- layerView.insertSortedNode(el, model.get('z'));
34298
- break;
34299
- case sortingTypes.EXACT:
34300
- default:
34301
- layerView.insertNode(el);
34302
- break;
34303
- }
36126
+ // layer can be null if it is added to the graph with 'dry' option
36127
+ const layerId = this.model.getCellLayerId(view.model);
36128
+ const layerView = this.getLayerView(layerId);
36129
+ layerView.insertCellView(view);
34304
36130
  view.onMount(isInitialInsert);
34305
36131
  },
34306
36132
  _hideView: function (viewLike) {
@@ -34355,7 +36181,7 @@ var joint = (function (exports) {
34355
36181
  // Find the first view climbing up the DOM tree starting at element `el`. Note that `el` can also
34356
36182
  // be a selector or a jQuery object.
34357
36183
  findView: function ($el) {
34358
- var el = isString($el) ? this.cells.querySelector($el) : $el instanceof $ ? $el[0] : $el;
36184
+ var el = isString($el) ? this.layers.querySelector($el) : $el instanceof $ ? $el[0] : $el;
34359
36185
  var id = this.findAttribute('model-id', el);
34360
36186
  if (id) return this._views[id];
34361
36187
  return undefined;
@@ -34393,7 +36219,7 @@ var joint = (function (exports) {
34393
36219
  var views = this.model.getElements().map(this.findViewByModel, this);
34394
36220
  return views.filter(function (view) {
34395
36221
  return view && view.vel.getBBox({
34396
- target: this.cells
36222
+ target: this.layers
34397
36223
  }).containsPoint(p);
34398
36224
  }, this);
34399
36225
  },
@@ -34407,7 +36233,7 @@ var joint = (function (exports) {
34407
36233
  var method = opt.strict ? 'containsRect' : 'intersect';
34408
36234
  return views.filter(function (view) {
34409
36235
  return view && rect[method](view.vel.getBBox({
34410
- target: this.cells
36236
+ target: this.layers
34411
36237
  }));
34412
36238
  }, this);
34413
36239
  },
@@ -35191,7 +37017,7 @@ var joint = (function (exports) {
35191
37017
  if (this.GUARDED_TAG_NAMES.includes(target.tagName)) {
35192
37018
  return true;
35193
37019
  }
35194
- if (view && view.model && view.model instanceof Cell) {
37020
+ if (view && view.model && view.model[CELL_MARKER]) {
35195
37021
  return false;
35196
37022
  }
35197
37023
  if (this.el === target || this.svg.contains(target)) {
@@ -35206,12 +37032,12 @@ var joint = (function (exports) {
35206
37032
  options.gridSize = gridSize;
35207
37033
  if (options.drawGrid && !options.drawGridSize) {
35208
37034
  // Do not redraw the grid if the `drawGridSize` is set.
35209
- this.getLayerView(LayersNames.GRID).renderGrid();
37035
+ this.getLayerView(paperLayers.GRID).renderGrid();
35210
37036
  }
35211
37037
  return this;
35212
37038
  },
35213
37039
  setGrid: function (drawGrid) {
35214
- this.getLayerView(LayersNames.GRID).setGrid(drawGrid);
37040
+ this.getLayerView(paperLayers.GRID).setGrid(drawGrid);
35215
37041
  return this;
35216
37042
  },
35217
37043
  updateBackgroundImage: function (opt) {
@@ -35536,194 +37362,9 @@ var joint = (function (exports) {
35536
37362
  }
35537
37363
  }, {
35538
37364
  sorting: sortingTypes,
35539
- Layers: LayersNames,
35540
- backgroundPatterns: {
35541
- flipXy: function (img) {
35542
- // d b
35543
- // q p
35544
-
35545
- var canvas = document.createElement('canvas');
35546
- var imgWidth = img.width;
35547
- var imgHeight = img.height;
35548
- canvas.width = 2 * imgWidth;
35549
- canvas.height = 2 * imgHeight;
35550
- var ctx = canvas.getContext('2d');
35551
- // top-left image
35552
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35553
- // xy-flipped bottom-right image
35554
- ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
35555
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35556
- // x-flipped top-right image
35557
- ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
35558
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35559
- // y-flipped bottom-left image
35560
- ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
35561
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35562
- return canvas;
35563
- },
35564
- flipX: function (img) {
35565
- // d b
35566
- // d b
35567
-
35568
- var canvas = document.createElement('canvas');
35569
- var imgWidth = img.width;
35570
- var imgHeight = img.height;
35571
- canvas.width = imgWidth * 2;
35572
- canvas.height = imgHeight;
35573
- var ctx = canvas.getContext('2d');
35574
- // left image
35575
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35576
- // flipped right image
35577
- ctx.translate(2 * imgWidth, 0);
35578
- ctx.scale(-1, 1);
35579
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35580
- return canvas;
35581
- },
35582
- flipY: function (img) {
35583
- // d d
35584
- // q q
35585
-
35586
- var canvas = document.createElement('canvas');
35587
- var imgWidth = img.width;
35588
- var imgHeight = img.height;
35589
- canvas.width = imgWidth;
35590
- canvas.height = imgHeight * 2;
35591
- var ctx = canvas.getContext('2d');
35592
- // top image
35593
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35594
- // flipped bottom image
35595
- ctx.translate(0, 2 * imgHeight);
35596
- ctx.scale(1, -1);
35597
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35598
- return canvas;
35599
- },
35600
- watermark: function (img, opt) {
35601
- // d
35602
- // d
35603
-
35604
- opt = opt || {};
35605
- var imgWidth = img.width;
35606
- var imgHeight = img.height;
35607
- var canvas = document.createElement('canvas');
35608
- canvas.width = imgWidth * 3;
35609
- canvas.height = imgHeight * 3;
35610
- var ctx = canvas.getContext('2d');
35611
- var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
35612
- var radians = toRad(angle);
35613
- var stepX = canvas.width / 4;
35614
- var stepY = canvas.height / 4;
35615
- for (var i = 0; i < 4; i++) {
35616
- for (var j = 0; j < 4; j++) {
35617
- if ((i + j) % 2 > 0) {
35618
- // reset the current transformations
35619
- ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
35620
- ctx.rotate(radians);
35621
- ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
35622
- }
35623
- }
35624
- }
35625
- return canvas;
35626
- }
35627
- },
35628
- gridPatterns: {
35629
- dot: [{
35630
- color: '#AAAAAA',
35631
- thickness: 1,
35632
- markup: 'rect',
35633
- render: function (el, opt) {
35634
- V(el).attr({
35635
- width: opt.thickness,
35636
- height: opt.thickness,
35637
- fill: opt.color
35638
- });
35639
- }
35640
- }],
35641
- fixedDot: [{
35642
- color: '#AAAAAA',
35643
- thickness: 1,
35644
- markup: 'rect',
35645
- render: function (el, opt) {
35646
- V(el).attr({
35647
- fill: opt.color
35648
- });
35649
- },
35650
- update: function (el, opt, paper) {
35651
- const {
35652
- sx,
35653
- sy
35654
- } = paper.scale();
35655
- const width = sx <= 1 ? opt.thickness : opt.thickness / sx;
35656
- const height = sy <= 1 ? opt.thickness : opt.thickness / sy;
35657
- V(el).attr({
35658
- width,
35659
- height
35660
- });
35661
- }
35662
- }],
35663
- mesh: [{
35664
- color: '#AAAAAA',
35665
- thickness: 1,
35666
- markup: 'path',
35667
- render: function (el, opt) {
35668
- var d;
35669
- var width = opt.width;
35670
- var height = opt.height;
35671
- var thickness = opt.thickness;
35672
- if (width - thickness >= 0 && height - thickness >= 0) {
35673
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
35674
- } else {
35675
- d = 'M 0 0 0 0';
35676
- }
35677
- V(el).attr({
35678
- 'd': d,
35679
- stroke: opt.color,
35680
- 'stroke-width': opt.thickness
35681
- });
35682
- }
35683
- }],
35684
- doubleMesh: [{
35685
- color: '#AAAAAA',
35686
- thickness: 1,
35687
- markup: 'path',
35688
- render: function (el, opt) {
35689
- var d;
35690
- var width = opt.width;
35691
- var height = opt.height;
35692
- var thickness = opt.thickness;
35693
- if (width - thickness >= 0 && height - thickness >= 0) {
35694
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
35695
- } else {
35696
- d = 'M 0 0 0 0';
35697
- }
35698
- V(el).attr({
35699
- 'd': d,
35700
- stroke: opt.color,
35701
- 'stroke-width': opt.thickness
35702
- });
35703
- }
35704
- }, {
35705
- color: '#000000',
35706
- thickness: 3,
35707
- scaleFactor: 4,
35708
- markup: 'path',
35709
- render: function (el, opt) {
35710
- var d;
35711
- var width = opt.width;
35712
- var height = opt.height;
35713
- var thickness = opt.thickness;
35714
- if (width - thickness >= 0 && height - thickness >= 0) {
35715
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
35716
- } else {
35717
- d = 'M 0 0 0 0';
35718
- }
35719
- V(el).attr({
35720
- 'd': d,
35721
- stroke: opt.color,
35722
- 'stroke-width': opt.thickness
35723
- });
35724
- }
35725
- }]
35726
- }
37365
+ Layers: paperLayers,
37366
+ backgroundPatterns,
37367
+ gridPatterns
35727
37368
  });
35728
37369
 
35729
37370
  const ToolView = View.extend({
@@ -35822,7 +37463,7 @@ var joint = (function (exports) {
35822
37463
  tools: null,
35823
37464
  relatedView: null,
35824
37465
  name: null
35825
- // layer?: LayersNames.TOOLS
37466
+ // layer?: Paper.Layers.TOOLS
35826
37467
  // z?: number
35827
37468
  },
35828
37469
  configure: function (options) {
@@ -35936,7 +37577,7 @@ var joint = (function (exports) {
35936
37577
  },
35937
37578
  getLayer() {
35938
37579
  const {
35939
- layer = LayersNames.TOOLS
37580
+ layer = Paper.Layers.TOOLS
35940
37581
  } = this.options;
35941
37582
  return layer;
35942
37583
  },
@@ -35965,21 +37606,26 @@ var joint = (function (exports) {
35965
37606
 
35966
37607
  var index$2 = {
35967
37608
  __proto__: null,
35968
- CELL_VIEW_MARKER: CELL_VIEW_MARKER,
35969
37609
  Cell: Cell,
37610
+ CellCollection: CellCollection,
35970
37611
  CellView: CellView,
37612
+ DEFAULT_GRAPH_LAYER_TYPE: DEFAULT_GRAPH_LAYER_TYPE,
35971
37613
  Element: Element$1,
35972
37614
  ElementView: ElementView,
35973
37615
  Graph: Graph,
37616
+ GraphLayer: GraphLayer,
37617
+ GraphLayerCollection: GraphLayerCollection,
37618
+ GraphLayerView: GraphLayerView,
37619
+ GridLayerView: GridLayerView,
35974
37620
  HighlighterView: HighlighterView,
35975
- LayersNames: LayersNames,
37621
+ LayerView: LayerView,
35976
37622
  Link: Link$1,
35977
37623
  LinkView: LinkView,
35978
37624
  Paper: Paper,
35979
- PaperLayer: PaperLayer,
35980
37625
  ToolView: ToolView,
35981
37626
  ToolsView: ToolsView,
35982
- attributes: attributes
37627
+ attributes: attributes,
37628
+ sortingTypes: sortingTypes
35983
37629
  };
35984
37630
 
35985
37631
  // Vertex Handles
@@ -37986,7 +39632,7 @@ var joint = (function (exports) {
37986
39632
  Remove: Remove
37987
39633
  };
37988
39634
 
37989
- var version = "4.2.0-alpha.1";
39635
+ var version = "4.2.0-beta.2";
37990
39636
 
37991
39637
  const Vectorizer = V;
37992
39638
  const layout$1 = {