@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.
@@ -0,0 +1,222 @@
1
+ import { isEmpty } from '../util/index.mjs';
2
+ import { Listener } from '../mvc/Listener.mjs';
3
+
4
+ /**
5
+ * @class GraphTopologyIndex
6
+ * @description Maintains an index of the graph topology (adjacency list)
7
+ * for fast graph queries.
8
+ */
9
+ export class GraphTopologyIndex extends Listener {
10
+
11
+ constructor(options) {
12
+ super(options);
13
+
14
+ // Make sure there are no arguments passed to the callbacks.
15
+ // See the `mvc.Listener` documentation for more details.
16
+ this.callbackArguments = [];
17
+
18
+ this.layerCollection = options.layerCollection;
19
+ if (!this.layerCollection) {
20
+ throw new Error('GraphTopologyIndex: "layerCollection" option is required.');
21
+ }
22
+
23
+ this.initializeIndex();
24
+ this.startListening();
25
+ }
26
+
27
+ /**
28
+ * @public
29
+ * @description Start listening to graph and layer collection events
30
+ * to maintain the topology index.
31
+ */
32
+ startListening() {
33
+ this.listenTo(this.layerCollection.graph, {
34
+ 'add': this._restructureOnAdd,
35
+ 'remove': this._restructureOnRemove,
36
+ 'reset': this._restructureOnReset
37
+ });
38
+ // Listening to the collection instead of the graph
39
+ // to avoid reacting to graph attribute change events
40
+ // e.g. graph.set('source', ...);
41
+ this.listenTo(this.layerCollection, {
42
+ 'change:source': this._restructureOnChangeSource,
43
+ 'change:target': this._restructureOnChangeTarget
44
+ });
45
+ }
46
+
47
+ /**
48
+ * @protected
49
+ * @description Initialize the internal data structures.
50
+ */
51
+ initializeIndex() {
52
+ // Outgoing edges per node. Note that we use a hash-table for the list
53
+ // of outgoing edges for a faster lookup.
54
+ // [nodeId] -> Object [edgeId] -> true
55
+ this._out = {};
56
+ // Ingoing edges per node.
57
+ // [nodeId] -> Object [edgeId] -> true
58
+ this._in = {};
59
+ // `_nodes` is useful for quick lookup of all the elements in the graph, without
60
+ // having to go through the whole cells array.
61
+ // [node ID] -> true
62
+ this._nodes = {};
63
+ // `_edges` is useful for quick lookup of all the links in the graph, without
64
+ // having to go through the whole cells array.
65
+ // [edgeId] -> true
66
+ this._edges = {};
67
+ }
68
+
69
+ /**
70
+ * @protected
71
+ * @description Restructure the topology index on graph reset.
72
+ * E.g. when fromJSON or resetCells is called.
73
+ */
74
+ _restructureOnReset() {
75
+ this.initializeIndex();
76
+ this.layerCollection.getCells().forEach(this._restructureOnAdd, this);
77
+ }
78
+
79
+ /**
80
+ * @protected
81
+ * @description Restructure the topology index on cell addition.
82
+ * @param {dia.Cell} cell - The cell being added.
83
+ */
84
+ _restructureOnAdd(cell) {
85
+ if (cell.isLink()) {
86
+ this._edges[cell.id] = true;
87
+ const { source, target } = cell.attributes;
88
+ if (source.id) {
89
+ (this._out[source.id] || (this._out[source.id] = {}))[cell.id] = true;
90
+ }
91
+ if (target.id) {
92
+ (this._in[target.id] || (this._in[target.id] = {}))[cell.id] = true;
93
+ }
94
+ } else {
95
+ this._nodes[cell.id] = true;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * @protected
101
+ * @description Restructure the topology index on cell removal.
102
+ * @param {dia.Cell} cell - The cell being removed.
103
+ */
104
+ _restructureOnRemove(cell) {
105
+ if (cell.isLink()) {
106
+ delete this._edges[cell.id];
107
+ const { source, target } = cell.attributes;
108
+ if (source.id && this._out[source.id] && this._out[source.id][cell.id]) {
109
+ delete this._out[source.id][cell.id];
110
+ }
111
+ if (target.id && this._in[target.id] && this._in[target.id][cell.id]) {
112
+ delete this._in[target.id][cell.id];
113
+ }
114
+ } else {
115
+ delete this._nodes[cell.id];
116
+ }
117
+ }
118
+
119
+ /**
120
+ * @protected
121
+ * @description Restructure the topology index on link source change.
122
+ * @param {dia.Link} link - The link being changed.
123
+ */
124
+ _restructureOnChangeSource(link) {
125
+
126
+ const prevSource = link.previous('source');
127
+ if (prevSource.id && this._out[prevSource.id]) {
128
+ delete this._out[prevSource.id][link.id];
129
+ }
130
+ const source = link.attributes.source;
131
+ if (source.id) {
132
+ (this._out[source.id] || (this._out[source.id] = {}))[link.id] = true;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * @protected
138
+ * @description Restructure the topology index on link target change.
139
+ * @param {dia.Link} link - The link being changed.
140
+ */
141
+ _restructureOnChangeTarget(link) {
142
+
143
+ const prevTarget = link.previous('target');
144
+ if (prevTarget.id && this._in[prevTarget.id]) {
145
+ delete this._in[prevTarget.id][link.id];
146
+ }
147
+ const target = link.get('target');
148
+ if (target.id) {
149
+ (this._in[target.id] || (this._in[target.id] = {}))[link.id] = true;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * @public
155
+ * @description Get all outbound edges for the node. Time complexity: O(1).
156
+ * @param {string} nodeId - The id of the node.
157
+ * @returns {Object} - An object of the form: [edgeId] -> true.
158
+ */
159
+ getOutboundEdges(nodeId) {
160
+ return this._out[nodeId] || {};
161
+ }
162
+
163
+ /**
164
+ * @public
165
+ * @description Get all inbound edges for the node. Time complexity: O(1).
166
+ * @param {string} nodeId - The id of the node.
167
+ * @returns {Object} - An object of the form: [edgeId] -> true.
168
+ */
169
+ getInboundEdges(nodeId) {
170
+ return this._in[nodeId] || {};
171
+ }
172
+
173
+ /**
174
+ * @public
175
+ * @description Get all sink nodes (leafs) in the graph. Time complexity: O(|V|).
176
+ * @returns {string[]} - Array of node ids.
177
+ */
178
+ getSinkNodes() {
179
+ const sinks = [];
180
+ for (const nodeId in this._nodes) {
181
+ if (!this._out[nodeId] || isEmpty(this._out[nodeId])) {
182
+ sinks.push(nodeId);
183
+ }
184
+ }
185
+ return sinks;
186
+ }
187
+
188
+ /**
189
+ * @public
190
+ * @description Get all source nodes (roots) in the graph. Time complexity: O(|V|).
191
+ * @returns {string[]} - Array of node ids.
192
+ */
193
+ getSourceNodes() {
194
+ const sources = [];
195
+ for (const nodeId in this._nodes) {
196
+ if (!this._in[nodeId] || isEmpty(this._in[nodeId])) {
197
+ sources.push(nodeId);
198
+ }
199
+ }
200
+ return sources;
201
+ }
202
+
203
+ /**
204
+ * @public
205
+ * @description Return `true` if `nodeId` is a source node (root). Time complexity: O(1).
206
+ * @param {string} nodeId - The id of the node to check.
207
+ * @returns {boolean}
208
+ */
209
+ isSourceNode(nodeId) {
210
+ return !this._in[nodeId] || isEmpty(this._in[nodeId]);
211
+ }
212
+
213
+ /**
214
+ * @public
215
+ * @description Return `true` if `nodeId` is a sink node (leaf). Time complexity: O(1).
216
+ * @param {string} nodeId - The id of the node to check.
217
+ * @returns {boolean}
218
+ */
219
+ isSinkNode(nodeId) {
220
+ return !this._out[nodeId] || isEmpty(this._out[nodeId]);
221
+ }
222
+ }
@@ -1,4 +1,4 @@
1
- import { PaperLayer } from '../PaperLayer.mjs';
1
+ import { LayerView } from './LayerView.mjs';
2
2
  import {
3
3
  isFunction,
4
4
  isString,
@@ -6,10 +6,10 @@ import {
6
6
  omit,
7
7
  assign,
8
8
  merge,
9
- } from '../../util/index.mjs';
10
- import V from '../../V/index.mjs';
9
+ } from '../util/index.mjs';
10
+ import V from '../V/index.mjs';
11
11
 
12
- export const GridLayer = PaperLayer.extend({
12
+ export const GridLayerView = LayerView.extend({
13
13
 
14
14
  style: {
15
15
  'pointer-events': 'none'
@@ -19,13 +19,20 @@ export const GridLayer = PaperLayer.extend({
19
19
  _gridSettings: null,
20
20
 
21
21
  init() {
22
- PaperLayer.prototype.init.apply(this, arguments);
23
- const { options: { paper }} = this;
22
+ LayerView.prototype.init.apply(this, arguments);
23
+ this.paper = this.options.paper;
24
24
  this._gridCache = null;
25
25
  this._gridSettings = [];
26
+ },
27
+
28
+ afterPaperReferenceSet(paper) {
26
29
  this.listenTo(paper, 'transform resize', this.updateGrid);
27
30
  },
28
31
 
32
+ beforePaperReferenceUnset(paper) {
33
+ this.stopListening(paper);
34
+ },
35
+
29
36
  setGrid(drawGrid) {
30
37
  this._gridSettings = this.getGridSettings(drawGrid);
31
38
  this.renderGrid();
@@ -51,7 +58,7 @@ export const GridLayer = PaperLayer.extend({
51
58
 
52
59
  renderGrid() {
53
60
 
54
- const { options: { paper }} = this;
61
+ const { paper } = this;
55
62
  const { _gridSettings: gridSettings } = this;
56
63
 
57
64
  this.removeGrid();
@@ -96,7 +103,7 @@ export const GridLayer = PaperLayer.extend({
96
103
 
97
104
  updateGrid() {
98
105
 
99
- const { _gridCache: grid, _gridSettings: gridSettings, options: { paper }} = this;
106
+ const { _gridCache: grid, _gridSettings: gridSettings, paper } = this;
100
107
  if (!grid) return;
101
108
  const { root: vSvg, patterns } = grid;
102
109
  const { x, y, width, height } = paper.getArea();
@@ -114,7 +121,7 @@ export const GridLayer = PaperLayer.extend({
114
121
  },
115
122
 
116
123
  _getPatternId(index) {
117
- return `pattern_${this.options.paper.cid}_${index}`;
124
+ return `pattern_${this.paper.cid}_${index}`;
118
125
  },
119
126
 
120
127
  _getGridRefs() {
@@ -143,30 +150,30 @@ export const GridLayer = PaperLayer.extend({
143
150
 
144
151
  _resolveDrawGridOption(opt) {
145
152
 
146
- var namespace = this.options.patterns;
153
+ const namespace = this.options.patterns;
147
154
  if (isString(opt) && Array.isArray(namespace[opt])) {
148
155
  return namespace[opt].map(function(item) {
149
156
  return assign({}, item);
150
157
  });
151
158
  }
152
159
 
153
- var options = opt || { args: [{}] };
154
- var isArray = Array.isArray(options);
155
- var name = options.name;
160
+ const options = opt || { args: [{}] };
161
+ const isArray = Array.isArray(options);
162
+ let name = options.name;
156
163
 
157
164
  if (!isArray && !name && !options.markup) {
158
165
  name = 'dot';
159
166
  }
160
167
 
161
168
  if (name && Array.isArray(namespace[name])) {
162
- var pattern = namespace[name].map(function(item) {
169
+ const pattern = namespace[name].map(function(item) {
163
170
  return assign({}, item);
164
171
  });
165
172
 
166
- var args = Array.isArray(options.args) ? options.args : [options.args || {}];
173
+ const args = Array.isArray(options.args) ? options.args : [options.args || {}];
167
174
 
168
175
  defaults(args[0], omit(opt, 'args'));
169
- for (var i = 0; i < args.length; i++) {
176
+ for (let i = 0; i < args.length; i++) {
170
177
  if (pattern[i]) {
171
178
  assign(pattern[i], args[i]);
172
179
  }
@@ -1,34 +1,62 @@
1
1
  import { View } from '../mvc/index.mjs';
2
- import { addClassNamePrefix } from '../util/util.mjs';
2
+ import { addClassNamePrefix, clone } from '../util/util.mjs';
3
+ import { LAYER_VIEW_MARKER } from './symbols.mjs';
3
4
 
4
- export const LayersNames = {
5
- GRID: 'grid',
6
- CELLS: 'cells',
7
- BACK: 'back',
8
- FRONT: 'front',
9
- TOOLS: 'tools',
10
- LABELS: 'labels'
11
- };
12
-
13
- export const PaperLayer = View.extend({
5
+ export const LayerView = View.extend({
14
6
 
15
7
  tagName: 'g',
16
8
  svgElement: true,
17
9
  pivotNodes: null,
18
10
  defaultTheme: null,
19
11
 
12
+ UPDATE_PRIORITY: 4,
13
+
20
14
  options: {
21
- name: ''
15
+ id: ''
22
16
  },
23
17
 
24
- className: function() {
25
- const { name } = this.options;
26
- if (!name) return null;
27
- return addClassNamePrefix(`${name}-layer`);
28
- },
18
+ paper: null,
29
19
 
30
20
  init: function() {
31
21
  this.pivotNodes = {};
22
+ this.id = this.options.id || this.cid;
23
+ },
24
+
25
+ setPaperReference: function(paper) {
26
+ this.paper = paper;
27
+ this.afterPaperReferenceSet(paper);
28
+ },
29
+
30
+ unsetPaperReference: function() {
31
+ this.beforePaperReferenceUnset();
32
+ this.paper = null;
33
+ },
34
+
35
+ assertPaperReference() {
36
+ if (!this.paper) {
37
+ throw new Error('LayerView: paper reference is not set.');
38
+ }
39
+ },
40
+
41
+ afterPaperReferenceSet: function() {
42
+ // Can be overridden in subclasses.
43
+ },
44
+
45
+ beforePaperReferenceUnset: function() {
46
+ // Can be overridden in subclasses.
47
+ },
48
+
49
+ // prevents id to be set on the DOM element
50
+ _setAttributes: function(attrs) {
51
+ const newAttrs = clone(attrs);
52
+ delete newAttrs.id;
53
+
54
+ View.prototype._setAttributes.call(this, newAttrs);
55
+ },
56
+
57
+ className: function() {
58
+ const { id } = this.options;
59
+ return addClassNamePrefix(`${id}-layer`);
32
60
  },
33
61
 
34
62
  insertSortedNode: function(node, z) {
@@ -79,4 +107,11 @@ export const PaperLayer = View.extend({
79
107
  return this.el.children.length === 0;
80
108
  },
81
109
 
110
+ reset: function() {
111
+ this.removePivots();
112
+ }
113
+ });
114
+
115
+ Object.defineProperty(LayerView.prototype, LAYER_VIEW_MARKER, {
116
+ value: true,
82
117
  });
@@ -0,0 +1,14 @@
1
+ import { GraphLayerView } from './GraphLayerView.mjs';
2
+ import { addClassNamePrefix } from '../util/util.mjs';
3
+
4
+ /**
5
+ * @class LegacyGraphLayerView
6
+ * @description A legacy GraphLayerView with an additional class name for backward compatibility.
7
+ */
8
+ export const LegacyGraphLayerView = GraphLayerView.extend({
9
+
10
+ className: function() {
11
+ const className = GraphLayerView.prototype.className.apply(this, arguments);
12
+ return className + ' ' + addClassNamePrefix('viewport');
13
+ }
14
+ });