@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,220 @@
1
+ import V from '../V/index.mjs';
2
+ import { HighlighterView } from '../dia/HighlighterView.mjs';
3
+
4
+ const MASK_CLIP = 20;
5
+
6
+ function forEachDescendant(vel, fn) {
7
+ const descendants = vel.children();
8
+ while (descendants.length > 0) {
9
+ const descendant = descendants.shift();
10
+ if (fn(descendant)) {
11
+ descendants.push(...descendant.children());
12
+ }
13
+ }
14
+ }
15
+
16
+ export const mask = HighlighterView.extend({
17
+
18
+ tagName: 'rect',
19
+ className: 'highlight-mask',
20
+ attributes: {
21
+ 'pointer-events': 'none'
22
+ },
23
+
24
+ options: {
25
+ padding: 3,
26
+ maskClip: MASK_CLIP,
27
+ deep: false,
28
+ attrs: {
29
+ 'stroke': '#FEB663',
30
+ 'stroke-width': 3,
31
+ 'stroke-linecap': 'butt',
32
+ 'stroke-linejoin': 'miter',
33
+ }
34
+ },
35
+
36
+ VISIBLE: 'white',
37
+ INVISIBLE: 'black',
38
+
39
+ MASK_ROOT_ATTRIBUTE_BLACKLIST: [
40
+ 'marker-start',
41
+ 'marker-end',
42
+ 'marker-mid',
43
+ 'transform',
44
+ 'stroke-dasharray',
45
+ 'class',
46
+ ],
47
+
48
+ MASK_CHILD_ATTRIBUTE_BLACKLIST: [
49
+ 'stroke',
50
+ 'fill',
51
+ 'stroke-width',
52
+ 'stroke-opacity',
53
+ 'stroke-dasharray',
54
+ 'fill-opacity',
55
+ 'marker-start',
56
+ 'marker-end',
57
+ 'marker-mid',
58
+ 'class',
59
+ ],
60
+
61
+ // TODO: change the list to a function callback
62
+ MASK_REPLACE_TAGS: [
63
+ 'FOREIGNOBJECT',
64
+ 'IMAGE',
65
+ 'USE',
66
+ 'TEXT',
67
+ 'TSPAN',
68
+ 'TEXTPATH'
69
+ ],
70
+
71
+ // TODO: change the list to a function callback
72
+ MASK_REMOVE_TAGS: [
73
+ 'TEXT',
74
+ 'TSPAN',
75
+ 'TEXTPATH'
76
+ ],
77
+
78
+ transformMaskChild(cellView, childEl) {
79
+ const {
80
+ MASK_CHILD_ATTRIBUTE_BLACKLIST,
81
+ MASK_REPLACE_TAGS,
82
+ MASK_REMOVE_TAGS
83
+ } = this;
84
+ const childTagName = childEl.tagName();
85
+ // Do not include the element in the mask's image
86
+ if (!V.isSVGGraphicsElement(childEl) || MASK_REMOVE_TAGS.includes(childTagName)) {
87
+ childEl.remove();
88
+ return false;
89
+ }
90
+ // Replace the element with a rectangle
91
+ if (MASK_REPLACE_TAGS.includes(childTagName)) {
92
+ // Note: clone() method does not change the children ids
93
+ const originalChild = cellView.vel.findOne(`#${childEl.id}`);
94
+ if (originalChild) {
95
+ const { node: originalNode } = originalChild;
96
+ let childBBox = cellView.getNodeBoundingRect(originalNode);
97
+ if (cellView.model.isElement()) {
98
+ childBBox = V.transformRect(childBBox, cellView.getNodeMatrix(originalNode));
99
+ }
100
+ const replacement = V('rect', childBBox.toJSON());
101
+ const { x: ox, y: oy } = childBBox.center();
102
+ const { angle, cx = ox, cy = oy } = originalChild.rotate();
103
+ if (angle) replacement.rotate(angle, cx, cy);
104
+ // Note: it's not important to keep the same sibling index since all subnodes are filled
105
+ childEl.parent().append(replacement);
106
+ }
107
+ childEl.remove();
108
+ return false;
109
+ }
110
+ // Keep the element, but clean it from certain attributes
111
+ MASK_CHILD_ATTRIBUTE_BLACKLIST.forEach(attrName => {
112
+ if (attrName === 'fill' && childEl.attr('fill') === 'none') return;
113
+ childEl.removeAttr(attrName);
114
+ });
115
+ return true;
116
+ },
117
+
118
+ transformMaskRoot(_cellView, rootEl) {
119
+ const { MASK_ROOT_ATTRIBUTE_BLACKLIST } = this;
120
+ MASK_ROOT_ATTRIBUTE_BLACKLIST.forEach(attrName => {
121
+ rootEl.removeAttr(attrName);
122
+ });
123
+ },
124
+
125
+ getMaskShape(cellView, vel) {
126
+ const { options, MASK_REPLACE_TAGS } = this;
127
+ const { deep } = options;
128
+ const tagName = vel.tagName();
129
+ let maskRoot;
130
+ if (tagName === 'G') {
131
+ if (!deep) return null;
132
+ maskRoot = vel.clone();
133
+ forEachDescendant(maskRoot, maskChild => this.transformMaskChild(cellView, maskChild));
134
+ } else {
135
+ if (MASK_REPLACE_TAGS.includes(tagName)) return null;
136
+ maskRoot = vel.clone();
137
+ }
138
+ this.transformMaskRoot(cellView, maskRoot);
139
+ return maskRoot;
140
+ },
141
+
142
+ getMaskId() {
143
+ return `highlight-mask-${this.cid}`;
144
+ },
145
+
146
+ getMask(cellView, vNode) {
147
+
148
+ const { VISIBLE, INVISIBLE, options } = this;
149
+ const { padding, attrs } = options;
150
+ // support both `strokeWidth` and `stroke-width` attribute names
151
+ const strokeWidth = parseFloat(V('g').attr(attrs).attr('stroke-width'));
152
+ const hasNodeFill = vNode.attr('fill') !== 'none';
153
+ let magnetStrokeWidth = parseFloat(vNode.attr('stroke-width'));
154
+ if (isNaN(magnetStrokeWidth)) magnetStrokeWidth = 1;
155
+ // stroke of the invisible shape
156
+ const minStrokeWidth = magnetStrokeWidth + padding * 2;
157
+ // stroke of the visible shape
158
+ const maxStrokeWidth = minStrokeWidth + strokeWidth * 2;
159
+ let maskEl = this.getMaskShape(cellView, vNode);
160
+ if (!maskEl) {
161
+ const nodeBBox = cellView.getNodeBoundingRect(vNode.node);
162
+ // Make sure the rect is visible
163
+ nodeBBox.inflate(nodeBBox.width ? 0 : 0.5, nodeBBox.height ? 0 : 0.5);
164
+ maskEl = V('rect', nodeBBox.toJSON());
165
+ }
166
+ maskEl.attr(attrs);
167
+ return V('mask', {
168
+ 'id': this.getMaskId()
169
+ }).append([
170
+ maskEl.clone().attr({
171
+ 'fill': hasNodeFill ? VISIBLE : 'none',
172
+ 'stroke': VISIBLE,
173
+ 'stroke-width': maxStrokeWidth
174
+ }),
175
+ maskEl.clone().attr({
176
+ 'fill': hasNodeFill ? INVISIBLE : 'none',
177
+ 'stroke': INVISIBLE,
178
+ 'stroke-width': minStrokeWidth
179
+ })
180
+ ]);
181
+ },
182
+
183
+ removeMask(paper) {
184
+ const maskNode = paper.svg.getElementById(this.getMaskId());
185
+ if (maskNode) {
186
+ paper.defs.removeChild(maskNode);
187
+ }
188
+ },
189
+
190
+ addMask(paper, maskEl) {
191
+ paper.defs.appendChild(maskEl.node);
192
+ },
193
+
194
+ highlight(cellView, node) {
195
+ const { options, vel } = this;
196
+ const { padding, attrs, maskClip = MASK_CLIP, layer } = options;
197
+ const color = ('stroke' in attrs) ? attrs['stroke'] : '#000000';
198
+ if (!layer && node === cellView.el) {
199
+ // If the highlighter is appended to the cellView
200
+ // and we measure the size of the cellView wrapping group
201
+ // it's necessary to remove the highlighter first
202
+ vel.remove();
203
+ }
204
+ const highlighterBBox = cellView.getNodeBoundingRect(node).inflate(padding + maskClip);
205
+ const highlightMatrix = this.getNodeMatrix(cellView, node);
206
+ const maskEl = this.getMask(cellView, V(node));
207
+ this.addMask(cellView.paper, maskEl);
208
+ vel.attr(highlighterBBox.toJSON());
209
+ vel.attr({
210
+ 'transform': V.matrixToTransformString(highlightMatrix),
211
+ 'mask': `url(#${maskEl.id})`,
212
+ 'fill': color
213
+ });
214
+ },
215
+
216
+ unhighlight(cellView) {
217
+ this.removeMask(cellView.paper);
218
+ }
219
+
220
+ });
@@ -0,0 +1,17 @@
1
+ import { HighlighterView } from '../dia/HighlighterView.mjs';
2
+
3
+ export const opacity = HighlighterView.extend({
4
+
5
+ UPDATABLE: false,
6
+ MOUNTABLE: false,
7
+
8
+ highlight: function(_cellView, node) {
9
+ const { alphaValue = 0.3 } = this.options;
10
+ node.style.opacity = alphaValue;
11
+ },
12
+
13
+ unhighlight: function(_cellView, node) {
14
+ node.style.opacity = '';
15
+ }
16
+
17
+ });
@@ -0,0 +1,100 @@
1
+ import { assign } from '../util/index.mjs';
2
+ import V from '../V/index.mjs';
3
+ import { HighlighterView } from '../dia/HighlighterView.mjs';
4
+
5
+ export const stroke = HighlighterView.extend({
6
+
7
+ tagName: 'path',
8
+ className: 'highlight-stroke',
9
+ attributes: {
10
+ 'pointer-events': 'none',
11
+ 'fill': 'none'
12
+ },
13
+
14
+ options: {
15
+ padding: 3,
16
+ rx: 0,
17
+ ry: 0,
18
+ useFirstSubpath: false,
19
+ attrs: {
20
+ 'stroke-width': 3,
21
+ 'stroke': '#FEB663'
22
+ }
23
+ },
24
+
25
+ getPathData(cellView, node) {
26
+ const { options } = this;
27
+ const { useFirstSubpath } = options;
28
+ let d;
29
+ try {
30
+ const vNode = V(node);
31
+ d = vNode.convertToPathData().trim();
32
+ if (vNode.tagName() === 'PATH' && useFirstSubpath) {
33
+ const secondSubpathIndex = d.search(/.M/i) + 1;
34
+ if (secondSubpathIndex > 0) {
35
+ d = d.substr(0, secondSubpathIndex);
36
+ }
37
+ }
38
+ } catch (error) {
39
+ // Failed to get path data from magnet element.
40
+ // Draw a rectangle around the node instead.
41
+ const nodeBBox = cellView.getNodeBoundingRect(node);
42
+ d = V.rectToPath(assign({}, options, nodeBBox.toJSON()));
43
+ }
44
+ return d;
45
+ },
46
+
47
+ highlightConnection(cellView) {
48
+ this.vel.attr('d', cellView.getSerializedConnection());
49
+ },
50
+
51
+ highlightNode(cellView, node) {
52
+ const { vel, options } = this;
53
+ const { padding, layer } = options;
54
+ let highlightMatrix = this.getNodeMatrix(cellView, node);
55
+ // Add padding to the highlight element.
56
+ if (padding) {
57
+ if (!layer && node === cellView.el) {
58
+ // If the highlighter is appended to the cellView
59
+ // and we measure the size of the cellView wrapping group
60
+ // it's necessary to remove the highlighter first
61
+ vel.remove();
62
+ }
63
+ let nodeBBox = cellView.getNodeBoundingRect(node);
64
+ const cx = nodeBBox.x + (nodeBBox.width / 2);
65
+ const cy = nodeBBox.y + (nodeBBox.height / 2);
66
+ nodeBBox = V.transformRect(nodeBBox, highlightMatrix);
67
+ const width = Math.max(nodeBBox.width, 1);
68
+ const height = Math.max(nodeBBox.height, 1);
69
+ const sx = (width + padding) / width;
70
+ const sy = (height + padding) / height;
71
+ const paddingMatrix = V.createSVGMatrix({
72
+ a: sx,
73
+ b: 0,
74
+ c: 0,
75
+ d: sy,
76
+ e: cx - sx * cx,
77
+ f: cy - sy * cy
78
+ });
79
+ highlightMatrix = highlightMatrix.multiply(paddingMatrix);
80
+ }
81
+ vel.attr({
82
+ 'd': this.getPathData(cellView, node),
83
+ 'transform': V.matrixToTransformString(highlightMatrix)
84
+ });
85
+ },
86
+
87
+ highlight(cellView, node) {
88
+ const { vel, options } = this;
89
+ vel.attr(options.attrs);
90
+ if (options.nonScalingStroke) {
91
+ vel.attr('vector-effect', 'non-scaling-stroke');
92
+ }
93
+ if (cellView.isNodeConnection(node)) {
94
+ this.highlightConnection(cellView);
95
+ } else {
96
+ this.highlightNode(cellView, node);
97
+ }
98
+ }
99
+
100
+ });
@@ -0,0 +1,4 @@
1
+ import * as Port from './ports/port.mjs';
2
+ import * as PortLabel from './ports/portLabel.mjs';
3
+
4
+ export { Port, PortLabel };
@@ -0,0 +1,188 @@
1
+ import { evalCalcAttribute, isCalcAttribute } from '../../dia/attributes/calc.mjs';
2
+ import * as g from '../../g/index.mjs';
3
+ import * as util from '../../util/index.mjs';
4
+
5
+ function portTransformAttrs(point, angle, opt) {
6
+
7
+ var trans = point.toJSON();
8
+
9
+ trans.angle = angle || 0;
10
+
11
+ return util.defaults({}, opt, trans);
12
+ }
13
+
14
+ function lineLayout(ports, p1, p2, elBBox) {
15
+ return ports.map(function(port, index, ports) {
16
+ var p = this.pointAt(((index + 0.5) / ports.length));
17
+ // `dx`,`dy` per port offset option
18
+ if (port.dx || port.dy) {
19
+ p.offset(port.dx || 0, port.dy || 0);
20
+ }
21
+ return portTransformAttrs(p.round(), 0, argTransform(elBBox, port));
22
+ }, g.line(p1, p2));
23
+ }
24
+
25
+ function ellipseLayout(ports, elBBox, startAngle, stepFn) {
26
+
27
+ var center = elBBox.center();
28
+ var ratio = elBBox.width / elBBox.height;
29
+ var p1 = elBBox.topMiddle();
30
+
31
+ var ellipse = g.Ellipse.fromRect(elBBox);
32
+
33
+ return ports.map(function(port, index, ports) {
34
+
35
+ var angle = startAngle + stepFn(index, ports.length);
36
+ var p2 = p1.clone()
37
+ .rotate(center, -angle)
38
+ .scale(ratio, 1, center);
39
+
40
+ var theta = port.compensateRotation ? -ellipse.tangentTheta(p2) : 0;
41
+
42
+ // `dx`,`dy` per port offset option
43
+ if (port.dx || port.dy) {
44
+ p2.offset(port.dx || 0, port.dy || 0);
45
+ }
46
+
47
+ // `dr` delta radius option
48
+ if (port.dr) {
49
+ p2.move(center, port.dr);
50
+ }
51
+
52
+ return portTransformAttrs(p2.round(), theta, argTransform(elBBox, port));
53
+ });
54
+ }
55
+
56
+
57
+ function argTransform(bbox, args) {
58
+ let { x, y, angle } = args;
59
+ if (util.isPercentage(x)) {
60
+ x = parseFloat(x) / 100 * bbox.width;
61
+ } else if (isCalcAttribute(x)) {
62
+ x = Number(evalCalcAttribute(x, bbox));
63
+ }
64
+ if (util.isPercentage(y)) {
65
+ y = parseFloat(y) / 100 * bbox.height;
66
+ } else if (isCalcAttribute(y)) {
67
+ y = Number(evalCalcAttribute(y, bbox));
68
+ }
69
+ return { x, y, angle };
70
+ }
71
+
72
+ // Creates a point stored in arguments
73
+ function argPoint(bbox, args) {
74
+ const { x, y } = argTransform(bbox, args);
75
+ return new g.Point(x || 0, y || 0);
76
+ }
77
+
78
+
79
+ /**
80
+ * @param {Array<Object>} ports
81
+ * @param {g.Rect} elBBox
82
+ * @param {Object=} opt opt Group options
83
+ * @returns {Array<g.Point>}
84
+ */
85
+ export const absolute = function(ports, elBBox) {
86
+ return ports.map(port => {
87
+ const transformation = argPoint(elBBox, port).round().toJSON();
88
+ transformation.angle = port.angle || 0;
89
+ return transformation;
90
+ });
91
+ };
92
+
93
+ /**
94
+ * @param {Array<Object>} ports
95
+ * @param {g.Rect} elBBox
96
+ * @param {Object=} opt opt Group options
97
+ * @returns {Array<g.Point>}
98
+ */
99
+ export const fn = function(ports, elBBox, opt) {
100
+ return opt.fn(ports, elBBox, opt);
101
+ };
102
+
103
+ /**
104
+ * @param {Array<Object>} ports
105
+ * @param {g.Rect} elBBox
106
+ * @param {Object=} opt opt Group options
107
+ * @returns {Array<g.Point>}
108
+ */
109
+ export const line = function(ports, elBBox, opt) {
110
+
111
+ var start = argPoint(elBBox, opt.start || elBBox.origin());
112
+ var end = argPoint(elBBox, opt.end || elBBox.corner());
113
+
114
+ return lineLayout(ports, start, end, elBBox);
115
+ };
116
+
117
+ /**
118
+ * @param {Array<Object>} ports
119
+ * @param {g.Rect} elBBox
120
+ * @param {Object=} opt opt Group options
121
+ * @returns {Array<g.Point>}
122
+ */
123
+ export const left = function(ports, elBBox, opt) {
124
+ return lineLayout(ports, elBBox.origin(), elBBox.bottomLeft(), elBBox);
125
+ };
126
+
127
+ /**
128
+ * @param {Array<Object>} ports
129
+ * @param {g.Rect} elBBox
130
+ * @param {Object=} opt opt Group options
131
+ * @returns {Array<g.Point>}
132
+ */
133
+ export const right = function(ports, elBBox, opt) {
134
+ return lineLayout(ports, elBBox.topRight(), elBBox.corner(), elBBox);
135
+ };
136
+
137
+ /**
138
+ * @param {Array<Object>} ports
139
+ * @param {g.Rect} elBBox
140
+ * @param {Object=} opt opt Group options
141
+ * @returns {Array<g.Point>}
142
+ */
143
+ export const top = function(ports, elBBox, opt) {
144
+ return lineLayout(ports, elBBox.origin(), elBBox.topRight(), elBBox);
145
+ };
146
+
147
+ /**
148
+ * @param {Array<Object>} ports
149
+ * @param {g.Rect} elBBox
150
+ * @param {Object=} opt opt Group options
151
+ * @returns {Array<g.Point>}
152
+ */
153
+ export const bottom = function(ports, elBBox, opt) {
154
+ return lineLayout(ports, elBBox.bottomLeft(), elBBox.corner(), elBBox);
155
+ };
156
+
157
+ /**
158
+ * @param {Array<Object>} ports
159
+ * @param {g.Rect} elBBox
160
+ * @param {Object=} opt Group options
161
+ * @returns {Array<g.Point>}
162
+ */
163
+ export const ellipseSpread = function(ports, elBBox, opt) {
164
+
165
+ var startAngle = opt.startAngle || 0;
166
+ var stepAngle = opt.step || 360 / ports.length;
167
+
168
+ return ellipseLayout(ports, elBBox, startAngle, function(index) {
169
+ return index * stepAngle;
170
+ });
171
+ };
172
+
173
+ /**
174
+ * @param {Array<Object>} ports
175
+ * @param {g.Rect} elBBox
176
+ * @param {Object=} opt Group options
177
+ * @returns {Array<g.Point>}
178
+ */
179
+ export const ellipse = function(ports, elBBox, opt) {
180
+
181
+ var startAngle = opt.startAngle || 0;
182
+ var stepAngle = opt.step || 20;
183
+
184
+ return ellipseLayout(ports, elBBox, startAngle, function(index, count) {
185
+ return (index + 0.5 - count / 2) * stepAngle;
186
+ });
187
+ };
188
+