@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.
Files changed (139) hide show
  1. package/LICENSE +376 -0
  2. package/README.md +49 -0
  3. package/dist/geometry.js +6486 -0
  4. package/dist/geometry.min.js +8 -0
  5. package/dist/joint.d.ts +5536 -0
  6. package/dist/joint.js +39629 -0
  7. package/dist/joint.min.js +8 -0
  8. package/dist/joint.nowrap.js +39626 -0
  9. package/dist/joint.nowrap.min.js +8 -0
  10. package/dist/vectorizer.js +9135 -0
  11. package/dist/vectorizer.min.js +8 -0
  12. package/dist/version.mjs +3 -0
  13. package/index.js +3 -0
  14. package/joint.mjs +27 -0
  15. package/package.json +192 -0
  16. package/src/V/annotation.mjs +0 -0
  17. package/src/V/index.mjs +2642 -0
  18. package/src/anchors/index.mjs +123 -0
  19. package/src/config/index.mjs +12 -0
  20. package/src/connectionPoints/index.mjs +202 -0
  21. package/src/connectionStrategies/index.mjs +73 -0
  22. package/src/connectors/curve.mjs +553 -0
  23. package/src/connectors/index.mjs +6 -0
  24. package/src/connectors/jumpover.mjs +452 -0
  25. package/src/connectors/normal.mjs +12 -0
  26. package/src/connectors/rounded.mjs +17 -0
  27. package/src/connectors/smooth.mjs +44 -0
  28. package/src/connectors/straight.mjs +110 -0
  29. package/src/dia/Cell.mjs +945 -0
  30. package/src/dia/CellView.mjs +1316 -0
  31. package/src/dia/Element.mjs +519 -0
  32. package/src/dia/ElementView.mjs +859 -0
  33. package/src/dia/Graph.mjs +1112 -0
  34. package/src/dia/HighlighterView.mjs +319 -0
  35. package/src/dia/Link.mjs +565 -0
  36. package/src/dia/LinkView.mjs +2207 -0
  37. package/src/dia/Paper.mjs +3171 -0
  38. package/src/dia/PaperLayer.mjs +75 -0
  39. package/src/dia/ToolView.mjs +69 -0
  40. package/src/dia/ToolsView.mjs +128 -0
  41. package/src/dia/attributes/calc.mjs +128 -0
  42. package/src/dia/attributes/connection.mjs +75 -0
  43. package/src/dia/attributes/defs.mjs +76 -0
  44. package/src/dia/attributes/eval.mjs +64 -0
  45. package/src/dia/attributes/index.mjs +69 -0
  46. package/src/dia/attributes/legacy.mjs +148 -0
  47. package/src/dia/attributes/offset.mjs +53 -0
  48. package/src/dia/attributes/props.mjs +30 -0
  49. package/src/dia/attributes/shape.mjs +92 -0
  50. package/src/dia/attributes/text.mjs +180 -0
  51. package/src/dia/index.mjs +13 -0
  52. package/src/dia/layers/GridLayer.mjs +176 -0
  53. package/src/dia/ports.mjs +874 -0
  54. package/src/elementTools/Control.mjs +153 -0
  55. package/src/elementTools/HoverConnect.mjs +37 -0
  56. package/src/elementTools/index.mjs +5 -0
  57. package/src/env/index.mjs +43 -0
  58. package/src/g/bezier.mjs +175 -0
  59. package/src/g/curve.mjs +956 -0
  60. package/src/g/ellipse.mjs +245 -0
  61. package/src/g/extend.mjs +64 -0
  62. package/src/g/geometry.helpers.mjs +58 -0
  63. package/src/g/index.mjs +17 -0
  64. package/src/g/intersection.mjs +511 -0
  65. package/src/g/line.bearing.mjs +30 -0
  66. package/src/g/line.length.mjs +5 -0
  67. package/src/g/line.mjs +356 -0
  68. package/src/g/line.squaredLength.mjs +10 -0
  69. package/src/g/path.mjs +2260 -0
  70. package/src/g/point.mjs +375 -0
  71. package/src/g/points.mjs +247 -0
  72. package/src/g/polygon.mjs +51 -0
  73. package/src/g/polyline.mjs +523 -0
  74. package/src/g/rect.mjs +556 -0
  75. package/src/g/types.mjs +10 -0
  76. package/src/highlighters/addClass.mjs +27 -0
  77. package/src/highlighters/index.mjs +5 -0
  78. package/src/highlighters/list.mjs +111 -0
  79. package/src/highlighters/mask.mjs +220 -0
  80. package/src/highlighters/opacity.mjs +17 -0
  81. package/src/highlighters/stroke.mjs +100 -0
  82. package/src/layout/index.mjs +4 -0
  83. package/src/layout/ports/port.mjs +188 -0
  84. package/src/layout/ports/portLabel.mjs +224 -0
  85. package/src/linkAnchors/index.mjs +76 -0
  86. package/src/linkTools/Anchor.mjs +235 -0
  87. package/src/linkTools/Arrowhead.mjs +103 -0
  88. package/src/linkTools/Boundary.mjs +48 -0
  89. package/src/linkTools/Button.mjs +121 -0
  90. package/src/linkTools/Connect.mjs +85 -0
  91. package/src/linkTools/HoverConnect.mjs +161 -0
  92. package/src/linkTools/Segments.mjs +393 -0
  93. package/src/linkTools/Vertices.mjs +253 -0
  94. package/src/linkTools/helpers.mjs +33 -0
  95. package/src/linkTools/index.mjs +8 -0
  96. package/src/mvc/Collection.mjs +560 -0
  97. package/src/mvc/Data.mjs +46 -0
  98. package/src/mvc/Dom/Dom.mjs +587 -0
  99. package/src/mvc/Dom/Event.mjs +130 -0
  100. package/src/mvc/Dom/animations.mjs +122 -0
  101. package/src/mvc/Dom/events.mjs +69 -0
  102. package/src/mvc/Dom/index.mjs +13 -0
  103. package/src/mvc/Dom/methods.mjs +392 -0
  104. package/src/mvc/Dom/props.mjs +77 -0
  105. package/src/mvc/Dom/vars.mjs +5 -0
  106. package/src/mvc/Events.mjs +337 -0
  107. package/src/mvc/Listener.mjs +33 -0
  108. package/src/mvc/Model.mjs +239 -0
  109. package/src/mvc/View.mjs +323 -0
  110. package/src/mvc/ViewBase.mjs +182 -0
  111. package/src/mvc/index.mjs +9 -0
  112. package/src/mvc/mvcUtils.mjs +90 -0
  113. package/src/polyfills/array.js +4 -0
  114. package/src/polyfills/base64.js +68 -0
  115. package/src/polyfills/index.mjs +5 -0
  116. package/src/polyfills/number.js +3 -0
  117. package/src/polyfills/string.js +3 -0
  118. package/src/polyfills/typedArray.js +47 -0
  119. package/src/routers/index.mjs +6 -0
  120. package/src/routers/manhattan.mjs +856 -0
  121. package/src/routers/metro.mjs +91 -0
  122. package/src/routers/normal.mjs +6 -0
  123. package/src/routers/oneSide.mjs +60 -0
  124. package/src/routers/orthogonal.mjs +323 -0
  125. package/src/routers/rightAngle.mjs +1056 -0
  126. package/src/shapes/index.mjs +3 -0
  127. package/src/shapes/standard.mjs +755 -0
  128. package/src/util/cloneCells.mjs +67 -0
  129. package/src/util/getRectPoint.mjs +65 -0
  130. package/src/util/index.mjs +5 -0
  131. package/src/util/svgTagTemplate.mjs +110 -0
  132. package/src/util/util.mjs +1754 -0
  133. package/src/util/utilHelpers.mjs +2402 -0
  134. package/src/util/wrappers.mjs +56 -0
  135. package/types/geometry.d.ts +815 -0
  136. package/types/index.d.ts +53 -0
  137. package/types/joint.d.ts +4391 -0
  138. package/types/joint.head.d.ts +12 -0
  139. package/types/vectorizer.d.ts +327 -0
