@joint/core 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +376 -0
- package/README.md +49 -0
- package/dist/geometry.js +6486 -0
- package/dist/geometry.min.js +8 -0
- package/dist/joint.d.ts +5536 -0
- package/dist/joint.js +39629 -0
- package/dist/joint.min.js +8 -0
- package/dist/joint.nowrap.js +39626 -0
- package/dist/joint.nowrap.min.js +8 -0
- package/dist/vectorizer.js +9135 -0
- package/dist/vectorizer.min.js +8 -0
- package/dist/version.mjs +3 -0
- package/index.js +3 -0
- package/joint.mjs +27 -0
- package/package.json +192 -0
- package/src/V/annotation.mjs +0 -0
- package/src/V/index.mjs +2642 -0
- package/src/anchors/index.mjs +123 -0
- package/src/config/index.mjs +12 -0
- package/src/connectionPoints/index.mjs +202 -0
- package/src/connectionStrategies/index.mjs +73 -0
- package/src/connectors/curve.mjs +553 -0
- package/src/connectors/index.mjs +6 -0
- package/src/connectors/jumpover.mjs +452 -0
- package/src/connectors/normal.mjs +12 -0
- package/src/connectors/rounded.mjs +17 -0
- package/src/connectors/smooth.mjs +44 -0
- package/src/connectors/straight.mjs +110 -0
- package/src/dia/Cell.mjs +945 -0
- package/src/dia/CellView.mjs +1316 -0
- package/src/dia/Element.mjs +519 -0
- package/src/dia/ElementView.mjs +859 -0
- package/src/dia/Graph.mjs +1112 -0
- package/src/dia/HighlighterView.mjs +319 -0
- package/src/dia/Link.mjs +565 -0
- package/src/dia/LinkView.mjs +2207 -0
- package/src/dia/Paper.mjs +3171 -0
- package/src/dia/PaperLayer.mjs +75 -0
- package/src/dia/ToolView.mjs +69 -0
- package/src/dia/ToolsView.mjs +128 -0
- package/src/dia/attributes/calc.mjs +128 -0
- package/src/dia/attributes/connection.mjs +75 -0
- package/src/dia/attributes/defs.mjs +76 -0
- package/src/dia/attributes/eval.mjs +64 -0
- package/src/dia/attributes/index.mjs +69 -0
- package/src/dia/attributes/legacy.mjs +148 -0
- package/src/dia/attributes/offset.mjs +53 -0
- package/src/dia/attributes/props.mjs +30 -0
- package/src/dia/attributes/shape.mjs +92 -0
- package/src/dia/attributes/text.mjs +180 -0
- package/src/dia/index.mjs +13 -0
- package/src/dia/layers/GridLayer.mjs +176 -0
- package/src/dia/ports.mjs +874 -0
- package/src/elementTools/Control.mjs +153 -0
- package/src/elementTools/HoverConnect.mjs +37 -0
- package/src/elementTools/index.mjs +5 -0
- package/src/env/index.mjs +43 -0
- package/src/g/bezier.mjs +175 -0
- package/src/g/curve.mjs +956 -0
- package/src/g/ellipse.mjs +245 -0
- package/src/g/extend.mjs +64 -0
- package/src/g/geometry.helpers.mjs +58 -0
- package/src/g/index.mjs +17 -0
- package/src/g/intersection.mjs +511 -0
- package/src/g/line.bearing.mjs +30 -0
- package/src/g/line.length.mjs +5 -0
- package/src/g/line.mjs +356 -0
- package/src/g/line.squaredLength.mjs +10 -0
- package/src/g/path.mjs +2260 -0
- package/src/g/point.mjs +375 -0
- package/src/g/points.mjs +247 -0
- package/src/g/polygon.mjs +51 -0
- package/src/g/polyline.mjs +523 -0
- package/src/g/rect.mjs +556 -0
- package/src/g/types.mjs +10 -0
- package/src/highlighters/addClass.mjs +27 -0
- package/src/highlighters/index.mjs +5 -0
- package/src/highlighters/list.mjs +111 -0
- package/src/highlighters/mask.mjs +220 -0
- package/src/highlighters/opacity.mjs +17 -0
- package/src/highlighters/stroke.mjs +100 -0
- package/src/layout/index.mjs +4 -0
- package/src/layout/ports/port.mjs +188 -0
- package/src/layout/ports/portLabel.mjs +224 -0
- package/src/linkAnchors/index.mjs +76 -0
- package/src/linkTools/Anchor.mjs +235 -0
- package/src/linkTools/Arrowhead.mjs +103 -0
- package/src/linkTools/Boundary.mjs +48 -0
- package/src/linkTools/Button.mjs +121 -0
- package/src/linkTools/Connect.mjs +85 -0
- package/src/linkTools/HoverConnect.mjs +161 -0
- package/src/linkTools/Segments.mjs +393 -0
- package/src/linkTools/Vertices.mjs +253 -0
- package/src/linkTools/helpers.mjs +33 -0
- package/src/linkTools/index.mjs +8 -0
- package/src/mvc/Collection.mjs +560 -0
- package/src/mvc/Data.mjs +46 -0
- package/src/mvc/Dom/Dom.mjs +587 -0
- package/src/mvc/Dom/Event.mjs +130 -0
- package/src/mvc/Dom/animations.mjs +122 -0
- package/src/mvc/Dom/events.mjs +69 -0
- package/src/mvc/Dom/index.mjs +13 -0
- package/src/mvc/Dom/methods.mjs +392 -0
- package/src/mvc/Dom/props.mjs +77 -0
- package/src/mvc/Dom/vars.mjs +5 -0
- package/src/mvc/Events.mjs +337 -0
- package/src/mvc/Listener.mjs +33 -0
- package/src/mvc/Model.mjs +239 -0
- package/src/mvc/View.mjs +323 -0
- package/src/mvc/ViewBase.mjs +182 -0
- package/src/mvc/index.mjs +9 -0
- package/src/mvc/mvcUtils.mjs +90 -0
- package/src/polyfills/array.js +4 -0
- package/src/polyfills/base64.js +68 -0
- package/src/polyfills/index.mjs +5 -0
- package/src/polyfills/number.js +3 -0
- package/src/polyfills/string.js +3 -0
- package/src/polyfills/typedArray.js +47 -0
- package/src/routers/index.mjs +6 -0
- package/src/routers/manhattan.mjs +856 -0
- package/src/routers/metro.mjs +91 -0
- package/src/routers/normal.mjs +6 -0
- package/src/routers/oneSide.mjs +60 -0
- package/src/routers/orthogonal.mjs +323 -0
- package/src/routers/rightAngle.mjs +1056 -0
- package/src/shapes/index.mjs +3 -0
- package/src/shapes/standard.mjs +755 -0
- package/src/util/cloneCells.mjs +67 -0
- package/src/util/getRectPoint.mjs +65 -0
- package/src/util/index.mjs +5 -0
- package/src/util/svgTagTemplate.mjs +110 -0
- package/src/util/util.mjs +1754 -0
- package/src/util/utilHelpers.mjs +2402 -0
- package/src/util/wrappers.mjs +56 -0
- package/types/geometry.d.ts +815 -0
- package/types/index.d.ts +53 -0
- package/types/joint.d.ts +4391 -0
- package/types/joint.head.d.ts +12 -0
- package/types/vectorizer.d.ts +327 -0
package/src/dia/Cell.mjs
ADDED
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
import {
|
|
2
|
+
uniqueId,
|
|
3
|
+
union,
|
|
4
|
+
result,
|
|
5
|
+
merge,
|
|
6
|
+
forIn,
|
|
7
|
+
isObject,
|
|
8
|
+
isEqual,
|
|
9
|
+
isString,
|
|
10
|
+
cloneDeep,
|
|
11
|
+
omit,
|
|
12
|
+
uuid,
|
|
13
|
+
isEmpty,
|
|
14
|
+
assign,
|
|
15
|
+
uniq,
|
|
16
|
+
toArray,
|
|
17
|
+
setByPath,
|
|
18
|
+
unsetByPath,
|
|
19
|
+
getByPath,
|
|
20
|
+
timing,
|
|
21
|
+
interpolate,
|
|
22
|
+
nextFrame,
|
|
23
|
+
without,
|
|
24
|
+
cancelFrame,
|
|
25
|
+
defaultsDeep,
|
|
26
|
+
has,
|
|
27
|
+
sortBy,
|
|
28
|
+
defaults
|
|
29
|
+
} from '../util/util.mjs';
|
|
30
|
+
import { Model } from '../mvc/Model.mjs';
|
|
31
|
+
import { cloneCells } from '../util/cloneCells.mjs';
|
|
32
|
+
import { attributes } from './attributes/index.mjs';
|
|
33
|
+
import * as g from '../g/index.mjs';
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
// Cell base model.
|
|
37
|
+
// --------------------------
|
|
38
|
+
|
|
39
|
+
const attributesMerger = function(a, b) {
|
|
40
|
+
if (Array.isArray(a)) {
|
|
41
|
+
return b;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Cell = Model.extend({
|
|
46
|
+
|
|
47
|
+
// This is the same as mvc.Model with the only difference that is uses util.merge
|
|
48
|
+
// instead of just _.extend. The reason is that we want to mixin attributes set in upper classes.
|
|
49
|
+
constructor: function(attributes, options) {
|
|
50
|
+
|
|
51
|
+
var defaults;
|
|
52
|
+
var attrs = attributes || {};
|
|
53
|
+
if (typeof this.preinitialize === 'function') {
|
|
54
|
+
// Check to support an older version
|
|
55
|
+
this.preinitialize.apply(this, arguments);
|
|
56
|
+
}
|
|
57
|
+
this.cid = uniqueId('c');
|
|
58
|
+
this.attributes = {};
|
|
59
|
+
if (options && options.collection) this.collection = options.collection;
|
|
60
|
+
if (options && options.parse) attrs = this.parse(attrs, options) || {};
|
|
61
|
+
if ((defaults = result(this, 'defaults'))) {
|
|
62
|
+
//<custom code>
|
|
63
|
+
// Replaced the call to _.defaults with util.merge.
|
|
64
|
+
const customizer = (options && options.mergeArrays === true) ? false : attributesMerger;
|
|
65
|
+
attrs = merge({}, defaults, attrs, customizer);
|
|
66
|
+
//</custom code>
|
|
67
|
+
}
|
|
68
|
+
this.set(attrs, options);
|
|
69
|
+
this.changed = {};
|
|
70
|
+
this.initialize.apply(this, arguments);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
translate: function(dx, dy, opt) {
|
|
74
|
+
|
|
75
|
+
throw new Error('Must define a translate() method.');
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
toJSON: function() {
|
|
79
|
+
|
|
80
|
+
const defaults = result(this.constructor.prototype, 'defaults');
|
|
81
|
+
const defaultAttrs = defaults.attrs || {};
|
|
82
|
+
const attrs = this.attributes.attrs;
|
|
83
|
+
const finalAttrs = {};
|
|
84
|
+
|
|
85
|
+
// Loop through all the attributes and
|
|
86
|
+
// omit the default attributes as they are implicitly reconstructible by the cell 'type'.
|
|
87
|
+
forIn(attrs, function(attr, selector) {
|
|
88
|
+
|
|
89
|
+
const defaultAttr = defaultAttrs[selector];
|
|
90
|
+
|
|
91
|
+
forIn(attr, function(value, name) {
|
|
92
|
+
|
|
93
|
+
// attr is mainly flat though it might have one more level (consider the `style` attribute).
|
|
94
|
+
// Check if the `value` is object and if yes, go one level deep.
|
|
95
|
+
if (isObject(value) && !Array.isArray(value)) {
|
|
96
|
+
|
|
97
|
+
forIn(value, function(value2, name2) {
|
|
98
|
+
|
|
99
|
+
if (!defaultAttr || !defaultAttr[name] || !isEqual(defaultAttr[name][name2], value2)) {
|
|
100
|
+
|
|
101
|
+
finalAttrs[selector] = finalAttrs[selector] || {};
|
|
102
|
+
(finalAttrs[selector][name] || (finalAttrs[selector][name] = {}))[name2] = value2;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
} else if (!defaultAttr || !isEqual(defaultAttr[name], value)) {
|
|
107
|
+
// `value` is not an object, default attribute for such a selector does not exist
|
|
108
|
+
// or it is different than the attribute value set on the model.
|
|
109
|
+
|
|
110
|
+
finalAttrs[selector] = finalAttrs[selector] || {};
|
|
111
|
+
finalAttrs[selector][name] = value;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const attributes = cloneDeep(omit(this.attributes, 'attrs'));
|
|
117
|
+
attributes.attrs = finalAttrs;
|
|
118
|
+
|
|
119
|
+
return attributes;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
initialize: function(options) {
|
|
123
|
+
|
|
124
|
+
const idAttribute = this.getIdAttribute();
|
|
125
|
+
if (!options || options[idAttribute] === undefined) {
|
|
126
|
+
this.set(idAttribute, this.generateId(), { silent: true });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this._transitionIds = {};
|
|
130
|
+
this._scheduledTransitionIds = {};
|
|
131
|
+
|
|
132
|
+
// Collect ports defined in `attrs` and keep collecting whenever `attrs` object changes.
|
|
133
|
+
this.processPorts();
|
|
134
|
+
this.on('change:attrs', this.processPorts, this);
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
getIdAttribute: function() {
|
|
138
|
+
return this.idAttribute || 'id';
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
generateId: function() {
|
|
142
|
+
return uuid();
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @deprecated
|
|
147
|
+
*/
|
|
148
|
+
processPorts: function() {
|
|
149
|
+
|
|
150
|
+
// Whenever `attrs` changes, we extract ports from the `attrs` object and store it
|
|
151
|
+
// in a more accessible way. Also, if any port got removed and there were links that had `target`/`source`
|
|
152
|
+
// set to that port, we remove those links as well (to follow the same behaviour as
|
|
153
|
+
// with a removed element).
|
|
154
|
+
|
|
155
|
+
var previousPorts = this.ports;
|
|
156
|
+
|
|
157
|
+
// Collect ports from the `attrs` object.
|
|
158
|
+
var ports = {};
|
|
159
|
+
forIn(this.get('attrs'), function(attrs, selector) {
|
|
160
|
+
|
|
161
|
+
if (attrs && attrs.port) {
|
|
162
|
+
|
|
163
|
+
// `port` can either be directly an `id` or an object containing an `id` (and potentially other data).
|
|
164
|
+
if (attrs.port.id !== undefined) {
|
|
165
|
+
ports[attrs.port.id] = attrs.port;
|
|
166
|
+
} else {
|
|
167
|
+
ports[attrs.port] = { id: attrs.port };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Collect ports that have been removed (compared to the previous ports) - if any.
|
|
173
|
+
// Use hash table for quick lookup.
|
|
174
|
+
var removedPorts = {};
|
|
175
|
+
forIn(previousPorts, function(port, id) {
|
|
176
|
+
|
|
177
|
+
if (!ports[id]) removedPorts[id] = true;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Remove all the incoming/outgoing links that have source/target port set to any of the removed ports.
|
|
181
|
+
if (this.graph && !isEmpty(removedPorts)) {
|
|
182
|
+
|
|
183
|
+
var inboundLinks = this.graph.getConnectedLinks(this, { inbound: true });
|
|
184
|
+
inboundLinks.forEach(function(link) {
|
|
185
|
+
|
|
186
|
+
if (removedPorts[link.get('target').port]) link.remove();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
var outboundLinks = this.graph.getConnectedLinks(this, { outbound: true });
|
|
190
|
+
outboundLinks.forEach(function(link) {
|
|
191
|
+
|
|
192
|
+
if (removedPorts[link.get('source').port]) link.remove();
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Update the `ports` object.
|
|
197
|
+
this.ports = ports;
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
remove: function(opt = {}) {
|
|
201
|
+
|
|
202
|
+
// Store the graph in a variable because `this.graph` won't be accessible
|
|
203
|
+
// after `this.trigger('remove', ...)` down below.
|
|
204
|
+
const { graph, collection } = this;
|
|
205
|
+
if (!graph) {
|
|
206
|
+
// The collection is a common mvc collection (not the graph collection).
|
|
207
|
+
if (collection) collection.remove(this, opt);
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
graph.startBatch('remove');
|
|
212
|
+
|
|
213
|
+
// First, unembed this cell from its parent cell if there is one.
|
|
214
|
+
const parentCell = this.getParentCell();
|
|
215
|
+
if (parentCell) {
|
|
216
|
+
parentCell.unembed(this, opt);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Remove also all the cells, which were embedded into this cell
|
|
220
|
+
const embeddedCells = this.getEmbeddedCells();
|
|
221
|
+
for (let i = 0, n = embeddedCells.length; i < n; i++) {
|
|
222
|
+
const embed = embeddedCells[i];
|
|
223
|
+
if (embed) {
|
|
224
|
+
embed.remove(opt);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.trigger('remove', this, graph.attributes.cells, opt);
|
|
229
|
+
|
|
230
|
+
graph.stopBatch('remove');
|
|
231
|
+
|
|
232
|
+
return this;
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
toFront: function(opt) {
|
|
236
|
+
var graph = this.graph;
|
|
237
|
+
if (graph) {
|
|
238
|
+
opt = defaults(opt || {}, { foregroundEmbeds: true });
|
|
239
|
+
|
|
240
|
+
let cells;
|
|
241
|
+
if (opt.deep) {
|
|
242
|
+
cells = this.getEmbeddedCells({ deep: true, breadthFirst: opt.breadthFirst !== false, sortSiblings: opt.foregroundEmbeds });
|
|
243
|
+
cells.unshift(this);
|
|
244
|
+
} else {
|
|
245
|
+
cells = [this];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
|
|
249
|
+
|
|
250
|
+
const maxZ = graph.maxZIndex();
|
|
251
|
+
let z = maxZ - cells.length + 1;
|
|
252
|
+
|
|
253
|
+
const collection = graph.get('cells');
|
|
254
|
+
|
|
255
|
+
let shouldUpdate = (collection.toArray().indexOf(sortedCells[0]) !== (collection.length - cells.length));
|
|
256
|
+
if (!shouldUpdate) {
|
|
257
|
+
shouldUpdate = sortedCells.some(function(cell, index) {
|
|
258
|
+
return cell.z() !== z + index;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (shouldUpdate) {
|
|
263
|
+
this.startBatch('to-front');
|
|
264
|
+
|
|
265
|
+
z = z + cells.length;
|
|
266
|
+
|
|
267
|
+
sortedCells.forEach(function(cell, index) {
|
|
268
|
+
cell.set('z', z + index, opt);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
this.stopBatch('to-front');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return this;
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
toBack: function(opt) {
|
|
279
|
+
var graph = this.graph;
|
|
280
|
+
if (graph) {
|
|
281
|
+
opt = defaults(opt || {}, { foregroundEmbeds: true });
|
|
282
|
+
|
|
283
|
+
let cells;
|
|
284
|
+
if (opt.deep) {
|
|
285
|
+
cells = this.getEmbeddedCells({ deep: true, breadthFirst: opt.breadthFirst !== false, sortSiblings: opt.foregroundEmbeds });
|
|
286
|
+
cells.unshift(this);
|
|
287
|
+
} else {
|
|
288
|
+
cells = [this];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
|
|
292
|
+
|
|
293
|
+
let z = graph.minZIndex();
|
|
294
|
+
|
|
295
|
+
var collection = graph.get('cells');
|
|
296
|
+
|
|
297
|
+
let shouldUpdate = (collection.toArray().indexOf(sortedCells[0]) !== 0);
|
|
298
|
+
if (!shouldUpdate) {
|
|
299
|
+
shouldUpdate = sortedCells.some(function(cell, index) {
|
|
300
|
+
return cell.z() !== z + index;
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (shouldUpdate) {
|
|
305
|
+
this.startBatch('to-back');
|
|
306
|
+
|
|
307
|
+
z -= cells.length;
|
|
308
|
+
|
|
309
|
+
sortedCells.forEach(function(cell, index) {
|
|
310
|
+
cell.set('z', z + index, opt);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
this.stopBatch('to-back');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return this;
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
parent: function(parent, opt) {
|
|
321
|
+
|
|
322
|
+
// getter
|
|
323
|
+
if (parent === undefined) return this.get('parent');
|
|
324
|
+
// setter
|
|
325
|
+
return this.set('parent', parent, opt);
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
embed: function(cell, opt) {
|
|
329
|
+
const cells = Array.isArray(cell) ? cell : [cell];
|
|
330
|
+
if (!this.canEmbed(cells)) {
|
|
331
|
+
throw new Error('Recursive embedding not allowed.');
|
|
332
|
+
}
|
|
333
|
+
if (cells.some(c => c.isEmbedded() && this.id !== c.parent())) {
|
|
334
|
+
throw new Error('Embedding of already embedded cells is not allowed.');
|
|
335
|
+
}
|
|
336
|
+
this._embedCells(cells, opt);
|
|
337
|
+
return this;
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
unembed: function(cell, opt) {
|
|
341
|
+
const cells = Array.isArray(cell) ? cell : [cell];
|
|
342
|
+
this._unembedCells(cells, opt);
|
|
343
|
+
return this;
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
canEmbed: function(cell) {
|
|
347
|
+
const cells = Array.isArray(cell) ? cell : [cell];
|
|
348
|
+
return cells.every(c => this !== c && !this.isEmbeddedIn(c));
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
_embedCells: function(cells, opt) {
|
|
352
|
+
const batchName = 'embed';
|
|
353
|
+
this.startBatch(batchName);
|
|
354
|
+
const embeds = assign([], this.get('embeds'));
|
|
355
|
+
cells.forEach(cell => {
|
|
356
|
+
// We keep all element ids after link ids.
|
|
357
|
+
embeds[cell.isLink() ? 'unshift' : 'push'](cell.id);
|
|
358
|
+
cell.parent(this.id, opt);
|
|
359
|
+
});
|
|
360
|
+
this.set('embeds', uniq(embeds), opt);
|
|
361
|
+
this.stopBatch(batchName);
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
_unembedCells: function(cells, opt) {
|
|
365
|
+
const batchName = 'unembed';
|
|
366
|
+
this.startBatch(batchName);
|
|
367
|
+
cells.forEach(cell => cell.unset('parent', opt));
|
|
368
|
+
this.set('embeds', without(this.get('embeds'), ...cells.map(cell => cell.id)), opt);
|
|
369
|
+
this.stopBatch(batchName);
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
getParentCell: function() {
|
|
373
|
+
|
|
374
|
+
// unlike link.source/target, cell.parent stores id directly as a string
|
|
375
|
+
var parentId = this.parent();
|
|
376
|
+
var graph = this.graph;
|
|
377
|
+
|
|
378
|
+
return (parentId && graph && graph.getCell(parentId)) || null;
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
// Return an array of ancestor cells.
|
|
382
|
+
// The array is ordered from the parent of the cell
|
|
383
|
+
// to the most distant ancestor.
|
|
384
|
+
getAncestors: function() {
|
|
385
|
+
|
|
386
|
+
var ancestors = [];
|
|
387
|
+
|
|
388
|
+
if (!this.graph) {
|
|
389
|
+
return ancestors;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
var parentCell = this.getParentCell();
|
|
393
|
+
while (parentCell) {
|
|
394
|
+
ancestors.push(parentCell);
|
|
395
|
+
parentCell = parentCell.getParentCell();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return ancestors;
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
getEmbeddedCells: function(opt) {
|
|
402
|
+
|
|
403
|
+
opt = opt || {};
|
|
404
|
+
|
|
405
|
+
// Cell models can only be retrieved when this element is part of a collection.
|
|
406
|
+
// There is no way this element knows about other cells otherwise.
|
|
407
|
+
// This also means that calling e.g. `translate()` on an element with embeds before
|
|
408
|
+
// adding it to a graph does not translate its embeds.
|
|
409
|
+
if (!this.graph) {
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (opt.deep) {
|
|
414
|
+
if (opt.breadthFirst) {
|
|
415
|
+
return this._getEmbeddedCellsBfs(opt.sortSiblings);
|
|
416
|
+
} else {
|
|
417
|
+
return this._getEmbeddedCellsDfs(opt.sortSiblings);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const embeddedIds = this.get('embeds');
|
|
422
|
+
if (isEmpty(embeddedIds)) {
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let cells = embeddedIds.map(this.graph.getCell, this.graph);
|
|
427
|
+
if (opt.sortSiblings) {
|
|
428
|
+
cells = sortBy(cells, cell => cell.z());
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return cells;
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
_getEmbeddedCellsBfs: function(sortSiblings) {
|
|
435
|
+
const cells = [];
|
|
436
|
+
|
|
437
|
+
const queue = [];
|
|
438
|
+
queue.push(this);
|
|
439
|
+
|
|
440
|
+
while (queue.length > 0) {
|
|
441
|
+
const current = queue.shift();
|
|
442
|
+
cells.push(current);
|
|
443
|
+
|
|
444
|
+
const embeddedCells = current.getEmbeddedCells({ sortSiblings: sortSiblings });
|
|
445
|
+
|
|
446
|
+
queue.push(...embeddedCells);
|
|
447
|
+
}
|
|
448
|
+
cells.shift();
|
|
449
|
+
|
|
450
|
+
return cells;
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
_getEmbeddedCellsDfs: function(sortSiblings) {
|
|
454
|
+
const cells = [];
|
|
455
|
+
|
|
456
|
+
const stack = [];
|
|
457
|
+
stack.push(this);
|
|
458
|
+
|
|
459
|
+
while (stack.length > 0) {
|
|
460
|
+
const current = stack.pop();
|
|
461
|
+
cells.push(current);
|
|
462
|
+
|
|
463
|
+
const embeddedCells = current.getEmbeddedCells({ sortSiblings: sortSiblings });
|
|
464
|
+
|
|
465
|
+
// When using the stack, cells that are embedded last are processed first.
|
|
466
|
+
// To maintain the original order, we need to push the cells in reverse order
|
|
467
|
+
for (let i = embeddedCells.length - 1; i >= 0; --i) {
|
|
468
|
+
stack.push(embeddedCells[i]);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
cells.shift();
|
|
472
|
+
|
|
473
|
+
return cells;
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
isEmbeddedIn: function(cell, opt) {
|
|
477
|
+
|
|
478
|
+
var cellId = isString(cell) ? cell : cell.id;
|
|
479
|
+
var parentId = this.parent();
|
|
480
|
+
|
|
481
|
+
opt = assign({ deep: true }, opt);
|
|
482
|
+
|
|
483
|
+
// See getEmbeddedCells().
|
|
484
|
+
if (this.graph && opt.deep) {
|
|
485
|
+
|
|
486
|
+
while (parentId) {
|
|
487
|
+
if (parentId === cellId) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
parentId = this.graph.getCell(parentId).parent();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return false;
|
|
494
|
+
|
|
495
|
+
} else {
|
|
496
|
+
|
|
497
|
+
// When this cell is not part of a collection check
|
|
498
|
+
// at least whether it's a direct child of given cell.
|
|
499
|
+
return parentId === cellId;
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
// Whether or not the cell is embedded in any other cell.
|
|
504
|
+
isEmbedded: function() {
|
|
505
|
+
|
|
506
|
+
return !!this.parent();
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
// Isolated cloning. Isolated cloning has two versions: shallow and deep (pass `{ deep: true }` in `opt`).
|
|
510
|
+
// Shallow cloning simply clones the cell and returns a new cell with different ID.
|
|
511
|
+
// Deep cloning clones the cell and all its embedded cells recursively.
|
|
512
|
+
clone: function(opt) {
|
|
513
|
+
|
|
514
|
+
opt = opt || {};
|
|
515
|
+
|
|
516
|
+
if (!opt.deep) {
|
|
517
|
+
// Shallow cloning.
|
|
518
|
+
|
|
519
|
+
var clone = Model.prototype.clone.apply(this, arguments);
|
|
520
|
+
// We don't want the clone to have the same ID as the original.
|
|
521
|
+
clone.set(this.getIdAttribute(), this.generateId());
|
|
522
|
+
// A shallow cloned element does not carry over the original embeds.
|
|
523
|
+
clone.unset('embeds');
|
|
524
|
+
// And can not be embedded in any cell
|
|
525
|
+
// as the clone is not part of the graph.
|
|
526
|
+
clone.unset('parent');
|
|
527
|
+
|
|
528
|
+
return clone;
|
|
529
|
+
|
|
530
|
+
} else {
|
|
531
|
+
// Deep cloning.
|
|
532
|
+
|
|
533
|
+
// For a deep clone, simply call `graph.cloneCells()` with the cell and all its embedded cells.
|
|
534
|
+
return toArray(cloneCells([this].concat(this.getEmbeddedCells({ deep: true }))));
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
// A convenient way to set nested properties.
|
|
539
|
+
// This method merges the properties you'd like to set with the ones
|
|
540
|
+
// stored in the cell and makes sure change events are properly triggered.
|
|
541
|
+
// You can either set a nested property with one object
|
|
542
|
+
// or use a property path.
|
|
543
|
+
// The most simple use case is:
|
|
544
|
+
// `cell.prop('name/first', 'John')` or
|
|
545
|
+
// `cell.prop({ name: { first: 'John' } })`.
|
|
546
|
+
// Nested arrays are supported too:
|
|
547
|
+
// `cell.prop('series/0/data/0/degree', 50)` or
|
|
548
|
+
// `cell.prop({ series: [ { data: [ { degree: 50 } ] } ] })`.
|
|
549
|
+
prop: function(props, value, opt) {
|
|
550
|
+
|
|
551
|
+
var delim = '/';
|
|
552
|
+
var _isString = isString(props);
|
|
553
|
+
|
|
554
|
+
if (_isString || Array.isArray(props)) {
|
|
555
|
+
// Get/set an attribute by a special path syntax that delimits
|
|
556
|
+
// nested objects by the colon character.
|
|
557
|
+
|
|
558
|
+
if (arguments.length > 1) {
|
|
559
|
+
|
|
560
|
+
var path;
|
|
561
|
+
var pathArray;
|
|
562
|
+
|
|
563
|
+
if (_isString) {
|
|
564
|
+
path = props;
|
|
565
|
+
pathArray = path.split('/');
|
|
566
|
+
} else {
|
|
567
|
+
path = props.join(delim);
|
|
568
|
+
pathArray = props.slice();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
var property = pathArray[0];
|
|
572
|
+
var pathArrayLength = pathArray.length;
|
|
573
|
+
|
|
574
|
+
const options = opt || {};
|
|
575
|
+
options.propertyPath = path;
|
|
576
|
+
options.propertyValue = value;
|
|
577
|
+
options.propertyPathArray = pathArray;
|
|
578
|
+
if (!('rewrite' in options)) {
|
|
579
|
+
options.rewrite = false;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
var update = {};
|
|
583
|
+
// Initialize the nested object. Sub-objects are either arrays or objects.
|
|
584
|
+
// An empty array is created if the sub-key is an integer. Otherwise, an empty object is created.
|
|
585
|
+
// Note that this imposes a limitation on object keys one can use with Inspector.
|
|
586
|
+
// Pure integer keys will cause issues and are therefore not allowed.
|
|
587
|
+
var initializer = update;
|
|
588
|
+
var prevProperty = property;
|
|
589
|
+
|
|
590
|
+
for (var i = 1; i < pathArrayLength; i++) {
|
|
591
|
+
var pathItem = pathArray[i];
|
|
592
|
+
var isArrayIndex = Number.isFinite(_isString ? Number(pathItem) : pathItem);
|
|
593
|
+
initializer = initializer[prevProperty] = isArrayIndex ? [] : {};
|
|
594
|
+
prevProperty = pathItem;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Fill update with the `value` on `path`.
|
|
598
|
+
update = setByPath(update, pathArray, value, '/');
|
|
599
|
+
|
|
600
|
+
var baseAttributes = merge({}, this.attributes);
|
|
601
|
+
// if rewrite mode enabled, we replace value referenced by path with
|
|
602
|
+
// the new one (we don't merge).
|
|
603
|
+
options.rewrite && unsetByPath(baseAttributes, path, '/');
|
|
604
|
+
|
|
605
|
+
// Merge update with the model attributes.
|
|
606
|
+
var attributes = merge(baseAttributes, update);
|
|
607
|
+
// Finally, set the property to the updated attributes.
|
|
608
|
+
return this.set(property, attributes[property], options);
|
|
609
|
+
|
|
610
|
+
} else {
|
|
611
|
+
|
|
612
|
+
return getByPath(this.attributes, props, delim);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const options = value || {};
|
|
617
|
+
// Note: '' is not the path to the root. It's a path with an empty string i.e. { '': {}}.
|
|
618
|
+
options.propertyPath = null;
|
|
619
|
+
options.propertyValue = props;
|
|
620
|
+
options.propertyPathArray = [];
|
|
621
|
+
if (!('rewrite' in options)) {
|
|
622
|
+
options.rewrite = false;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Create a new object containing only the changed attributes.
|
|
626
|
+
const changedAttributes = {};
|
|
627
|
+
for (const key in props) {
|
|
628
|
+
// Merging the values of changed attributes with the current ones.
|
|
629
|
+
const { changedValue } = merge({}, { changedValue: this.attributes[key] }, { changedValue: props[key] });
|
|
630
|
+
changedAttributes[key] = changedValue;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return this.set(changedAttributes, options);
|
|
634
|
+
},
|
|
635
|
+
|
|
636
|
+
// A convenient way to unset nested properties
|
|
637
|
+
removeProp: function(path, opt) {
|
|
638
|
+
|
|
639
|
+
opt = opt || {};
|
|
640
|
+
|
|
641
|
+
var pathArray = Array.isArray(path) ? path : path.split('/');
|
|
642
|
+
|
|
643
|
+
// Once a property is removed from the `attrs` attribute
|
|
644
|
+
// the cellView will recognize a `dirty` flag and re-render itself
|
|
645
|
+
// in order to remove the attribute from SVG element.
|
|
646
|
+
var property = pathArray[0];
|
|
647
|
+
if (property === 'attrs') opt.dirty = true;
|
|
648
|
+
|
|
649
|
+
if (pathArray.length === 1) {
|
|
650
|
+
// A top level property
|
|
651
|
+
return this.unset(path, opt);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// A nested property
|
|
655
|
+
var nestedPath = pathArray.slice(1);
|
|
656
|
+
var propertyValue = this.get(property);
|
|
657
|
+
if (propertyValue === undefined || propertyValue === null) return this;
|
|
658
|
+
propertyValue = cloneDeep(propertyValue);
|
|
659
|
+
|
|
660
|
+
unsetByPath(propertyValue, nestedPath, '/');
|
|
661
|
+
|
|
662
|
+
return this.set(property, propertyValue, opt);
|
|
663
|
+
},
|
|
664
|
+
|
|
665
|
+
// A convenient way to set nested attributes.
|
|
666
|
+
attr: function(attrs, value, opt) {
|
|
667
|
+
|
|
668
|
+
var args = Array.from(arguments);
|
|
669
|
+
if (args.length === 0) {
|
|
670
|
+
return this.get('attrs');
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (Array.isArray(attrs)) {
|
|
674
|
+
args[0] = ['attrs'].concat(attrs);
|
|
675
|
+
} else if (isString(attrs)) {
|
|
676
|
+
// Get/set an attribute by a special path syntax that delimits
|
|
677
|
+
// nested objects by the colon character.
|
|
678
|
+
args[0] = 'attrs/' + attrs;
|
|
679
|
+
|
|
680
|
+
} else {
|
|
681
|
+
|
|
682
|
+
args[0] = { 'attrs' : attrs };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return this.prop.apply(this, args);
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
// A convenient way to unset nested attributes
|
|
689
|
+
removeAttr: function(path, opt) {
|
|
690
|
+
|
|
691
|
+
if (Array.isArray(path)) {
|
|
692
|
+
|
|
693
|
+
return this.removeProp(['attrs'].concat(path));
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return this.removeProp('attrs/' + path, opt);
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
transition: function(path, value, opt, delim) {
|
|
700
|
+
|
|
701
|
+
delim = delim || '/';
|
|
702
|
+
|
|
703
|
+
var defaults = {
|
|
704
|
+
duration: 100,
|
|
705
|
+
delay: 10,
|
|
706
|
+
timingFunction: timing.linear,
|
|
707
|
+
valueFunction: interpolate.number
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
opt = assign(defaults, opt);
|
|
711
|
+
|
|
712
|
+
var firstFrameTime = 0;
|
|
713
|
+
var interpolatingFunction;
|
|
714
|
+
|
|
715
|
+
var setter = function(runtime) {
|
|
716
|
+
|
|
717
|
+
var id, progress, propertyValue;
|
|
718
|
+
|
|
719
|
+
firstFrameTime = firstFrameTime || runtime;
|
|
720
|
+
runtime -= firstFrameTime;
|
|
721
|
+
progress = runtime / opt.duration;
|
|
722
|
+
|
|
723
|
+
if (progress < 1) {
|
|
724
|
+
this._transitionIds[path] = id = nextFrame(setter);
|
|
725
|
+
} else {
|
|
726
|
+
progress = 1;
|
|
727
|
+
delete this._transitionIds[path];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
propertyValue = interpolatingFunction(opt.timingFunction(progress));
|
|
731
|
+
|
|
732
|
+
opt.transitionId = id;
|
|
733
|
+
|
|
734
|
+
this.prop(path, propertyValue, opt);
|
|
735
|
+
|
|
736
|
+
if (!id) this.trigger('transition:end', this, path);
|
|
737
|
+
|
|
738
|
+
}.bind(this);
|
|
739
|
+
|
|
740
|
+
const { _scheduledTransitionIds } = this;
|
|
741
|
+
let initialId;
|
|
742
|
+
|
|
743
|
+
var initiator = (callback) => {
|
|
744
|
+
|
|
745
|
+
if (_scheduledTransitionIds[path]) {
|
|
746
|
+
_scheduledTransitionIds[path] = without(_scheduledTransitionIds[path], initialId);
|
|
747
|
+
if (_scheduledTransitionIds[path].length === 0) {
|
|
748
|
+
delete _scheduledTransitionIds[path];
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
this.stopPendingTransitions(path, delim);
|
|
753
|
+
|
|
754
|
+
interpolatingFunction = opt.valueFunction(getByPath(this.attributes, path, delim), value);
|
|
755
|
+
|
|
756
|
+
this._transitionIds[path] = nextFrame(callback);
|
|
757
|
+
|
|
758
|
+
this.trigger('transition:start', this, path);
|
|
759
|
+
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
initialId = setTimeout(initiator, opt.delay, setter);
|
|
763
|
+
|
|
764
|
+
_scheduledTransitionIds[path] || (_scheduledTransitionIds[path] = []);
|
|
765
|
+
_scheduledTransitionIds[path].push(initialId);
|
|
766
|
+
|
|
767
|
+
return initialId;
|
|
768
|
+
},
|
|
769
|
+
|
|
770
|
+
getTransitions: function() {
|
|
771
|
+
return union(
|
|
772
|
+
Object.keys(this._transitionIds),
|
|
773
|
+
Object.keys(this._scheduledTransitionIds)
|
|
774
|
+
);
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
stopScheduledTransitions: function(path, delim = '/') {
|
|
778
|
+
const { _scheduledTransitionIds = {}} = this;
|
|
779
|
+
let transitions = Object.keys(_scheduledTransitionIds);
|
|
780
|
+
if (path) {
|
|
781
|
+
const pathArray = path.split(delim);
|
|
782
|
+
transitions = transitions.filter((key) => {
|
|
783
|
+
return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
transitions.forEach((key) => {
|
|
787
|
+
const transitionIds = _scheduledTransitionIds[key];
|
|
788
|
+
// stop the initiator
|
|
789
|
+
transitionIds.forEach(transitionId => clearTimeout(transitionId));
|
|
790
|
+
delete _scheduledTransitionIds[key];
|
|
791
|
+
// Note: we could trigger transition:cancel` event here
|
|
792
|
+
});
|
|
793
|
+
return this;
|
|
794
|
+
},
|
|
795
|
+
|
|
796
|
+
stopPendingTransitions(path, delim = '/') {
|
|
797
|
+
const { _transitionIds = {}} = this;
|
|
798
|
+
let transitions = Object.keys(_transitionIds);
|
|
799
|
+
if (path) {
|
|
800
|
+
const pathArray = path.split(delim);
|
|
801
|
+
transitions = transitions.filter((key) => {
|
|
802
|
+
return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
transitions.forEach((key) => {
|
|
806
|
+
const transitionId = _transitionIds[key];
|
|
807
|
+
// stop the setter
|
|
808
|
+
cancelFrame(transitionId);
|
|
809
|
+
delete _transitionIds[key];
|
|
810
|
+
this.trigger('transition:end', this, key);
|
|
811
|
+
});
|
|
812
|
+
},
|
|
813
|
+
|
|
814
|
+
stopTransitions: function(path, delim = '/') {
|
|
815
|
+
this.stopScheduledTransitions(path, delim);
|
|
816
|
+
this.stopPendingTransitions(path, delim);
|
|
817
|
+
return this;
|
|
818
|
+
},
|
|
819
|
+
|
|
820
|
+
// A shorcut making it easy to create constructs like the following:
|
|
821
|
+
// `var el = (new joint.shapes.standard.Rectangle()).addTo(graph)`.
|
|
822
|
+
addTo: function(graph, opt) {
|
|
823
|
+
|
|
824
|
+
graph.addCell(this, opt);
|
|
825
|
+
return this;
|
|
826
|
+
},
|
|
827
|
+
|
|
828
|
+
// A shortcut for an equivalent call: `paper.findViewByModel(cell)`
|
|
829
|
+
// making it easy to create constructs like the following:
|
|
830
|
+
// `cell.findView(paper).highlight()`
|
|
831
|
+
findView: function(paper) {
|
|
832
|
+
|
|
833
|
+
return paper.findViewByModel(this);
|
|
834
|
+
},
|
|
835
|
+
|
|
836
|
+
isElement: function() {
|
|
837
|
+
|
|
838
|
+
return false;
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
isLink: function() {
|
|
842
|
+
|
|
843
|
+
return false;
|
|
844
|
+
},
|
|
845
|
+
|
|
846
|
+
startBatch: function(name, opt) {
|
|
847
|
+
|
|
848
|
+
if (this.graph) { this.graph.startBatch(name, assign({}, opt, { cell: this })); }
|
|
849
|
+
return this;
|
|
850
|
+
},
|
|
851
|
+
|
|
852
|
+
stopBatch: function(name, opt) {
|
|
853
|
+
|
|
854
|
+
if (this.graph) { this.graph.stopBatch(name, assign({}, opt, { cell: this })); }
|
|
855
|
+
return this;
|
|
856
|
+
},
|
|
857
|
+
|
|
858
|
+
getChangeFlag: function(attributes) {
|
|
859
|
+
|
|
860
|
+
var flag = 0;
|
|
861
|
+
if (!attributes) return flag;
|
|
862
|
+
for (var key in attributes) {
|
|
863
|
+
if (!attributes.hasOwnProperty(key) || !this.hasChanged(key)) continue;
|
|
864
|
+
flag |= attributes[key];
|
|
865
|
+
}
|
|
866
|
+
return flag;
|
|
867
|
+
},
|
|
868
|
+
|
|
869
|
+
angle: function() {
|
|
870
|
+
|
|
871
|
+
// To be overridden.
|
|
872
|
+
return 0;
|
|
873
|
+
},
|
|
874
|
+
|
|
875
|
+
position: function() {
|
|
876
|
+
|
|
877
|
+
// To be overridden.
|
|
878
|
+
return new g.Point(0, 0);
|
|
879
|
+
},
|
|
880
|
+
|
|
881
|
+
z: function() {
|
|
882
|
+
return this.get('z') || 0;
|
|
883
|
+
},
|
|
884
|
+
|
|
885
|
+
getPointFromConnectedLink: function() {
|
|
886
|
+
|
|
887
|
+
// To be overridden
|
|
888
|
+
return new g.Point();
|
|
889
|
+
},
|
|
890
|
+
|
|
891
|
+
getBBox: function() {
|
|
892
|
+
|
|
893
|
+
// To be overridden
|
|
894
|
+
return new g.Rect(0, 0, 0, 0);
|
|
895
|
+
},
|
|
896
|
+
|
|
897
|
+
getPointRotatedAroundCenter(angle, x, y) {
|
|
898
|
+
const point = new g.Point(x, y);
|
|
899
|
+
if (angle) point.rotate(this.getBBox().center(), angle);
|
|
900
|
+
return point;
|
|
901
|
+
},
|
|
902
|
+
|
|
903
|
+
getAbsolutePointFromRelative(x, y) {
|
|
904
|
+
// Rotate the position to take the model angle into account
|
|
905
|
+
return this.getPointRotatedAroundCenter(
|
|
906
|
+
-this.angle(),
|
|
907
|
+
// Transform the relative position to absolute
|
|
908
|
+
this.position().offset(x, y)
|
|
909
|
+
);
|
|
910
|
+
},
|
|
911
|
+
|
|
912
|
+
getRelativePointFromAbsolute(x, y) {
|
|
913
|
+
return this
|
|
914
|
+
// Rotate the coordinates to mitigate the element's rotation.
|
|
915
|
+
.getPointRotatedAroundCenter(this.angle(), x, y)
|
|
916
|
+
// Transform the absolute position into relative
|
|
917
|
+
.difference(this.position());
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
}, {
|
|
921
|
+
|
|
922
|
+
getAttributeDefinition: function(attrName) {
|
|
923
|
+
|
|
924
|
+
var defNS = this.attributes;
|
|
925
|
+
var globalDefNS = attributes;
|
|
926
|
+
return (defNS && defNS[attrName]) || globalDefNS[attrName];
|
|
927
|
+
},
|
|
928
|
+
|
|
929
|
+
define: function(type, defaults, protoProps, staticProps) {
|
|
930
|
+
|
|
931
|
+
protoProps = assign({
|
|
932
|
+
defaults: defaultsDeep({ type: type }, defaults, this.prototype.defaults)
|
|
933
|
+
}, protoProps);
|
|
934
|
+
|
|
935
|
+
var Cell = this.extend(protoProps, staticProps);
|
|
936
|
+
// es5 backward compatibility
|
|
937
|
+
/* eslint-disable no-undef */
|
|
938
|
+
if (typeof joint !== 'undefined' && has(joint, 'shapes')) {
|
|
939
|
+
setByPath(joint.shapes, type, Cell, '.');
|
|
940
|
+
}
|
|
941
|
+
/* eslint-enable no-undef */
|
|
942
|
+
return Cell;
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
|