@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.
package/src/dia/Paper.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import V from '../V/index.mjs';
2
+ import * as g from '../g/index.mjs';
2
3
  import {
3
4
  isNumber,
4
5
  assign,
@@ -12,7 +13,6 @@ import {
12
13
  isFunction,
13
14
  isPlainObject,
14
15
  getByPath,
15
- sortElements,
16
16
  isString,
17
17
  guid,
18
18
  normalizeEvent,
@@ -23,6 +23,7 @@ import {
23
23
  result,
24
24
  camelCase,
25
25
  cloneDeep,
26
+ clone,
26
27
  invoke,
27
28
  hashCode,
28
29
  filter as _filter,
@@ -34,23 +35,37 @@ import {
34
35
  import { ViewBase } from '../mvc/ViewBase.mjs';
35
36
  import { Rect, Point, toRad } from '../g/index.mjs';
36
37
  import { View, views as viewsRegistry } from '../mvc/index.mjs';
37
- import { CellView, CELL_VIEW_MARKER } from './CellView.mjs';
38
+ import { CellView } from './CellView.mjs';
38
39
  import { ElementView } from './ElementView.mjs';
39
40
  import { LinkView } from './LinkView.mjs';
40
- import { Cell } from './Cell.mjs';
41
41
  import { Graph } from './Graph.mjs';
42
- import { LayersNames, PaperLayer } from './PaperLayer.mjs';
42
+ import { LayerView } from './LayerView.mjs';
43
+ import { GraphLayerView } from './GraphLayerView.mjs';
44
+ import { LegacyGraphLayerView } from './LegacyGraphLayerView.mjs';
43
45
  import { HighlighterView } from './HighlighterView.mjs';
44
46
  import { Deque } from '../alg/Deque.mjs';
47
+ import {
48
+ CELL_MARKER, CELL_VIEW_MARKER, LAYER_VIEW_MARKER, GRAPH_LAYER_VIEW_MARKER
49
+ } from './symbols.mjs';
45
50
  import * as highlighters from '../highlighters/index.mjs';
46
51
  import * as linkAnchors from '../linkAnchors/index.mjs';
47
52
  import * as connectionPoints from '../connectionPoints/index.mjs';
48
53
  import * as anchors from '../anchors/index.mjs';
49
54
 
50
55
  import $ from '../mvc/Dom/index.mjs';
51
- import { GridLayer } from './layers/GridLayer.mjs';
56
+ import { GridLayerView } from './GridLayerView.mjs';
57
+
58
+ const paperLayers = {
59
+ GRID: 'grid',
60
+ BACK: 'back',
61
+ /** @deprecated */
62
+ CELLS: 'cells',
63
+ FRONT: 'front',
64
+ TOOLS: 'tools',
65
+ LABELS: 'labels'
66
+ };
52
67
 
53
- const sortingTypes = {
68
+ export const sortingTypes = {
54
69
  NONE: 'sorting-none',
55
70
  APPROX: 'sorting-approximate',
56
71
  EXACT: 'sorting-exact'
@@ -85,24 +100,223 @@ const defaultHighlighting = {
85
100
  }
86
101
  };
87
102
 
88
- const defaultLayers = [{
89
- name: LayersNames.GRID,
90
- }, {
91
- name: LayersNames.BACK,
103
+ const gridPatterns = {
104
+
105
+ dot: [{
106
+ color: '#AAAAAA',
107
+ thickness: 1,
108
+ markup: 'rect',
109
+ render: function(el, opt) {
110
+ V(el).attr({
111
+ width: opt.thickness,
112
+ height: opt.thickness,
113
+ fill: opt.color
114
+ });
115
+ }
116
+ }],
117
+
118
+ fixedDot: [{
119
+ color: '#AAAAAA',
120
+ thickness: 1,
121
+ markup: 'rect',
122
+ render: function(el, opt) {
123
+ V(el).attr({ fill: opt.color });
124
+ },
125
+ update: function(el, opt, paper) {
126
+ const { sx, sy } = paper.scale();
127
+ const width = sx <= 1 ? opt.thickness : opt.thickness / sx;
128
+ const height = sy <= 1 ? opt.thickness : opt.thickness / sy;
129
+ V(el).attr({ width, height });
130
+ }
131
+ }],
132
+
133
+ mesh: [{
134
+ color: '#AAAAAA',
135
+ thickness: 1,
136
+ markup: 'path',
137
+ render: function(el, opt) {
138
+
139
+ var d;
140
+ var width = opt.width;
141
+ var height = opt.height;
142
+ var thickness = opt.thickness;
143
+
144
+ if (width - thickness >= 0 && height - thickness >= 0) {
145
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
146
+ } else {
147
+ d = 'M 0 0 0 0';
148
+ }
149
+
150
+ V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
151
+ }
152
+ }],
153
+
154
+ doubleMesh: [{
155
+ color: '#AAAAAA',
156
+ thickness: 1,
157
+ markup: 'path',
158
+ render: function(el, opt) {
159
+
160
+ var d;
161
+ var width = opt.width;
162
+ var height = opt.height;
163
+ var thickness = opt.thickness;
164
+
165
+ if (width - thickness >= 0 && height - thickness >= 0) {
166
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
167
+ } else {
168
+ d = 'M 0 0 0 0';
169
+ }
170
+
171
+ V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
172
+ }
173
+ }, {
174
+ color: '#000000',
175
+ thickness: 3,
176
+ scaleFactor: 4,
177
+ markup: 'path',
178
+ render: function(el, opt) {
179
+
180
+ var d;
181
+ var width = opt.width;
182
+ var height = opt.height;
183
+ var thickness = opt.thickness;
184
+
185
+ if (width - thickness >= 0 && height - thickness >= 0) {
186
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
187
+ } else {
188
+ d = 'M 0 0 0 0';
189
+ }
190
+
191
+ V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
192
+ }
193
+ }]
194
+ };
195
+
196
+ const backgroundPatterns = {
197
+
198
+ flipXy: function(img) {
199
+ // d b
200
+ // q p
201
+
202
+ var canvas = document.createElement('canvas');
203
+ var imgWidth = img.width;
204
+ var imgHeight = img.height;
205
+
206
+ canvas.width = 2 * imgWidth;
207
+ canvas.height = 2 * imgHeight;
208
+
209
+ var ctx = canvas.getContext('2d');
210
+ // top-left image
211
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
212
+ // xy-flipped bottom-right image
213
+ ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
214
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
215
+ // x-flipped top-right image
216
+ ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
217
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
218
+ // y-flipped bottom-left image
219
+ ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
220
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
221
+
222
+ return canvas;
223
+ },
224
+
225
+ flipX: function(img) {
226
+ // d b
227
+ // d b
228
+
229
+ var canvas = document.createElement('canvas');
230
+ var imgWidth = img.width;
231
+ var imgHeight = img.height;
232
+
233
+ canvas.width = imgWidth * 2;
234
+ canvas.height = imgHeight;
235
+
236
+ var ctx = canvas.getContext('2d');
237
+ // left image
238
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
239
+ // flipped right image
240
+ ctx.translate(2 * imgWidth, 0);
241
+ ctx.scale(-1, 1);
242
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
243
+
244
+ return canvas;
245
+ },
246
+
247
+ flipY: function(img) {
248
+ // d d
249
+ // q q
250
+
251
+ var canvas = document.createElement('canvas');
252
+ var imgWidth = img.width;
253
+ var imgHeight = img.height;
254
+
255
+ canvas.width = imgWidth;
256
+ canvas.height = imgHeight * 2;
257
+
258
+ var ctx = canvas.getContext('2d');
259
+ // top image
260
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
261
+ // flipped bottom image
262
+ ctx.translate(0, 2 * imgHeight);
263
+ ctx.scale(1, -1);
264
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
265
+
266
+ return canvas;
267
+ },
268
+
269
+ watermark: function(img, opt) {
270
+ // d
271
+ // d
272
+
273
+ opt = opt || {};
274
+
275
+ var imgWidth = img.width;
276
+ var imgHeight = img.height;
277
+
278
+ var canvas = document.createElement('canvas');
279
+ canvas.width = imgWidth * 3;
280
+ canvas.height = imgHeight * 3;
281
+
282
+ var ctx = canvas.getContext('2d');
283
+ var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
284
+ var radians = toRad(angle);
285
+ var stepX = canvas.width / 4;
286
+ var stepY = canvas.height / 4;
287
+
288
+ for (var i = 0; i < 4; i++) {
289
+ for (var j = 0; j < 4; j++) {
290
+ if ((i + j) % 2 > 0) {
291
+ // reset the current transformations
292
+ ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
293
+ ctx.rotate(radians);
294
+ ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
295
+ }
296
+ }
297
+ }
298
+
299
+ return canvas;
300
+ }
301
+ };
302
+
303
+ const implicitLayers = [{
304
+ id: paperLayers.GRID,
305
+ type: 'GridLayerView',
306
+ patterns: gridPatterns
92
307
  }, {
93
- name: LayersNames.CELLS,
308
+ id: paperLayers.BACK,
94
309
  }, {
95
- name: LayersNames.LABELS,
310
+ id: paperLayers.LABELS,
96
311
  }, {
97
- name: LayersNames.FRONT
312
+ id: paperLayers.FRONT
98
313
  }, {
99
- name: LayersNames.TOOLS
314
+ id: paperLayers.TOOLS
100
315
  }];
101
316
 
102
317
  const CELL_VIEW_PLACEHOLDER_MARKER = Symbol('joint.cellViewPlaceholderMarker');
103
318
 
104
319
  export const Paper = View.extend({
105
-
106
320
  className: 'paper',
107
321
 
108
322
  options: {
@@ -178,7 +392,7 @@ export const Paper = View.extend({
178
392
  // }
179
393
  defaultLink: function() {
180
394
  // Do not create hard dependency on the joint.shapes.standard namespace (by importing the standard.Link model directly)
181
- const { cellNamespace } = this.model.get('cells');
395
+ const { cellNamespace } = this.model.layerCollection;
182
396
  const ctor = getByPath(cellNamespace, ['standard', 'Link']);
183
397
  if (!ctor) throw new Error('dia.Paper: no default link model found. Use `options.defaultLink` to specify a default link model.');
184
398
  return new ctor();
@@ -276,12 +490,24 @@ export const Paper = View.extend({
276
490
 
277
491
  // no docs yet
278
492
  onViewUpdate: function(view, flag, priority, opt, paper) {
279
- // Do not update connected links when:
280
- // 1. the view was just inserted (added to the graph and rendered)
281
- // 2. the view was just mounted (added back to the paper by viewport function)
282
- // 3. the change was marked as `isolate`.
283
- // 4. the view model was just removed from the graph
284
- if ((flag & (paper.FLAG_INSERT | paper.FLAG_REMOVE)) || opt.mounting || opt.isolate) return;
493
+ if (opt.mounting || opt.isolate) {
494
+ // Do not update connected links when:
495
+ // - the view was just mounted (added back to the paper by viewport function)
496
+ // - the change was marked as `isolate`.
497
+ return;
498
+ }
499
+ // Always update connected links when the view model was replaced with another model
500
+ // with the same id.
501
+ // Note: the removal is done in 2 steps: remove the old model, add the new model.
502
+ // We update connected links on the add step.
503
+ if (!(opt.replace && opt.add)) {
504
+ if ((flag & (paper.FLAG_INSERT | paper.FLAG_REMOVE))) {
505
+ // Do not update connected links when:
506
+ // - the view was just inserted (added to the graph and rendered)
507
+ // - the view model was just removed from the graph
508
+ return;
509
+ }
510
+ }
285
511
  paper.requestConnectedLinksUpdate(view, priority, opt);
286
512
  },
287
513
 
@@ -300,6 +526,8 @@ export const Paper = View.extend({
300
526
 
301
527
  cellViewNamespace: null,
302
528
 
529
+ layerViewNamespace: null,
530
+
303
531
  routerNamespace: null,
304
532
 
305
533
  connectorNamespace: null,
@@ -312,7 +540,7 @@ export const Paper = View.extend({
312
540
 
313
541
  connectionPointNamespace: connectionPoints,
314
542
 
315
- overflow: false
543
+ overflow: false,
316
544
  },
317
545
 
318
546
  events: {
@@ -356,11 +584,13 @@ export const Paper = View.extend({
356
584
  `,
357
585
 
358
586
  svg: null,
359
- viewport: null,
360
587
  defs: null,
361
588
  tools: null,
362
589
  layers: null,
363
590
 
591
+ // deprecated, use layers element instead
592
+ viewport: null,
593
+
364
594
  // For storing the current transformation matrix (CTM) of the paper's viewport.
365
595
  _viewportMatrix: null,
366
596
  // For verifying whether the CTM is up-to-date. The viewport transform attribute
@@ -371,7 +601,6 @@ export const Paper = View.extend({
371
601
  // Paper Layers
372
602
  _layers: null,
373
603
 
374
- SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'],
375
604
  UPDATE_DELAYING_BATCHES: ['translate'],
376
605
  // If you interact with these elements,
377
606
  // the default interaction such as `element move` is prevented.
@@ -394,14 +623,16 @@ export const Paper = View.extend({
394
623
  // to mitigate the differences between the model and view geometry.
395
624
  DEFAULT_FIND_BUFFER: 200,
396
625
 
397
- // Default layer to insert the cell views into.
398
- DEFAULT_CELL_LAYER: LayersNames.CELLS,
399
-
400
- // Update flags
401
626
  FLAG_INSERT: 1<<30,
402
627
  FLAG_REMOVE: 1<<29,
403
628
  FLAG_INIT: 1<<28,
404
629
 
630
+ // Layers that are always present on the paper (e.g. grid, back, front, tools)
631
+ implicitLayers,
632
+
633
+ // Reference layer for inserting new graph layers.
634
+ graphLayerRefId: paperLayers.LABELS,
635
+
405
636
  init: function() {
406
637
 
407
638
  const { options } = this;
@@ -411,6 +642,14 @@ export const Paper = View.extend({
411
642
  /* eslint-enable no-undef */
412
643
  }
413
644
 
645
+ const defaultLayerViewNamespace = {
646
+ LayerView,
647
+ GraphLayerView,
648
+ GridLayerView,
649
+ };
650
+
651
+ this.layerViewNamespace = defaultsDeep({}, options.layerViewNamespace || {}, defaultLayerViewNamespace);
652
+
414
653
  const model = this.model = options.model || new Graph;
415
654
 
416
655
  // This property tells us if we need to keep the compatibility
@@ -420,20 +659,19 @@ export const Paper = View.extend({
420
659
  // Layers (SVGGroups)
421
660
  this._layers = {
422
661
  viewsMap: {},
423
- namesMap: {},
424
662
  order: [],
425
663
  };
426
664
 
427
- this.cloneOptions();
428
- this.render();
429
- this._setDimensions();
430
- this.startListening();
431
-
432
665
  // Hash of all cell views.
433
666
  this._views = {};
434
667
  this._viewPlaceholders = {};
435
668
  this._idToCid = {};
436
669
 
670
+ this.cloneOptions();
671
+ this.render();
672
+ this._setDimensions();
673
+ this.startListening();
674
+
437
675
  // Mouse wheel events buffer
438
676
  this._mw_evt_buffer = {
439
677
  event: null,
@@ -441,7 +679,7 @@ export const Paper = View.extend({
441
679
  };
442
680
 
443
681
  // Render existing cells in the graph
444
- this.resetViews(model.attributes.cells.models);
682
+ this.resetViews(model.getCells());
445
683
  },
446
684
 
447
685
  _resetUpdates: function() {
@@ -466,10 +704,13 @@ export const Paper = View.extend({
466
704
  var model = this.model;
467
705
  this.listenTo(model, 'add', this.onCellAdded)
468
706
  .listenTo(model, 'remove', this.onCellRemoved)
469
- .listenTo(model, 'change', this.onCellChange)
470
707
  .listenTo(model, 'reset', this.onGraphReset)
471
- .listenTo(model, 'sort', this.onGraphSort)
472
708
  .listenTo(model, 'batch:stop', this.onGraphBatchStop);
709
+
710
+ this.listenTo(model, 'layer:add', this.onGraphLayerAdd)
711
+ .listenTo(model, 'layer:remove', this.onGraphLayerRemove)
712
+ .listenTo(model, 'layers:sort', this.onGraphLayerCollectionSort);
713
+
473
714
  this.on('cell:highlight', this.onCellHighlight)
474
715
  .on('cell:unhighlight', this.onCellUnhighlight)
475
716
  .on('transform', this.update);
@@ -496,27 +737,15 @@ export const Paper = View.extend({
496
737
  }
497
738
  },
498
739
 
499
- onCellChange: function(cell, opt) {
500
- if (cell === this.model.attributes.cells) return;
501
- if (
502
- cell.hasChanged('layer') ||
503
- (cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX)
504
- ) {
505
- const viewLike = this._getCellViewLike(cell);
506
- if (viewLike) {
507
- this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
508
- }
509
- }
510
- },
511
-
512
- onGraphReset: function(collection, opt) {
513
- this.resetLayers();
514
- this.resetViews(collection.models, opt);
515
- },
516
-
517
- onGraphSort: function() {
518
- if (this.model.hasActiveBatch(this.SORT_DELAYING_BATCHES)) return;
519
- this.sortViews();
740
+ onGraphReset: function(_collection, opt) {
741
+ // Re-render all graph layer views
742
+ // but keep the implicit layer views.
743
+ this.renderGraphLayerViews();
744
+ this.resetLayerViews();
745
+ // Backward compatibility: reassign the `cells` property
746
+ // with the default layer view.
747
+ this.assertLayerViews();
748
+ this.resetViews(this.model.getCells(), opt);
520
749
  },
521
750
 
522
751
  onGraphBatchStop: function(data) {
@@ -529,10 +758,95 @@ export const Paper = View.extend({
529
758
  this.updateViews(data);
530
759
  }
531
760
  }
532
- var sortDelayingBatches = this.SORT_DELAYING_BATCHES;
533
- if (sortDelayingBatches.includes(name) && !graph.hasActiveBatch(sortDelayingBatches)) {
534
- this.sortViews();
761
+ },
762
+
763
+ /**
764
+ * @protected
765
+ * @description When a new layer is added to the graph, we create a new layer view
766
+ **/
767
+ onGraphLayerAdd: function(layer, _, opt) {
768
+ if (this.hasLayerView(layer.id)) return;
769
+
770
+ const layerView = this.createLayerView({
771
+ id: layer.id,
772
+ model: layer
773
+ });
774
+
775
+ const layers = this.model.getLayers();
776
+ let before;
777
+ // Note: There is always at least one graph layer.
778
+ if (layers[layers.length - 1] === layer) {
779
+ // This is the last layer, so insert before the labels layer
780
+ before = paperLayers.LABELS;
781
+ } else {
782
+ // There is a layer after the current one, so insert before that one
783
+ const index = layers.indexOf(layer);
784
+ before = layers[index + 1].id;
535
785
  }
786
+
787
+ this.addLayerView(layerView, { before });
788
+ },
789
+
790
+ /**
791
+ * @protected
792
+ * @description When a layer is removed from the graph, we remove the corresponding layer view
793
+ **/
794
+ onGraphLayerRemove: function(layer, _, opt) {
795
+ if (!this.hasLayerView(layer)) return;
796
+
797
+ // Request layer removal. Since the UPDATE_PRIORITY is lower
798
+ // than cells update priority, the cell views will be removed first.
799
+ this.requestLayerViewRemoval(layer);
800
+ },
801
+
802
+ /**
803
+ * @protected
804
+ * @description When the graph layer collection is sorted,
805
+ * we reorder all graph layer views.
806
+ **/
807
+ onGraphLayerCollectionSort: function(layerCollection) {
808
+ layerCollection.each(layer => {
809
+ if (!this.hasLayerView(layer)) return;
810
+
811
+ this.moveLayerView(layer, { before: this.graphLayerRefId });
812
+ });
813
+ },
814
+
815
+ /**
816
+ * @protected
817
+ * @description Resets all graph layer views.
818
+ */
819
+ renderGraphLayerViews: function() {
820
+ // Remove all existing graph layer views
821
+ // Note: we don't use `getGraphLayerViews()` here because
822
+ // rendered graph layer views could be different from the ones
823
+ // in the graph layer collection (`onResetGraphLayerCollectionReset`).
824
+ this.getLayerViews().forEach(layerView => {
825
+ if (!layerView[GRAPH_LAYER_VIEW_MARKER]) return;
826
+ this._removeLayerView(layerView);
827
+ });
828
+ // Create and insert new graph layer views
829
+ this.model.getLayers().forEach(layer => {
830
+ const layerView = this.createLayerView({
831
+ id: layer.id,
832
+ model: layer
833
+ });
834
+ // Insert the layer view into the paper layers, just before the labels layer.
835
+ // All cell layers are positioned between the "back" and "labels" layers,
836
+ // with the default "cells" layer originally occupying this position.
837
+ this.addLayerView(layerView, { before: this.graphLayerRefId });
838
+ });
839
+ },
840
+
841
+ /**
842
+ * @protected
843
+ * @description Renders all implicit layer views.
844
+ */
845
+ renderImplicitLayerViews: function() {
846
+ this.implicitLayers.forEach(layerInit => {
847
+ const layerView = this.createLayerView(layerInit);
848
+ this.addLayerView(layerView);
849
+ });
536
850
  },
537
851
 
538
852
  cloneOptions: function() {
@@ -628,121 +942,301 @@ export const Paper = View.extend({
628
942
  }];
629
943
  },
630
944
 
631
- hasLayerView(layerName) {
632
- return (layerName in this._layers.viewsMap);
945
+ /**
946
+ * @public
947
+ * @description Checks whether the layer view exists by the given layer id or layer model.
948
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
949
+ * @return {boolean} True if the layer view exists, false otherwise.
950
+ */
951
+ hasLayerView(layerRef) {
952
+ let layerId;
953
+ if (isString(layerRef)) {
954
+ layerId = layerRef;
955
+ } else if (layerRef) {
956
+ layerId = layerRef.id;
957
+ } else {
958
+ return false;
959
+ }
960
+ return (layerId in this._layers.viewsMap);
633
961
  },
634
962
 
635
- getLayerView(layerName) {
636
- const { _layers: { viewsMap }} = this;
637
- if (layerName in viewsMap) return viewsMap[layerName];
638
- throw new Error(`dia.Paper: Unknown layer "${layerName}".`);
963
+ /**
964
+ * @public
965
+ * @description Returns the layer view by the given layer id or layer model.
966
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
967
+ * @return {dia.LayerView} The layer view.
968
+ * @throws {Error} if the layer view is not found
969
+ */
970
+ getLayerView(layerRef) {
971
+
972
+ let layerId;
973
+ if (isString(layerRef)) {
974
+ layerId = layerRef;
975
+ } else if (layerRef) {
976
+ layerId = layerRef.id;
977
+ } else {
978
+ throw new Error('dia.Paper: No layer provided.');
979
+ }
980
+
981
+ const layerView = this._layers.viewsMap[layerId];
982
+ if (!layerView) {
983
+ throw new Error(`dia.Paper: Unknown layer view "${layerId}".`);
984
+ }
985
+
986
+ return layerView;
639
987
  },
640
988
 
641
- getLayerNode(layerName) {
642
- return this.getLayerView(layerName).el;
989
+ /**
990
+ * @deprecated use `getLayerView(layerId).el` instead
991
+ */
992
+ getLayerNode(layerId) {
993
+ return this.getLayerView(layerId).el;
643
994
  },
644
995
 
645
- _removeLayer(layerView) {
646
- this._unregisterLayer(layerView);
996
+ /**
997
+ * @protected
998
+ * @description Removes the given layer view from the paper.
999
+ * It does not check whether the layer view is empty.
1000
+ * @param {dia.LayerView} layerView - The layer view to remove.
1001
+ */
1002
+ _removeLayerView(layerView) {
1003
+ this._unregisterLayerView(layerView);
647
1004
  layerView.remove();
648
1005
  },
649
1006
 
650
- _unregisterLayer(layerView) {
651
- const { _layers: { viewsMap, namesMap, order }} = this;
652
- const layerName = this._getLayerName(layerView);
653
- order.splice(order.indexOf(layerName), 1);
654
- delete namesMap[layerView.cid];
655
- delete viewsMap[layerName];
1007
+
1008
+ /**
1009
+ * @protected
1010
+ * @description Removes all layer views from the paper.
1011
+ * It does not check whether the layer views are empty.
1012
+ */
1013
+ _removeLayerViews: function() {
1014
+ Object.values(this._layers.viewsMap).forEach(layerView => {
1015
+ this._removeLayerView(layerView);
1016
+ });
656
1017
  },
657
1018
 
658
- _registerLayer(layerName, layerView, beforeLayerView) {
659
- const { _layers: { viewsMap, namesMap, order }} = this;
660
- if (beforeLayerView) {
661
- const beforeLayerName = this._getLayerName(beforeLayerView);
662
- order.splice(order.indexOf(beforeLayerName), 0, layerName);
663
- } else {
664
- order.push(layerName);
1019
+ /**
1020
+ * @protected
1021
+ * @description Unregisters the given layer view from the paper.
1022
+ * @param {dia.LayerView} layerView - The layer view to unregister.
1023
+ */
1024
+ _unregisterLayerView(layerView) {
1025
+ const { _layers: { viewsMap, order }} = this;
1026
+ const layerId = layerView.id;
1027
+ // Remove the layer id from the order list.
1028
+ const layerIndex = order.indexOf(layerId);
1029
+ if (layerIndex !== -1) {
1030
+ order.splice(layerIndex, 1);
665
1031
  }
666
- viewsMap[layerName] = layerView;
667
- namesMap[layerView.cid] = layerName;
1032
+ // Unlink the layer view from the paper.
1033
+ layerView.unsetPaperReference();
1034
+ // Remove the layer view from the paper's registry.
1035
+ delete viewsMap[layerId];
668
1036
  },
669
1037
 
670
- _getLayerView(layer) {
671
- const { _layers: { namesMap, viewsMap }} = this;
672
- if (layer instanceof PaperLayer) {
673
- if (layer.cid in namesMap) return layer;
674
- return null;
1038
+ /**
1039
+ * @protected
1040
+ * @description Registers the given layer view in the paper.
1041
+ * @param {dia.LayerView} layerView - The layer view to register.
1042
+ * @throws {Error} if the layer view is not an instance of dia.LayerView
1043
+ * @throws {Error} if the layer view already exists in the paper
1044
+ */
1045
+ _registerLayerView(layerView) {
1046
+ if (!layerView || !layerView[LAYER_VIEW_MARKER]) {
1047
+ throw new Error('dia.Paper: The layer view must be an instance of dia.LayerView.');
675
1048
  }
676
- if (layer in viewsMap) return viewsMap[layer];
677
- return null;
1049
+
1050
+ if (this.hasLayerView(layerView.id)) {
1051
+ throw new Error(`dia.Paper: The layer view "${layerView.id}" already exists.`);
1052
+ }
1053
+ // Link the layer view back to the paper.
1054
+ layerView.setPaperReference(this);
1055
+ // Store the layer view in the paper's registry.
1056
+ this._layers.viewsMap[layerView.id] = layerView;
678
1057
  },
679
1058
 
680
- _getLayerName(layerView) {
681
- const { _layers: { namesMap }} = this;
682
- return namesMap[layerView.cid];
1059
+ /**
1060
+ * @public
1061
+ * @description Removes the layer view by the given layer id or layer model.
1062
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
1063
+ * @throws {Error} if the layer view is not empty
1064
+ */
1065
+ removeLayerView(layerRef) {
1066
+ const layerView = this.getLayerView(layerRef);
1067
+ if (!layerView.isEmpty()) {
1068
+ throw new Error('dia.Paper: The layer view is not empty.');
1069
+ }
1070
+
1071
+ this._removeLayerView(layerView);
683
1072
  },
684
1073
 
685
- _requireLayerView(layer) {
686
- const layerView = this._getLayerView(layer);
687
- if (!layerView) {
688
- if (layer instanceof PaperLayer) {
689
- throw new Error('dia.Paper: The layer is not registered.');
1074
+ /**
1075
+ * @protected
1076
+ * @description Schedules the layer view removal by the given layer id or layer model.
1077
+ * The actual removal will be performed during the paper update cycle.
1078
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
1079
+ * @param {Object} [opt] - Update options.
1080
+ */
1081
+ requestLayerViewRemoval(layerRef, opt) {
1082
+ const layerView = this.getLayerView(layerRef);
1083
+ const { FLAG_REMOVE } = this;
1084
+ const { UPDATE_PRIORITY } = layerView;
1085
+
1086
+ this.requestViewUpdate(layerView, FLAG_REMOVE, UPDATE_PRIORITY, opt);
1087
+ },
1088
+
1089
+ /**
1090
+ * @public
1091
+ * @internal not documented
1092
+ * @description Schedules the cell view insertion into the appropriate layer view.
1093
+ * The actual insertion will be performed during the paper update cycle.
1094
+ * @param {dia.Cell} cell - The cell model whose view should be inserted.
1095
+ * @param {Object} [opt] - Update options.
1096
+ */
1097
+ requestCellViewInsertion(cell, opt) {
1098
+ const viewLike = this._getCellViewLike(cell);
1099
+ if (!viewLike) return;
1100
+ this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
1101
+ },
1102
+
1103
+ /**
1104
+ * @private
1105
+ * Helper method for addLayerView and moveLayerView methods
1106
+ */
1107
+ _getBeforeLayerViewFromOptions(layerView, options) {
1108
+ let { before = null, index } = options;
1109
+
1110
+ if (before && index !== undefined) {
1111
+ throw new Error('dia.Paper: Options "before" and "index" are mutually exclusive.');
1112
+ }
1113
+
1114
+ let computedBefore;
1115
+ if (index !== undefined) {
1116
+ const { _layers: { order }} = this;
1117
+ if (index >= order.length) {
1118
+ // If index is greater than the number of layers,
1119
+ // return before as null (move to the end).
1120
+ computedBefore = null;
1121
+ } else if (index < 0) {
1122
+ // If index is negative, move to the beginning.
1123
+ computedBefore = order[0];
690
1124
  } else {
691
- throw new Error(`dia.Paper: Unknown layer "${layer}".`);
1125
+ const originalIndex = order.indexOf(layerView.id);
1126
+ if (originalIndex !== -1 && index > originalIndex) {
1127
+ // If moving a layer upwards in the stack, we need to adjust the index
1128
+ // to account for the layer being removed from its original position.
1129
+ index += 1;
1130
+ }
1131
+ // Otherwise, get the layer ID at the specified index.
1132
+ computedBefore = order[index] || null;
692
1133
  }
1134
+ } else {
1135
+ computedBefore = before;
693
1136
  }
694
- return layerView;
1137
+
1138
+ return computedBefore ? this.getLayerView(computedBefore) : null;
695
1139
  },
696
1140
 
697
- hasLayer(layer) {
698
- return this._getLayerView(layer) !== null;
1141
+ /**
1142
+ * @public
1143
+ * @description Adds the layer view to the paper.
1144
+ * @param {dia.LayerView} layerView - The layer view to add.
1145
+ * @param {Object} [options] - Adding options.
1146
+ * @param {string|dia.GraphLayer} [options.before] - Layer id or layer model before
1147
+ */
1148
+ addLayerView(layerView, options = {}) {
1149
+ this._registerLayerView(layerView);
1150
+
1151
+ const beforeLayerView = this._getBeforeLayerViewFromOptions(layerView, options);
1152
+ this.insertLayerView(layerView, beforeLayerView);
699
1153
  },
700
1154
 
701
- removeLayer(layer) {
702
- const layerView = this._requireLayerView(layer);
703
- if (!layerView.isEmpty()) {
704
- throw new Error('dia.Paper: The layer is not empty.');
705
- }
706
- this._removeLayer(layerView);
1155
+ /**
1156
+ * @public
1157
+ * @description Moves the layer view.
1158
+ * @param {Paper.LayerRef} layerRef - The layer view reference to move.
1159
+ * @param {Object} [options] - Moving options.
1160
+ * @param {Paper.LayerRef} [options.before] - Layer id or layer model before
1161
+ * @param {number} [options.index] - Zero-based index to which to move the layer view.
1162
+ */
1163
+ moveLayerView(layerRef, options = {}) {
1164
+ const layerView = this.getLayerView(layerRef);
1165
+
1166
+ const beforeLayerView = this._getBeforeLayerViewFromOptions(layerView, options);
1167
+ this.insertLayerView(layerView, beforeLayerView);
707
1168
  },
708
1169
 
709
- addLayer(layerName, layerView, options = {}) {
710
- if (!layerName || typeof layerName !== 'string') {
711
- throw new Error('dia.Paper: The layer name must be provided.');
712
- }
713
- if (this._getLayerView(layerName)) {
714
- throw new Error(`dia.Paper: The layer "${layerName}" already exists.`);
715
- }
716
- if (!(layerView instanceof PaperLayer)) {
717
- throw new Error('dia.Paper: The layer view is not an instance of dia.PaperLayer.');
718
- }
719
- const { insertBefore } = options;
720
- if (!insertBefore) {
721
- this._registerLayer(layerName, layerView, null);
722
- this.layers.appendChild(layerView.el);
723
- } else {
724
- const beforeLayerView = this._requireLayerView(insertBefore);
725
- this._registerLayer(layerName, layerView, beforeLayerView);
1170
+ /**
1171
+ * @protected
1172
+ * @description Inserts the layer view into the paper.
1173
+ * If the layer view already exists in the paper, it is moved to the new position.
1174
+ * @param {dia.LayerView} layerView - The layer view to insert.
1175
+ * @param {dia.LayerView} [before] - Layer view before
1176
+ * which the layer view should be inserted.
1177
+ */
1178
+ insertLayerView(layerView, beforeLayerView) {
1179
+ const layerId = layerView.id;
1180
+
1181
+ const { _layers: { order }} = this;
1182
+ const currentLayerIndex = order.indexOf(layerId);
1183
+
1184
+ // Should the layer view be inserted before another layer view?
1185
+ if (beforeLayerView) {
1186
+ const beforeLayerViewId = beforeLayerView.id;
1187
+ if (layerId === beforeLayerViewId) {
1188
+ // The layer view is already in the right place.
1189
+ return;
1190
+ }
1191
+
1192
+ let beforeLayerPosition = order.indexOf(beforeLayerViewId);
1193
+ // Remove from the `order` list if the layer view is already in the order.
1194
+ if (currentLayerIndex !== -1) {
1195
+ if (currentLayerIndex < beforeLayerPosition) {
1196
+ beforeLayerPosition -= 1;
1197
+ }
1198
+ order.splice(currentLayerIndex, 1);
1199
+ }
1200
+ order.splice(beforeLayerPosition, 0, layerId);
726
1201
  this.layers.insertBefore(layerView.el, beforeLayerView.el);
1202
+ return;
727
1203
  }
728
- },
729
1204
 
730
- moveLayer(layer, insertBefore) {
731
- const layerView = this._requireLayerView(layer);
732
- if (layerView === this._getLayerView(insertBefore)) return;
733
- const layerName = this._getLayerName(layerView);
734
- this._unregisterLayer(layerView);
735
- this.addLayer(layerName, layerView, { insertBefore });
1205
+ // Remove from the `order` list if the layer view is already in the order.
1206
+ // This is needed for the case when the layer view is inserted in the new position.
1207
+ if (currentLayerIndex !== -1) {
1208
+ order.splice(currentLayerIndex, 1);
1209
+ }
1210
+ order.push(layerId);
1211
+ this.layers.appendChild(layerView.el);
736
1212
  },
737
1213
 
738
- getLayerNames() {
739
- // Returns a sorted array of layer names.
1214
+ /**
1215
+ * @protected
1216
+ * @description Returns an array of layer view ids in the order they are rendered.
1217
+ * @returns {string[]} An array of layer view ids.
1218
+ */
1219
+ getLayerViewOrder() {
740
1220
  return this._layers.order.slice();
741
1221
  },
742
1222
 
743
- getLayers() {
744
- // Returns a sorted array of layer views.
745
- return this.getLayerNames().map(name => this.getLayerView(name));
1223
+ /**
1224
+ * @public
1225
+ * @description Returns an array of layer views in the order they are rendered.
1226
+ * @returns {dia.LayerView[]} An array of layer views.
1227
+ */
1228
+ getLayerViews() {
1229
+ return this.getLayerViewOrder().map(id => this.getLayerView(id));
1230
+ },
1231
+
1232
+ /**
1233
+ * @public
1234
+ * @description Returns an array of graph layer views in the order they are rendered.
1235
+ * @returns {dia.GraphLayerView[]} An array of graph layer views.
1236
+ */
1237
+ getGraphLayerViews() {
1238
+ const { _layers: { viewsMap }} = this;
1239
+ return this.model.getLayers().map(layer => viewsMap[layer.id]);
746
1240
  },
747
1241
 
748
1242
  render: function() {
@@ -758,7 +1252,7 @@ export const Paper = View.extend({
758
1252
  this.defs = defs;
759
1253
  this.layers = layers;
760
1254
 
761
- this.renderLayers();
1255
+ this.renderLayerViews();
762
1256
 
763
1257
  V.ensureId(svg);
764
1258
 
@@ -780,48 +1274,86 @@ export const Paper = View.extend({
780
1274
  V(this.svg).prepend(V.createSVGStyle(css));
781
1275
  },
782
1276
 
783
- createLayer(name) {
784
- switch (name) {
785
- case LayersNames.GRID:
786
- return new GridLayer({ name, paper: this, patterns: this.constructor.gridPatterns });
787
- default:
788
- return new PaperLayer({ name });
1277
+ /**
1278
+ * @protected
1279
+ * @description Creates a layer view instance based on the provided options.
1280
+ * It finds the appropriate layer view constructor from the paper's
1281
+ * `layerViewNamespace` and instantiates it.
1282
+ * @param {*} options See `dia.LayerView` options.
1283
+ * @returns {dia.LayerView}
1284
+ */
1285
+ createLayerView(options) {
1286
+ if (options == null) {
1287
+ throw new Error('dia.Paper: Layer view options are required.');
789
1288
  }
1289
+
1290
+ if (options.id == null) {
1291
+ throw new Error('dia.Paper: Layer view id is required.');
1292
+ }
1293
+
1294
+ const viewOptions = clone(options);
1295
+
1296
+ let viewConstructor;
1297
+ if (viewOptions.model) {
1298
+ const modelType = viewOptions.model.get('type') || viewOptions.model.constructor.name;
1299
+ const type = modelType + 'View';
1300
+
1301
+ // For backward compatibility we use the LegacyGraphLayerView for the default `cells` layer.
1302
+ if (this.model.layersController.legacyMode) {
1303
+ viewConstructor = LegacyGraphLayerView;
1304
+ } else {
1305
+ viewConstructor = this.layerViewNamespace[type] || LayerView;
1306
+ }
1307
+ } else {
1308
+ // Paper layers
1309
+ const type = viewOptions.type;
1310
+ viewConstructor = this.layerViewNamespace[type] || LayerView;
1311
+ }
1312
+
1313
+ return new viewConstructor(viewOptions);
790
1314
  },
791
1315
 
792
- renderLayer: function(name) {
793
- const layerView = this.createLayer(name);
794
- this.addLayer(name, layerView);
795
- return layerView;
1316
+ /**
1317
+ * @protected
1318
+ * @description Renders all paper layer views and graph layer views.
1319
+ */
1320
+ renderLayerViews: function() {
1321
+ this._removeLayerViews();
1322
+ // Render the paper layers.
1323
+ this.renderImplicitLayerViews();
1324
+ // Render the layers.
1325
+ this.renderGraphLayerViews();
1326
+ // Ensure that essential layer views are present.
1327
+ this.assertLayerViews();
796
1328
  },
797
1329
 
798
- renderLayers: function(layers = defaultLayers) {
799
- this.removeLayers();
800
- layers.forEach(({ name }) => this.renderLayer(name));
801
- // Throws an exception if doesn't exist
802
- const cellsLayerView = this.getLayerView(LayersNames.CELLS);
803
- const toolsLayerView = this.getLayerView(LayersNames.TOOLS);
804
- const labelsLayerView = this.getLayerView(LayersNames.LABELS);
1330
+ /**
1331
+ * @protected
1332
+ * @description Ensures that essential layer views are present on the paper.
1333
+ * @throws {Error} if any of the essential layer views is missing
1334
+ */
1335
+ assertLayerViews: function() {
1336
+ // Throws an exception if essential layer views are missing.
1337
+ const cellsLayerView = this.getLayerView(this.model.getDefaultLayer().id);
1338
+ const toolsLayerView = this.getLayerView(paperLayers.TOOLS);
1339
+ const labelsLayerView = this.getLayerView(paperLayers.LABELS);
1340
+
805
1341
  // backwards compatibility
806
1342
  this.tools = toolsLayerView.el;
807
1343
  this.cells = this.viewport = cellsLayerView.el;
808
- // user-select: none;
809
- cellsLayerView.vel.addClass(addClassNamePrefix('viewport'));
1344
+ // Backwards compatibility: same as `LegacyGraphLayerView` we keep
1345
+ // the `viewport` class on the labels layer.
810
1346
  labelsLayerView.vel.addClass(addClassNamePrefix('viewport'));
811
- cellsLayerView.el.style.webkitUserSelect = 'none';
812
- cellsLayerView.el.style.userSelect = 'none';
813
1347
  labelsLayerView.el.style.webkitUserSelect = 'none';
814
1348
  labelsLayerView.el.style.userSelect = 'none';
815
1349
  },
816
1350
 
817
- removeLayers: function() {
818
- const { _layers: { viewsMap }} = this;
819
- Object.values(viewsMap).forEach(layerView => this._removeLayer(layerView));
820
- },
821
-
822
- resetLayers: function() {
823
- const { _layers: { viewsMap }} = this;
824
- Object.values(viewsMap).forEach(layerView => layerView.removePivots());
1351
+ /**
1352
+ * @protected
1353
+ * @description Resets all layer views.
1354
+ */
1355
+ resetLayerViews: function() {
1356
+ this.getLayerViews().forEach(layerView => layerView.reset());
825
1357
  },
826
1358
 
827
1359
  update: function() {
@@ -947,28 +1479,27 @@ export const Paper = View.extend({
947
1479
 
948
1480
  clientMatrix: function() {
949
1481
 
950
- return V.createSVGMatrix(this.cells.getScreenCTM());
1482
+ return V.createSVGMatrix(this.layers.getScreenCTM());
951
1483
  },
952
1484
 
953
1485
  requestConnectedLinksUpdate: function(view, priority, opt) {
954
- if (view instanceof CellView) {
955
- var model = view.model;
956
- var links = this.model.getConnectedLinks(model);
957
- for (var j = 0, n = links.length; j < n; j++) {
958
- var link = links[j];
959
- var linkView = this._getCellViewLike(link);
960
- if (!linkView) continue;
961
- // We do not have to update placeholder views.
962
- // They will be updated on initial render.
963
- if (linkView[CELL_VIEW_PLACEHOLDER_MARKER]) continue;
964
- var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
965
- this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
966
- }
1486
+ if (!view || !view[CELL_VIEW_MARKER]) return;
1487
+ var model = view.model;
1488
+ var links = this.model.getConnectedLinks(model);
1489
+ for (var j = 0, n = links.length; j < n; j++) {
1490
+ var link = links[j];
1491
+ var linkView = this._getCellViewLike(link);
1492
+ if (!linkView) continue;
1493
+ // We do not have to update placeholder views.
1494
+ // They will be updated on initial render.
1495
+ if (linkView[CELL_VIEW_PLACEHOLDER_MARKER]) continue;
1496
+ var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
1497
+ this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
967
1498
  }
968
1499
  },
969
1500
 
970
1501
  forcePostponedViewUpdate: function(view, flag) {
971
- if (!view || !(view instanceof CellView)) return false;
1502
+ if (!view || !view[CELL_VIEW_MARKER]) return false;
972
1503
  const model = view.model;
973
1504
  if (model.isElement()) return false;
974
1505
  const dumpOptions = { silent: true };
@@ -1070,6 +1601,12 @@ export const Paper = View.extend({
1070
1601
  if (!view) return 0;
1071
1602
  const { FLAG_REMOVE, FLAG_INSERT, FLAG_INIT } = this;
1072
1603
  const { model } = view;
1604
+ if (view[GRAPH_LAYER_VIEW_MARKER]) {
1605
+ if (flag & FLAG_REMOVE) {
1606
+ this.removeLayerView(view);
1607
+ return 0;
1608
+ }
1609
+ }
1073
1610
  if (view[CELL_VIEW_MARKER]) {
1074
1611
  if (flag & FLAG_REMOVE) {
1075
1612
  this.removeView(model);
@@ -1669,7 +2206,7 @@ export const Paper = View.extend({
1669
2206
  }
1670
2207
  this.options.frozen = updates.keyFrozen = false;
1671
2208
  if (updates.sort) {
1672
- this.sortViews();
2209
+ this.sortLayerViews();
1673
2210
  updates.sort = false;
1674
2211
  }
1675
2212
  },
@@ -1704,8 +2241,8 @@ export const Paper = View.extend({
1704
2241
  this.freeze();
1705
2242
  this._updates.disabled = true;
1706
2243
  //clean up all DOM elements/views to prevent memory leaks
1707
- this.removeLayers();
1708
2244
  this.removeViews();
2245
+ this._removeLayerViews();
1709
2246
  },
1710
2247
 
1711
2248
  getComputedSize: function() {
@@ -1953,7 +2490,17 @@ export const Paper = View.extend({
1953
2490
  return this.model.getBBox() || new Rect();
1954
2491
  }
1955
2492
 
1956
- return V(this.cells).getBBox();
2493
+ const graphLayerViews = this.getGraphLayerViews();
2494
+ // Return an empty rectangle if there are no layers
2495
+ // should not happen in practice
2496
+ if (graphLayerViews.length === 0) {
2497
+ return new Rect();
2498
+ }
2499
+
2500
+ // Combine content area rectangles from all layers,
2501
+ // considering only graph layer views to exclude non-cell elements (e.g., grid, tools)
2502
+ const bbox = g.Rect.fromRectUnion(...graphLayerViews.map(view => view.vel.getBBox()));
2503
+ return bbox;
1957
2504
  },
1958
2505
 
1959
2506
  // Return the dimensions of the content bbox in the paper units (as it appears on screen).
@@ -2031,7 +2578,7 @@ export const Paper = View.extend({
2031
2578
  cid,
2032
2579
  model: cell,
2033
2580
  interactive,
2034
- labelsLayer: labelsLayer === true ? LayersNames.LABELS : labelsLayer
2581
+ labelsLayer: labelsLayer === true ? paperLayers.LABELS : labelsLayer
2035
2582
  });
2036
2583
  },
2037
2584
 
@@ -2184,7 +2731,7 @@ export const Paper = View.extend({
2184
2731
  this.renderView(cells[i], opt);
2185
2732
  }
2186
2733
  this.unfreeze({ key });
2187
- this.sortViews();
2734
+ this.sortLayerViews();
2188
2735
  },
2189
2736
 
2190
2737
  removeViews: function() {
@@ -2200,8 +2747,7 @@ export const Paper = View.extend({
2200
2747
  this._idToCid = {};
2201
2748
  },
2202
2749
 
2203
- sortViews: function() {
2204
-
2750
+ sortLayerViews: function() {
2205
2751
  if (!this.isExactSorting()) {
2206
2752
  // noop
2207
2753
  return;
@@ -2211,41 +2757,21 @@ export const Paper = View.extend({
2211
2757
  this._updates.sort = true;
2212
2758
  return;
2213
2759
  }
2214
- this.sortViewsExact();
2760
+ this.sortLayerViewsExact();
2215
2761
  },
2216
2762
 
2217
- sortViewsExact: function() {
2218
-
2219
- // Run insertion sort algorithm in order to efficiently sort DOM elements according to their
2220
- // associated model `z` attribute.
2221
-
2222
- var cellNodes = Array.from(this.cells.childNodes).filter(node => node.getAttribute('model-id'));
2223
- var cells = this.model.get('cells');
2224
-
2225
- sortElements(cellNodes, function(a, b) {
2226
- var cellA = cells.get(a.getAttribute('model-id'));
2227
- var cellB = cells.get(b.getAttribute('model-id'));
2228
- var zA = cellA.attributes.z || 0;
2229
- var zB = cellB.attributes.z || 0;
2230
- return (zA === zB) ? 0 : (zA < zB) ? -1 : 1;
2231
- });
2763
+ sortLayerViewsExact: function() {
2764
+ this.getGraphLayerViews().forEach(view => view.sortExact());
2232
2765
  },
2233
2766
 
2234
2767
  insertView: function(view, isInitialInsert) {
2235
- const { el, model } = view;
2236
2768
 
2237
- const layerName = model.get('layer') || this.DEFAULT_CELL_LAYER;
2238
- const layerView = this.getLayerView(layerName);
2769
+ // layer can be null if it is added to the graph with 'dry' option
2770
+ const layerId = this.model.getCellLayerId(view.model);
2771
+ const layerView = this.getLayerView(layerId);
2772
+
2773
+ layerView.insertCellView(view);
2239
2774
 
2240
- switch (this.options.sorting) {
2241
- case sortingTypes.APPROX:
2242
- layerView.insertSortedNode(el, model.get('z'));
2243
- break;
2244
- case sortingTypes.EXACT:
2245
- default:
2246
- layerView.insertNode(el);
2247
- break;
2248
- }
2249
2775
  view.onMount(isInitialInsert);
2250
2776
  },
2251
2777
 
@@ -2308,7 +2834,7 @@ export const Paper = View.extend({
2308
2834
  findView: function($el) {
2309
2835
 
2310
2836
  var el = isString($el)
2311
- ? this.cells.querySelector($el)
2837
+ ? this.layers.querySelector($el)
2312
2838
  : $el instanceof $ ? $el[0] : $el;
2313
2839
 
2314
2840
  var id = this.findAttribute('model-id', el);
@@ -2354,7 +2880,7 @@ export const Paper = View.extend({
2354
2880
  var views = this.model.getElements().map(this.findViewByModel, this);
2355
2881
 
2356
2882
  return views.filter(function(view) {
2357
- return view && view.vel.getBBox({ target: this.cells }).containsPoint(p);
2883
+ return view && view.vel.getBBox({ target: this.layers }).containsPoint(p);
2358
2884
  }, this);
2359
2885
  },
2360
2886
 
@@ -2368,7 +2894,7 @@ export const Paper = View.extend({
2368
2894
  var method = opt.strict ? 'containsRect' : 'intersect';
2369
2895
 
2370
2896
  return views.filter(function(view) {
2371
- return view && rect[method](view.vel.getBBox({ target: this.cells }));
2897
+ return view && rect[method](view.vel.getBBox({ target: this.layers }));
2372
2898
  }, this);
2373
2899
  },
2374
2900
 
@@ -3286,7 +3812,7 @@ export const Paper = View.extend({
3286
3812
  return true;
3287
3813
  }
3288
3814
 
3289
- if (view && view.model && (view.model instanceof Cell)) {
3815
+ if (view && view.model && (view.model[CELL_MARKER])) {
3290
3816
  return false;
3291
3817
  }
3292
3818
 
@@ -3302,13 +3828,13 @@ export const Paper = View.extend({
3302
3828
  options.gridSize = gridSize;
3303
3829
  if (options.drawGrid && !options.drawGridSize) {
3304
3830
  // Do not redraw the grid if the `drawGridSize` is set.
3305
- this.getLayerView(LayersNames.GRID).renderGrid();
3831
+ this.getLayerView(paperLayers.GRID).renderGrid();
3306
3832
  }
3307
3833
  return this;
3308
3834
  },
3309
3835
 
3310
3836
  setGrid: function(drawGrid) {
3311
- this.getLayerView(LayersNames.GRID).setGrid(drawGrid);
3837
+ this.getLayerView(paperLayers.GRID).setGrid(drawGrid);
3312
3838
  return this;
3313
3839
  },
3314
3840
 
@@ -3657,201 +4183,8 @@ export const Paper = View.extend({
3657
4183
 
3658
4184
  sorting: sortingTypes,
3659
4185
 
3660
- Layers: LayersNames,
3661
-
3662
- backgroundPatterns: {
3663
-
3664
- flipXy: function(img) {
3665
- // d b
3666
- // q p
3667
-
3668
- var canvas = document.createElement('canvas');
3669
- var imgWidth = img.width;
3670
- var imgHeight = img.height;
3671
-
3672
- canvas.width = 2 * imgWidth;
3673
- canvas.height = 2 * imgHeight;
3674
-
3675
- var ctx = canvas.getContext('2d');
3676
- // top-left image
3677
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
3678
- // xy-flipped bottom-right image
3679
- ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
3680
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
3681
- // x-flipped top-right image
3682
- ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
3683
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
3684
- // y-flipped bottom-left image
3685
- ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
3686
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
3687
-
3688
- return canvas;
3689
- },
3690
-
3691
- flipX: function(img) {
3692
- // d b
3693
- // d b
4186
+ Layers: paperLayers,
3694
4187
 
3695
- var canvas = document.createElement('canvas');
3696
- var imgWidth = img.width;
3697
- var imgHeight = img.height;
3698
-
3699
- canvas.width = imgWidth * 2;
3700
- canvas.height = imgHeight;
3701
-
3702
- var ctx = canvas.getContext('2d');
3703
- // left image
3704
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
3705
- // flipped right image
3706
- ctx.translate(2 * imgWidth, 0);
3707
- ctx.scale(-1, 1);
3708
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
3709
-
3710
- return canvas;
3711
- },
3712
-
3713
- flipY: function(img) {
3714
- // d d
3715
- // q q
3716
-
3717
- var canvas = document.createElement('canvas');
3718
- var imgWidth = img.width;
3719
- var imgHeight = img.height;
3720
-
3721
- canvas.width = imgWidth;
3722
- canvas.height = imgHeight * 2;
3723
-
3724
- var ctx = canvas.getContext('2d');
3725
- // top image
3726
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
3727
- // flipped bottom image
3728
- ctx.translate(0, 2 * imgHeight);
3729
- ctx.scale(1, -1);
3730
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
3731
-
3732
- return canvas;
3733
- },
3734
-
3735
- watermark: function(img, opt) {
3736
- // d
3737
- // d
3738
-
3739
- opt = opt || {};
3740
-
3741
- var imgWidth = img.width;
3742
- var imgHeight = img.height;
3743
-
3744
- var canvas = document.createElement('canvas');
3745
- canvas.width = imgWidth * 3;
3746
- canvas.height = imgHeight * 3;
3747
-
3748
- var ctx = canvas.getContext('2d');
3749
- var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
3750
- var radians = toRad(angle);
3751
- var stepX = canvas.width / 4;
3752
- var stepY = canvas.height / 4;
3753
-
3754
- for (var i = 0; i < 4; i++) {
3755
- for (var j = 0; j < 4; j++) {
3756
- if ((i + j) % 2 > 0) {
3757
- // reset the current transformations
3758
- ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
3759
- ctx.rotate(radians);
3760
- ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
3761
- }
3762
- }
3763
- }
3764
-
3765
- return canvas;
3766
- }
3767
- },
3768
-
3769
- gridPatterns: {
3770
- dot: [{
3771
- color: '#AAAAAA',
3772
- thickness: 1,
3773
- markup: 'rect',
3774
- render: function(el, opt) {
3775
- V(el).attr({
3776
- width: opt.thickness,
3777
- height: opt.thickness,
3778
- fill: opt.color
3779
- });
3780
- }
3781
- }],
3782
- fixedDot: [{
3783
- color: '#AAAAAA',
3784
- thickness: 1,
3785
- markup: 'rect',
3786
- render: function(el, opt) {
3787
- V(el).attr({ fill: opt.color });
3788
- },
3789
- update: function(el, opt, paper) {
3790
- const { sx, sy } = paper.scale();
3791
- const width = sx <= 1 ? opt.thickness : opt.thickness / sx;
3792
- const height = sy <= 1 ? opt.thickness : opt.thickness / sy;
3793
- V(el).attr({ width, height });
3794
- }
3795
- }],
3796
- mesh: [{
3797
- color: '#AAAAAA',
3798
- thickness: 1,
3799
- markup: 'path',
3800
- render: function(el, opt) {
3801
-
3802
- var d;
3803
- var width = opt.width;
3804
- var height = opt.height;
3805
- var thickness = opt.thickness;
3806
-
3807
- if (width - thickness >= 0 && height - thickness >= 0) {
3808
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
3809
- } else {
3810
- d = 'M 0 0 0 0';
3811
- }
3812
-
3813
- V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
3814
- }
3815
- }],
3816
- doubleMesh: [{
3817
- color: '#AAAAAA',
3818
- thickness: 1,
3819
- markup: 'path',
3820
- render: function(el, opt) {
3821
-
3822
- var d;
3823
- var width = opt.width;
3824
- var height = opt.height;
3825
- var thickness = opt.thickness;
3826
-
3827
- if (width - thickness >= 0 && height - thickness >= 0) {
3828
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
3829
- } else {
3830
- d = 'M 0 0 0 0';
3831
- }
3832
-
3833
- V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
3834
- }
3835
- }, {
3836
- color: '#000000',
3837
- thickness: 3,
3838
- scaleFactor: 4,
3839
- markup: 'path',
3840
- render: function(el, opt) {
3841
-
3842
- var d;
3843
- var width = opt.width;
3844
- var height = opt.height;
3845
- var thickness = opt.thickness;
3846
-
3847
- if (width - thickness >= 0 && height - thickness >= 0) {
3848
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
3849
- } else {
3850
- d = 'M 0 0 0 0';
3851
- }
3852
-
3853
- V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
3854
- }
3855
- }]
3856
- }
4188
+ backgroundPatterns,
4189
+ gridPatterns,
3857
4190
  });