@@ -0,0 +1,874 @@
1
+ import * as util from '../util/index.mjs';
2
+ import V from '../V/index.mjs';
3
+ import { Rect, Point } from '../g/index.mjs';
4
+ import * as Port from '../layout/ports/port.mjs';
5
+ import * as PortLabel from '../layout/ports/portLabel.mjs';
6
+
7
+ var PortData = function(data) {
8
+
9
+ var clonedData = util.cloneDeep(data) || {};
10
+ this.ports = [];
11
+ this.groups = {};
12
+ this.portLayoutNamespace = Port;
13
+ this.portLabelLayoutNamespace = PortLabel;
14
+
15
+ this._init(clonedData);
16
+ };
17
+
18
+ PortData.prototype = {
19
+
20
+ getPorts: function() {
21
+ return this.ports;
22
+ },
23
+
24
+ getGroup: function(name) {
25
+ return this.groups[name] || {};
26
+ },
27
+
28
+ getPortsByGroup: function(groupName) {
29
+
30
+ return this.ports.filter(function(port) {
31
+ return port.group === groupName;
32
+ });
33
+ },
34
+
35
+ getGroupPortsMetrics: function(groupName, elBBox) {
36
+
37
+ var group = this.getGroup(groupName);
38
+ var ports = this.getPortsByGroup(groupName);
39
+
40
+ var groupPosition = group.position || {};
41
+ var groupPositionName = groupPosition.name;
42
+ var namespace = this.portLayoutNamespace;
43
+ if (!namespace[groupPositionName]) {
44
+ groupPositionName = 'left';
45
+ }
46
+
47
+ var groupArgs = groupPosition.args || {};
48
+ var portsArgs = ports.map(function(port) {
49
+ return port && port.position && port.position.args;
50
+ });
51
+ var groupPortTransformations = namespace[groupPositionName](portsArgs, elBBox, groupArgs);
52
+
53
+ var accumulator = {
54
+ ports: ports,
55
+ result: []
56
+ };
57
+
58
+ util.toArray(groupPortTransformations).reduce(function(res, portTransformation, index) {
59
+ var port = res.ports[index];
60
+ res.result.push({
61
+ portId: port.id,
62
+ portTransformation: portTransformation,
63
+ labelTransformation: this._getPortLabelLayout(port, Point(portTransformation), elBBox),
64
+ portAttrs: port.attrs,
65
+ portSize: port.size,
66
+ labelSize: port.label.size
67
+ });
68
+ return res;
69
+ }.bind(this), accumulator);
70
+
71
+ return accumulator.result;
72
+ },
73
+
74
+ _getPortLabelLayout: function(port, portPosition, elBBox) {
75
+
76
+ var namespace = this.portLabelLayoutNamespace;
77
+ var labelPosition = port.label.position.name || 'left';
78
+
79
+ if (namespace[labelPosition]) {
80
+ return namespace[labelPosition](portPosition, elBBox, port.label.position.args);
81
+ }
82
+
83
+ return null;
84
+ },
85
+
86
+ _init: function(data) {
87
+
88
+ // prepare groups
89
+ if (util.isObject(data.groups)) {
90
+ var groups = Object.keys(data.groups);
91
+ for (var i = 0, n = groups.length; i < n; i++) {
92
+ var key = groups[i];
93
+ this.groups[key] = this._evaluateGroup(data.groups[key]);
94
+ }
95
+ }
96
+
97
+ // prepare ports
98
+ var ports = util.toArray(data.items);
99
+ for (var j = 0, m = ports.length; j < m; j++) {
100
+ this.ports.push(this._evaluatePort(ports[j]));
101
+ }
102
+ },
103
+
104
+ _evaluateGroup: function(group) {
105
+
106
+ return util.merge(group, {
107
+ position: this._getPosition(group.position, true),
108
+ label: this._getLabel(group, true)
109
+ });
110
+ },
111
+
112
+ _evaluatePort: function(port) {
113
+
114
+ var evaluated = util.assign({}, port);
115
+
116
+ var group = this.getGroup(port.group);
117
+
118
+ evaluated.markup = evaluated.markup || group.markup;
119
+ evaluated.attrs = util.merge({}, group.attrs, evaluated.attrs);
120
+ evaluated.position = this._createPositionNode(group, evaluated);
121
+ evaluated.label = util.merge({}, group.label, this._getLabel(evaluated));
122
+ evaluated.z = this._getZIndex(group, evaluated);
123
+ evaluated.size = util.assign({}, group.size, evaluated.size);
124
+
125
+ return evaluated;
126
+ },
127
+
128
+ _getZIndex: function(group, port) {
129
+
130
+ if (util.isNumber(port.z)) {
131
+ return port.z;
132
+ }
133
+ if (util.isNumber(group.z) || group.z === 'auto') {
134
+ return group.z;
135
+ }
136
+ return 'auto';
137
+ },
138
+
139
+ _createPositionNode: function(group, port) {
140
+
141
+ return util.merge({
142
+ name: 'left',
143
+ args: {}
144
+ }, group.position, { args: port.args });
145
+ },
146
+
147
+ _getPosition: function(position, setDefault) {
148
+
149
+ var args = {};
150
+ var positionName;
151
+
152
+ if (util.isFunction(position)) {
153
+ positionName = 'fn';
154
+ args.fn = position;
155
+ } else if (util.isString(position)) {
156
+ positionName = position;
157
+ } else if (position === undefined) {
158
+ positionName = setDefault ? 'left' : null;
159
+ } else if (Array.isArray(position)) {
160
+ positionName = 'absolute';
161
+ args.x = position[0];
162
+ args.y = position[1];
163
+ } else if (util.isObject(position)) {
164
+ positionName = position.name;
165
+ util.assign(args, position.args);
166
+ }
167
+
168
+ var result = { args: args };
169
+
170
+ if (positionName) {
171
+ result.name = positionName;
172
+ }
173
+ return result;
174
+ },
175
+
176
+ _getLabel: function(item, setDefaults) {
177
+
178
+ var label = item.label || {};
179
+
180
+ var ret = label;
181
+ ret.position = this._getPosition(label.position, setDefaults);
182
+
183
+ return ret;
184
+ }
185
+ };
186
+
187
+ export const elementPortPrototype = {
188
+
189
+ _initializePorts: function() {
190
+
191
+ this._createPortData();
192
+ this.on('change:ports', function() {
193
+
194
+ this._processRemovedPort();
195
+ this._createPortData();
196
+ }, this);
197
+ },
198
+
199
+ /**
200
+ * remove links tied wiht just removed element
201
+ * @private
202
+ */
203
+ _processRemovedPort: function() {
204
+
205
+ var current = this.get('ports') || {};
206
+ var currentItemsMap = {};
207
+
208
+ util.toArray(current.items).forEach(function(item) {
209
+ currentItemsMap[item.id] = true;
210
+ });
211
+
212
+ var previous = this.previous('ports') || {};
213
+ var removed = {};
214
+
215
+ util.toArray(previous.items).forEach(function(item) {
216
+ if (!currentItemsMap[item.id]) {
217
+ removed[item.id] = true;
218
+ }
219
+ });
220
+
221
+ var graph = this.graph;
222
+ if (graph && !util.isEmpty(removed)) {
223
+
224
+ var inboundLinks = graph.getConnectedLinks(this, { inbound: true });
225
+ inboundLinks.forEach(function(link) {
226
+
227
+ if (removed[link.get('target').port]) link.remove();
228
+ });
229
+
230
+ var outboundLinks = graph.getConnectedLinks(this, { outbound: true });
231
+ outboundLinks.forEach(function(link) {
232
+
233
+ if (removed[link.get('source').port]) link.remove();
234
+ });
235
+ }
236
+ },
237
+
238
+ /**
239
+ * @returns {boolean}
240
+ */
241
+ hasPorts: function() {
242
+
243
+ var ports = this.prop('ports/items');
244
+ return Array.isArray(ports) && ports.length > 0;
245
+ },
246
+
247
+ /**
248
+ * @param {string} id
249
+ * @returns {boolean}
250
+ */
251
+ hasPort: function(id) {
252
+
253
+ return this.getPortIndex(id) !== -1;
254
+ },
255
+
256
+ /**
257
+ * @returns {Array<object>}
258
+ */
259
+ getPorts: function() {
260
+
261
+ return util.cloneDeep(this.prop('ports/items')) || [];
262
+ },
263
+
264
+ /**
265
+ * @returns {Array<object>}
266
+ */
267
+ getGroupPorts: function(groupName) {
268
+ const groupPorts = util.toArray(this.prop(['ports','items'])).filter(port => port.group === groupName);
269
+ return util.cloneDeep(groupPorts);
270
+ },
271
+
272
+ /**
273
+ * @param {string} id
274
+ * @returns {object}
275
+ */
276
+ getPort: function(id) {
277
+
278
+ return util.cloneDeep(util.toArray(this.prop('ports/items')).find(function(port) {
279
+ return port.id && port.id === id;
280
+ }));
281
+ },
282
+
283
+ /**
284
+ * @param {string} groupName
285
+ * @returns {Object<portId, {x: number, y: number, angle: number}>}
286
+ */
287
+ getPortsPositions: function(groupName) {
288
+
289
+ var portsMetrics = this._portSettingsData.getGroupPortsMetrics(groupName, Rect(this.size()));
290
+
291
+ return portsMetrics.reduce(function(positions, metrics) {
292
+ var transformation = metrics.portTransformation;
293
+ positions[metrics.portId] = {
294
+ x: transformation.x,
295
+ y: transformation.y,
296
+ angle: transformation.angle
297
+ };
298
+ return positions;
299
+ }, {});
300
+ },
301
+
302
+ /**
303
+ * @param {string|Port} port port id or port
304
+ * @returns {number} port index
305
+ */
306
+ getPortIndex: function(port) {
307
+
308
+ var id = util.isObject(port) ? port.id : port;
309
+
310
+ if (!this._isValidPortId(id)) {
311
+ return -1;
312
+ }
313
+
314
+ return util.toArray(this.prop('ports/items')).findIndex(function(item) {
315
+ return item.id === id;
316
+ });
317
+ },
318
+
319
+ /**
320
+ * @param {object} port
321
+ * @param {object} [opt]
322
+ * @returns {joint.dia.Element}
323
+ */
324
+ addPort: function(port, opt) {
325
+
326
+ if (!util.isObject(port) || Array.isArray(port)) {
327
+ throw new Error('Element: addPort requires an object.');
328
+ }
329
+
330
+ var ports = util.assign([], this.prop('ports/items'));
331
+ ports.push(port);
332
+ this.prop('ports/items', ports, opt);
333
+
334
+ return this;
335
+ },
336
+
337
+ /**
338
+ * @param {string|Port|number} before
339
+ * @param {object} port
340
+ * @param {object} [opt]
341
+ * @returns {joint.dia.Element}
342
+ */
343
+ insertPort: function(before, port, opt) {
344
+ const index = (typeof before === 'number') ? before : this.getPortIndex(before);
345
+
346
+ if (!util.isObject(port) || Array.isArray(port)) {
347
+ throw new Error('dia.Element: insertPort requires an object.');
348
+ }
349
+
350
+ const ports = util.assign([], this.prop('ports/items'));
351
+ ports.splice(index, 0, port);
352
+ this.prop('ports/items', ports, opt);
353
+
354
+ return this;
355
+ },
356
+
357
+ /**
358
+ * @param {string} portId
359
+ * @param {string|object=} path
360
+ * @param {*=} value
361
+ * @param {object=} opt
362
+ * @returns {joint.dia.Element}
363
+ */
364
+ portProp: function(portId, path, value, opt) {
365
+
366
+ var index = this.getPortIndex(portId);
367
+
368
+ if (index === -1) {
369
+ throw new Error('Element: unable to find port with id ' + portId);
370
+ }
371
+
372
+ var args = Array.prototype.slice.call(arguments, 1);
373
+ if (Array.isArray(path)) {
374
+ args[0] = ['ports', 'items', index].concat(path);
375
+ } else if (util.isString(path)) {
376
+
377
+ // Get/set an attribute by a special path syntax that delimits
378
+ // nested objects by the colon character.
379
+ args[0] = ['ports/items/', index, '/', path].join('');
380
+
381
+ } else {
382
+
383
+ args = ['ports/items/' + index];
384
+ if (util.isPlainObject(path)) {
385
+ args.push(path);
386
+ args.push(value);
387
+ }
388
+ }
389
+
390
+ return this.prop.apply(this, args);
391
+ },
392
+
393
+ _validatePorts: function() {
394
+
395
+ var portsAttr = this.get('ports') || {};
396
+
397
+ var errorMessages = [];
398
+ portsAttr = portsAttr || {};
399
+ var ports = util.toArray(portsAttr.items);
400
+
401
+ ports.forEach(function(p) {
402
+
403
+ if (typeof p !== 'object') {
404
+ errorMessages.push('Element: invalid port ', p);
405
+ }
406
+
407
+ if (!this._isValidPortId(p.id)) {
408
+ p.id = this.generatePortId();
409
+ }
410
+ }, this);
411
+
412
+ if (util.uniq(ports, 'id').length !== ports.length) {
413
+ errorMessages.push('Element: found id duplicities in ports.');
414
+ }
415
+
416
+ return errorMessages;
417
+ },
418
+
419
+ generatePortId: function() {
420
+ return this.generateId();
421
+ },
422
+
423
+ /**
424
+ * @param {string} id port id
425
+ * @returns {boolean}
426
+ * @private
427
+ */
428
+ _isValidPortId: function(id) {
429
+
430
+ return id !== null && id !== undefined && !util.isObject(id);
431
+ },
432
+
433
+ addPorts: function(ports, opt) {
434
+
435
+ if (ports.length) {
436
+ this.prop('ports/items', util.assign([], this.prop('ports/items')).concat(ports), opt);
437
+ }
438
+
439
+ return this;
440
+ },
441
+
442
+ removePort: function(port, opt) {
443
+ const options = opt || {};
444
+ const index = this.getPortIndex(port);
445
+ if (index !== -1) {
446
+ const ports = util.assign([], this.prop(['ports', 'items']));
447
+ ports.splice(index, 1);
448
+ options.rewrite = true;
449
+ this.startBatch('port-remove');
450
+ this.prop(['ports', 'items'], ports, options);
451
+ this.stopBatch('port-remove');
452
+ }
453
+ return this;
454
+ },
455
+
456
+ removePorts: function(portsForRemoval, opt) {
457
+ let options, newPorts;
458
+ if (Array.isArray(portsForRemoval)) {
459
+ options = opt || {};
460
+ if (portsForRemoval.length === 0) return this.this;
461
+ const currentPorts = util.assign([], this.prop(['ports', 'items']));
462
+ newPorts = currentPorts.filter(function(cp) {
463
+ return !portsForRemoval.some(function(rp) {
464
+ const rpId = util.isObject(rp) ? rp.id : rp;
465
+ return cp.id === rpId;
466
+ });
467
+ });
468
+ } else {
469
+ options = portsForRemoval || {};
470
+ newPorts = [];
471
+ }
472
+ this.startBatch('port-remove');
473
+ options.rewrite = true;
474
+ this.prop(['ports', 'items'], newPorts, options);
475
+ this.stopBatch('port-remove');
476
+ return this;
477
+ },
478
+
479
+ /**
480
+ * @private
481
+ */
482
+ _createPortData: function() {
483
+
484
+ var err = this._validatePorts();
485
+
486
+ if (err.length > 0) {
487
+ this.set('ports', this.previous('ports'));
488
+ throw new Error(err.join(' '));
489
+ }
490
+
491
+ var prevPortData;
492
+
493
+ if (this._portSettingsData) {
494
+
495
+ prevPortData = this._portSettingsData.getPorts();
496
+ }
497
+
498
+ this._portSettingsData = new PortData(this.get('ports'));
499
+
500
+ var curPortData = this._portSettingsData.getPorts();
501
+
502
+ if (prevPortData) {
503
+
504
+ var added = curPortData.filter(function(item) {
505
+ if (!prevPortData.find(function(prevPort) {
506
+ return prevPort.id === item.id;
507
+ })) {
508
+ return item;
509
+ }
510
+ });
511
+
512
+ var removed = prevPortData.filter(function(item) {
513
+ if (!curPortData.find(function(curPort) {
514
+ return curPort.id === item.id;
515
+ })) {
516
+ return item;
517
+ }
518
+ });
519
+
520
+ if (removed.length > 0) {
521
+ this.trigger('ports:remove', this, removed);
522
+ }
523
+
524
+ if (added.length > 0) {
525
+ this.trigger('ports:add', this, added);
526
+ }
527
+ }
528
+ }
529
+ };
530
+
531
+ export const elementViewPortPrototype = {
532
+
533
+ portContainerMarkup: 'g',
534
+ portMarkup: [{
535
+ tagName: 'circle',
536
+ selector: 'circle',
537
+ attributes: {
538
+ 'r': 10,
539
+ 'fill': '#FFFFFF',
540
+ 'stroke': '#000000'
541
+ }
542
+ }],
543
+ portLabelMarkup: [{
544
+ tagName: 'text',
545
+ selector: 'text',
546
+ attributes: {
547
+ 'fill': '#000000'
548
+ }
549
+ }],
550
+ /** @type {Object<string, {portElement: Vectorizer, portLabelElement: Vectorizer}>} */
551
+ _portElementsCache: null,
552
+
553
+ /**
554
+ * @private
555
+ */
556
+ _initializePorts: function() {
557
+ this._cleanPortsCache();
558
+ },
559
+
560
+ /**
561
+ * @typedef {Object} Port
562
+ *
563
+ * @property {string} id
564
+ * @property {Object} position
565
+ * @property {Object} label
566
+ * @property {Object} attrs
567
+ * @property {string} markup
568
+ * @property {string} group
569
+ */
570
+
571
+ /**
572
+ * @private
573
+ */
574
+ _refreshPorts: function() {
575
+
576
+ this._removePorts();
577
+ this._cleanPortsCache();
578
+ this._renderPorts();
579
+ },
580
+
581
+ _cleanPortsCache: function() {
582
+ this._portElementsCache = {};
583
+ },
584
+
585
+ /**
586
+ * @private
587
+ */
588
+ _renderPorts: function() {
589
+
590
+ // references to rendered elements without z-index
591
+ var elementReferences = [];
592
+ var elem = this._getContainerElement();
593
+
594
+ for (var i = 0, count = elem.node.childNodes.length; i < count; i++) {
595
+ elementReferences.push(elem.node.childNodes[i]);
596
+ }
597
+
598
+ var portsGropsByZ = util.groupBy(this.model._portSettingsData.getPorts(), 'z');
599
+ var withoutZKey = 'auto';
600
+
601
+ // render non-z first
602
+ util.toArray(portsGropsByZ[withoutZKey]).forEach(function(port) {
603
+ var portElement = this._getPortElement(port);
604
+ elem.append(portElement);
605
+ elementReferences.push(portElement);
606
+ }, this);
607
+
608
+ var groupNames = Object.keys(portsGropsByZ);
609
+ for (var k = 0; k < groupNames.length; k++) {
610
+ var groupName = groupNames[k];
611
+ if (groupName !== withoutZKey) {
612
+ var z = parseInt(groupName, 10);
613
+ this._appendPorts(portsGropsByZ[groupName], z, elementReferences);
614
+ }
615
+ }
616
+
617
+ this._updatePorts();
618
+ },
619
+
620
+ /**
621
+ * @returns {V}
622
+ * @private
623
+ */
624
+ _getContainerElement: function() {
625
+
626
+ return this.rotatableNode || this.vel;
627
+ },
628
+
629
+ /**
630
+ * @param {Array<Port>}ports
631
+ * @param {number} z
632
+ * @param refs
633
+ * @private
634
+ */
635
+ _appendPorts: function(ports, z, refs) {
636
+
637
+ var containerElement = this._getContainerElement();
638
+ var portElements = util.toArray(ports).map(this._getPortElement, this);
639
+
640
+ if (refs[z] || z < 0) {
641
+ V(refs[Math.max(z, 0)]).before(portElements);
642
+ } else {
643
+ containerElement.append(portElements);
644
+ }
645
+ },
646
+
647
+ /**
648
+ * Try to get element from cache,
649
+ * @param port
650
+ * @returns {*}
651
+ * @private
652
+ */
653
+ _getPortElement: function(port) {
654
+
655
+ if (this._portElementsCache[port.id]) {
656
+ return this._portElementsCache[port.id].portElement;
657
+ }
658
+ return this._createPortElement(port);
659
+ },
660
+
661
+ findPortNodes: function(portId, selector) {
662
+ const portCache = this._portElementsCache[portId];
663
+ if (!portCache) return [];
664
+ if (!selector) return [portCache.portContentElement.node];
665
+ const portRoot = portCache.portElement.node;
666
+ const portSelectors = portCache.portSelectors;
667
+ return this.findBySelector(selector, portRoot, portSelectors);
668
+ },
669
+
670
+ findPortNode: function(portId, selector) {
671
+ const [node = null] = this.findPortNodes(portId, selector);
672
+ return node;
673
+ },
674
+
675
+ /**
676
+ * @private
677
+ */
678
+ _updatePorts: function() {
679
+
680
+ // layout ports without group
681
+ this._updatePortGroup(undefined);
682
+ // layout ports with explicit group
683
+ var groupsNames = Object.keys(this.model._portSettingsData.groups);
684
+ groupsNames.forEach(this._updatePortGroup, this);
685
+ },
686
+
687
+ /**
688
+ * @private
689
+ */
690
+ _removePorts: function() {
691
+ util.invoke(this._portElementsCache, 'portElement.remove');
692
+ },
693
+
694
+ /**
695
+ * @param {Port} port
696
+ * @returns {V}
697
+ * @private
698
+ */
699
+ _createPortElement: function(port) {
700
+
701
+ let portElement;
702
+ let labelElement;
703
+ let labelSelectors;
704
+ let portSelectors;
705
+
706
+ var portContainerElement = V(this.portContainerMarkup).addClass('joint-port');
707
+
708
+ var portMarkup = this._getPortMarkup(port);
709
+ if (Array.isArray(portMarkup)) {
710
+ var portDoc = this.parseDOMJSON(portMarkup, portContainerElement.node);
711
+ var portFragment = portDoc.fragment;
712
+ if (portFragment.childNodes.length > 1) {
713
+ portElement = V('g').append(portFragment);
714
+ } else {
715
+ portElement = V(portFragment.firstChild);
716
+ }
717
+ portSelectors = portDoc.selectors;
718
+ } else {
719
+ portElement = V(portMarkup);
720
+ if (Array.isArray(portElement)) {
721
+ portElement = V('g').append(portElement);
722
+ }
723
+ }
724
+
725
+ if (!portElement) {
726
+ throw new Error('ElementView: Invalid port markup.');
727
+ }
728
+
729
+ portElement.attr({
730
+ 'port': port.id,
731
+ 'port-group': port.group
732
+ });
733
+
734
+ const labelMarkupDef = this._getPortLabelMarkup(port.label);
735
+ if (Array.isArray(labelMarkupDef)) {
736
+ // JSON Markup
737
+ const { fragment, selectors } = this.parseDOMJSON(labelMarkupDef, portContainerElement.node);
738
+ const childCount = fragment.childNodes.length;
739
+ if (childCount > 0) {
740
+ labelSelectors = selectors;
741
+ labelElement = (childCount === 1) ? V(fragment.firstChild) : V('g').append(fragment);
742
+ }
743
+ } else {
744
+ // String Markup
745
+ labelElement = V(labelMarkupDef);
746
+ if (Array.isArray(labelElement)) {
747
+ labelElement = V('g').append(labelElement);
748
+ }
749
+ }
750
+
751
+ var portContainerSelectors;
752
+ if (portSelectors && labelSelectors) {
753
+ for (var key in labelSelectors) {
754
+ if (portSelectors[key] && key !== this.selector) throw new Error('ElementView: selectors within port must be unique.');
755
+ }
756
+ portContainerSelectors = util.assign({}, portSelectors, labelSelectors);
757
+ } else {
758
+ portContainerSelectors = portSelectors || labelSelectors || {};
759
+ }
760
+
761
+ // The `portRootSelector` points to the root SVGNode of the port.
762
+ // Either the implicit wrapping group <g/> in case the port consist of multiple SVGNodes.
763
+ // Or the single SVGNode of the port.
764
+ const portRootSelector = 'portRoot';
765
+ // The `labelRootSelector` points to the root SVGNode of the label.
766
+ const labelRootSelector = 'labelRoot';
767
+ // The `labelTextSelector` points to all text SVGNodes of the label.
768
+ const labelTextSelector = 'labelText';
769
+
770
+ if (!(portRootSelector in portContainerSelectors)) {
771
+ portContainerSelectors[portRootSelector] = portElement.node;
772
+ }
773
+
774
+ if (labelElement) {
775
+ const labelNode = labelElement.node;
776
+ if (!(labelRootSelector in portContainerSelectors)) {
777
+ portContainerSelectors[labelRootSelector] = labelNode;
778
+ }
779
+ if (!(labelTextSelector in portContainerSelectors)) {
780
+ // If the label is a <text> element, we can use it directly.
781
+ // Otherwise, we need to find the <text> element within the label.
782
+ const labelTextNode = (labelElement.tagName() === 'TEXT')
783
+ ? labelNode
784
+ : Array.from(labelNode.querySelectorAll('text'));
785
+ portContainerSelectors[labelTextSelector] = labelTextNode;
786
+ if (!labelSelectors) labelSelectors = {};
787
+ labelSelectors[labelTextSelector] = labelTextNode;
788
+ }
789
+ }
790
+
791
+ portContainerElement.append(portElement.addClass('joint-port-body'));
792
+ if (labelElement) {
793
+ portContainerElement.append(labelElement.addClass('joint-port-label'));
794
+ }
795
+
796
+ this._portElementsCache[port.id] = {
797
+ portElement: portContainerElement,
798
+ portLabelElement: labelElement,
799
+ portSelectors: portContainerSelectors,
800
+ portLabelSelectors: labelSelectors,
801
+ portContentElement: portElement,
802
+ portContentSelectors: portSelectors
803
+ };
804
+
805
+ return portContainerElement;
806
+ },
807
+
808
+ /**
809
+ * @param {string=} groupName
810
+ * @private
811
+ */
812
+ _updatePortGroup: function(groupName) {
813
+
814
+ var elementBBox = Rect(this.model.size());
815
+ var portsMetrics = this.model._portSettingsData.getGroupPortsMetrics(groupName, elementBBox);
816
+
817
+ for (var i = 0, n = portsMetrics.length; i < n; i++) {
818
+ var metrics = portsMetrics[i];
819
+ var portId = metrics.portId;
820
+ var cached = this._portElementsCache[portId] || {};
821
+ var portTransformation = metrics.portTransformation;
822
+ var labelTransformation = metrics.labelTransformation;
823
+ if (labelTransformation && cached.portLabelElement) {
824
+ this.updateDOMSubtreeAttributes(cached.portLabelElement.node, labelTransformation.attrs, {
825
+ rootBBox: new Rect(metrics.labelSize),
826
+ selectors: cached.portLabelSelectors
827
+ });
828
+ this.applyPortTransform(cached.portLabelElement, labelTransformation, (-portTransformation.angle || 0));
829
+ }
830
+ this.updateDOMSubtreeAttributes(cached.portElement.node, metrics.portAttrs, {
831
+ rootBBox: new Rect(metrics.portSize),
832
+ selectors: cached.portSelectors
833
+ });
834
+ this.applyPortTransform(cached.portElement, portTransformation);
835
+ }
836
+ },
837
+
838
+ /**
839
+ * @param {Vectorizer} element
840
+ * @param {{dx:number, dy:number, angle: number, attrs: Object, x:number: y:number}} transformData
841
+ * @param {number=} initialAngle
842
+ * @constructor
843
+ */
844
+ applyPortTransform: function(element, transformData, initialAngle) {
845
+
846
+ var matrix = V.createSVGMatrix()
847
+ .rotate(initialAngle || 0)
848
+ .translate(transformData.x || 0, transformData.y || 0)
849
+ .rotate(transformData.angle || 0);
850
+
851
+ element.transform(matrix, { absolute: true });
852
+ },
853
+
854
+ /**
855
+ * @param {Port} port
856
+ * @returns {string}
857
+ * @private
858
+ */
859
+ _getPortMarkup: function(port) {
860
+
861
+ return port.markup || this.model.get('portMarkup') || this.model.portMarkup || this.portMarkup;
862
+ },
863
+
864
+ /**
865
+ * @param {Object} label
866
+ * @returns {string}
867
+ * @private
868
+ */
869
+ _getPortLabelMarkup: function(label) {
870
+
871
+ return label.markup || this.model.get('portLabelMarkup') || this.model.portLabelMarkup || this.portLabelMarkup;
872
+ }
873
+ };
874
+