@joint/core 4.1.2 → 4.2.0-alpha.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 (45) hide show
  1. package/README.md +1 -1
  2. package/dist/geometry.js +128 -123
  3. package/dist/geometry.min.js +2 -2
  4. package/dist/joint.d.ts +79 -16
  5. package/dist/joint.js +2249 -1730
  6. package/dist/joint.min.js +2 -2
  7. package/dist/joint.nowrap.js +2248 -1727
  8. package/dist/joint.nowrap.min.js +2 -2
  9. package/dist/vectorizer.js +469 -272
  10. package/dist/vectorizer.min.js +2 -2
  11. package/dist/version.mjs +1 -1
  12. package/package.json +28 -22
  13. package/src/V/create.mjs +51 -0
  14. package/src/V/index.mjs +69 -154
  15. package/src/V/namespace.mjs +9 -0
  16. package/src/V/transform.mjs +183 -0
  17. package/src/V/traverse.mjs +16 -0
  18. package/src/anchors/index.mjs +140 -33
  19. package/src/cellTools/Boundary.mjs +1 -1
  20. package/src/cellTools/Control.mjs +1 -1
  21. package/src/connectionPoints/index.mjs +24 -9
  22. package/src/connectionStrategies/index.mjs +1 -1
  23. package/src/connectors/jumpover.mjs +1 -1
  24. package/src/dia/Cell.mjs +6 -2
  25. package/src/dia/CellView.mjs +47 -39
  26. package/src/dia/Element.mjs +79 -35
  27. package/src/dia/ElementView.mjs +9 -3
  28. package/src/dia/HighlighterView.mjs +32 -11
  29. package/src/dia/Paper.mjs +134 -22
  30. package/src/dia/PaperLayer.mjs +9 -2
  31. package/src/dia/attributes/text.mjs +12 -3
  32. package/src/dia/layers/GridLayer.mjs +5 -0
  33. package/src/dia/ports.mjs +152 -39
  34. package/src/env/index.mjs +1 -1
  35. package/src/g/rect.mjs +7 -0
  36. package/src/highlighters/stroke.mjs +1 -1
  37. package/src/linkAnchors/index.mjs +2 -2
  38. package/src/linkTools/Anchor.mjs +2 -2
  39. package/src/linkTools/Vertices.mjs +4 -6
  40. package/src/mvc/Dom/methods.mjs +2 -2
  41. package/src/util/util.mjs +1 -1
  42. package/src/util/utilHelpers.mjs +2 -0
  43. package/types/geometry.d.ts +2 -0
  44. package/types/joint.d.ts +81 -20
  45. package/src/V/annotation.mjs +0 -0
@@ -2,20 +2,112 @@ import * as util from '../util/index.mjs';
2
2
  import { toRad } from '../g/index.mjs';
3
3
  import { resolveRef } from '../linkAnchors/index.mjs';
4
4
 
5
+ const Side = {
6
+ LEFT: 'left',
7
+ RIGHT: 'right',
8
+ TOP: 'top',
9
+ BOTTOM: 'bottom',
10
+ };
11
+
12
+ const SideMode = {
13
+ PREFER_HORIZONTAL: 'prefer-horizontal',
14
+ PREFER_VERTICAL: 'prefer-vertical',
15
+ HORIZONTAL: 'horizontal',
16
+ VERTICAL: 'vertical',
17
+ AUTO: 'auto',
18
+ };
19
+
20
+ function getModelBBoxFromConnectedLink(element, link, endType, rotate) {
21
+
22
+ const portId = link.get(endType).port;
23
+ if (element.hasPort(portId)) {
24
+ return element.getPortBBox(portId, { rotate });
25
+ }
26
+
27
+ return element.getBBox({ rotate });
28
+ }
29
+
30
+ function getMiddleSide(rect, point, opt) {
31
+
32
+ const { preferenceThreshold = 0, mode } = opt;
33
+ const { x, y } = point;
34
+ const { x: left , y: top, width, height } = rect;
35
+
36
+ switch (mode) {
37
+
38
+ case SideMode.PREFER_VERTICAL: {
39
+ const {
40
+ top: topThreshold,
41
+ bottom: bottomThreshold
42
+ } = util.normalizeSides(preferenceThreshold);
43
+ const bottom = top + height;
44
+ if (y > top - topThreshold && y < bottom + bottomThreshold) {
45
+ const cx = left + width / 2;
46
+ return (x < cx) ? Side.LEFT : Side.RIGHT;
47
+ }
48
+ }
49
+ // eslint-disable-next-line no-fallthrough
50
+ case SideMode.VERTICAL: {
51
+ const cy = top + height / 2;
52
+ return (y < cy) ? Side.TOP : Side.BOTTOM;
53
+ }
54
+
55
+ case SideMode.PREFER_HORIZONTAL: {
56
+ const {
57
+ left: leftThreshold,
58
+ right: rightThreshold
59
+ } = util.normalizeSides(preferenceThreshold);
60
+ const right = left + width;
61
+ if (x > left - leftThreshold && x < right + rightThreshold) {
62
+ const cy = top + height / 2;
63
+ return (y < cy) ? Side.TOP : Side.BOTTOM;
64
+ }
65
+ }
66
+ // eslint-disable-next-line no-fallthrough
67
+ case SideMode.HORIZONTAL: {
68
+ const cx = left + width / 2;
69
+ return (x < cx) ? Side.LEFT : Side.RIGHT;
70
+ }
71
+
72
+ case SideMode.AUTO:
73
+ default: {
74
+ return rect.sideNearestToPoint(point);
75
+ }
76
+ }
77
+ }
78
+
5
79
  function bboxWrapper(method) {
6
80
 
7
- return function(view, magnet, ref, opt) {
81
+ return function(elementView, magnet, ref, opt, endType, linkView) {
82
+
83
+ const rotate = !!opt.rotate;
84
+ const element = elementView.model;
85
+ const link = linkView.model;
86
+ const angle = element.angle();
87
+
88
+ let bbox, center;
89
+ if (opt.useModelGeometry) {
90
+ bbox = getModelBBoxFromConnectedLink(element, link, endType, !rotate);
91
+ center = bbox.center();
92
+ } else {
93
+ center = element.getCenter();
94
+ bbox = (rotate) ? elementView.getNodeUnrotatedBBox(magnet) : elementView.getNodeBBox(magnet);
95
+ }
8
96
 
9
- var rotate = !!opt.rotate;
10
- var bbox = (rotate) ? view.getNodeUnrotatedBBox(magnet) : view.getNodeBBox(magnet);
11
- var anchor = bbox[method]();
97
+ const anchor = bbox[method]();
12
98
 
13
- var dx = opt.dx;
99
+ let dx = opt.dx;
14
100
  if (dx) {
15
- var dxPercentage = util.isPercentage(dx);
16
- dx = parseFloat(dx);
101
+ const isDxPercentage = util.isPercentage(dx);
102
+ if (!isDxPercentage && util.isCalcExpression(dx)) {
103
+ // calc expression
104
+ dx = Number(util.evalCalcExpression(dx, bbox));
105
+ } else {
106
+ // percentage or a number
107
+ dx = parseFloat(dx);
108
+ }
17
109
  if (isFinite(dx)) {
18
- if (dxPercentage) {
110
+ if (isDxPercentage) {
19
111
  dx /= 100;
20
112
  dx *= bbox.width;
21
113
  }
@@ -23,12 +115,18 @@ function bboxWrapper(method) {
23
115
  }
24
116
  }
25
117
 
26
- var dy = opt.dy;
118
+ let dy = opt.dy;
27
119
  if (dy) {
28
- var dyPercentage = util.isPercentage(dy);
29
- dy = parseFloat(dy);
120
+ const isDyPercentage = util.isPercentage(dy);
121
+ if (!isDyPercentage && util.isCalcExpression(dy)) {
122
+ // calc expression
123
+ dy = Number(util.evalCalcExpression(dy, bbox));
124
+ } else {
125
+ // percentage or a number
126
+ dy = parseFloat(dy);
127
+ }
30
128
  if (isFinite(dy)) {
31
- if (dyPercentage) {
129
+ if (isDyPercentage) {
32
130
  dy /= 100;
33
131
  dy *= bbox.height;
34
132
  }
@@ -36,19 +134,27 @@ function bboxWrapper(method) {
36
134
  }
37
135
  }
38
136
 
39
- return (rotate) ? anchor.rotate(view.model.getBBox().center(), -view.model.angle()) : anchor;
137
+ return (rotate) ? anchor.rotate(center, -angle) : anchor;
40
138
  };
41
139
  }
42
140
 
43
- function _perpendicular(view, magnet, refPoint, opt) {
141
+ function _perpendicular(elementView, magnet, refPoint, opt, endType, linkView) {
44
142
 
45
- var angle = view.model.angle();
46
- var bbox = view.getNodeBBox(magnet);
47
- var anchor = bbox.center();
48
- var topLeft = bbox.origin();
49
- var bottomRight = bbox.corner();
143
+ const element = elementView.model;
144
+ const angle = element.angle();
50
145
 
51
- var padding = opt.padding;
146
+ let bbox;
147
+ if (opt.useModelGeometry) {
148
+ bbox = getModelBBoxFromConnectedLink(element, linkView.model, endType, true);
149
+ } else {
150
+ bbox = elementView.getNodeBBox(magnet);
151
+ }
152
+
153
+ const anchor = bbox.center();
154
+ const topLeft = bbox.origin();
155
+ const bottomRight = bbox.corner();
156
+
157
+ let padding = opt.padding;
52
158
  if (!isFinite(padding)) padding = 0;
53
159
 
54
160
  if ((topLeft.y + padding) <= refPoint.y && refPoint.y <= (bottomRight.y - padding)) {
@@ -64,16 +170,17 @@ function _perpendicular(view, magnet, refPoint, opt) {
64
170
  return anchor;
65
171
  }
66
172
 
67
- function _midSide(view, magnet, refPoint, opt) {
68
-
173
+ function _midSide(view, magnet, refPoint, opt, endType, linkView) {
69
174
  var rotate = !!opt.rotate;
70
- var bbox, angle, center;
71
- if (rotate) {
72
- bbox = view.getNodeUnrotatedBBox(magnet);
73
- center = view.model.getBBox().center();
74
- angle = view.model.angle();
175
+ var angle = view.model.angle();
176
+ var center = view.model.getCenter();
177
+
178
+ var bbox;
179
+ if (opt.useModelGeometry) {
180
+ bbox = getModelBBoxFromConnectedLink(view.model, linkView.model, endType, !rotate);
181
+ center = bbox.center();
75
182
  } else {
76
- bbox = view.getNodeBBox(magnet);
183
+ bbox = rotate ? view.getNodeUnrotatedBBox(magnet) : view.getNodeBBox(magnet);
77
184
  }
78
185
 
79
186
  var padding = opt.padding;
@@ -81,19 +188,19 @@ function _midSide(view, magnet, refPoint, opt) {
81
188
 
82
189
  if (rotate) refPoint.rotate(center, angle);
83
190
 
84
- var side = bbox.sideNearestToPoint(refPoint);
191
+ var side = getMiddleSide(bbox, refPoint, opt);
85
192
  var anchor;
86
193
  switch (side) {
87
- case 'left':
194
+ case Side.LEFT:
88
195
  anchor = bbox.leftMiddle();
89
196
  break;
90
- case 'right':
197
+ case Side.RIGHT:
91
198
  anchor = bbox.rightMiddle();
92
199
  break;
93
- case 'top':
200
+ case Side.TOP:
94
201
  anchor = bbox.topMiddle();
95
202
  break;
96
- case 'bottom':
203
+ case Side.BOTTOM:
97
204
  anchor = bbox.bottomMiddle();
98
205
  break;
99
206
  }
@@ -35,7 +35,7 @@ export const Boundary = ToolView.extend({
35
35
  var angle = model.angle();
36
36
  if (angle) {
37
37
  if (rotate) {
38
- var origin = model.getBBox().center();
38
+ var origin = model.getCenter();
39
39
  vel.rotate(angle, origin.x, origin.y, { absolute: true });
40
40
  } else {
41
41
  bbox = bbox.bbox(angle);
@@ -103,7 +103,7 @@ export const Control = ToolView.extend({
103
103
  const model = relatedView.model;
104
104
  const angle = model.angle();
105
105
  const center = bbox.center();
106
- if (angle) center.rotate(model.getBBox().center(), -angle);
106
+ if (angle) center.rotate(model.getCenter(), -angle);
107
107
  bbox.inflate(padding);
108
108
  extrasNode.setAttribute('x', -bbox.width / 2);
109
109
  extrasNode.setAttribute('y', -bbox.height / 2);
@@ -78,10 +78,12 @@ function anchorConnectionPoint(line, _view, _magnet, opt) {
78
78
 
79
79
  function bboxIntersection(line, view, magnet, opt) {
80
80
 
81
- var bbox = view.getNodeBBox(magnet);
81
+ const bbox = (opt.useModelGeometry)
82
+ ? getNodeModelBBox(view, magnet, true)
83
+ : view.getNodeBBox(magnet);
82
84
  if (opt.stroke) bbox.inflate(stroke(magnet) / 2);
83
- var intersections = line.intersect(bbox);
84
- var cp = (intersections)
85
+ const intersections = line.intersect(bbox);
86
+ const cp = (intersections)
85
87
  ? line.start.chooseClosest(intersections)
86
88
  : line.end;
87
89
  return offsetPoint(cp, line.start, opt.offset);
@@ -89,22 +91,35 @@ function bboxIntersection(line, view, magnet, opt) {
89
91
 
90
92
  function rectangleIntersection(line, view, magnet, opt) {
91
93
 
92
- var angle = view.model.angle();
94
+ const angle = view.model.angle();
93
95
  if (angle === 0) {
94
96
  return bboxIntersection(line, view, magnet, opt);
95
97
  }
96
98
 
97
- var bboxWORotation = view.getNodeUnrotatedBBox(magnet);
99
+ const bboxWORotation = (opt.useModelGeometry)
100
+ ? getNodeModelBBox(view, magnet, false)
101
+ : view.getNodeUnrotatedBBox(magnet);
98
102
  if (opt.stroke) bboxWORotation.inflate(stroke(magnet) / 2);
99
- var center = bboxWORotation.center();
100
- var lineWORotation = line.clone().rotate(center, angle);
101
- var intersections = lineWORotation.setLength(1e6).intersect(bboxWORotation);
102
- var cp = (intersections)
103
+ const center = bboxWORotation.center();
104
+ const lineWORotation = line.clone().rotate(center, angle);
105
+ const intersections = lineWORotation.setLength(1e6).intersect(bboxWORotation);
106
+ const cp = (intersections)
103
107
  ? lineWORotation.start.chooseClosest(intersections).rotate(center, -angle)
104
108
  : line.end;
105
109
  return offsetPoint(cp, line.start, opt.offset);
106
110
  }
107
111
 
112
+ function getNodeModelBBox(elementView, magnet, rotate) {
113
+ const element = elementView.model;
114
+
115
+ const portId = elementView.findAttribute('port', magnet);
116
+ if (element.hasPort(portId)) {
117
+ return element.getPortBBox(portId, { rotate });
118
+ }
119
+
120
+ return element.getBBox({ rotate });
121
+ }
122
+
108
123
  function findShapeNode(magnet) {
109
124
  if (!magnet) return null;
110
125
  var node = magnet;
@@ -21,7 +21,7 @@ function pinnedElementEnd(relative, end, view, magnet, coords) {
21
21
 
22
22
  var angle = view.model.angle();
23
23
  var bbox = view.getNodeUnrotatedBBox(magnet);
24
- var origin = view.model.getBBox().center();
24
+ var origin = view.model.getCenter();
25
25
  coords.rotate(origin, angle);
26
26
  var dx = coords.x - bbox.x;
27
27
  var dy = coords.y - bbox.y;
@@ -337,7 +337,7 @@ function buildRoundedSegment(offset, path, curr, prev, next) {
337
337
  * @property {number} size optional size of a jump arc
338
338
  * @return {string} created `D` attribute of SVG path
339
339
  */
340
- export const jumpover = function(sourcePoint, targetPoint, route, opt) { // eslint-disable-line max-params
340
+ export const jumpover = function(sourcePoint, targetPoint, route, opt) {
341
341
 
342
342
  setupUpdating(this);
343
343
 
package/src/dia/Cell.mjs CHANGED
@@ -39,7 +39,7 @@ import * as g from '../g/index.mjs';
39
39
 
40
40
  const attributesMerger = function(a, b) {
41
41
  if (Array.isArray(a)) {
42
- return b;
42
+ return cloneDeep(b);
43
43
  }
44
44
  };
45
45
 
@@ -921,9 +921,13 @@ export const Cell = Model.extend({
921
921
  return new g.Rect(0, 0, 0, 0);
922
922
  },
923
923
 
924
+ getCenter: function() {
925
+ return this.getBBox().center();
926
+ },
927
+
924
928
  getPointRotatedAroundCenter(angle, x, y) {
925
929
  const point = new g.Point(x, y);
926
- if (angle) point.rotate(this.getBBox().center(), angle);
930
+ if (angle) point.rotate(this.getCenter(), angle);
927
931
  return point;
928
932
  },
929
933
 
@@ -795,7 +795,15 @@ export const CellView = View.extend({
795
795
  getNodeBoundingRect: function(magnet) {
796
796
 
797
797
  var metrics = this.nodeCache(magnet);
798
- if (metrics.boundingRect === undefined) metrics.boundingRect = V(magnet).getBBox();
798
+ if (metrics.boundingRect === undefined) {
799
+ const { measureNode } = this.paper.options;
800
+ if (typeof measureNode === 'function') {
801
+ // Measure the node bounding box using the paper's measureNode method.
802
+ metrics.boundingRect = measureNode(magnet, this);
803
+ } else {
804
+ metrics.boundingRect = V(magnet).getBBox();
805
+ }
806
+ }
799
807
  return new Rect(metrics.boundingRect);
800
808
  },
801
809
 
@@ -810,7 +818,12 @@ export const CellView = View.extend({
810
818
  } else {
811
819
  target = el;
812
820
  }
813
- metrics.magnetMatrix = V(magnet).getTransformToElement(target);
821
+ metrics.magnetMatrix = V(magnet).getTransformToElement(target, {
822
+ // We use `safe` mode if the magnet is not visible (not in the DOM render tree).
823
+ // The browser would not be able to calculate the transformation matrix
824
+ // using `getScreenCTM()` method.
825
+ safe: !magnet.checkVisibility()
826
+ });
814
827
  }
815
828
  return V.createSVGMatrix(metrics.magnetMatrix);
816
829
  },
@@ -994,33 +1007,38 @@ export const CellView = View.extend({
994
1007
  const refNodeId = refNode ? V.ensureId(refNode) : '';
995
1008
  let refBBox = bboxCache[refNodeId];
996
1009
  if (!refBBox) {
997
- // Get the bounding box of the reference element using to the common ancestor
998
- // transformation space.
999
- //
1000
- // @example 1
1001
- // <g transform="translate(11, 13)">
1002
- // <rect @selector="b" x="1" y="2" width="3" height="4"/>
1003
- // <rect @selector="a"/>
1004
- // </g>
1005
- //
1006
- // In this case, the reference bounding box can not be affected
1007
- // by the `transform` attribute of the `<g>` element,
1008
- // because the exact transformation will be applied to the `a` element
1009
- // as well as to the `b` element.
1010
- //
1011
- // @example 2
1012
- // <g transform="translate(11, 13)">
1013
- // <rect @selector="b" x="1" y="2" width="3" height="4"/>
1014
- // </g>
1015
- // <rect @selector="a"/>
1016
- //
1017
- // In this case, the reference bounding box have to be affected by the
1018
- // `transform` attribute of the `<g>` element, because the `a` element
1019
- // is not descendant of the `<g>` element and will not be affected
1020
- // by the transformation.
1021
- refBBox = bboxCache[refNodeId] = (refNode)
1022
- ? V(refNode).getBBox({ target: getCommonAncestorNode(node, refNode) })
1023
- : opt.rootBBox;
1010
+ if (refNode) {
1011
+ // Get the bounding box of the reference element using to the common ancestor
1012
+ // transformation space.
1013
+ //
1014
+ // @example 1
1015
+ // <g transform="translate(11, 13)">
1016
+ // <rect @selector="b" x="1" y="2" width="3" height="4"/>
1017
+ // <rect @selector="a"/>
1018
+ // </g>
1019
+ //
1020
+ // In this case, the reference bounding box can not be affected
1021
+ // by the `transform` attribute of the `<g>` element,
1022
+ // because the exact transformation will be applied to the `a` element
1023
+ // as well as to the `b` element.
1024
+ //
1025
+ // @example 2
1026
+ // <g transform="translate(11, 13)">
1027
+ // <rect @selector="b" x="1" y="2" width="3" height="4"/>
1028
+ // </g>
1029
+ // <rect @selector="a"/>
1030
+ //
1031
+ // In this case, the reference bounding box have to be affected by the
1032
+ // `transform` attribute of the `<g>` element, because the `a` element
1033
+ // is not descendant of the `<g>` element and will not be affected
1034
+ // by the transformation.
1035
+ const refRect = this.getNodeBoundingRect(refNode);
1036
+ const refTMatrix = V(refNode).getTransformToElement(V.getCommonAncestor(node, refNode));
1037
+ refBBox = V.transformRect(refRect, refTMatrix);
1038
+ } else {
1039
+ refBBox = opt.rootBBox;
1040
+ }
1041
+ bboxCache[refNodeId] = refBBox;
1024
1042
  }
1025
1043
 
1026
1044
  if (roAttrs) {
@@ -1375,14 +1393,4 @@ Object.defineProperty(CellView.prototype, 'useCSSSelectors', {
1375
1393
  }
1376
1394
  });
1377
1395
 
1378
- // TODO: Move to Vectorizer library.
1379
- function getCommonAncestorNode(node1, node2) {
1380
- let parent = node1;
1381
- do {
1382
- if (parent.contains(node2)) return parent;
1383
- parent = parent.parentNode;
1384
- } while (parent);
1385
- return null;
1386
- }
1387
-
1388
1396