plotboilerplate 1.20.2 → 1.21.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 (127) hide show
  1. package/dist/index.esm.js +1985 -1368
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/index.esm.min.js +1 -1
  4. package/dist/index.esm.min.js.map +1 -1
  5. package/dist/plotboilerplate.browser.min.js +1 -1
  6. package/dist/plotboilerplate.browser.min.js.map +1 -1
  7. package/dist/plotboilerplate.js +1170 -541
  8. package/dist/plotboilerplate.min.js +1 -1
  9. package/package.json +1 -1
  10. package/src/cjs/BezierPath.d.ts +27 -49
  11. package/src/cjs/BezierPath.js +67 -234
  12. package/src/cjs/BezierPath.js.map +1 -1
  13. package/src/cjs/Bounds.d.ts +14 -1
  14. package/src/cjs/Bounds.js +21 -1
  15. package/src/cjs/Bounds.js.map +1 -1
  16. package/src/cjs/CubicBezierCurve.d.ts +70 -1
  17. package/src/cjs/CubicBezierCurve.js +130 -12
  18. package/src/cjs/CubicBezierCurve.js.map +1 -1
  19. package/src/cjs/Line.d.ts +2 -1
  20. package/src/cjs/Line.js +1 -0
  21. package/src/cjs/Line.js.map +1 -1
  22. package/src/cjs/PBText.d.ts +2 -1
  23. package/src/cjs/PBText.js +10 -9
  24. package/src/cjs/PBText.js.map +1 -1
  25. package/src/cjs/PlotBoilerplate.d.ts +4 -13
  26. package/src/cjs/PlotBoilerplate.js +61 -15
  27. package/src/cjs/PlotBoilerplate.js.map +1 -1
  28. package/src/cjs/Polygon.d.ts +35 -1
  29. package/src/cjs/Polygon.js +87 -1
  30. package/src/cjs/Polygon.js.map +1 -1
  31. package/src/cjs/VEllipse.js +1 -1
  32. package/src/cjs/VEllipse.js.map +1 -1
  33. package/src/cjs/Vector.d.ts +4 -4
  34. package/src/cjs/Vector.js +2 -2
  35. package/src/cjs/Vector.js.map +1 -1
  36. package/src/cjs/VertTuple.d.ts +2 -1
  37. package/src/cjs/VertTuple.js +4 -3
  38. package/src/cjs/VertTuple.js.map +1 -1
  39. package/src/cjs/Vertex.d.ts +14 -3
  40. package/src/cjs/Vertex.js +18 -3
  41. package/src/cjs/Vertex.js.map +1 -1
  42. package/src/cjs/draw.d.ts +154 -75
  43. package/src/cjs/draw.js +308 -137
  44. package/src/cjs/draw.js.map +1 -1
  45. package/src/cjs/drawgl.d.ts +87 -50
  46. package/src/cjs/drawgl.js +86 -31
  47. package/src/cjs/drawgl.js.map +1 -1
  48. package/src/cjs/drawutilssvg.d.ts +226 -53
  49. package/src/cjs/drawutilssvg.js +374 -92
  50. package/src/cjs/drawutilssvg.js.map +1 -1
  51. package/src/cjs/interfaces/DrawLib.d.ts +157 -79
  52. package/src/cjs/interfaces/DrawLib.js +6 -0
  53. package/src/cjs/interfaces/DrawLib.js.map +1 -1
  54. package/src/cjs/interfaces/core.d.ts +13 -2
  55. package/src/cjs/interfaces/core.js +1 -0
  56. package/src/cjs/interfaces/core.js.map +1 -1
  57. package/src/cjs/utils/creategui.js +16 -10
  58. package/src/cjs/utils/detectDarkMode.d.ts +10 -0
  59. package/src/cjs/utils/detectDarkMode.js +32 -0
  60. package/src/cjs/utils/detectDarkMode.js.map +1 -0
  61. package/src/esm/BezierPath.d.ts +27 -49
  62. package/src/esm/BezierPath.js +67 -234
  63. package/src/esm/BezierPath.js.map +1 -1
  64. package/src/esm/Bounds.d.ts +14 -1
  65. package/src/esm/Bounds.js +19 -1
  66. package/src/esm/Bounds.js.map +1 -1
  67. package/src/esm/CubicBezierCurve.d.ts +70 -1
  68. package/src/esm/CubicBezierCurve.js +130 -12
  69. package/src/esm/CubicBezierCurve.js.map +1 -1
  70. package/src/esm/Line.d.ts +2 -1
  71. package/src/esm/Line.js +1 -0
  72. package/src/esm/Line.js.map +1 -1
  73. package/src/esm/PBText.d.ts +2 -1
  74. package/src/esm/PBText.js +10 -9
  75. package/src/esm/PBText.js.map +1 -1
  76. package/src/esm/PlotBoilerplate.d.ts +4 -13
  77. package/src/esm/PlotBoilerplate.js +62 -16
  78. package/src/esm/PlotBoilerplate.js.map +1 -1
  79. package/src/esm/Polygon.d.ts +35 -1
  80. package/src/esm/Polygon.js +85 -1
  81. package/src/esm/Polygon.js.map +1 -1
  82. package/src/esm/VEllipse.js +1 -1
  83. package/src/esm/VEllipse.js.map +1 -1
  84. package/src/esm/Vector.d.ts +4 -4
  85. package/src/esm/Vector.js +2 -2
  86. package/src/esm/Vector.js.map +1 -1
  87. package/src/esm/VertTuple.d.ts +2 -1
  88. package/src/esm/VertTuple.js +4 -3
  89. package/src/esm/VertTuple.js.map +1 -1
  90. package/src/esm/Vertex.d.ts +14 -3
  91. package/src/esm/Vertex.js +18 -3
  92. package/src/esm/Vertex.js.map +1 -1
  93. package/src/esm/draw.d.ts +154 -75
  94. package/src/esm/draw.js +304 -135
  95. package/src/esm/draw.js.map +1 -1
  96. package/src/esm/drawgl.d.ts +87 -50
  97. package/src/esm/drawgl.js +86 -31
  98. package/src/esm/drawgl.js.map +1 -1
  99. package/src/esm/drawutilssvg.d.ts +226 -53
  100. package/src/esm/drawutilssvg.js +369 -92
  101. package/src/esm/drawutilssvg.js.map +1 -1
  102. package/src/esm/interfaces/DrawLib.d.ts +157 -79
  103. package/src/esm/interfaces/DrawLib.js +6 -0
  104. package/src/esm/interfaces/DrawLib.js.map +1 -1
  105. package/src/esm/interfaces/core.d.ts +13 -2
  106. package/src/esm/interfaces/core.js +1 -0
  107. package/src/esm/interfaces/core.js.map +1 -1
  108. package/src/esm/utils/detectDarkMode.d.ts +10 -0
  109. package/src/esm/utils/detectDarkMode.js +28 -0
  110. package/src/esm/utils/detectDarkMode.js.map +1 -0
  111. package/src/ts/BezierPath.ts +72 -241
  112. package/src/ts/Bounds.ts +25 -1
  113. package/src/ts/CubicBezierCurve.ts +139 -12
  114. package/src/ts/Line.ts +2 -1
  115. package/src/ts/PBText.ts +10 -9
  116. package/src/ts/PlotBoilerplate.ts +56 -19
  117. package/src/ts/Polygon.ts +91 -1
  118. package/src/ts/VEllipse.ts +2 -2
  119. package/src/ts/Vector.ts +4 -4
  120. package/src/ts/VertTuple.ts +4 -3
  121. package/src/ts/Vertex.ts +20 -4
  122. package/src/ts/draw.ts +311 -115
  123. package/src/ts/drawgl.ts +133 -71
  124. package/src/ts/drawutilssvg.ts +543 -121
  125. package/src/ts/interfaces/DrawLib.ts +219 -83
  126. package/src/ts/interfaces/core.ts +14 -2
  127. package/src/ts/utils/detectDarkMode.ts +30 -0
package/dist/index.esm.js CHANGED
@@ -1,17 +1,3 @@
1
- /**
2
- * @classdesc A static UIDGenerator.
3
- *
4
- * @author Ikaros Kappler
5
- * @date 2021-01-20
6
- * @version 1.0.0
7
- */
8
- class UIDGenerator {
9
- static next() {
10
- return `${UIDGenerator.current++}`;
11
- }
12
- }
13
- UIDGenerator.current = 0;
14
-
15
1
  /**
16
2
  * @author Ikaros Kappler
17
3
  * @date 2018-08-26
@@ -65,6 +51,20 @@ VertexAttr.model = {
65
51
  visible: true
66
52
  };
67
53
 
54
+ /**
55
+ * @classdesc A static UIDGenerator.
56
+ *
57
+ * @author Ikaros Kappler
58
+ * @date 2021-01-20
59
+ * @version 1.0.0
60
+ */
61
+ class UIDGenerator {
62
+ static next() {
63
+ return `${UIDGenerator.current++}`;
64
+ }
65
+ }
66
+ UIDGenerator.current = 0;
67
+
68
68
  /**
69
69
  * @author Ikaros Kappler
70
70
  * @date 2018-08-27
@@ -358,6 +358,8 @@ class VertexListeners {
358
358
  * @modified 2022-02-02 Added the `destroy` method.
359
359
  * @modified 2022-02-02 Cleared the `Vertex.toSVGString` function (deprecated). Use `drawutilssvg` instead.
360
360
  * @modified 2022-11-28 Added the `subXY`, `subX` and `subY` methods to the `Vertex` class.
361
+ * @modified 2023-09-29 Downgraded types for the `Vertex.utils.buildArrowHead` function (replacing Vertex params by more generic XYCoords type).
362
+ * @modified 2023-09-29 Added the `Vertex.abs()` method as it seems useful.
361
363
  * @version 2.8.0
362
364
  *
363
365
  * @file Vertex
@@ -874,6 +876,19 @@ class Vertex {
874
876
  this.y = -this.y;
875
877
  return this;
876
878
  }
879
+ /**
880
+ * Set both coordinates of this vertex to their absolute value (abs(x), abs(y)).
881
+ *
882
+ * @method abs
883
+ * @return {Vertex} this
884
+ * @instance
885
+ * @memberof Vertex
886
+ */
887
+ abs() {
888
+ this.x = Math.abs(this.x);
889
+ this.y = Math.abs(this.y);
890
+ return this;
891
+ }
877
892
  /**
878
893
  * Get a string representation of this vertex.
879
894
  *
@@ -932,15 +947,15 @@ Vertex.utils = {
932
947
  * Example:
933
948
  * buildArrowHead( new Vertex(0,0), new Vertex(50,100), 8, 1.0, 1.0 )
934
949
  *
935
- * @param {Vertex} zA - The start vertex of the vector to calculate the arrow head for.
936
- * @param {Vertex} zB - The end vertex of the vector.
950
+ * @param {XYCoords} zA - The start vertex of the vector to calculate the arrow head for.
951
+ * @param {XYCoords} zB - The end vertex of the vector.
937
952
  * @param {number} headlen - The length of the arrow head (along the vector direction. A good value is 12).
938
953
  * @param {number} scaleX - The horizontal scaling during draw.
939
954
  * @param {number} scaleY - the vertical scaling during draw.
940
955
  **/
941
956
  // @DEPRECATED: use Vector.utils.buildArrowHead instead!!!
942
957
  buildArrowHead: (zA, zB, headlen, scaleX, scaleY) => {
943
- // console.warn('This function is deprecated! Use Vector.utils.buildArrowHead instead!');
958
+ console.warn("[DEPRECATION] Vertex.utils.buildArrowHead is deprecated. Please use Vector.utils.buildArrowHead instead.");
944
959
  var angle = Math.atan2((zB.y - zA.y) * scaleY, (zB.x - zA.x) * scaleX);
945
960
  var vertices = [];
946
961
  vertices.push(new Vertex(zB.x * scaleX - headlen * Math.cos(angle), zB.y * scaleY - headlen * Math.sin(angle)));
@@ -965,433 +980,287 @@ Vertex.utils = {
965
980
  };
966
981
 
967
982
  /**
968
- * @author Ikaros Kappler
969
- * @date 2018-04-14
970
- * @modified 2018-11-17 Added the containsVert function.
971
- * @modified 2018-12-04 Added the toSVGString function.
972
- * @modified 2019-03-20 Added JSDoc tags.
973
- * @modified 2019-10-25 Added the scale function.
974
- * @modified 2019-11-06 JSDoc update.
975
- * @modified 2019-11-07 Added toCubicBezierPath(number) function.
976
- * @modified 2019-11-22 Added the rotate(number,Vertex) function.
977
- * @modified 2020-03-24 Ported this class from vanilla-JS to Typescript.
978
- * @modified 2020-10-30 Added the `addVertex` function.
979
- * @modified 2020-10-31 Added the `getVertexAt` function.
980
- * @modified 2020-11-06 Added the `move` function.
981
- * @modified 2020-11-10 Added the `getBounds` function.
982
- * @modified 2020-11-11 Generalized `move(Vertex)` to `move(XYCoords)`.
983
+ * @author Ikaros Kappler
984
+ * @date 2020-03-24
985
+ * @modified 2020-05-04 Fixed a serious bug in the pointDistance function.
986
+ * @modified 2020-05-12 The angle(line) param was still not optional. Changed that.
987
+ * @modified 2020-11-11 Generalized the `add` and `sub` param from `Vertex` to `XYCoords`.
988
+ * @modified 2020-12-04 Changed`vtutils.dist2` params from `Vertex` to `XYCoords` (generalized).
989
+ * @modified 2020-12-04 Changed `getClosestT` param from `Vertex` to `XYCoords` (generalized).
990
+ * @modified 2020-12-04 Added the `hasPoint(XYCoords)` function.
983
991
  * @modified 2021-01-20 Added UID.
984
- * @modified 2021-01-29 Added the `signedArea` function (was global function in the demos before).
985
- * @modified 2021-01-29 Added the `isClockwise` function.
986
- * @modified 2021-01-29 Added the `area` function.
987
- * @modified 2021-01-29 Changed the param type for `containsVert` from Vertex to XYCoords.
988
- * @modified 2021-12-14 Added the `perimeter()` function.
989
- * @modified 2021-12-16 Added the `getEvenDistributionPolygon()` function.
990
992
  * @modified 2022-02-02 Added the `destroy` method.
991
- * @modified 2022-02-02 Cleared the `Polygon.toSVGString` function (deprecated). Use `drawutilssvg` instead.
992
- * @modified 2022-03-08 Added the `Polygon.clone()` function.
993
- * @version 1.10.0
994
- *
995
- * @file Polygon
996
- * @public
997
- **/
993
+ * @modified 2023-09-29 Fixed a calculation error in the VertTuple.hasPoint() function; distance measure was broken!
994
+ * @version 1.2.1
995
+ */
998
996
  /**
999
- * @classdesc A polygon class. Any polygon consists of an array of vertices; polygons can be open or closed.
1000
- *
1001
- * @requires BezierPath
1002
- * @requires Bounds
1003
- * @requires SVGSerializabe
997
+ * @classdesc An abstract base classes for vertex tuple constructs, like Lines or Vectors.
998
+ * @abstract
1004
999
  * @requires UID
1005
- * @requires UIDGenerator
1006
1000
  * @requires Vertex
1007
1001
  * @requires XYCoords
1008
1002
  */
1009
- class Polygon {
1003
+ class VertTuple {
1010
1004
  /**
1011
- * The constructor.
1005
+ * Creates an instance.
1012
1006
  *
1013
1007
  * @constructor
1014
- * @name Polygon
1015
- * @param {Vertex[]} vertices - An array of 2d vertices that shape the polygon.
1016
- * @param {boolean} isOpen - Indicates if the polygon should be rendered as an open or closed shape.
1008
+ * @name VertTuple
1009
+ * @param {Vertex} a The tuple's first point.
1010
+ * @param {Vertex} b The tuple's second point.
1017
1011
  **/
1018
- constructor(vertices, isOpen) {
1019
- /**
1020
- * Required to generate proper CSS classes and other class related IDs.
1021
- **/
1022
- this.className = "Polygon";
1012
+ constructor(a, b, factory) {
1023
1013
  this.uid = UIDGenerator.next();
1024
- if (typeof vertices == "undefined")
1025
- vertices = [];
1026
- this.vertices = vertices;
1027
- this.isOpen = isOpen || false;
1014
+ this.a = a;
1015
+ this.b = b;
1016
+ this.factory = factory;
1028
1017
  }
1029
1018
  /**
1030
- * Add a vertex to the end of the `vertices` array.
1019
+ * Get the length of this line.
1031
1020
  *
1032
- * @method addVert
1033
- * @param {Vertex} vert - The vertex to add.
1021
+ * @method length
1034
1022
  * @instance
1035
- * @memberof Polygon
1023
+ * @memberof VertTuple
1036
1024
  **/
1037
- addVertex(vert) {
1038
- this.vertices.push(vert);
1025
+ length() {
1026
+ return Math.sqrt(Math.pow(this.b.x - this.a.x, 2) + Math.pow(this.b.y - this.a.y, 2));
1039
1027
  }
1040
1028
  /**
1041
- * Get the polygon vertex at the given position (index).
1042
- *
1043
- * The index may exceed the total vertex count, and will be wrapped around then (modulo).
1044
- *
1045
- * For k >= 0:
1046
- * - getVertexAt( vertices.length ) == getVertexAt( 0 )
1047
- * - getVertexAt( vertices.length + k ) == getVertexAt( k )
1048
- * - getVertexAt( -k ) == getVertexAt( vertices.length -k )
1029
+ * Set the length of this vector to the given amount. This only works if this
1030
+ * vector is not a null vector.
1049
1031
  *
1050
- * @metho getVertexAt
1051
- * @param {number} index - The index of the desired vertex.
1052
- * @instance
1053
- * @memberof Polygon
1054
- * @return {Vertex} At the given index.
1032
+ * @method setLength
1033
+ * @param {number} length - The desired length.
1034
+ * @memberof VertTuple
1035
+ * @return {T} this (for chaining)
1055
1036
  **/
1056
- getVertexAt(index) {
1057
- if (index < 0)
1058
- return this.vertices[this.vertices.length - (Math.abs(index) % this.vertices.length)];
1059
- else
1060
- return this.vertices[index % this.vertices.length];
1037
+ setLength(length) {
1038
+ return this.scale(length / this.length());
1061
1039
  }
1062
1040
  /**
1063
- * Move the polygon's vertices by the given amount.
1041
+ * Substract the given vertex from this line's end points.
1064
1042
  *
1065
- * @method move
1066
- * @param {XYCoords} amount - The amount to move.
1043
+ * @method sub
1044
+ * @param {XYCoords} amount The amount (x,y) to substract.
1045
+ * @return {VertTuple} this
1067
1046
  * @instance
1068
- * @memberof Polygon
1069
- * @return {Polygon} this for chaining
1047
+ * @memberof VertTuple
1070
1048
  **/
1071
- move(amount) {
1072
- for (var i in this.vertices) {
1073
- this.vertices[i].add(amount);
1074
- }
1049
+ sub(amount) {
1050
+ this.a.sub(amount);
1051
+ this.b.sub(amount);
1075
1052
  return this;
1076
1053
  }
1077
1054
  /**
1078
- * Check if the given vertex is inside this polygon.<br>
1079
- * <br>
1080
- * Ray-casting algorithm found at<br>
1081
- * https://stackoverflow.com/questions/22521982/check-if-point-inside-a-polygon
1055
+ * Add the given vertex to this line's end points.
1082
1056
  *
1083
- * @method containsVert
1084
- * @param {XYCoords} vert - The vertex to check. The new x-component.
1085
- * @return {boolean} True if the passed vertex is inside this polygon. The polygon is considered closed.
1057
+ * @method add
1058
+ * @param {XYCoords} amount The amount (x,y) to add.
1059
+ * @return {Line} this
1086
1060
  * @instance
1087
- * @memberof Polygon
1061
+ * @memberof VertTuple
1088
1062
  **/
1089
- containsVert(vert) {
1090
- // ray-casting algorithm based on
1091
- // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
1092
- var inside = false;
1093
- for (var i = 0, j = this.vertices.length - 1; i < this.vertices.length; j = i++) {
1094
- let xi = this.vertices[i].x, yi = this.vertices[i].y;
1095
- let xj = this.vertices[j].x, yj = this.vertices[j].y;
1096
- var intersect = yi > vert.y != yj > vert.y && vert.x < ((xj - xi) * (vert.y - yi)) / (yj - yi) + xi;
1097
- if (intersect)
1098
- inside = !inside;
1099
- }
1100
- return inside;
1063
+ add(amount) {
1064
+ this.a.add(amount);
1065
+ this.b.add(amount);
1066
+ return this;
1101
1067
  }
1102
1068
  /**
1103
- * Calculate the area of the given polygon (unsigned).
1104
- *
1105
- * Note that this does not work for self-intersecting polygons.
1069
+ * Normalize this line (set to length 1).
1106
1070
  *
1107
- * @method area
1071
+ * @method normalize
1072
+ * @return {VertTuple} this
1108
1073
  * @instance
1109
- * @memberof Polygon
1110
- * @return {number}
1111
- */
1112
- area() {
1113
- return Polygon.utils.area(this.vertices);
1074
+ * @memberof VertTuple
1075
+ **/
1076
+ normalize() {
1077
+ this.b.set(this.a.x + (this.b.x - this.a.x) / this.length(), this.a.y + (this.b.y - this.a.y) / this.length());
1078
+ return this;
1114
1079
  }
1115
1080
  /**
1116
- * Calulate the signed polyon area by interpreting the polygon as a matrix
1117
- * and calculating its determinant.
1081
+ * Scale this line by the given factor.
1118
1082
  *
1119
- * @method signedArea
1083
+ * @method scale
1084
+ * @param {number} factor The factor for scaling (1.0 means no scale).
1085
+ * @return {VertTuple} this
1120
1086
  * @instance
1121
- * @memberof Polygon
1122
- * @return {number}
1123
- */
1124
- signedArea() {
1125
- return Polygon.utils.signedArea(this.vertices);
1087
+ * @memberof VertTuple
1088
+ **/
1089
+ scale(factor) {
1090
+ this.b.set(this.a.x + (this.b.x - this.a.x) * factor, this.a.y + (this.b.y - this.a.y) * factor);
1091
+ return this;
1126
1092
  }
1127
1093
  /**
1128
- * Get the winding order of this polgon: clockwise or counterclockwise.
1094
+ * Move this line to a new location.
1129
1095
  *
1130
- * @method isClockwise
1096
+ * @method moveTo
1097
+ * @param {Vertex} newA - The new desired location of 'a'. Vertex 'b' will be moved, too.
1098
+ * @return {VertTuple} this
1131
1099
  * @instance
1132
- * @memberof Polygon
1133
- * @return {boolean}
1134
- */
1135
- isClockwise() {
1136
- return Polygon.utils.signedArea(this.vertices) < 0;
1100
+ * @memberof VertTuple
1101
+ **/
1102
+ moveTo(newA) {
1103
+ let diff = this.a.difference(newA);
1104
+ this.a.add(diff);
1105
+ this.b.add(diff);
1106
+ return this;
1137
1107
  }
1138
1108
  /**
1139
- * Get the perimeter of this polygon.
1140
- * The perimeter is the absolute length of the outline.
1141
- *
1142
- * If this polygon is open then the last segment (connecting the first and the
1143
- * last vertex) will be skipped.
1109
+ * Get the angle between this and the passed line (in radians).
1144
1110
  *
1145
- * @method perimeter
1111
+ * @method angle
1112
+ * @param {VertTuple} line - (optional) The line to calculate the angle to. If null the baseline (x-axis) will be used.
1113
+ * @return {number} this
1146
1114
  * @instance
1147
- * @memberof Polygon
1148
- * @return {number}
1149
- */
1150
- perimeter() {
1151
- let length = 0;
1152
- for (var i = 1; i < this.vertices.length; i++) {
1153
- length += this.vertices[i - 1].distance(this.vertices[i]);
1154
- }
1155
- if (!this.isOpen && this.vertices.length > 1) {
1156
- length += this.vertices[0].distance(this.vertices[this.vertices.length - 1]);
1115
+ * @memberof VertTuple
1116
+ **/
1117
+ angle(line) {
1118
+ if (line == null || typeof line == "undefined") {
1119
+ line = this.factory(new Vertex(0, 0), new Vertex(100, 0));
1157
1120
  }
1158
- return length;
1121
+ // Compute the angle from x axis and the return the difference :)
1122
+ const v0 = this.b.clone().sub(this.a);
1123
+ const v1 = line.b.clone().sub(line.a);
1124
+ // Thank you, Javascript, for this second atan function. No additional math is needed here!
1125
+ // The result might be negative, but isn't it usually nicer to determine angles in positive values only?
1126
+ return Math.atan2(v1.x, v1.y) - Math.atan2(v0.x, v0.y);
1159
1127
  }
1160
1128
  /**
1161
- * Scale the polygon relative to the given center.
1129
+ * Get line point at position t in [0 ... 1]:<br>
1130
+ * <pre>[P(0)]=[A]--------------------[P(t)]------[B]=[P(1)]</pre><br>
1131
+ * <br>
1132
+ * The counterpart of this function is Line.getClosestT(Vertex).
1162
1133
  *
1163
- * @method scale
1164
- * @param {number} factor - The scale factor.
1165
- * @param {Vertex} center - The center of scaling.
1166
- * @return {Polygon} this, for chaining.
1134
+ * @method vertAt
1135
+ * @param {number} t The position scalar.
1136
+ * @return {Vertex} The vertex a position t.
1167
1137
  * @instance
1168
- * @memberof Polygon
1138
+ * @memberof VertTuple
1169
1139
  **/
1170
- scale(factor, center) {
1171
- for (var i in this.vertices) {
1172
- if (typeof this.vertices[i].scale == "function")
1173
- this.vertices[i].scale(factor, center);
1174
- else
1175
- console.log("There seems to be a null vertex!", this.vertices[i]);
1176
- }
1177
- return this;
1140
+ vertAt(t) {
1141
+ return new Vertex(this.a.x + (this.b.x - this.a.x) * t, this.a.y + (this.b.y - this.a.y) * t);
1178
1142
  }
1179
1143
  /**
1180
- * Rotate the polygon around the given center.
1144
+ * Get the denominator of this and the given line.
1181
1145
  *
1182
- * @method rotate
1183
- * @param {number} angle - The rotation angle.
1184
- * @param {Vertex} center - The center of rotation.
1146
+ * If the denominator is zero (or close to zero) both line are co-linear.
1147
+ *
1148
+ * @method denominator
1149
+ * @param {VertTuple} line
1185
1150
  * @instance
1186
- * @memberof Polygon
1187
- * @return {Polygon} this, for chaining.
1151
+ * @memberof VertTuple
1152
+ * @return {Number}
1188
1153
  **/
1189
- rotate(angle, center) {
1190
- for (var i in this.vertices) {
1191
- this.vertices[i].rotate(angle, center);
1192
- }
1193
- return this;
1154
+ denominator(line) {
1155
+ // http://jsfiddle.net/justin_c_rounds/Gd2S2/
1156
+ return (line.b.y - line.a.y) * (this.b.x - this.a.x) - (line.b.x - line.a.x) * (this.b.y - this.a.y);
1194
1157
  }
1195
1158
  /**
1196
- * Convert this polygon into a new polygon with n evenly distributed vertices.
1159
+ * Checks if this and the given line are co-linear.
1197
1160
  *
1198
- * @param {number} pointCount - Must not be negative.
1161
+ * The constant Vertex.EPSILON is used for tolerance.
1162
+ *
1163
+ * @method colinear
1164
+ * @param {VertTuple} line
1165
+ * @instance
1166
+ * @memberof VertTuple
1167
+ * @return true if both lines are co-linear.
1199
1168
  */
1200
- getEvenDistributionPolygon(pointCount) {
1201
- if (pointCount <= 0) {
1202
- throw new Error("pointCount must be larger than zero; is " + pointCount + ".");
1203
- }
1204
- const result = new Polygon([], this.isOpen);
1205
- if (this.vertices.length === 0) {
1206
- return result;
1207
- }
1208
- // Fetch and add the start point from the source polygon
1209
- let polygonPoint = new Vertex(this.vertices[0]);
1210
- result.vertices.push(polygonPoint);
1211
- if (this.vertices.length === 1) {
1212
- return result;
1213
- }
1214
- const perimeter = this.perimeter();
1215
- const stepSize = perimeter / pointCount;
1216
- const n = this.vertices.length;
1217
- let polygonIndex = 1;
1218
- let nextPolygonPoint = new Vertex(this.vertices[1]);
1219
- let segmentLength = polygonPoint.distance(nextPolygonPoint);
1220
- let loopMax = this.isOpen ? n : n + 1;
1221
- let curSegmentU = stepSize;
1222
- var i = 1;
1223
- while (i < pointCount && polygonIndex < loopMax) {
1224
- // Check if next eq point is inside this segment
1225
- if (curSegmentU < segmentLength) {
1226
- let newPoint = polygonPoint.clone().lerpAbs(nextPolygonPoint, curSegmentU);
1227
- result.vertices.push(newPoint);
1228
- curSegmentU += stepSize;
1229
- i++;
1230
- }
1231
- else {
1232
- polygonIndex++;
1233
- polygonPoint = nextPolygonPoint;
1234
- nextPolygonPoint = new Vertex(this.vertices[polygonIndex % n]);
1235
- curSegmentU = curSegmentU - segmentLength;
1236
- segmentLength = polygonPoint.distance(nextPolygonPoint);
1237
- }
1238
- }
1239
- return result;
1169
+ colinear(line) {
1170
+ return Math.abs(this.denominator(line)) < Vertex.EPSILON;
1240
1171
  }
1241
1172
  /**
1242
- * Get the bounding box (bounds) of this polygon.
1173
+ * Get the closest position T from this line to the specified point.
1243
1174
  *
1244
- * @method getBounds
1175
+ * The counterpart for this function is Line.vertAt(Number).
1176
+ *
1177
+ * @name getClosetT
1178
+ * @method getClosestT
1179
+ * @param {XYCoords} p The point (vertex) to measure the distance to.
1180
+ * @return {number} The line position t of minimal distance to p.
1245
1181
  * @instance
1246
- * @memberof Polygon
1247
- * @return {Bounds} The rectangular bounds of this polygon.
1182
+ * @memberof VertTuple
1248
1183
  **/
1249
- getBounds() {
1250
- return Bounds.computeFromVertices(this.vertices);
1184
+ getClosestT(p) {
1185
+ var l2 = VertTuple.vtutils.dist2(this.a, this.b);
1186
+ if (l2 === 0)
1187
+ return 0;
1188
+ var t = ((p.x - this.a.x) * (this.b.x - this.a.x) + (p.y - this.a.y) * (this.b.y - this.a.y)) / l2;
1189
+ // Do not wrap to [0,1] here.
1190
+ // Other results are of interest, too.
1191
+ // t = Math.max(0, Math.min(1, t));
1192
+ return t;
1251
1193
  }
1252
1194
  /**
1253
- * Create a deep copy of this polygon.
1195
+ * Check if the given point is located on this line. Optionally also check if
1196
+ * that point is located between point `a` and `b`.
1254
1197
  *
1255
- * @return {Polygon} The cloned polygon.
1198
+ * @method hasPoint
1199
+ * @param {Vertex} point The point to check.
1200
+ * @param {boolean=} insideBoundsOnly If set to to true (default=false) the point must be between start and end point of the line.
1201
+ * @return {boolean} True if the given point is on this line.
1202
+ * @instance
1203
+ * @memberof VertTuple
1256
1204
  */
1257
- clone() {
1258
- return new Polygon(this.vertices.map(vert => vert.clone()), this.isOpen);
1205
+ hasPoint(point, insideBoundsOnly) {
1206
+ const t = this.getClosestT(point);
1207
+ // Compare to pointDistance?
1208
+ const distance = Math.sqrt(VertTuple.vtutils.dist2(point, this.vertAt(t)));
1209
+ if (typeof insideBoundsOnly !== "undefined" && insideBoundsOnly) {
1210
+ return distance < Vertex.EPSILON && t >= 0 && t <= 1;
1211
+ }
1212
+ else {
1213
+ return distance < Vertex.EPSILON; // t >= 0 && t <= 1;
1214
+ }
1259
1215
  }
1260
1216
  /**
1261
- * Convert this polygon to a sequence of quadratic Bézier curves.<br>
1262
- * <br>
1263
- * The first vertex in the returned array is the start point.<br>
1264
- * The following sequence are pairs of control-point-and-end-point:
1265
- * <pre>startPoint, controlPoint0, pathPoint1, controlPoint1, pathPoint2, controlPoint2, ..., endPoint</pre>
1217
+ * Get the closest point on this line to the specified point.
1266
1218
  *
1267
- * @method toQuadraticBezierData
1268
- * @return {Vertex[]} An array of 2d vertices that shape the quadratic Bézier curve.
1219
+ * @method getClosestPoint
1220
+ * @param {Vertex} p The point (vertex) to measre the distance to.
1221
+ * @return {Vertex} The point on the line that is closest to p.
1269
1222
  * @instance
1270
- * @memberof Polygon
1223
+ * @memberof VertTuple
1271
1224
  **/
1272
- toQuadraticBezierData() {
1273
- if (this.vertices.length < 3)
1274
- return [];
1275
- var qbezier = [];
1276
- var cc0 = this.vertices[0];
1277
- var cc1 = this.vertices[1];
1278
- var edgeCenter = new Vertex(cc0.x + (cc1.x - cc0.x) / 2, cc0.y + (cc1.y - cc0.y) / 2);
1279
- qbezier.push(edgeCenter);
1280
- var limit = this.isOpen ? this.vertices.length : this.vertices.length + 1;
1281
- for (var t = 1; t < limit; t++) {
1282
- cc0 = this.vertices[t % this.vertices.length];
1283
- cc1 = this.vertices[(t + 1) % this.vertices.length];
1284
- var edgeCenter = new Vertex(cc0.x + (cc1.x - cc0.x) / 2, cc0.y + (cc1.y - cc0.y) / 2);
1285
- qbezier.push(cc0);
1286
- qbezier.push(edgeCenter);
1287
- cc0 = cc1;
1288
- }
1289
- return qbezier;
1225
+ getClosestPoint(p) {
1226
+ var t = this.getClosestT(p);
1227
+ return this.vertAt(t);
1290
1228
  }
1291
1229
  /**
1292
- * Convert this polygon to a quadratic bezier curve, represented as an SVG data string.
1230
+ * The the minimal distance between this line and the specified point.
1293
1231
  *
1294
- * @method toQuadraticBezierSVGString
1295
- * @return {string} The 'd' part for an SVG 'path' element.
1232
+ * @method pointDistance
1233
+ * @param {Vertex} p The point (vertex) to measre the distance to.
1234
+ * @return {number} The absolute minimal distance.
1296
1235
  * @instance
1297
- * @memberof Polygon
1236
+ * @memberof VertTuple
1298
1237
  **/
1299
- toQuadraticBezierSVGString() {
1300
- var qdata = this.toQuadraticBezierData();
1301
- if (qdata.length == 0)
1302
- return "";
1303
- var buffer = ["M " + qdata[0].x + " " + qdata[0].y];
1304
- for (var i = 1; i < qdata.length; i += 2) {
1305
- buffer.push("Q " + qdata[i].x + " " + qdata[i].y + ", " + qdata[i + 1].x + " " + qdata[i + 1].y);
1306
- }
1307
- return buffer.join(" ");
1238
+ pointDistance(p) {
1239
+ // Taken From:
1240
+ // https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
1241
+ return Math.sqrt(VertTuple.vtutils.dist2(p, this.vertAt(this.getClosestT(p))));
1308
1242
  }
1309
1243
  /**
1310
- * Convert this polygon to a sequence of cubic Bézier curves.<br>
1311
- * <br>
1312
- * The first vertex in the returned array is the start point.<br>
1313
- * The following sequence are triplets of (first-control-point, secnond-control-point, end-point):<br>
1314
- * <pre>startPoint, controlPoint0_0, controlPoint1_1, pathPoint1, controlPoint1_0, controlPoint1_1, ..., endPoint</pre>
1244
+ * Create a deep clone of this instance.
1315
1245
  *
1316
- * @method toCubicBezierData
1317
- * @param {number=} threshold - An optional threshold (default=1.0) how strong the curve segments
1318
- * should over-/under-drive. Should be between 0.0 and 1.0 for best
1319
- * results but other values are allowed.
1320
- * @return {Vertex[]} An array of 2d vertices that shape the cubic Bézier curve.
1246
+ * @method cloneLine
1247
+ * @return {T} A type safe clone if this instance.
1321
1248
  * @instance
1322
- * @memberof Polygon
1249
+ * @memberof VertTuple
1323
1250
  **/
1324
- toCubicBezierData(threshold) {
1325
- if (typeof threshold == "undefined")
1326
- threshold = 1.0;
1327
- if (this.vertices.length < 3)
1328
- return [];
1329
- var cbezier = [];
1330
- var a = this.vertices[0];
1331
- var b = this.vertices[1];
1332
- var edgeCenter = new Vertex(a.x + (b.x - a.x) / 2, a.y + (b.y - a.y) / 2);
1333
- cbezier.push(edgeCenter);
1334
- var limit = this.isOpen ? this.vertices.length - 1 : this.vertices.length;
1335
- for (var t = 0; t < limit; t++) {
1336
- var a = this.vertices[t % this.vertices.length];
1337
- var b = this.vertices[(t + 1) % this.vertices.length];
1338
- var c = this.vertices[(t + 2) % this.vertices.length];
1339
- var aCenter = new Vertex(a.x + (b.x - a.x) / 2, a.y + (b.y - a.y) / 2);
1340
- var bCenter = new Vertex(b.x + (c.x - b.x) / 2, b.y + (c.y - b.y) / 2);
1341
- var a2 = new Vertex(aCenter.x + (b.x - aCenter.x) * threshold, aCenter.y + (b.y - aCenter.y) * threshold);
1342
- var b0 = new Vertex(bCenter.x + (b.x - bCenter.x) * threshold, bCenter.y + (b.y - bCenter.y) * threshold);
1343
- cbezier.push(a2);
1344
- cbezier.push(b0);
1345
- cbezier.push(bCenter);
1346
- }
1347
- return cbezier;
1251
+ clone() {
1252
+ return this.factory(this.a.clone(), this.b.clone());
1348
1253
  }
1349
1254
  /**
1350
- * Convert this polygon to a cubic bezier curve, represented as an SVG data string.
1255
+ * Create a string representation of this line.
1351
1256
  *
1352
- * @method toCubicBezierSVGString
1353
- * @return {string} The 'd' part for an SVG 'path' element.
1354
- * @instance
1355
- * @memberof Polygon
1356
- **/
1357
- toCubicBezierSVGString(threshold) {
1358
- var qdata = this.toCubicBezierData(threshold);
1359
- if (qdata.length == 0)
1360
- return "";
1361
- var buffer = ["M " + qdata[0].x + " " + qdata[0].y];
1362
- for (var i = 1; i < qdata.length; i += 3) {
1363
- buffer.push("C " +
1364
- qdata[i].x +
1365
- " " +
1366
- qdata[i].y +
1367
- ", " +
1368
- qdata[i + 1].x +
1369
- " " +
1370
- qdata[i + 1].y +
1371
- ", " +
1372
- qdata[i + 2].x +
1373
- " " +
1374
- qdata[i + 2].y);
1375
- }
1376
- return buffer.join(" ");
1377
- }
1378
- /**
1379
- * Convert this polygon to a cubic bezier path instance.
1380
- *
1381
- * @method toCubicBezierPath
1382
- * @param {number} threshold - The threshold, usually from 0.0 to 1.0.
1383
- * @return {BezierPath} - A bezier path instance.
1257
+ * @method totring
1258
+ * @return {string} The string representing this line.
1384
1259
  * @instance
1385
- * @memberof Polygon
1260
+ * @memberof VertTuple
1386
1261
  **/
1387
- toCubicBezierPath(threshold) {
1388
- var qdata = this.toCubicBezierData(threshold);
1389
- // Conver the linear path vertices to a two-dimensional path array
1390
- var pathdata = [];
1391
- for (var i = 0; i + 3 < qdata.length; i += 3) {
1392
- pathdata.push([qdata[i], qdata[i + 3], qdata[i + 1], qdata[i + 2]]);
1393
- }
1394
- return BezierPath.fromArray(pathdata);
1262
+ toString() {
1263
+ return "{ a : " + this.a.toString() + ", b : " + this.b.toString() + " }";
1395
1264
  }
1396
1265
  /**
1397
1266
  * This function should invalidate any installed listeners and invalidate this object.
@@ -1399,476 +1268,857 @@ class Polygon {
1399
1268
  * should not be used.
1400
1269
  */
1401
1270
  destroy() {
1402
- for (var i = 0; i < this.vertices.length; i++) {
1403
- this.vertices[i].destroy();
1404
- }
1271
+ this.a.destroy();
1272
+ this.b.destroy();
1405
1273
  this.isDestroyed = true;
1406
1274
  }
1407
1275
  }
1408
- Polygon.utils = {
1409
- /**
1410
- * Calculate the area of the given polygon (unsigned).
1411
- *
1412
- * Note that this does not work for self-intersecting polygons.
1413
- *
1414
- * @name area
1415
- * @return {number}
1416
- */
1417
- area(vertices) {
1418
- // Found at:
1419
- // https://stackoverflow.com/questions/16285134/calculating-polygon-area
1420
- let total = 0.0;
1421
- for (var i = 0, l = vertices.length; i < l; i++) {
1422
- const addX = vertices[i].x;
1423
- const addY = vertices[(i + 1) % l].y;
1424
- const subX = vertices[(i + 1) % l].x;
1425
- const subY = vertices[i].y;
1426
- total += addX * addY * 0.5;
1427
- total -= subX * subY * 0.5;
1428
- }
1429
- return Math.abs(total);
1430
- },
1431
- /**
1432
- * Calulate the signed polyon area by interpreting the polygon as a matrix
1433
- * and calculating its determinant.
1434
- *
1435
- * @name signedArea
1436
- * @return {number}
1437
- */
1438
- signedArea(vertices) {
1439
- let sum = 0;
1440
- const n = vertices.length;
1441
- for (var i = 0; i < n; i++) {
1442
- const j = (i + 1) % n;
1443
- sum += (vertices[j].x - vertices[i].x) * (vertices[i].y + vertices[j].y);
1444
- }
1445
- return sum;
1276
+ /**
1277
+ * @private
1278
+ **/
1279
+ VertTuple.vtutils = {
1280
+ dist2: (v, w) => {
1281
+ return (v.x - w.x) * (v.x - w.x) + (v.y - w.y) * (v.y - w.y);
1446
1282
  }
1447
1283
  };
1448
1284
 
1449
1285
  /**
1450
1286
  * @author Ikaros Kappler
1451
- * @date 2020-05-11
1452
- * @modified 2020-10-30 Added the static computeFromVertices function.
1453
- * @modified 2020-11-19 Set min, max, width and height to private.
1454
- * @modified 2021-02-02 Added the `toPolygon` method.
1455
- * @modified 2021-06-21 (mid-summer) Added `getCenter` method.
1456
- * @modified 2022-02-01 Added the `toString` function.
1457
- * @modified 2022-10-09 Added the `fromDimension` function.
1458
- * @modified 2022-11-28 Added the `clone` method.
1459
- * @version 1.6.0
1287
+ * @date 2016-03-12
1288
+ * @modified 2018-12-05 Refactored the code from the morley-triangle script.
1289
+ * @modified 2019-03-20 Added JSDoc tags.
1290
+ * @modified 2019-04-28 Fixed a bug in the Line.sub( Vertex ) function (was not working).
1291
+ * @modified 2019-09-02 Added the Line.add( Vertex ) function.
1292
+ * @modified 2019-09-02 Added the Line.denominator( Line ) function.
1293
+ * @modified 2019-09-02 Added the Line.colinear( Line ) function.
1294
+ * @modified 2019-09-02 Fixed an error in the Line.intersection( Line ) function (class Point was renamed to Vertex).
1295
+ * @modified 2019-12-15 Added the Line.moveTo(Vertex) function.
1296
+ * @modified 2020-03-16 The Line.angle(Line) parameter is now optional. The baseline (x-axis) will be used if not defined.
1297
+ * @modified 2020-03-23 Ported to Typescript from JS.
1298
+ * @modified 2020-12-04 The `intersection` function returns undefined if both lines are parallel.
1299
+ * @modified 2022-02-02 Added the `destroy` method.
1300
+ * @modified 2022-10-09 Changed the actual return value of the `intersection` function to null (was undefined before).
1301
+ * @modified 2022-10-17 Adding these methods from the `PathSegment` interface: getStartPoint, getEndPoint, revert.
1302
+ * @modified 2023-09-25 Changed param type of `intersection()` from Line to VertTuple.
1303
+ * @version 2.3.0
1304
+ *
1305
+ * @file Line
1306
+ * @public
1460
1307
  **/
1461
1308
  /**
1462
- * @classdesc A bounds class with min and max values. Implementing IBounds.
1309
+ * @classdesc A line consists of two vertices a and b.<br>
1310
+ * <br>
1311
+ * This is some refactored code from my 'Morley Triangle' test<br>
1312
+ * https://github.com/IkarosKappler/morleys-trisector-theorem
1463
1313
  *
1464
- * @requires XYCoords
1465
1314
  * @requires Vertex
1466
- * @requires IBounds
1467
- **/
1468
- class Bounds {
1315
+ */
1316
+ class Line extends VertTuple {
1469
1317
  /**
1470
- * The constructor.
1318
+ * Creates an instance of Line.
1471
1319
  *
1472
1320
  * @constructor
1473
- * @name Bounds
1474
- * @param {XYCoords} min - The min values (x,y) as a XYCoords tuple.
1475
- * @param {XYCoords} max - The max values (x,y) as a XYCoords tuple.
1321
+ * @name Line
1322
+ * @param {Vertex} a The line's first point.
1323
+ * @param {Vertex} b The line's second point.
1476
1324
  **/
1477
- constructor(min, max) {
1478
- this.min = min;
1479
- this.max = max;
1480
- this.width = max.x - min.x;
1481
- this.height = max.y - min.y;
1325
+ constructor(a, b) {
1326
+ super(a, b, (a, b) => new Line(a, b));
1327
+ /**
1328
+ * Required to generate proper CSS classes and other class related IDs.
1329
+ **/
1330
+ this.className = "Line";
1482
1331
  }
1483
1332
  /**
1484
- * Convert this rectangular bounding box to a polygon with four vertices.
1333
+ * Get the intersection if this line and the specified line.
1485
1334
  *
1486
- * @method toPolygon
1335
+ * @method intersection
1336
+ * @param {Line} line The second line.
1337
+ * @return {Vertex|undefined} The intersection (may lie outside the end-points) or `undefined` if both lines are parallel.
1487
1338
  * @instance
1488
- * @memberof Bounds
1489
- * @return {Polygon} This bound rectangle as a polygon.
1490
- */
1491
- toPolygon() {
1492
- return new Polygon([new Vertex(this.min), new Vertex(this.max.x, this.min.y), new Vertex(this.max), new Vertex(this.min.x, this.max.y)], false);
1339
+ * @memberof Line
1340
+ **/
1341
+ // !!! DO NOT MOVE TO VertTuple
1342
+ intersection(line) {
1343
+ const denominator = this.denominator(line);
1344
+ if (denominator == 0) {
1345
+ return null;
1346
+ }
1347
+ let a = this.a.y - line.a.y;
1348
+ let b = this.a.x - line.a.x;
1349
+ const numerator1 = (line.b.x - line.a.x) * a - (line.b.y - line.a.y) * b;
1350
+ const numerator2 = (this.b.x - this.a.x) * a - (this.b.y - this.a.y) * b;
1351
+ a = numerator1 / denominator; // NaN if parallel lines
1352
+ b = numerator2 / denominator;
1353
+ // Catch NaN?
1354
+ const x = this.a.x + a * (this.b.x - this.a.x);
1355
+ const y = this.a.y + a * (this.b.y - this.a.y);
1356
+ if (isNaN(a) || isNaN(x) || isNaN(y)) {
1357
+ return null;
1358
+ }
1359
+ // if we cast these lines infinitely in both directions, they intersect here:
1360
+ return new Vertex(x, y);
1493
1361
  }
1362
+ //--- Implement PathSegment ---
1494
1363
  /**
1495
- * Get the center of this boinding box.
1364
+ * Get the start point of this path segment.
1496
1365
  *
1497
- * @method getCenter
1498
- * @instance
1499
- * @memberof Bounds
1500
- * @returns {Vertex} The center of these bounds.
1366
+ * @method getStartPoint
1367
+ * @memberof PathSegment
1368
+ * @return {Vertex} The start point of this path segment.
1501
1369
  */
1502
- getCenter() {
1503
- return new Vertex(this.min.x + (this.max.x - this.min.x) / 2.0, this.min.y + (this.max.y - this.min.y) / 2);
1370
+ getStartPoint() {
1371
+ return this.a;
1504
1372
  }
1505
1373
  /**
1506
- * Convert these bounds to a human readable form.
1507
- *
1508
- * Note: the returned format might change in the future, so please do not
1509
- * rely on the returned string format.
1374
+ * Get the end point of this path segment.
1510
1375
  *
1511
- * @method toString
1512
- * @instance
1513
- * @memberof Bounds
1514
- * @returns {string} Get these bounds in a human readable form.
1376
+ * @method getEndPoint
1377
+ * @memberof PathSegment
1378
+ * @return {Vertex} The end point of this path segment.
1515
1379
  */
1516
- toString() {
1517
- return `{ min: ${this.min.toString()}, max : ${this.max.toString()}, width: ${this.width}, height : ${this.height} }`;
1380
+ getEndPoint() {
1381
+ return this.b;
1518
1382
  }
1519
1383
  /**
1520
- * Clone this bounds object (create a deep clone).
1384
+ * Get the tangent's end point at the start point of this segment.
1521
1385
  *
1522
- * @method clone
1523
- * @instance
1524
- * @memberof Bounds
1525
- * @returns {Bounds} Creates a deep clone of this bounds object.
1386
+ * @method getStartTangent
1387
+ * @memberof PathSegment
1388
+ * @return {Vertex} The end point of the starting point's tangent.
1526
1389
  */
1527
- clone() {
1528
- return new Bounds({ x: this.min.x, y: this.min.y }, { x: this.max.x, y: this.max.y });
1390
+ getStartTangent() {
1391
+ return this.b;
1529
1392
  }
1530
1393
  /**
1531
- * Compute the minimal bounding box for a given set of vertices.
1394
+ * Get the tangent's end point at the end point of this segment.
1532
1395
  *
1533
- * An empty vertex array will return an empty bounding box located at (0,0).
1396
+ * @method getEndTangent
1397
+ * @memberof PathSegment
1398
+ * @return {Vertex} The end point of the ending point's tangent.
1399
+ */
1400
+ getEndTangent() {
1401
+ return this.a;
1402
+ }
1403
+ /**
1404
+ * Inverse this path segment (in-place) and return this same instance (useful for chaining).
1534
1405
  *
1535
- * @static
1536
- * @method computeFromVertices
1537
- * @memberof Bounds
1538
- * @param {Array<Vertex>} vertices - The set of vertices you want to get the bounding box for.
1539
- * @return The minimal Bounds for the given vertices.
1540
- **/
1541
- static computeFromVertices(vertices) {
1542
- if (vertices.length == 0)
1543
- return new Bounds(new Vertex(0, 0), new Vertex(0, 0));
1544
- let xMin = vertices[0].x;
1545
- let xMax = vertices[0].x;
1546
- let yMin = vertices[0].y;
1547
- let yMax = vertices[0].y;
1548
- let vert;
1549
- for (var i in vertices) {
1550
- vert = vertices[i];
1551
- xMin = Math.min(xMin, vert.x);
1552
- xMax = Math.max(xMax, vert.x);
1553
- yMin = Math.min(yMin, vert.y);
1554
- yMax = Math.max(yMax, vert.y);
1555
- }
1556
- return new Bounds(new Vertex(xMin, yMin), new Vertex(xMax, yMax));
1557
- }
1558
- /**
1559
- * Create a new `Bounds` instance just from `width` and `height`, located at (0,0) or the optionally given origin.
1560
- *
1561
- * @param {number} width - The width of the bounds
1562
- * @param {number} height - The height of the bounds
1563
- * @param {XYCoords={x:0,y:0}} origin - [optional] A origin to locate the new Bounds object at.
1564
- * @returns {Bounds} A new `Bounds` instance width given width and height, located at (0,0) or the given origin..
1406
+ * @method reverse
1407
+ * @memberof PathSegment
1408
+ * @return {PathSegment} This path segment instance (for chaining).
1565
1409
  */
1566
- static fromDimension(width, height, origin) {
1567
- return new Bounds(origin !== null && origin !== void 0 ? origin : { x: 0, y: 0 }, { x: (origin ? origin.x : 0) + width, y: (origin ? origin.y : 0) + height });
1410
+ reverse() {
1411
+ var tmp = this.a;
1412
+ this.a = this.b;
1413
+ this.b = tmp;
1414
+ return this;
1568
1415
  }
1569
- } // END class bounds
1416
+ }
1570
1417
 
1571
1418
  /**
1572
- * @author Ikaros Kappler
1573
- * @date 2020-03-24
1574
- * @modified 2020-05-04 Fixed a serious bug in the pointDistance function.
1575
- * @modified 2020-05-12 The angle(line) param was still not optional. Changed that.
1576
- * @modified 2020-11-11 Generalized the `add` and `sub` param from `Vertex` to `XYCoords`.
1577
- * @modified 2020-12-04 Changed`vtutils.dist2` params from `Vertex` to `XYCoords` (generalized).
1578
- * @modified 2020-12-04 Changed `getClosestT` param from `Vertex` to `XYCoords` (generalized).
1579
- * @modified 2020-12-04 Added the `hasPoint(XYCoords)` function.
1419
+ * @author Ikaros Kappler
1420
+ * @date 2018-04-14
1421
+ * @modified 2018-11-17 Added the containsVert function.
1422
+ * @modified 2018-12-04 Added the toSVGString function.
1423
+ * @modified 2019-03-20 Added JSDoc tags.
1424
+ * @modified 2019-10-25 Added the scale function.
1425
+ * @modified 2019-11-06 JSDoc update.
1426
+ * @modified 2019-11-07 Added toCubicBezierPath(number) function.
1427
+ * @modified 2019-11-22 Added the rotate(number,Vertex) function.
1428
+ * @modified 2020-03-24 Ported this class from vanilla-JS to Typescript.
1429
+ * @modified 2020-10-30 Added the `addVertex` function.
1430
+ * @modified 2020-10-31 Added the `getVertexAt` function.
1431
+ * @modified 2020-11-06 Added the `move` function.
1432
+ * @modified 2020-11-10 Added the `getBounds` function.
1433
+ * @modified 2020-11-11 Generalized `move(Vertex)` to `move(XYCoords)`.
1580
1434
  * @modified 2021-01-20 Added UID.
1435
+ * @modified 2021-01-29 Added the `signedArea` function (was global function in the demos before).
1436
+ * @modified 2021-01-29 Added the `isClockwise` function.
1437
+ * @modified 2021-01-29 Added the `area` function.
1438
+ * @modified 2021-01-29 Changed the param type for `containsVert` from Vertex to XYCoords.
1439
+ * @modified 2021-12-14 Added the `perimeter()` function.
1440
+ * @modified 2021-12-16 Added the `getEvenDistributionPolygon()` function.
1581
1441
  * @modified 2022-02-02 Added the `destroy` method.
1582
- * @version 1.2.0
1583
- */
1442
+ * @modified 2022-02-02 Cleared the `Polygon.toSVGString` function (deprecated). Use `drawutilssvg` instead.
1443
+ * @modified 2022-03-08 Added the `Polygon.clone()` function.
1444
+ * @modified 2023-09-25 Added the `Polygon.getInterpolationPolygon(number)` function.
1445
+ * @modified 2023-09-25 Added the `Polygon.lineIntersections(Line,boolean)` function.
1446
+ * @modified 2023-09-29 Added the `Polygon.closestLineIntersection(Line,boolean)` function.
1447
+ * @version 1.11.0
1448
+ *
1449
+ * @file Polygon
1450
+ * @public
1451
+ **/
1584
1452
  /**
1585
- * @classdesc An abstract base classes for vertex tuple constructs, like Lines or Vectors.
1586
- * @abstract
1453
+ * @classdesc A polygon class. Any polygon consists of an array of vertices; polygons can be open or closed.
1454
+ *
1455
+ * @requires BezierPath
1456
+ * @requires Bounds
1457
+ * @requires SVGSerializabe
1587
1458
  * @requires UID
1459
+ * @requires UIDGenerator
1588
1460
  * @requires Vertex
1589
1461
  * @requires XYCoords
1590
1462
  */
1591
- class VertTuple {
1463
+ class Polygon {
1592
1464
  /**
1593
- * Creates an instance.
1465
+ * The constructor.
1594
1466
  *
1595
1467
  * @constructor
1596
- * @name VertTuple
1597
- * @param {Vertex} a The tuple's first point.
1598
- * @param {Vertex} b The tuple's second point.
1468
+ * @name Polygon
1469
+ * @param {Vertex[]} vertices - An array of 2d vertices that shape the polygon.
1470
+ * @param {boolean} isOpen - Indicates if the polygon should be rendered as an open or closed shape.
1599
1471
  **/
1600
- constructor(a, b, factory) {
1472
+ constructor(vertices, isOpen) {
1473
+ /**
1474
+ * Required to generate proper CSS classes and other class related IDs.
1475
+ **/
1476
+ this.className = "Polygon";
1601
1477
  this.uid = UIDGenerator.next();
1602
- this.a = a;
1603
- this.b = b;
1604
- this.factory = factory;
1478
+ if (typeof vertices == "undefined")
1479
+ vertices = [];
1480
+ this.vertices = vertices;
1481
+ this.isOpen = isOpen || false;
1605
1482
  }
1606
1483
  /**
1607
- * Get the length of this line.
1484
+ * Add a vertex to the end of the `vertices` array.
1608
1485
  *
1609
- * @method length
1486
+ * @method addVert
1487
+ * @param {Vertex} vert - The vertex to add.
1610
1488
  * @instance
1611
- * @memberof VertTuple
1489
+ * @memberof Polygon
1612
1490
  **/
1613
- length() {
1614
- return Math.sqrt(Math.pow(this.b.x - this.a.x, 2) + Math.pow(this.b.y - this.a.y, 2));
1491
+ addVertex(vert) {
1492
+ this.vertices.push(vert);
1615
1493
  }
1616
1494
  /**
1617
- * Set the length of this vector to the given amount. This only works if this
1618
- * vector is not a null vector.
1495
+ * Get the polygon vertex at the given position (index).
1619
1496
  *
1620
- * @method setLength
1621
- * @param {number} length - The desired length.
1622
- * @memberof VertTuple
1623
- * @return {T} this (for chaining)
1624
- **/
1625
- setLength(length) {
1626
- return this.scale(length / this.length());
1627
- }
1628
- /**
1629
- * Substract the given vertex from this line's end points.
1497
+ * The index may exceed the total vertex count, and will be wrapped around then (modulo).
1630
1498
  *
1631
- * @method sub
1632
- * @param {XYCoords} amount The amount (x,y) to substract.
1633
- * @return {VertTuple} this
1499
+ * For k >= 0:
1500
+ * - getVertexAt( vertices.length ) == getVertexAt( 0 )
1501
+ * - getVertexAt( vertices.length + k ) == getVertexAt( k )
1502
+ * - getVertexAt( -k ) == getVertexAt( vertices.length -k )
1503
+ *
1504
+ * @metho getVertexAt
1505
+ * @param {number} index - The index of the desired vertex.
1634
1506
  * @instance
1635
- * @memberof VertTuple
1507
+ * @memberof Polygon
1508
+ * @return {Vertex} At the given index.
1636
1509
  **/
1637
- sub(amount) {
1638
- this.a.sub(amount);
1639
- this.b.sub(amount);
1640
- return this;
1510
+ getVertexAt(index) {
1511
+ if (index < 0)
1512
+ return this.vertices[this.vertices.length - (Math.abs(index) % this.vertices.length)];
1513
+ else
1514
+ return this.vertices[index % this.vertices.length];
1641
1515
  }
1642
1516
  /**
1643
- * Add the given vertex to this line's end points.
1517
+ * Move the polygon's vertices by the given amount.
1644
1518
  *
1645
- * @method add
1646
- * @param {XYCoords} amount The amount (x,y) to add.
1647
- * @return {Line} this
1519
+ * @method move
1520
+ * @param {XYCoords} amount - The amount to move.
1648
1521
  * @instance
1649
- * @memberof VertTuple
1522
+ * @memberof Polygon
1523
+ * @return {Polygon} this for chaining
1650
1524
  **/
1651
- add(amount) {
1652
- this.a.add(amount);
1653
- this.b.add(amount);
1525
+ move(amount) {
1526
+ for (var i in this.vertices) {
1527
+ this.vertices[i].add(amount);
1528
+ }
1654
1529
  return this;
1655
1530
  }
1656
1531
  /**
1657
- * Normalize this line (set to length 1).
1532
+ * Check if the given vertex is inside this polygon.<br>
1533
+ * <br>
1534
+ * Ray-casting algorithm found at<br>
1535
+ * https://stackoverflow.com/questions/22521982/check-if-point-inside-a-polygon
1658
1536
  *
1659
- * @method normalize
1660
- * @return {VertTuple} this
1537
+ * @method containsVert
1538
+ * @param {XYCoords} vert - The vertex to check. The new x-component.
1539
+ * @return {boolean} True if the passed vertex is inside this polygon. The polygon is considered closed.
1661
1540
  * @instance
1662
- * @memberof VertTuple
1541
+ * @memberof Polygon
1663
1542
  **/
1664
- normalize() {
1665
- this.b.set(this.a.x + (this.b.x - this.a.x) / this.length(), this.a.y + (this.b.y - this.a.y) / this.length());
1666
- return this;
1543
+ containsVert(vert) {
1544
+ // ray-casting algorithm based on
1545
+ // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
1546
+ var inside = false;
1547
+ for (var i = 0, j = this.vertices.length - 1; i < this.vertices.length; j = i++) {
1548
+ let xi = this.vertices[i].x, yi = this.vertices[i].y;
1549
+ let xj = this.vertices[j].x, yj = this.vertices[j].y;
1550
+ var intersect = yi > vert.y != yj > vert.y && vert.x < ((xj - xi) * (vert.y - yi)) / (yj - yi) + xi;
1551
+ if (intersect)
1552
+ inside = !inside;
1553
+ }
1554
+ return inside;
1667
1555
  }
1668
1556
  /**
1669
- * Scale this line by the given factor.
1557
+ * Calculate the area of the given polygon (unsigned).
1670
1558
  *
1671
- * @method scale
1672
- * @param {number} factor The factor for scaling (1.0 means no scale).
1673
- * @return {VertTuple} this
1559
+ * Note that this does not work for self-intersecting polygons.
1560
+ *
1561
+ * @method area
1674
1562
  * @instance
1675
- * @memberof VertTuple
1676
- **/
1677
- scale(factor) {
1678
- this.b.set(this.a.x + (this.b.x - this.a.x) * factor, this.a.y + (this.b.y - this.a.y) * factor);
1679
- return this;
1563
+ * @memberof Polygon
1564
+ * @return {number}
1565
+ */
1566
+ area() {
1567
+ return Polygon.utils.area(this.vertices);
1680
1568
  }
1681
1569
  /**
1682
- * Move this line to a new location.
1570
+ * Calulate the signed polyon area by interpreting the polygon as a matrix
1571
+ * and calculating its determinant.
1683
1572
  *
1684
- * @method moveTo
1685
- * @param {Vertex} newA - The new desired location of 'a'. Vertex 'b' will be moved, too.
1686
- * @return {VertTuple} this
1573
+ * @method signedArea
1687
1574
  * @instance
1688
- * @memberof VertTuple
1689
- **/
1690
- moveTo(newA) {
1691
- let diff = this.a.difference(newA);
1692
- this.a.add(diff);
1693
- this.b.add(diff);
1694
- return this;
1575
+ * @memberof Polygon
1576
+ * @return {number}
1577
+ */
1578
+ signedArea() {
1579
+ return Polygon.utils.signedArea(this.vertices);
1695
1580
  }
1696
1581
  /**
1697
- * Get the angle between this and the passed line (in radians).
1582
+ * Get the winding order of this polgon: clockwise or counterclockwise.
1698
1583
  *
1699
- * @method angle
1700
- * @param {VertTuple} line - (optional) The line to calculate the angle to. If null the baseline (x-axis) will be used.
1701
- * @return {number} this
1584
+ * @method isClockwise
1702
1585
  * @instance
1703
- * @memberof VertTuple
1704
- **/
1705
- angle(line) {
1706
- if (line == null || typeof line == "undefined") {
1707
- line = this.factory(new Vertex(0, 0), new Vertex(100, 0));
1708
- }
1709
- // Compute the angle from x axis and the return the difference :)
1710
- const v0 = this.b.clone().sub(this.a);
1711
- const v1 = line.b.clone().sub(line.a);
1712
- // Thank you, Javascript, for this second atan function. No additional math is needed here!
1713
- // The result might be negative, but isn't it usually nicer to determine angles in positive values only?
1714
- return Math.atan2(v1.x, v1.y) - Math.atan2(v0.x, v0.y);
1586
+ * @memberof Polygon
1587
+ * @return {boolean}
1588
+ */
1589
+ isClockwise() {
1590
+ return Polygon.utils.signedArea(this.vertices) < 0;
1715
1591
  }
1716
1592
  /**
1717
- * Get line point at position t in [0 ... 1]:<br>
1718
- * <pre>[P(0)]=[A]--------------------[P(t)]------[B]=[P(1)]</pre><br>
1719
- * <br>
1720
- * The counterpart of this function is Line.getClosestT(Vertex).
1593
+ * Get the perimeter of this polygon.
1594
+ * The perimeter is the absolute length of the outline.
1721
1595
  *
1722
- * @method vertAt
1723
- * @param {number} t The position scalar.
1724
- * @return {Vertex} The vertex a position t.
1596
+ * If this polygon is open then the last segment (connecting the first and the
1597
+ * last vertex) will be skipped.
1598
+ *
1599
+ * @method perimeter
1725
1600
  * @instance
1726
- * @memberof VertTuple
1727
- **/
1728
- vertAt(t) {
1729
- return new Vertex(this.a.x + (this.b.x - this.a.x) * t, this.a.y + (this.b.y - this.a.y) * t);
1601
+ * @memberof Polygon
1602
+ * @return {number}
1603
+ */
1604
+ perimeter() {
1605
+ let length = 0;
1606
+ for (var i = 1; i < this.vertices.length; i++) {
1607
+ length += this.vertices[i - 1].distance(this.vertices[i]);
1608
+ }
1609
+ if (!this.isOpen && this.vertices.length > 1) {
1610
+ length += this.vertices[0].distance(this.vertices[this.vertices.length - 1]);
1611
+ }
1612
+ return length;
1730
1613
  }
1731
1614
  /**
1732
- * Get the denominator of this and the given line.
1615
+ * Scale the polygon relative to the given center.
1733
1616
  *
1734
- * If the denominator is zero (or close to zero) both line are co-linear.
1617
+ * @method scale
1618
+ * @param {number} factor - The scale factor.
1619
+ * @param {Vertex} center - The center of scaling.
1620
+ * @return {Polygon} this, for chaining.
1621
+ * @instance
1622
+ * @memberof Polygon
1623
+ **/
1624
+ scale(factor, center) {
1625
+ for (var i in this.vertices) {
1626
+ if (typeof this.vertices[i].scale == "function")
1627
+ this.vertices[i].scale(factor, center);
1628
+ else
1629
+ console.log("There seems to be a null vertex!", this.vertices[i]);
1630
+ }
1631
+ return this;
1632
+ }
1633
+ /**
1634
+ * Rotate the polygon around the given center.
1735
1635
  *
1736
- * @method denominator
1737
- * @param {VertTuple} line
1636
+ * @method rotate
1637
+ * @param {number} angle - The rotation angle.
1638
+ * @param {Vertex} center - The center of rotation.
1738
1639
  * @instance
1739
- * @memberof VertTuple
1740
- * @return {Number}
1640
+ * @memberof Polygon
1641
+ * @return {Polygon} this, for chaining.
1741
1642
  **/
1742
- denominator(line) {
1743
- // http://jsfiddle.net/justin_c_rounds/Gd2S2/
1744
- return (line.b.y - line.a.y) * (this.b.x - this.a.x) - (line.b.x - line.a.x) * (this.b.y - this.a.y);
1643
+ rotate(angle, center) {
1644
+ for (var i in this.vertices) {
1645
+ this.vertices[i].rotate(angle, center);
1646
+ }
1647
+ return this;
1745
1648
  }
1746
1649
  /**
1747
- * Checks if this and the given line are co-linear.
1650
+ * Get all line intersections with this polygon.
1748
1651
  *
1749
- * The constant Vertex.EPSILON is used for tolerance.
1652
+ * See demo `47-closest-vector-projection-on-polygon` for how it works.
1750
1653
  *
1751
- * @method colinear
1752
- * @param {VertTuple} line
1654
+ * @param {VertTuple} line - The line to find intersections with.
1655
+ * @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
1656
+ * @returns {Array<Vertex>} - An array of all intersections within the polygon bounds.
1657
+ */
1658
+ lineIntersections(line, inVectorBoundsOnly = false) {
1659
+ // Find the intersections of all lines inside the edge bounds
1660
+ const intersectionPoints = [];
1661
+ for (var i = 0; i < this.vertices.length; i++) {
1662
+ const polyLine = new Line(this.vertices[i], this.vertices[(i + 1) % this.vertices.length]);
1663
+ const intersection = polyLine.intersection(line);
1664
+ // true => only inside bounds
1665
+ // ignore last edge if open
1666
+ if ((!this.isOpen || i + 1 !== this.vertices.length) &&
1667
+ intersection !== null &&
1668
+ polyLine.hasPoint(intersection, true) &&
1669
+ (!inVectorBoundsOnly || line.hasPoint(intersection, inVectorBoundsOnly))) {
1670
+ intersectionPoints.push(intersection);
1671
+ }
1672
+ }
1673
+ return intersectionPoints;
1674
+ }
1675
+ /**
1676
+ * Get the closest line-polygon-intersection point (closest the line point A).
1677
+ *
1678
+ * See demo `47-closest-vector-projection-on-polygon` for how it works.
1679
+ *
1680
+ * @param {VertTuple} line - The line to find intersections with.
1681
+ * @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are considered (located strictly between start and end vertex).
1682
+ * @returns {Array<Vertex>} - An array of all intersections within the polygon bounds.
1683
+ */
1684
+ closestLineIntersection(line, inVectorBoundsOnly = false) {
1685
+ const allIntersections = this.lineIntersections(line, inVectorBoundsOnly);
1686
+ if (allIntersections.length <= 0) {
1687
+ // Empty polygon -> no intersections
1688
+ return null;
1689
+ }
1690
+ // Find the closest intersection
1691
+ let closestIntersection = new Vertex(Number.MAX_VALUE, Number.MAX_VALUE);
1692
+ let curDist = Number.MAX_VALUE;
1693
+ for (var i in allIntersections) {
1694
+ const curVert = allIntersections[i];
1695
+ const dist = curVert.distance(line.a);
1696
+ if (dist < curDist) {
1697
+ // && line.hasPoint(curVert)) {
1698
+ curDist = dist;
1699
+ closestIntersection = curVert;
1700
+ }
1701
+ }
1702
+ return closestIntersection;
1703
+ }
1704
+ /**
1705
+ * Construct a new polygon from this polygon with more vertices on each edge. The
1706
+ * interpolation count determines the number of additional vertices on each edge.
1707
+ * An interpolation count of `0` will return a polygon that equals the source
1708
+ * polygon.
1709
+ *
1710
+ * @param {number} interpolationCount
1711
+ * @returns {Polygon} A polygon with `interpolationCount` more vertices (as as factor).
1712
+ */
1713
+ getInterpolationPolygon(interpolationCount) {
1714
+ const verts = [];
1715
+ for (var i = 0; i < this.vertices.length; i++) {
1716
+ const curVert = this.vertices[i];
1717
+ const nextVert = this.vertices[(i + 1) % this.vertices.length];
1718
+ verts.push(curVert.clone());
1719
+ // Add interpolation points
1720
+ if (!this.isOpen || i + 1 !== this.vertices.length) {
1721
+ const lerpAmount = 1.0 / (interpolationCount + 1);
1722
+ for (var j = 1; j <= interpolationCount; j++) {
1723
+ verts.push(curVert.clone().lerp(nextVert, lerpAmount * j));
1724
+ }
1725
+ }
1726
+ }
1727
+ return new Polygon(verts, this.isOpen);
1728
+ }
1729
+ /**
1730
+ * Convert this polygon into a new polygon with n evenly distributed vertices.
1731
+ *
1732
+ * @param {number} pointCount - Must not be negative.
1733
+ */
1734
+ getEvenDistributionPolygon(pointCount) {
1735
+ if (pointCount <= 0) {
1736
+ throw new Error("pointCount must be larger than zero; is " + pointCount + ".");
1737
+ }
1738
+ const result = new Polygon([], this.isOpen);
1739
+ if (this.vertices.length === 0) {
1740
+ return result;
1741
+ }
1742
+ // Fetch and add the start point from the source polygon
1743
+ let polygonPoint = new Vertex(this.vertices[0]);
1744
+ result.vertices.push(polygonPoint);
1745
+ if (this.vertices.length === 1) {
1746
+ return result;
1747
+ }
1748
+ const perimeter = this.perimeter();
1749
+ const stepSize = perimeter / pointCount;
1750
+ const n = this.vertices.length;
1751
+ let polygonIndex = 1;
1752
+ let nextPolygonPoint = new Vertex(this.vertices[1]);
1753
+ let segmentLength = polygonPoint.distance(nextPolygonPoint);
1754
+ let loopMax = this.isOpen ? n : n + 1;
1755
+ let curSegmentU = stepSize;
1756
+ var i = 1;
1757
+ while (i < pointCount && polygonIndex < loopMax) {
1758
+ // Check if next eq point is inside this segment
1759
+ if (curSegmentU < segmentLength) {
1760
+ let newPoint = polygonPoint.clone().lerpAbs(nextPolygonPoint, curSegmentU);
1761
+ result.vertices.push(newPoint);
1762
+ curSegmentU += stepSize;
1763
+ i++;
1764
+ }
1765
+ else {
1766
+ polygonIndex++;
1767
+ polygonPoint = nextPolygonPoint;
1768
+ nextPolygonPoint = new Vertex(this.vertices[polygonIndex % n]);
1769
+ curSegmentU = curSegmentU - segmentLength;
1770
+ segmentLength = polygonPoint.distance(nextPolygonPoint);
1771
+ }
1772
+ }
1773
+ return result;
1774
+ }
1775
+ /**
1776
+ * Get the bounding box (bounds) of this polygon.
1777
+ *
1778
+ * @method getBounds
1753
1779
  * @instance
1754
- * @memberof VertTuple
1755
- * @return true if both lines are co-linear.
1780
+ * @memberof Polygon
1781
+ * @return {Bounds} The rectangular bounds of this polygon.
1782
+ **/
1783
+ getBounds() {
1784
+ return Bounds.computeFromVertices(this.vertices);
1785
+ }
1786
+ /**
1787
+ * Create a deep copy of this polygon.
1788
+ *
1789
+ * @return {Polygon} The cloned polygon.
1756
1790
  */
1757
- colinear(line) {
1758
- return Math.abs(this.denominator(line)) < Vertex.EPSILON;
1791
+ clone() {
1792
+ return new Polygon(this.vertices.map(vert => vert.clone()), this.isOpen);
1759
1793
  }
1760
1794
  /**
1761
- * Get the closest position T from this line to the specified point.
1795
+ * Convert this polygon to a sequence of quadratic Bézier curves.<br>
1796
+ * <br>
1797
+ * The first vertex in the returned array is the start point.<br>
1798
+ * The following sequence are pairs of control-point-and-end-point:
1799
+ * <pre>startPoint, controlPoint0, pathPoint1, controlPoint1, pathPoint2, controlPoint2, ..., endPoint</pre>
1762
1800
  *
1763
- * The counterpart for this function is Line.vertAt(Number).
1801
+ * @method toQuadraticBezierData
1802
+ * @return {Vertex[]} An array of 2d vertices that shape the quadratic Bézier curve.
1803
+ * @instance
1804
+ * @memberof Polygon
1805
+ **/
1806
+ toQuadraticBezierData() {
1807
+ if (this.vertices.length < 3)
1808
+ return [];
1809
+ var qbezier = [];
1810
+ var cc0 = this.vertices[0];
1811
+ var cc1 = this.vertices[1];
1812
+ var edgeCenter = new Vertex(cc0.x + (cc1.x - cc0.x) / 2, cc0.y + (cc1.y - cc0.y) / 2);
1813
+ qbezier.push(edgeCenter);
1814
+ var limit = this.isOpen ? this.vertices.length : this.vertices.length + 1;
1815
+ for (var t = 1; t < limit; t++) {
1816
+ cc0 = this.vertices[t % this.vertices.length];
1817
+ cc1 = this.vertices[(t + 1) % this.vertices.length];
1818
+ var edgeCenter = new Vertex(cc0.x + (cc1.x - cc0.x) / 2, cc0.y + (cc1.y - cc0.y) / 2);
1819
+ qbezier.push(cc0);
1820
+ qbezier.push(edgeCenter);
1821
+ cc0 = cc1;
1822
+ }
1823
+ return qbezier;
1824
+ }
1825
+ /**
1826
+ * Convert this polygon to a quadratic bezier curve, represented as an SVG data string.
1764
1827
  *
1765
- * @name getClosetT
1766
- * @method getClosestT
1767
- * @param {XYCoords} p The point (vertex) to measure the distance to.
1768
- * @return {number} The line position t of minimal distance to p.
1828
+ * @method toQuadraticBezierSVGString
1829
+ * @return {string} The 'd' part for an SVG 'path' element.
1769
1830
  * @instance
1770
- * @memberof VertTuple
1831
+ * @memberof Polygon
1771
1832
  **/
1772
- getClosestT(p) {
1773
- var l2 = VertTuple.vtutils.dist2(this.a, this.b);
1774
- if (l2 === 0)
1775
- return 0;
1776
- var t = ((p.x - this.a.x) * (this.b.x - this.a.x) + (p.y - this.a.y) * (this.b.y - this.a.y)) / l2;
1777
- // Do not wrap to [0,1] here.
1778
- // Other results are of interest, too.
1779
- // t = Math.max(0, Math.min(1, t));
1780
- return t;
1833
+ toQuadraticBezierSVGString() {
1834
+ var qdata = this.toQuadraticBezierData();
1835
+ if (qdata.length == 0)
1836
+ return "";
1837
+ var buffer = ["M " + qdata[0].x + " " + qdata[0].y];
1838
+ for (var i = 1; i < qdata.length; i += 2) {
1839
+ buffer.push("Q " + qdata[i].x + " " + qdata[i].y + ", " + qdata[i + 1].x + " " + qdata[i + 1].y);
1840
+ }
1841
+ return buffer.join(" ");
1842
+ }
1843
+ /**
1844
+ * Convert this polygon to a sequence of cubic Bézier curves.<br>
1845
+ * <br>
1846
+ * The first vertex in the returned array is the start point.<br>
1847
+ * The following sequence are triplets of (first-control-point, secnond-control-point, end-point):<br>
1848
+ * <pre>startPoint, controlPoint0_0, controlPoint1_1, pathPoint1, controlPoint1_0, controlPoint1_1, ..., endPoint</pre>
1849
+ *
1850
+ * @method toCubicBezierData
1851
+ * @param {number=} threshold - An optional threshold (default=1.0) how strong the curve segments
1852
+ * should over-/under-drive. Should be between 0.0 and 1.0 for best
1853
+ * results but other values are allowed.
1854
+ * @return {Vertex[]} An array of 2d vertices that shape the cubic Bézier curve.
1855
+ * @instance
1856
+ * @memberof Polygon
1857
+ **/
1858
+ toCubicBezierData(threshold) {
1859
+ if (typeof threshold == "undefined")
1860
+ threshold = 1.0;
1861
+ if (this.vertices.length < 3)
1862
+ return [];
1863
+ var cbezier = [];
1864
+ var a = this.vertices[0];
1865
+ var b = this.vertices[1];
1866
+ var edgeCenter = new Vertex(a.x + (b.x - a.x) / 2, a.y + (b.y - a.y) / 2);
1867
+ cbezier.push(edgeCenter);
1868
+ var limit = this.isOpen ? this.vertices.length - 1 : this.vertices.length;
1869
+ for (var t = 0; t < limit; t++) {
1870
+ var a = this.vertices[t % this.vertices.length];
1871
+ var b = this.vertices[(t + 1) % this.vertices.length];
1872
+ var c = this.vertices[(t + 2) % this.vertices.length];
1873
+ var aCenter = new Vertex(a.x + (b.x - a.x) / 2, a.y + (b.y - a.y) / 2);
1874
+ var bCenter = new Vertex(b.x + (c.x - b.x) / 2, b.y + (c.y - b.y) / 2);
1875
+ var a2 = new Vertex(aCenter.x + (b.x - aCenter.x) * threshold, aCenter.y + (b.y - aCenter.y) * threshold);
1876
+ var b0 = new Vertex(bCenter.x + (b.x - bCenter.x) * threshold, bCenter.y + (b.y - bCenter.y) * threshold);
1877
+ cbezier.push(a2);
1878
+ cbezier.push(b0);
1879
+ cbezier.push(bCenter);
1880
+ }
1881
+ return cbezier;
1882
+ }
1883
+ /**
1884
+ * Convert this polygon to a cubic bezier curve, represented as an SVG data string.
1885
+ *
1886
+ * @method toCubicBezierSVGString
1887
+ * @return {string} The 'd' part for an SVG 'path' element.
1888
+ * @instance
1889
+ * @memberof Polygon
1890
+ **/
1891
+ toCubicBezierSVGString(threshold) {
1892
+ var qdata = this.toCubicBezierData(threshold);
1893
+ if (qdata.length == 0)
1894
+ return "";
1895
+ var buffer = ["M " + qdata[0].x + " " + qdata[0].y];
1896
+ for (var i = 1; i < qdata.length; i += 3) {
1897
+ buffer.push("C " +
1898
+ qdata[i].x +
1899
+ " " +
1900
+ qdata[i].y +
1901
+ ", " +
1902
+ qdata[i + 1].x +
1903
+ " " +
1904
+ qdata[i + 1].y +
1905
+ ", " +
1906
+ qdata[i + 2].x +
1907
+ " " +
1908
+ qdata[i + 2].y);
1909
+ }
1910
+ return buffer.join(" ");
1911
+ }
1912
+ /**
1913
+ * Convert this polygon to a cubic bezier path instance.
1914
+ *
1915
+ * @method toCubicBezierPath
1916
+ * @param {number} threshold - The threshold, usually from 0.0 to 1.0.
1917
+ * @return {BezierPath} - A bezier path instance.
1918
+ * @instance
1919
+ * @memberof Polygon
1920
+ **/
1921
+ toCubicBezierPath(threshold) {
1922
+ var qdata = this.toCubicBezierData(threshold);
1923
+ // Conver the linear path vertices to a two-dimensional path array
1924
+ var pathdata = [];
1925
+ for (var i = 0; i + 3 < qdata.length; i += 3) {
1926
+ pathdata.push([qdata[i], qdata[i + 3], qdata[i + 1], qdata[i + 2]]);
1927
+ }
1928
+ return BezierPath.fromArray(pathdata);
1929
+ }
1930
+ /**
1931
+ * This function should invalidate any installed listeners and invalidate this object.
1932
+ * After calling this function the object might not hold valid data any more and
1933
+ * should not be used.
1934
+ */
1935
+ destroy() {
1936
+ for (var i = 0; i < this.vertices.length; i++) {
1937
+ this.vertices[i].destroy();
1938
+ }
1939
+ this.isDestroyed = true;
1940
+ }
1941
+ }
1942
+ Polygon.utils = {
1943
+ /**
1944
+ * Calculate the area of the given polygon (unsigned).
1945
+ *
1946
+ * Note that this does not work for self-intersecting polygons.
1947
+ *
1948
+ * @name area
1949
+ * @return {number}
1950
+ */
1951
+ area(vertices) {
1952
+ // Found at:
1953
+ // https://stackoverflow.com/questions/16285134/calculating-polygon-area
1954
+ let total = 0.0;
1955
+ for (var i = 0, l = vertices.length; i < l; i++) {
1956
+ const addX = vertices[i].x;
1957
+ const addY = vertices[(i + 1) % l].y;
1958
+ const subX = vertices[(i + 1) % l].x;
1959
+ const subY = vertices[i].y;
1960
+ total += addX * addY * 0.5;
1961
+ total -= subX * subY * 0.5;
1962
+ }
1963
+ return Math.abs(total);
1964
+ },
1965
+ /**
1966
+ * Calulate the signed polyon area by interpreting the polygon as a matrix
1967
+ * and calculating its determinant.
1968
+ *
1969
+ * @name signedArea
1970
+ * @return {number}
1971
+ */
1972
+ signedArea(vertices) {
1973
+ let sum = 0;
1974
+ const n = vertices.length;
1975
+ for (var i = 0; i < n; i++) {
1976
+ const j = (i + 1) % n;
1977
+ sum += (vertices[j].x - vertices[i].x) * (vertices[i].y + vertices[j].y);
1978
+ }
1979
+ return sum;
1980
+ }
1981
+ };
1982
+
1983
+ /**
1984
+ * @author Ikaros Kappler
1985
+ * @date 2020-05-11
1986
+ * @modified 2020-10-30 Added the static computeFromVertices function.
1987
+ * @modified 2020-11-19 Set min, max, width and height to private.
1988
+ * @modified 2021-02-02 Added the `toPolygon` method.
1989
+ * @modified 2021-06-21 (mid-summer) Added `getCenter` method.
1990
+ * @modified 2022-02-01 Added the `toString` function.
1991
+ * @modified 2022-10-09 Added the `fromDimension` function.
1992
+ * @modified 2022-11-28 Added the `clone` method.
1993
+ * @modified 2023-09-29 Added the `randomPoint` method.
1994
+ * @version 1.7.0
1995
+ **/
1996
+ /**
1997
+ * @classdesc A bounds class with min and max values. Implementing IBounds.
1998
+ *
1999
+ * @requires XYCoords
2000
+ * @requires Vertex
2001
+ * @requires IBounds
2002
+ **/
2003
+ class Bounds {
2004
+ /**
2005
+ * The constructor.
2006
+ *
2007
+ * @constructor
2008
+ * @name Bounds
2009
+ * @param {XYCoords} min - The min values (x,y) as a XYCoords tuple.
2010
+ * @param {XYCoords} max - The max values (x,y) as a XYCoords tuple.
2011
+ **/
2012
+ constructor(min, max) {
2013
+ this.min = min;
2014
+ this.max = max;
2015
+ this.width = max.x - min.x;
2016
+ this.height = max.y - min.y;
2017
+ }
2018
+ /**
2019
+ * Convert this rectangular bounding box to a polygon with four vertices.
2020
+ *
2021
+ * @method toPolygon
2022
+ * @instance
2023
+ * @memberof Bounds
2024
+ * @return {Polygon} This bound rectangle as a polygon.
2025
+ */
2026
+ toPolygon() {
2027
+ return new Polygon([new Vertex(this.min), new Vertex(this.max.x, this.min.y), new Vertex(this.max), new Vertex(this.min.x, this.max.y)], false);
1781
2028
  }
1782
2029
  /**
1783
- * Check if the given point is located on this line. Optionally also check if
1784
- * that point is located between point `a` and `b`.
2030
+ * Get the center of this boinding box.
1785
2031
  *
1786
- * @method hasPoint
1787
- * @param {Vertex} point The point to check.
1788
- * @param {boolean=} insideBoundsOnly If set to to true (default=false) the point must be between start and end point of the line.
1789
- * @return {boolean} True if the given point is on this line.
2032
+ * @method getCenter
1790
2033
  * @instance
1791
- * @memberof VertTuple
2034
+ * @memberof Bounds
2035
+ * @returns {Vertex} The center of these bounds.
1792
2036
  */
1793
- hasPoint(point, insideBoundsOnly) {
1794
- const t = this.getClosestT(point);
1795
- // Compare to pointDistance?
1796
- if (typeof insideBoundsOnly !== "undefined" && insideBoundsOnly) {
1797
- const distance = Math.sqrt(VertTuple.vtutils.dist2(point, this.vertAt(t)));
1798
- return distance < Vertex.EPSILON && t >= 0 && t <= 1;
1799
- }
1800
- else {
1801
- return t >= 0 && t <= 1;
1802
- }
2037
+ getCenter() {
2038
+ return new Vertex(this.min.x + (this.max.x - this.min.x) / 2.0, this.min.y + (this.max.y - this.min.y) / 2);
1803
2039
  }
1804
2040
  /**
1805
- * Get the closest point on this line to the specified point.
2041
+ * Generate a random point inside this bounds object. Safe areas at the border to avoid
2042
+ * included.
1806
2043
  *
1807
- * @method getClosestPoint
1808
- * @param {Vertex} p The point (vertex) to measre the distance to.
1809
- * @return {Vertex} The point on the line that is closest to p.
2044
+ * @method randomPoint
1810
2045
  * @instance
1811
- * @memberof VertTuple
1812
- **/
1813
- getClosestPoint(p) {
1814
- var t = this.getClosestT(p);
1815
- return this.vertAt(t);
2046
+ * @memberof Bounds
2047
+ * @param {horizontalSafeArea} - (optional) The horizonal (left and right) safe area. No vertex will be created here. Can be used as percent in (0.0 ... 0.1) interval.
2048
+ * @param {verticalSafeArea} - (optional) The vertical (top and bottom) safe area. No vertex will be created here. Can be used as percent in (0.0 ... 0.1) interval
2049
+ * @returns {Vertex} A pseudo random point inside these bounds.
2050
+ */
2051
+ randomPoint(horizontalSafeArea = 0, verticalSafeArea = 0) {
2052
+ // Check if the safe areas are meant as percent
2053
+ const absHorizontalSafeArea = horizontalSafeArea > 0 && horizontalSafeArea < 1 ? this.width * horizontalSafeArea : horizontalSafeArea;
2054
+ const absVerticalSafeArea = verticalSafeArea > 0 && verticalSafeArea < 1 ? this.height * verticalSafeArea : verticalSafeArea;
2055
+ return new Vertex(this.min.x + absHorizontalSafeArea + Math.random() * (this.width - 2 * absHorizontalSafeArea), this.min.y + absVerticalSafeArea + Math.random() * (this.height - 2 * absVerticalSafeArea));
1816
2056
  }
1817
2057
  /**
1818
- * The the minimal distance between this line and the specified point.
2058
+ * Convert these bounds to a human readable form.
1819
2059
  *
1820
- * @method pointDistance
1821
- * @param {Vertex} p The point (vertex) to measre the distance to.
1822
- * @return {number} The absolute minimal distance.
2060
+ * Note: the returned format might change in the future, so please do not
2061
+ * rely on the returned string format.
2062
+ *
2063
+ * @method toString
1823
2064
  * @instance
1824
- * @memberof VertTuple
1825
- **/
1826
- pointDistance(p) {
1827
- // Taken From:
1828
- // https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
1829
- return Math.sqrt(VertTuple.vtutils.dist2(p, this.vertAt(this.getClosestT(p))));
2065
+ * @memberof Bounds
2066
+ * @returns {string} Get these bounds in a human readable form.
2067
+ */
2068
+ toString() {
2069
+ return `{ min: ${this.min.toString()}, max : ${this.max.toString()}, width: ${this.width}, height : ${this.height} }`;
1830
2070
  }
1831
2071
  /**
1832
- * Create a deep clone of this instance.
2072
+ * Clone this bounds object (create a deep clone).
1833
2073
  *
1834
- * @method cloneLine
1835
- * @return {T} A type safe clone if this instance.
2074
+ * @method clone
1836
2075
  * @instance
1837
- * @memberof VertTuple
1838
- **/
2076
+ * @memberof Bounds
2077
+ * @returns {Bounds} Creates a deep clone of this bounds object.
2078
+ */
1839
2079
  clone() {
1840
- return this.factory(this.a.clone(), this.b.clone());
2080
+ return new Bounds({ x: this.min.x, y: this.min.y }, { x: this.max.x, y: this.max.y });
1841
2081
  }
1842
2082
  /**
1843
- * Create a string representation of this line.
2083
+ * Compute the minimal bounding box for a given set of vertices.
1844
2084
  *
1845
- * @method totring
1846
- * @return {string} The string representing this line.
1847
- * @instance
1848
- * @memberof VertTuple
2085
+ * An empty vertex array will return an empty bounding box located at (0,0).
2086
+ *
2087
+ * @static
2088
+ * @method computeFromVertices
2089
+ * @memberof Bounds
2090
+ * @param {Array<Vertex>} vertices - The set of vertices you want to get the bounding box for.
2091
+ * @return The minimal Bounds for the given vertices.
1849
2092
  **/
1850
- toString() {
1851
- return "{ a : " + this.a.toString() + ", b : " + this.b.toString() + " }";
2093
+ static computeFromVertices(vertices) {
2094
+ if (vertices.length == 0)
2095
+ return new Bounds(new Vertex(0, 0), new Vertex(0, 0));
2096
+ let xMin = vertices[0].x;
2097
+ let xMax = vertices[0].x;
2098
+ let yMin = vertices[0].y;
2099
+ let yMax = vertices[0].y;
2100
+ let vert;
2101
+ for (var i in vertices) {
2102
+ vert = vertices[i];
2103
+ xMin = Math.min(xMin, vert.x);
2104
+ xMax = Math.max(xMax, vert.x);
2105
+ yMin = Math.min(yMin, vert.y);
2106
+ yMax = Math.max(yMax, vert.y);
2107
+ }
2108
+ return new Bounds(new Vertex(xMin, yMin), new Vertex(xMax, yMax));
1852
2109
  }
1853
2110
  /**
1854
- * This function should invalidate any installed listeners and invalidate this object.
1855
- * After calling this function the object might not hold valid data any more and
1856
- * should not be used.
2111
+ * Create a new `Bounds` instance just from `width` and `height`, located at (0,0) or the optionally given origin.
2112
+ *
2113
+ * @param {number} width - The width of the bounds
2114
+ * @param {number} height - The height of the bounds
2115
+ * @param {XYCoords={x:0,y:0}} origin - [optional] A origin to locate the new Bounds object at.
2116
+ * @returns {Bounds} A new `Bounds` instance width given width and height, located at (0,0) or the given origin..
1857
2117
  */
1858
- destroy() {
1859
- this.a.destroy();
1860
- this.b.destroy();
1861
- this.isDestroyed = true;
1862
- }
1863
- }
1864
- /**
1865
- * @private
1866
- **/
1867
- VertTuple.vtutils = {
1868
- dist2: (v, w) => {
1869
- return (v.x - w.x) * (v.x - w.x) + (v.y - w.y) * (v.y - w.y);
2118
+ static fromDimension(width, height, origin) {
2119
+ return new Bounds(origin !== null && origin !== void 0 ? origin : { x: 0, y: 0 }, { x: (origin ? origin.x : 0) + width, y: (origin ? origin.y : 0) + height });
1870
2120
  }
1871
- };
2121
+ } // END class bounds
1872
2122
 
1873
2123
  /**
1874
2124
  * @author Ikaros Kappler
@@ -2015,8 +2265,8 @@ Vector.utils = {
2015
2265
  * Example:
2016
2266
  * buildArrowHead( new Vertex(0,0), new Vertex(50,100), 8, 1.0, 1.0 )
2017
2267
  *
2018
- * @param {Vertex} zA - The start vertex of the vector to calculate the arrow head for.
2019
- * @param {Vertex} zB - The end vertex of the vector.
2268
+ * @param {XYCoords} zA - The start vertex of the vector to calculate the arrow head for.
2269
+ * @param {XYCoords} zB - The end vertex of the vector.
2020
2270
  * @param {number} headlen - The length of the arrow head (along the vector direction. A good value is 12).
2021
2271
  * @param {number} scaleX - The horizontal scaling during draw.
2022
2272
  * @param {number} scaleY - the vertical scaling during draw.
@@ -2055,7 +2305,9 @@ Vector.utils = {
2055
2305
  * @modified 2022-02-02 Added the `destroy` method.
2056
2306
  * @modified 2022-02-02 Cleared the `toSVGPathData` function (deprecated). Use `drawutilssvg` instead.
2057
2307
  * @modified 2022-10-17 The `CubicBezierCurve` class now implements the new `PathSegment` interface.
2058
- * @version 2.7.1
2308
+ * @modified 2023-09-30 Added the function `CubicbezierCurve.getSubCurve(number,number)` – similar to `getSubCurveAt(...)` but with absolute position parameters.
2309
+ * @modified 2023-10-07 Added the `trimEnd`, `trimEndAt`, `trimStart`, `trimStartAt` methods.
2310
+ * @version 2.8.0
2059
2311
  *
2060
2312
  * @file CubicBezierCurve
2061
2313
  * @public
@@ -2442,6 +2694,91 @@ class CubicBezierCurve {
2442
2694
  // Note: my implementation does NOT normalize tangent vectors!
2443
2695
  return new Vertex(tX, tY);
2444
2696
  }
2697
+ /**
2698
+ * Trim off a start section of this curve. The position parameter `uValue` is the absolute position on the
2699
+ * curve in `[0...arcLength]`.
2700
+ * The remaining curve will be the one in the bounds `[uValue,1]` (so `[0.0,uValue]` is cut off).
2701
+ *
2702
+ * Note this function just converts the absolute parameter to a relative one and call `trimStartAt`.
2703
+ *
2704
+ * @method trimStart
2705
+ * @instance
2706
+ * @memberof CubicBezierCurve
2707
+ * @param {number} uValue - The absolute position parameter where to cut off the head curve.
2708
+ * @returns {CubicBezierCurve} `this` for chanining.
2709
+ */
2710
+ trimStart(uValue) {
2711
+ return this.trimStartAt(this.convertU2T(uValue));
2712
+ }
2713
+ /**
2714
+ * Trim off a start section of this curve. The position parameter `t` is the relative position in [0..1].
2715
+ * The remaining curve will be the one in the bounds `[uValue,1]` (so `[0.0,uValue]` is cut off).
2716
+ *
2717
+ * @method trimStartAt
2718
+ * @instance
2719
+ * @memberof CubicBezierCurve
2720
+ * @param {number} t - The relative position parameter where to cut off the head curve.
2721
+ * @returns {CubicBezierCurve} `this` for chanining.
2722
+ */
2723
+ trimStartAt(t) {
2724
+ const subCurbePoints = CubicBezierCurve.utils.getSubCurvePointsAt(this, t, 1.0);
2725
+ this.startPoint.set(subCurbePoints[0]);
2726
+ this.startControlPoint.set(subCurbePoints[2]);
2727
+ this.endPoint.set(subCurbePoints[1]);
2728
+ this.endControlPoint.set(subCurbePoints[3]);
2729
+ this.updateArcLengths();
2730
+ return this;
2731
+ }
2732
+ /**
2733
+ * Trim off the end of this curve. The position parameter `uValue` is the absolute position on the
2734
+ * curve in `[0...arcLength]`.
2735
+ * The remaining curve will be the one in the bounds `[0,uValue]` (so `[1.0-uValue,1.0]` is cut off).
2736
+ *
2737
+ * Note this function just converts the absolute parameter to a relative one and call `trimEndAt`.
2738
+ *
2739
+ * @method trimEnd
2740
+ * @instance
2741
+ * @memberof CubicBezierCurve
2742
+ * @param {number} uValue - The absolute position parameter where to cut off the tail curve.
2743
+ * @returns {CubicBezierCurve} `this` for chanining.
2744
+ */
2745
+ trimEnd(uValue) {
2746
+ return this.trimEndAt(this.convertU2T(uValue));
2747
+ }
2748
+ /**
2749
+ * Trim off the end of this curve. The position parameter `t` is the relative position in [0..1].
2750
+ * The remaining curve will be the one in the bounds `[0,t]` (so `[1.0-t,1.0]` is cut off).
2751
+ *
2752
+ * @method trimEndAt
2753
+ * @instance
2754
+ * @memberof CubicBezierCurve
2755
+ * @param {number} t - The relative position parameter where to cut off the tail curve.
2756
+ * @returns {CubicBezierCurve} `this` for chanining.
2757
+ */
2758
+ trimEndAt(t) {
2759
+ const subCurbePoints = CubicBezierCurve.utils.getSubCurvePointsAt(this, 0.0, t);
2760
+ this.startPoint.set(subCurbePoints[0]);
2761
+ this.startControlPoint.set(subCurbePoints[2]);
2762
+ this.endPoint.set(subCurbePoints[1]);
2763
+ this.endControlPoint.set(subCurbePoints[3]);
2764
+ this.updateArcLengths();
2765
+ return this;
2766
+ }
2767
+ /**
2768
+ * Get a sub curve at the given start end end positions (values on the curve's length, between 0 and curve.arcLength).
2769
+ *
2770
+ * tStart >= tEnd is allowed, you will get a reversed sub curve then.
2771
+ *
2772
+ * @method getSubCurve
2773
+ * @param {number} tStart – The start position of the desired sub curve (must be in [0..arcLength]).
2774
+ * @param {number} tEnd – The end position if the desired cub curve (must be in [0..arcLength]).
2775
+ * @instance
2776
+ * @memberof CubicBezierCurve
2777
+ * @return {CubicBezierCurve} The sub curve as a new curve.
2778
+ **/
2779
+ getSubCurve(uStart, uEnd) {
2780
+ return this.getSubCurveAt(this.convertU2T(uStart), this.convertU2T(uEnd));
2781
+ }
2445
2782
  /**
2446
2783
  * Get a sub curve at the given start end end offsets (values between 0.0 and 1.0).
2447
2784
  *
@@ -2455,17 +2792,19 @@ class CubicBezierCurve {
2455
2792
  * @return {CubicBezierCurve} The sub curve as a new curve.
2456
2793
  **/
2457
2794
  getSubCurveAt(tStart, tEnd) {
2458
- const startVec = new Vector(this.getPointAt(tStart), this.getTangentAt(tStart));
2459
- const endVec = new Vector(this.getPointAt(tEnd), this.getTangentAt(tEnd).inv());
2460
- // Tangents are relative. Make absolute.
2461
- startVec.b.add(startVec.a);
2462
- endVec.b.add(endVec.a);
2463
- // This 'splits' the curve at the given point at t.
2464
- startVec.scale(0.33333333 * (tEnd - tStart));
2465
- endVec.scale(0.33333333 * (tEnd - tStart));
2466
- // Draw the bezier curve
2467
- // pb.draw.cubicBezier( startVec.a, endVec.a, startVec.b, endVec.b, '#8800ff', 2 );
2468
- return new CubicBezierCurve(startVec.a, endVec.a, startVec.b, endVec.b);
2795
+ // const startVec: Vector = new Vector(this.getPointAt(tStart), this.getTangentAt(tStart));
2796
+ // const endVec: Vector = new Vector(this.getPointAt(tEnd), this.getTangentAt(tEnd).inv());
2797
+ // // Tangents are relative. Make absolute.
2798
+ // startVec.b.add(startVec.a);
2799
+ // endVec.b.add(endVec.a);
2800
+ // // This 'splits' the curve at the given point at t.
2801
+ // startVec.scale(0.33333333 * (tEnd - tStart));
2802
+ // endVec.scale(0.33333333 * (tEnd - tStart));
2803
+ // // Draw the bezier curve
2804
+ // // pb.draw.cubicBezier( startVec.a, endVec.a, startVec.b, endVec.b, '#8800ff', 2 );
2805
+ // return new CubicBezierCurve(startVec.a, endVec.a, startVec.b, endVec.b);
2806
+ const subCurbePoints = CubicBezierCurve.utils.getSubCurvePointsAt(this, tStart, tEnd);
2807
+ return new CubicBezierCurve(subCurbePoints[0], subCurbePoints[1], subCurbePoints[2], subCurbePoints[3]);
2469
2808
  }
2470
2809
  /**
2471
2810
  * Convert a relative curve position u to the absolute curve position t.
@@ -2718,6 +3057,35 @@ CubicBezierCurve.START_CONTROL_POINT = 1;
2718
3057
  CubicBezierCurve.END_CONTROL_POINT = 2;
2719
3058
  /** @constant {number} */
2720
3059
  CubicBezierCurve.END_POINT = 3;
3060
+ /**
3061
+ * Helper utils.
3062
+ */
3063
+ CubicBezierCurve.utils = {
3064
+ /**
3065
+ * Get the points of a sub curve at the given start end end offsets (values between 0.0 and 1.0).
3066
+ *
3067
+ * tStart >= tEnd is allowed, you will get a reversed sub curve then.
3068
+ *
3069
+ * @method getSubCurvePointsAt
3070
+ * @param {CubicBezierCurve} curve – The curve to get the sub curve points from.
3071
+ * @param {number} tStart – The start offset of the desired sub curve (must be in [0..1]).
3072
+ * @param {number} tEnd – The end offset if the desired cub curve (must be in [0..1]).
3073
+ * @instance
3074
+ * @memberof CubicBezierCurve
3075
+ * @return {CubicBezierCurve} The sub curve as a new curve.
3076
+ **/
3077
+ getSubCurvePointsAt: (curve, tStart, tEnd) => {
3078
+ const startVec = new Vector(curve.getPointAt(tStart), curve.getTangentAt(tStart));
3079
+ const endVec = new Vector(curve.getPointAt(tEnd), curve.getTangentAt(tEnd).inv());
3080
+ // Tangents are relative. Make absolute.
3081
+ startVec.b.add(startVec.a);
3082
+ endVec.b.add(endVec.a);
3083
+ // This 'splits' the curve at the given point at t.
3084
+ startVec.scale(0.33333333 * (tEnd - tStart));
3085
+ endVec.scale(0.33333333 * (tEnd - tStart));
3086
+ return [startVec.a, endVec.a, startVec.b, endVec.b];
3087
+ }
3088
+ };
2721
3089
 
2722
3090
  /**
2723
3091
  * @author Ikaros Kappler
@@ -2744,7 +3112,9 @@ CubicBezierCurve.END_POINT = 3;
2744
3112
  * @modified 2022-01-31 Added `BezierPath.getEvenDistributionVertices(number)`.
2745
3113
  * @modified 2022-02-02 Added the `destroy` method.
2746
3114
  * @modified 2022-02-02 Cleared the `toSVGString` function (deprecated). Use `drawutilssvg` instead.
2747
- * @version 2.5.0
3115
+ * @modified 2023-10-06 Adding the `BezierPath.toPathPoints()` method.
3116
+ * @modified 2023-10-07 Adding the `BezierPath.fromCurve(CubicBezierCurve)` static function.
3117
+ * @version 2.6.0
2748
3118
  *
2749
3119
  * @file BezierPath
2750
3120
  * @public
@@ -2775,7 +3145,7 @@ class BezierPath {
2775
3145
  * @name BezierPath
2776
3146
  * @param {Vertex[]} pathPoints - An array of path vertices (no control points).
2777
3147
  **/
2778
- constructor(pathPoints) {
3148
+ constructor() {
2779
3149
  /**
2780
3150
  * Required to generate proper CSS classes and other class related IDs.
2781
3151
  **/
@@ -2788,7 +3158,11 @@ class BezierPath {
2788
3158
  this.END_CONTROL_POINT = 2;
2789
3159
  /** @constant {number} */
2790
3160
  this.END_POINT = 3;
3161
+ // pathPoints: Array<Vertex> | undefined | null) {
2791
3162
  this.uid = UIDGenerator.next();
3163
+ // if (!pathPoints) {
3164
+ // pathPoints = [];
3165
+ // }
2792
3166
  this.totalArcLength = 0.0;
2793
3167
  // Set this flag to true if you want the first point and
2794
3168
  // last point of the path to be auto adjusted, too.
@@ -2927,229 +3301,20 @@ class BezierPath {
2927
3301
  * @return {number} The number of curves in this path.
2928
3302
  **/
2929
3303
  getCurveCount() {
2930
- return this.bezierCurves.length;
2931
- }
2932
- /**
2933
- * Get the cubic bezier curve at the given index.
2934
- *
2935
- * @method getCurveAt
2936
- * @param {number} index - The curve index from 0 to getCurveCount()-1.
2937
- * @instance
2938
- * @memberof BezierPath
2939
- * @return {CubicBezierCurve} The curve at the specified index.
2940
- **/
2941
- getCurveAt(curveIndex) {
2942
- return this.bezierCurves[curveIndex];
2943
- }
2944
- /**
2945
- * Remove the end point of this path (which removes the last curve from this path).<br>
2946
- * <br>
2947
- * Please note that this function does never remove the first curve, thus the path
2948
- * cannot be empty after this call.
2949
- *
2950
- * @method removeEndPoint
2951
- * @instance
2952
- * @memberof BezierPath
2953
- * @return {boolean} Indicating if the last curve was removed.
2954
- **/
2955
- /*
2956
- BezierPath.prototype.removeEndPoint = function() {
2957
- if( this.bezierCurves.length <= 1 )
2958
- return false;
2959
-
2960
- var newArray = [ this.bezierCurves.length-1 ];
2961
- for( var i = 0; i < this.bezierCurves.length-1; i++ ) {
2962
- newArray[i] = this.bezierCurves[i];
2963
- }
2964
-
2965
- // Update arc length
2966
- this.totalArcLength -= this.bezierCurves[ this.bezierCurves.length-1 ].getLength();
2967
- this.bezierCurves = newArray;
2968
- return true;
2969
- }
2970
- */
2971
- /**
2972
- * Remove the start point of this path (which removes the first curve from this path).<br>
2973
- * <br>
2974
- * Please note that this function does never remove the last curve, thus the path
2975
- * cannot be empty after this call.<br>
2976
- *
2977
- * @method removeStartPoint
2978
- * @instance
2979
- * @memberof BezierPath
2980
- * @return {boolean} Indicating if the first curve was removed.
2981
- **/
2982
- /*
2983
- BezierPath.prototype.removeStartPoint = function() {
2984
-
2985
- if( this.bezierCurves.length <= 1 )
2986
- return false;
2987
-
2988
- var newArray = [ this.bezierCurves.length-1 ];
2989
- for( var i = 1; i < this.bezierCurves.length; i++ ) {
2990
-
2991
- newArray[i-1] = this.bezierCurves[i];
2992
-
2993
- }
2994
-
2995
- // Update arc length
2996
- this.totalArcLength -= this.bezierCurves[ 0 ].getLength();
2997
- this.bezierCurves = newArray;
2998
-
2999
- return true;
3000
- }
3001
- */
3002
- /**
3003
- * Removes a path point inside the path.
3004
- *
3005
- * This function joins the bezier curve at the given index with
3006
- * its predecessor, which means that the start point at the given
3007
- * curve index will be removed.
3008
- *
3009
- * @method joinAt
3010
- * @param {number} curveIndex - The index of the curve to be joined with its predecessor.
3011
- * @instance
3012
- * @memberof BezierPath
3013
- * @return {boolean} True if the passed index indicated an inner vertex and the two curves were joined.
3014
- **/
3015
- /*
3016
- BezierPath.prototype.joinAt = function( curveIndex ) {
3017
-
3018
- if( curveIndex < 0 || curveIndex >= this.bezierCurves.length )
3019
- return false;
3020
-
3021
- var leftCurve = this.bezierCurves[ curveIndex-1 ];
3022
- var rightCurve = this.bezierCurves[ curveIndex ];
3023
-
3024
- // Make the length of the new handle double that long
3025
- var leftControlPoint = leftCurve.getStartControlPoint().clone();
3026
- leftControlPoint.sub( leftCurve.getStartPoint() );
3027
- leftControlPoint.multiplyScalar( 2.0 );
3028
- leftControlPoint.add( leftCurve.getStartPoint() );
3029
-
3030
- var rightControlPoint = rightCurve.getEndControlPoint().clone();
3031
- rightControlPoint.sub( rightCurve.getEndPoint() );
3032
- rightControlPoint.multiplyScalar( 2.0 );
3033
- rightControlPoint.add( rightCurve.getEndPoint() );
3034
-
3035
- var newCurve = new IKRS.CubicBezierCurve( leftCurve.getStartPoint(),
3036
- rightCurve.getEndPoint(),
3037
- leftControlPoint,
3038
- rightControlPoint
3039
- );
3040
- // Place into array
3041
- var newArray = [ this.bezierCurves.length - 1 ];
3042
-
3043
- for( var i = 0; i < curveIndex-1; i++ )
3044
- newArray[ i ] = this.bezierCurves[i];
3045
-
3046
- newArray[ curveIndex-1 ] = newCurve;
3047
-
3048
- // Shift trailing curves left
3049
- for( var i = curveIndex; i+1 < this.bezierCurves.length; i++ )
3050
- newArray[ i ] = this.bezierCurves[ i+1 ];
3051
-
3052
- this.bezierCurves = newArray;
3053
- this.updateArcLengths();
3054
-
3055
- return true;
3056
- }
3057
- */
3058
- /**
3059
- * Add a new inner curve point to the path.<br>
3060
- * <br>
3061
- * This function splits the bezier curve at the given index and given
3062
- * curve segment index.
3063
- *
3064
- * @method splitAt
3065
- * @param {number} curveIndex - The index of the curve to split.
3066
- * @param {nunber} segmentIndex - The index of the curve segment where the split should be performed.
3067
- * @instance
3068
- * @memberof BezierPath
3069
- * @return {boolean} True if the passed indices were valid and the path was split.
3070
- **/
3071
- /*
3072
- BezierPath.prototype.splitAt = function( curveIndex,
3073
- segmentIndex
3074
- ) {
3075
- // Must be a valid curve index
3076
- if( curveIndex < 0 || curveIndex >= this.bezierCurves.length )
3077
- return false;
3078
-
3079
- var oldCurve = this.bezierCurves[ curveIndex ];
3080
-
3081
- // Segment must be an INNER point!
3082
- // (the outer points are already bezier end/start points!)
3083
- if( segmentIndex < 1 || segmentIndex-1 >= oldCurve.segmentCache.length )
3084
- return false;
3085
-
3086
- // Make room for a new curve
3087
- for( var c = this.bezierCurves.length; c > curveIndex; c-- ) {
3088
- // Move one position to the right
3089
- this.bezierCurves[ c ] = this.bezierCurves[ c-1 ];
3090
- }
3091
-
3092
- // Accumulate segment lengths
3093
- var u = 0;
3094
- for( var i = 0; i < segmentIndex; i++ )
3095
- u += oldCurve.segmentLengths[i];
3096
- //var tangent = oldCurve.getTangentAt( u );
3097
- var tangent = oldCurve.getTangent( u );
3098
- tangent = tangent.multiplyScalar( 0.25 );
3099
-
3100
- var leftEndControlPoint = oldCurve.segmentCache[ segmentIndex ].clone();
3101
- leftEndControlPoint.sub( tangent );
3102
-
3103
- var rightStartControlPoint = oldCurve.segmentCache[ segmentIndex ].clone();
3104
- rightStartControlPoint.add( tangent );
3105
-
3106
- // Make the old existing handles a quarter that long
3107
- var leftStartControlPoint = oldCurve.getStartControlPoint().clone();
3108
- // move to (0,0)
3109
- leftStartControlPoint.sub( oldCurve.getStartPoint() );
3110
- leftStartControlPoint.multiplyScalar( 0.25 );
3111
- leftStartControlPoint.add( oldCurve.getStartPoint() );
3112
-
3113
- var rightEndControlPoint = oldCurve.getEndControlPoint().clone();
3114
- // move to (0,0)
3115
- rightEndControlPoint.sub( oldCurve.getEndPoint() );
3116
- rightEndControlPoint.multiplyScalar( 0.25 );
3117
- rightEndControlPoint.add( oldCurve.getEndPoint() );
3118
-
3119
- var newLeft = new CubicBezierCurve( oldCurve.getStartPoint(), // old start point
3120
- oldCurve.segmentCache[ segmentIndex ], // new end point
3121
- leftStartControlPoint, // old start control point
3122
- leftEndControlPoint // new end control point
3123
- );
3124
- var newRight = new CubicBezierCurve( oldCurve.segmentCache[ segmentIndex ], // new start point
3125
- oldCurve.getEndPoint(), // old end point
3126
- rightStartControlPoint, // new start control point
3127
- rightEndControlPoint // old end control point
3128
- );
3129
-
3130
- // Insert split curve(s) at free index
3131
- this.bezierCurves[ curveIndex ] = newLeft;
3132
- this.bezierCurves[ curveIndex + 1 ] = newRight;
3133
-
3134
- // Update total arc length, even if there is only a very little change!
3135
- this.totalArcLength -= oldCurve.getLength();
3136
- this.totalArcLength += newLeft.getLength();
3137
- this.totalArcLength += newRight.getLength();
3138
-
3139
- return true;
3140
- };
3141
- */
3142
- /*
3143
- insertVertexAt( t:number ) : void {
3144
- console.log('Inserting vertex at', t );
3145
- // Find the curve index
3146
- var u : number = 0;
3147
- var curveIndex : number = -1;
3148
- var localT : number = 0.0;
3149
- for( var i = 0; curveIndex == -1 && i < this.bezierCurves.length; i++ ) {
3150
-
3151
- }
3152
- }; */
3304
+ return this.bezierCurves.length;
3305
+ }
3306
+ /**
3307
+ * Get the cubic bezier curve at the given index.
3308
+ *
3309
+ * @method getCurveAt
3310
+ * @param {number} index - The curve index from 0 to getCurveCount()-1.
3311
+ * @instance
3312
+ * @memberof BezierPath
3313
+ * @return {CubicBezierCurve} The curve at the specified index.
3314
+ **/
3315
+ getCurveAt(curveIndex) {
3316
+ return this.bezierCurves[curveIndex];
3317
+ }
3153
3318
  /**
3154
3319
  * Move the whole bezier path by the given (x,y)-amount.
3155
3320
  *
@@ -3184,22 +3349,6 @@ class BezierPath {
3184
3349
  * @return {BezierPath} this for chaining.
3185
3350
  **/
3186
3351
  scale(anchor, scaleFactor) {
3187
- // var scaleFactors : XYCoords = { x : scaleFactor, y : scaleFactor };
3188
- // for (var i = 0; i < this.bezierCurves.length; i++) {
3189
- // var curve = this.bezierCurves[i];
3190
- // curve.getStartPoint().scale(scaleFactor, anchor);
3191
- // curve.getStartControlPoint().scale(scaleFactor, anchor);
3192
- // curve.getEndControlPoint().scale(scaleFactor, anchor);
3193
- // // Do NOT scale the end point here!
3194
- // // Don't forget that the curves are connected and on curve's end point
3195
- // // the the successor's start point (same instance)!
3196
- // }
3197
- // // Finally move the last end point (was not scaled yet)
3198
- // if (this.bezierCurves.length > 0 && !this.adjustCircular) {
3199
- // this.bezierCurves[this.bezierCurves.length - 1].getEndPoint().scale(scaleFactor, anchor);
3200
- // }
3201
- // this.updateArcLengths();
3202
- // return this;
3203
3352
  return this.scaleXY({ x: scaleFactor, y: scaleFactor }, anchor);
3204
3353
  }
3205
3354
  /**
@@ -3695,7 +3844,7 @@ class BezierPath {
3695
3844
  * @return {BezierPath}
3696
3845
  **/
3697
3846
  clone() {
3698
- var path = new BezierPath(undefined);
3847
+ var path = new BezierPath(); // undefined);
3699
3848
  for (var i = 0; i < this.bezierCurves.length; i++) {
3700
3849
  path.bezierCurves.push(this.bezierCurves[i].clone());
3701
3850
  // Connect splines
@@ -3735,6 +3884,10 @@ class BezierPath {
3735
3884
  * This function should invalidate any installed listeners and invalidate this object.
3736
3885
  * After calling this function the object might not hold valid data any more and
3737
3886
  * should not be used.
3887
+ *
3888
+ * @method destroy
3889
+ * @instance
3890
+ * @memberof BezierPath
3738
3891
  */
3739
3892
  destroy() {
3740
3893
  for (var i = 0; i < this.bezierCurves.length; i++) {
@@ -3742,6 +3895,40 @@ class BezierPath {
3742
3895
  }
3743
3896
  this.isDestroyed = true;
3744
3897
  }
3898
+ /**
3899
+ * Convert this path to an array of path points that can be drawn by the default DrawLib
3900
+ * implementations.
3901
+ *
3902
+ * @method toPathPoints
3903
+ * @instance
3904
+ * @memberof BezierPath
3905
+ * @return {Array<XYCoords>}
3906
+ */
3907
+ toPathPoints() {
3908
+ if (this.bezierCurves.length === 0) {
3909
+ return [];
3910
+ }
3911
+ if (this.bezierCurves.length === 1) {
3912
+ return [
3913
+ this.bezierCurves[0].startPoint,
3914
+ this.bezierCurves[0].startControlPoint,
3915
+ this.bezierCurves[0].endControlPoint,
3916
+ this.bezierCurves[0].endPoint
3917
+ ];
3918
+ }
3919
+ const arr = [];
3920
+ arr.push(this.bezierCurves[0].startPoint);
3921
+ arr.push(this.bezierCurves[0].startControlPoint);
3922
+ for (var i = 1; i < this.bezierCurves.length; i++) {
3923
+ arr.push(this.bezierCurves[i - 1].endControlPoint);
3924
+ arr.push(this.bezierCurves[i - 1].endPoint);
3925
+ arr.push(this.bezierCurves[i].startPoint);
3926
+ arr.push(this.bezierCurves[i].startControlPoint);
3927
+ }
3928
+ arr.push(this.bezierCurves[0].endControlPoint);
3929
+ arr.push(this.bezierCurves[0].endPoint);
3930
+ return arr;
3931
+ }
3745
3932
  /**
3746
3933
  * Create a JSON string representation of this bézier curve.
3747
3934
  *
@@ -3782,6 +3969,20 @@ class BezierPath {
3782
3969
  var obj = JSON.parse(jsonString);
3783
3970
  return BezierPath.fromArray(obj);
3784
3971
  }
3972
+ /**
3973
+ * Construct a new path with a single curve. Adding more curves is always possible.
3974
+ *
3975
+ * @method fromCurve
3976
+ * @param {CubicBezierCurve} curve - The curve to construct a new path from.
3977
+ * @static
3978
+ * @memberof BezierPath
3979
+ * @return {BezierPath} The constructed bezier path instance.
3980
+ */
3981
+ static fromCurve(curve) {
3982
+ const path = new BezierPath(); // []);
3983
+ path.addCurve(curve);
3984
+ return path;
3985
+ }
3785
3986
  /**
3786
3987
  * Create a BezierPath instance from the given array.
3787
3988
  *
@@ -3793,13 +3994,15 @@ class BezierPath {
3793
3994
  * @return {BezierPath} The bezier path instance retrieved from the array data.
3794
3995
  **/
3795
3996
  static fromArray(obj) {
3796
- if (!Array.isArray(obj))
3997
+ if (!Array.isArray(obj)) {
3797
3998
  throw "[BezierPath.fromArray] Passed object must be an array.";
3999
+ }
3798
4000
  const arr = obj; // FORCE?
3799
- if (arr.length < 1)
4001
+ if (arr.length < 1) {
3800
4002
  throw "[BezierPath.fromArray] Passed array must contain at least one bezier curve (has " + arr.length + ").";
4003
+ }
3801
4004
  // Create an empty bezier path
3802
- var bPath = new BezierPath(undefined);
4005
+ var bPath = new BezierPath(); // undefined);
3803
4006
  var lastCurve = null;
3804
4007
  for (var i = 0; i < arr.length; i++) {
3805
4008
  // Convert object (or array?) to bezier curve
@@ -3909,7 +4112,7 @@ class BezierPath {
3909
4112
  */
3910
4113
  static fromReducedList(pointArray, adjustCircular) {
3911
4114
  // Convert to object
3912
- var bezierPath = new BezierPath(null); // No points yet
4115
+ var bezierPath = new BezierPath(); // null); // No points yet
3913
4116
  var startPoint = new Vertex();
3914
4117
  var startControlPoint;
3915
4118
  var endControlPoint;
@@ -3952,138 +4155,6 @@ BezierPath.END_CONTROL_POINT = 2;
3952
4155
  /** @constant {number} */
3953
4156
  BezierPath.END_POINT = 3;
3954
4157
 
3955
- /**
3956
- * @author Ikaros Kappler
3957
- * @date 2016-03-12
3958
- * @modified 2018-12-05 Refactored the code from the morley-triangle script.
3959
- * @modified 2019-03-20 Added JSDoc tags.
3960
- * @modified 2019-04-28 Fixed a bug in the Line.sub( Vertex ) function (was not working).
3961
- * @modified 2019-09-02 Added the Line.add( Vertex ) function.
3962
- * @modified 2019-09-02 Added the Line.denominator( Line ) function.
3963
- * @modified 2019-09-02 Added the Line.colinear( Line ) function.
3964
- * @modified 2019-09-02 Fixed an error in the Line.intersection( Line ) function (class Point was renamed to Vertex).
3965
- * @modified 2019-12-15 Added the Line.moveTo(Vertex) function.
3966
- * @modified 2020-03-16 The Line.angle(Line) parameter is now optional. The baseline (x-axis) will be used if not defined.
3967
- * @modified 2020-03-23 Ported to Typescript from JS.
3968
- * @modified 2020-12-04 The `intersection` function returns undefined if both lines are parallel.
3969
- * @modified 2022-02-02 Added the `destroy` method.
3970
- * @modified 2022-10-09 Changed the actual return value of the `intersection` function to null (was undefined before).
3971
- * @modified 2022-10-17 Adding these methods from the `PathSegment` interface: getStartPoint, getEndPoint, revert.
3972
- * @version 2.3.0
3973
- *
3974
- * @file Line
3975
- * @public
3976
- **/
3977
- /**
3978
- * @classdesc A line consists of two vertices a and b.<br>
3979
- * <br>
3980
- * This is some refactored code from my 'Morley Triangle' test<br>
3981
- * https://github.com/IkarosKappler/morleys-trisector-theorem
3982
- *
3983
- * @requires Vertex
3984
- */
3985
- class Line extends VertTuple {
3986
- /**
3987
- * Creates an instance of Line.
3988
- *
3989
- * @constructor
3990
- * @name Line
3991
- * @param {Vertex} a The line's first point.
3992
- * @param {Vertex} b The line's second point.
3993
- **/
3994
- constructor(a, b) {
3995
- super(a, b, (a, b) => new Line(a, b));
3996
- /**
3997
- * Required to generate proper CSS classes and other class related IDs.
3998
- **/
3999
- this.className = "Line";
4000
- }
4001
- /**
4002
- * Get the intersection if this line and the specified line.
4003
- *
4004
- * @method intersection
4005
- * @param {Line} line The second line.
4006
- * @return {Vertex|undefined} The intersection (may lie outside the end-points) or `undefined` if both lines are parallel.
4007
- * @instance
4008
- * @memberof Line
4009
- **/
4010
- // !!! DO NOT MOVE TO VertTuple
4011
- intersection(line) {
4012
- const denominator = this.denominator(line);
4013
- if (denominator == 0) {
4014
- return null;
4015
- }
4016
- let a = this.a.y - line.a.y;
4017
- let b = this.a.x - line.a.x;
4018
- const numerator1 = (line.b.x - line.a.x) * a - (line.b.y - line.a.y) * b;
4019
- const numerator2 = (this.b.x - this.a.x) * a - (this.b.y - this.a.y) * b;
4020
- a = numerator1 / denominator; // NaN if parallel lines
4021
- b = numerator2 / denominator;
4022
- // Catch NaN?
4023
- const x = this.a.x + a * (this.b.x - this.a.x);
4024
- const y = this.a.y + a * (this.b.y - this.a.y);
4025
- if (isNaN(a) || isNaN(x) || isNaN(y)) {
4026
- return null;
4027
- }
4028
- // if we cast these lines infinitely in both directions, they intersect here:
4029
- return new Vertex(x, y);
4030
- }
4031
- //--- Implement PathSegment ---
4032
- /**
4033
- * Get the start point of this path segment.
4034
- *
4035
- * @method getStartPoint
4036
- * @memberof PathSegment
4037
- * @return {Vertex} The start point of this path segment.
4038
- */
4039
- getStartPoint() {
4040
- return this.a;
4041
- }
4042
- /**
4043
- * Get the end point of this path segment.
4044
- *
4045
- * @method getEndPoint
4046
- * @memberof PathSegment
4047
- * @return {Vertex} The end point of this path segment.
4048
- */
4049
- getEndPoint() {
4050
- return this.b;
4051
- }
4052
- /**
4053
- * Get the tangent's end point at the start point of this segment.
4054
- *
4055
- * @method getStartTangent
4056
- * @memberof PathSegment
4057
- * @return {Vertex} The end point of the starting point's tangent.
4058
- */
4059
- getStartTangent() {
4060
- return this.b;
4061
- }
4062
- /**
4063
- * Get the tangent's end point at the end point of this segment.
4064
- *
4065
- * @method getEndTangent
4066
- * @memberof PathSegment
4067
- * @return {Vertex} The end point of the ending point's tangent.
4068
- */
4069
- getEndTangent() {
4070
- return this.a;
4071
- }
4072
- /**
4073
- * Inverse this path segment (in-place) and return this same instance (useful for chaining).
4074
- *
4075
- * @method reverse
4076
- * @memberof PathSegment
4077
- * @return {PathSegment} This path segment instance (for chaining).
4078
- */
4079
- reverse() {
4080
- var tmp = this.a;
4081
- this.a = this.b;
4082
- this.b = tmp;
4083
- return this;
4084
- }
4085
- }
4086
-
4087
4158
  /**
4088
4159
  * @author Ikaros Kappler
4089
4160
  * @date 2020-05-04
@@ -4476,7 +4547,15 @@ CircleSector.circleSectorUtils = {
4476
4547
  * @modified 2022-11-10 Tweaking some type issues.
4477
4548
  * @modified 2023-02-04 Fixed a typo in the CSS classname for cubic Bézier paths: cubicBezier (was cubierBezier).
4478
4549
  * @modified 2023-02-10 The methods `setCurrentClassName` and `setCurrentId` also accept `null` now.
4479
- * @version 1.6.4
4550
+ * @modified 2023-09-29 Added initialization checks for null parameters.
4551
+ * @modified 2023-09-29 Added a missing implementation to the `drawurilssvg.do(XYCoords,string)` function. Didn't draw anything.
4552
+ * @modified 2023-09-29 Downgrading all `Vertex` param type to the more generic `XYCoords` type in these render functions: line, arrow, texturedPoly, cubicBezier, cubicBezierPath, handle, handleLine, dot, point, circle, circleArc, ellipse, grid, raster.
4553
+ * @modified 2023-09-29 Added the `headLength` parameter to the 'DrawLib.arrow()` function.
4554
+ * @modified 2023-09-29 Added the `arrowHead(...)` function to the 'DrawLib.arrow()` interface.
4555
+ * @modified 2023-09-29 Added the `cubicBezierArrow(...)` function to the 'DrawLib.arrow()` interface.
4556
+ * @modified 2023-10-04 Adding `strokeOptions` param to these draw function: line, arrow, cubicBezierArrow, cubicBezier, cubicBezierPath, circle, circleArc, ellipse, square, rect, polygon, polyline.
4557
+ *
4558
+ * @version 1.6.7
4480
4559
  **/
4481
4560
  const RAD_TO_DEG = 180 / Math.PI;
4482
4561
  /**
@@ -4513,6 +4592,9 @@ class drawutilssvg {
4513
4592
  this.cache = new Map();
4514
4593
  this.setSize(canvasSize);
4515
4594
  if (isSecondary) {
4595
+ if (!gNode || !bufferGNode || !nodeDefs || !bufferNodeDefs) {
4596
+ throw "Cannot create secondary svg draw lib with undefinde gNode|bufferGNode|nodeDefs|bufferNodeDefs.";
4597
+ }
4516
4598
  this.gNode = gNode;
4517
4599
  this.bufferGNode = bufferGNode;
4518
4600
  this.nodeDefs = nodeDefs;
@@ -4653,6 +4735,9 @@ class drawutilssvg {
4653
4735
  if (this.drawlibConfiguration.blendMode) {
4654
4736
  node.style["mix-blend-mode"] = this.drawlibConfiguration.blendMode;
4655
4737
  }
4738
+ // if (this.lineDashEnabled && this.lineDash && this.lineDash.length > 0 && drawutilssvg.nodeSupportsLineDash(nodeName)) {
4739
+ // node.setAttribute("stroke-dasharray", this.lineDash.join(" "));
4740
+ // }
4656
4741
  return node;
4657
4742
  }
4658
4743
  /**
@@ -4675,23 +4760,58 @@ class drawutilssvg {
4675
4760
  * @param {number=1} lineWidth - (optional) A line width to use for drawing (default is 1).
4676
4761
  * @return {SVGElement} The node itself (for chaining).
4677
4762
  */
4678
- _bindFillDraw(node, className, color, lineWidth) {
4763
+ _bindFillDraw(node, className, color, lineWidth, strokeOptions) {
4764
+ this._configureNode(node, className, this.fillShapes, color, lineWidth, strokeOptions);
4765
+ return this._bindNode(node, undefined);
4766
+ }
4767
+ /**
4768
+ * Bind this given node to a parent. If no parent is passed then the global
4769
+ * node buffer will be used.
4770
+ *
4771
+ * @method _bindNode
4772
+ * @private
4773
+ * @instance
4774
+ * @memberof drawutilssvg
4775
+ * @param {SVGElement} node - The SVG node to bind.
4776
+ * @param {SVGElement=} bindingParent - (optional) You may pass node other than the glober buffer node.
4777
+ * @returns {SVGElement} The passed node itself.
4778
+ */
4779
+ _bindNode(node, bindingParent) {
4780
+ if (!node.parentNode) {
4781
+ // Attach to DOM only if not already attached
4782
+ (bindingParent !== null && bindingParent !== void 0 ? bindingParent : this.bufferGNode).appendChild(node);
4783
+ }
4784
+ return node;
4785
+ }
4786
+ /**
4787
+ * Add custom CSS class names and the globally defined CSS classname to the
4788
+ * given node.
4789
+ *
4790
+ * @method addCSSClasses
4791
+ * @private
4792
+ * @instance
4793
+ * @memberof drawutilssvg
4794
+ * @param {SVGElement} node - The SVG node to bind.
4795
+ * @param {string} className - The additional custom classname to add.
4796
+ * @returns {void}
4797
+ */
4798
+ _addCSSClasses(node, className) {
4679
4799
  if (this.curClassName) {
4680
4800
  node.setAttribute("class", `${className} ${this.curClassName}`);
4681
4801
  }
4682
4802
  else {
4683
4803
  node.setAttribute("class", className);
4684
4804
  }
4685
- node.setAttribute("fill", this.fillShapes && color ? color : "none");
4686
- node.setAttribute("stroke", this.fillShapes ? "none" : color || "none");
4805
+ }
4806
+ _configureNode(node, className, fillMode, color, lineWidth, strokeOptions) {
4807
+ this._addCSSClasses(node, className);
4808
+ node.setAttribute("fill", fillMode && color ? color : "none");
4809
+ node.setAttribute("stroke", fillMode ? "none" : color || "none");
4687
4810
  node.setAttribute("stroke-width", `${lineWidth || 1}`);
4688
4811
  if (this.curId) {
4689
4812
  node.setAttribute("id", `${this.curId}`); // Maybe React-style 'key' would be better?
4690
4813
  }
4691
- if (!node.parentNode) {
4692
- // Attach to DOM only if not already attached
4693
- this.bufferGNode.appendChild(node);
4694
- }
4814
+ this.applyStrokeOpts(node, strokeOptions);
4695
4815
  return node;
4696
4816
  }
4697
4817
  /**
@@ -4713,7 +4833,7 @@ class drawutilssvg {
4713
4833
  * that under the hood the same gl context and gl program will be used.
4714
4834
  */
4715
4835
  copyInstance(fillShapes) {
4716
- var copy = new drawutilssvg(this.svgNode, this.offset, this.scale, this.canvasSize, fillShapes, null, // no DrawConfig
4836
+ var copy = new drawutilssvg(this.svgNode, this.offset, this.scale, this.canvasSize, fillShapes, null, // no DrawConfig – this will work as long as `isSecondary===true`
4717
4837
  true, // isSecondary
4718
4838
  this.gNode, this.bufferGNode, this.nodeDefs, this.bufferedNodeDefs);
4719
4839
  return copy;
@@ -4728,6 +4848,20 @@ class drawutilssvg {
4728
4848
  setConfiguration(configuration) {
4729
4849
  this.drawlibConfiguration = configuration;
4730
4850
  }
4851
+ // /**
4852
+ // * Set or clear the line-dash configuration. Pass `null` for un-dashed lines.
4853
+ // *
4854
+ // * See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray
4855
+ // * and https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
4856
+ // * for how line dashes work.
4857
+ // *
4858
+ // * @method
4859
+ // * @param {Array<number> lineDashes - The line-dash array configuration.
4860
+ // * @returns {void}
4861
+ // */
4862
+ // setLineDash(lineDashes: Array<number>) {
4863
+ // this.lineDash = lineDashes;
4864
+ // }
4731
4865
  /**
4732
4866
  * This method shouled be called each time the currently drawn `Drawable` changes.
4733
4867
  * It is used by some libraries for identifying elemente on re-renders.
@@ -4802,6 +4936,26 @@ class drawutilssvg {
4802
4936
  this.nodeDefs = this.bufferedNodeDefs;
4803
4937
  this.bufferedNodeDefs = tmpDefsNode;
4804
4938
  }
4939
+ /**
4940
+ * A private helper method to apply stroke options to the current
4941
+ * context.
4942
+ * @param {StrokeOptions=} strokeOptions -
4943
+ */
4944
+ applyStrokeOpts(node, strokeOptions) {
4945
+ if (strokeOptions &&
4946
+ strokeOptions.dashArray &&
4947
+ strokeOptions.dashArray.length > 0 &&
4948
+ drawutilssvg.nodeSupportsLineDash(node.tagName)) {
4949
+ node.setAttribute("stroke-dasharray", strokeOptions.dashArray
4950
+ .map((dashArayElem) => {
4951
+ return dashArayElem * this.scale.x;
4952
+ })
4953
+ .join(" "));
4954
+ if (strokeOptions.dashOffset) {
4955
+ node.setAttribute("stroke-dashoffset", `${strokeOptions.dashOffset * this.scale.x}`);
4956
+ }
4957
+ }
4958
+ }
4805
4959
  _x(x) {
4806
4960
  return this.offset.x + this.scale.x * x;
4807
4961
  }
@@ -4812,47 +4966,101 @@ class drawutilssvg {
4812
4966
  * Draw the line between the given two points with the specified (CSS-) color.
4813
4967
  *
4814
4968
  * @method line
4815
- * @param {Vertex} zA - The start point of the line.
4816
- * @param {Vertex} zB - The end point of the line.
4969
+ * @param {XYCoords} zA - The start point of the line.
4970
+ * @param {XYCoords} zB - The end point of the line.
4817
4971
  * @param {string} color - Any valid CSS color string.
4818
4972
  * @param {number=1} lineWidth? - [optional] The line's width.
4973
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
4974
+ *
4819
4975
  * @return {void}
4820
4976
  * @instance
4821
4977
  * @memberof drawutilssvg
4822
4978
  **/
4823
- line(zA, zB, color, lineWidth) {
4824
- const line = this.makeNode("line");
4825
- line.setAttribute("x1", `${this._x(zA.x)}`);
4826
- line.setAttribute("y1", `${this._y(zA.y)}`);
4827
- line.setAttribute("x2", `${this._x(zB.x)}`);
4828
- line.setAttribute("y2", `${this._y(zB.y)}`);
4829
- return this._bindFillDraw(line, "line", color, lineWidth || 1);
4979
+ line(zA, zB, color, lineWidth, strokeOptions) {
4980
+ // const line: SVGElement = this.makeNode("line");
4981
+ // this.applyStrokeOpts(line, strokeOptions);
4982
+ // line.setAttribute("x1", `${this._x(zA.x)}`);
4983
+ // line.setAttribute("y1", `${this._y(zA.y)}`);
4984
+ // line.setAttribute("x2", `${this._x(zB.x)}`);
4985
+ // line.setAttribute("y2", `${this._y(zB.y)}`);
4986
+ const line = this.makeLineNode(zA, zB, color, lineWidth, strokeOptions);
4987
+ return this._bindFillDraw(line, "line", color, lineWidth || 1, strokeOptions);
4830
4988
  }
4831
4989
  /**
4832
4990
  * Draw a line and an arrow at the end (zB) of the given line with the specified (CSS-) color.
4833
4991
  *
4834
4992
  * @method arrow
4835
- * @param {Vertex} zA - The start point of the arrow-line.
4836
- * @param {Vertex} zB - The end point of the arrow-line.
4993
+ * @param {XYCoords} zA - The start point of the arrow-line.
4994
+ * @param {XYCoords} zB - The end point of the arrow-line.
4837
4995
  * @param {string} color - Any valid CSS color string.
4838
4996
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
4997
+ * @param {headLength=8} headLength - (optional) The length of the arrow head (default is 8 units).
4998
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
4999
+ *
4839
5000
  * @return {void}
4840
5001
  * @instance
4841
5002
  * @memberof drawutilssvg
4842
5003
  **/
4843
- arrow(zA, zB, color, lineWidth) {
4844
- const node = this.makeNode("path");
4845
- var headlen = 8; // length of head in pixels
4846
- var vertices = Vertex.utils.buildArrowHead(zA, zB, headlen, this.scale.x, this.scale.y);
4847
- const d = ["M", this._x(zA.x), this._y(zA.y)];
4848
- for (var i = 0; i <= vertices.length; i++) {
4849
- d.push("L");
4850
- // Note: only use offset here (the vertices are already scaled)
4851
- d.push(this.offset.x + vertices[i % vertices.length].x);
4852
- d.push(this.offset.y + vertices[i % vertices.length].y);
4853
- }
4854
- node.setAttribute("d", d.join(" "));
4855
- return this._bindFillDraw(node, "arrow", color, lineWidth || 1);
5004
+ arrow(zA, zB, color, lineWidth, headLength = 8, strokeOptions) {
5005
+ const group = this.makeNode("g");
5006
+ const arrowHeadBasePosition = { x: 0, y: 0 };
5007
+ // Just create the child nodes, don't bind them to the root node.
5008
+ const arrowHead = this.makeArrowHeadNode(zA, zB, color, lineWidth, headLength, undefined, arrowHeadBasePosition);
5009
+ const line = this.makeLineNode(zA, arrowHeadBasePosition, color, lineWidth, strokeOptions);
5010
+ group.appendChild(line);
5011
+ group.appendChild(arrowHead);
5012
+ this._addCSSClasses(group, "linear-arrow");
5013
+ this._bindNode(group, undefined);
5014
+ return group;
5015
+ }
5016
+ /**
5017
+ * Draw a cubic Bézier curve and and an arrow at the end (endControlPoint) of the given line width the specified (CSS-) color and arrow size.
5018
+ *
5019
+ * @method cubicBezierArrow
5020
+ * @param {XYCoords} startPoint - The start point of the cubic Bézier curve
5021
+ * @param {XYCoords} endPoint - The end point the cubic Bézier curve.
5022
+ * @param {XYCoords} startControlPoint - The start control point the cubic Bézier curve.
5023
+ * @param {XYCoords} endControlPoint - The end control point the cubic Bézier curve.
5024
+ * @param {string} color - The CSS color to draw the curve with.
5025
+ * @param {number} lineWidth - (optional) The line width to use.
5026
+ * @param {headLength=8} headLength - (optional) The length of the arrow head (default is 8 units).
5027
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5028
+ *
5029
+ * @return {void}
5030
+ * @instance
5031
+ * @memberof DrawLib
5032
+ */
5033
+ cubicBezierArrow(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth, headLength = 8, strokeOptions) {
5034
+ const group = this.makeNode("g");
5035
+ // Just create the child nodes, don't bind them to the root node.
5036
+ const arrowHeadBasePosition = new Vertex(0, 0);
5037
+ const arrowHead = this.makeArrowHeadNode(endControlPoint, endPoint, color, lineWidth, headLength, undefined, arrowHeadBasePosition);
5038
+ const diff = arrowHeadBasePosition.difference(endPoint);
5039
+ const bezier = this.makeCubicBezierNode(startPoint, { x: endPoint.x - diff.x, y: endPoint.y - diff.y }, startControlPoint, { x: endControlPoint.x - diff.x, y: endControlPoint.y - diff.y }, color, lineWidth, strokeOptions);
5040
+ group.appendChild(bezier);
5041
+ group.appendChild(arrowHead);
5042
+ this._addCSSClasses(group, "cubicbezier-arrow");
5043
+ this._bindNode(group, undefined);
5044
+ return group;
5045
+ }
5046
+ /**
5047
+ * Draw just an arrow head a the end of an imaginary line (zB) of the given line width the specified (CSS-) color and size.
5048
+ *
5049
+ * @method arrow
5050
+ * @param {XYCoords} zA - The start point of the arrow-line.
5051
+ * @param {XYCoords} zB - The end point of the arrow-line.
5052
+ * @param {string} color - Any valid CSS color string.
5053
+ * @param {number=1} lineWidth - (optional) The line width to use; default is 1.
5054
+ * @param {number=8} headLength - (optional) The length of the arrow head (default is 8 pixels).
5055
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5056
+ *
5057
+ * @return {void}
5058
+ * @instance
5059
+ * @memberof DrawLib
5060
+ **/
5061
+ arrowHead(zA, zB, color, lineWidth, headLength = 8, strokeOptions) {
5062
+ const node = this.makeArrowHeadNode(zA, zB, color, lineWidth, headLength, strokeOptions);
5063
+ return this._bindFillDraw(node, "arrowhead", color, lineWidth || 1, strokeOptions);
4856
5064
  }
4857
5065
  /**
4858
5066
  * Draw an image at the given position with the given size.<br>
@@ -4861,8 +5069,8 @@ class drawutilssvg {
4861
5069
  *
4862
5070
  * @method image
4863
5071
  * @param {Image} image - The image object to draw.
4864
- * @param {Vertex} position - The position to draw the the upper left corner at.
4865
- * @param {Vertex} size - The x/y-size to draw the image with.
5072
+ * @param {XYCoords} position - The position to draw the the upper left corner at.
5073
+ * @param {XYCoords} size - The x/y-size to draw the image with.
4866
5074
  * @param {number=0.0} alpha - (optional, default=0.0) The transparency (1.0=opaque, 0.0=transparent).
4867
5075
  * @return {void}
4868
5076
  * @instance
@@ -4905,16 +5113,16 @@ class drawutilssvg {
4905
5113
  * @param {Image} textureImage - The image object to draw.
4906
5114
  * @param {Bounds} textureSize - The texture size to use; these are the original bounds to map the polygon vertices to.
4907
5115
  * @param {Polygon} polygon - The polygon to use as clip path.
4908
- * @param {Vertex} polygonPosition - The polygon's position (relative), measured at the bounding box's center.
5116
+ * @param {XYCoords} polygonPosition - The polygon's position (relative), measured at the bounding box's center.
4909
5117
  * @param {number} rotation - The rotation to use for the polygon (and for the texture).
4910
5118
  * @return {void}
4911
5119
  * @instance
4912
5120
  * @memberof drawutilssvg
4913
5121
  **/
4914
5122
  texturedPoly(textureImage, textureSize, polygon, polygonPosition, rotation) {
4915
- polygon.getBounds();
5123
+ // const basePolygonBounds: Bounds = polygon.getBounds();
4916
5124
  const rotatedScalingOrigin = new Vertex(textureSize.min).clone().rotate(rotation, polygonPosition);
4917
- polygonPosition.clone().add(rotatedScalingOrigin.difference(textureSize.min).inv());
5125
+ // const rotationCenter = polygonPosition.clone().add(rotatedScalingOrigin.difference(textureSize.min).inv());
4918
5126
  // Create something like this
4919
5127
  // ...
4920
5128
  // <defs>
@@ -4968,36 +5176,21 @@ class drawutilssvg {
4968
5176
  * Draw the given (cubic) bézier curve.
4969
5177
  *
4970
5178
  * @method cubicBezier
4971
- * @param {Vertex} startPoint - The start point of the cubic Bézier curve
4972
- * @param {Vertex} endPoint - The end point the cubic Bézier curve.
4973
- * @param {Vertex} startControlPoint - The start control point the cubic Bézier curve.
4974
- * @param {Vertex} endControlPoint - The end control point the cubic Bézier curve.
5179
+ * @param {XYCoords} startPoint - The start point of the cubic Bézier curve
5180
+ * @param {XYCoords} endPoint - The end point the cubic Bézier curve.
5181
+ * @param {XYCoords} startControlPoint - The start control point the cubic Bézier curve.
5182
+ * @param {XYCoords} endControlPoint - The end control point the cubic Bézier curve.
4975
5183
  * @param {string} color - The CSS color to draw the curve with.
4976
5184
  * @param {number} lineWidth - (optional) The line width to use.
4977
- * @return {void}
4978
- * @instance
4979
- * @memberof drawutilssvg
4980
- */
4981
- cubicBezier(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth) {
4982
- if (startPoint instanceof CubicBezierCurve) {
4983
- return this.cubicBezier(startPoint.startPoint, startPoint.endPoint, startPoint.startControlPoint, startPoint.endControlPoint, color, lineWidth);
4984
- }
4985
- const node = this.makeNode("path");
4986
- // Draw curve
4987
- const d = [
4988
- "M",
4989
- this._x(startPoint.x),
4990
- this._y(startPoint.y),
4991
- "C",
4992
- this._x(startControlPoint.x),
4993
- this._y(startControlPoint.y),
4994
- this._x(endControlPoint.x),
4995
- this._y(endControlPoint.y),
4996
- this._x(endPoint.x),
4997
- this._y(endPoint.y)
4998
- ];
4999
- node.setAttribute("d", d.join(" "));
5000
- return this._bindFillDraw(node, "cubicBezier", color, lineWidth);
5185
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5186
+ *
5187
+ * @return {void}
5188
+ * @instance
5189
+ * @memberof drawutilssvg
5190
+ */
5191
+ cubicBezier(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth, strokeOptions) {
5192
+ const node = this.makeCubicBezierNode(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth, strokeOptions);
5193
+ return this._bindNode(node, undefined);
5001
5194
  }
5002
5195
  /**
5003
5196
  * Draw the given (cubic) Bézier path.
@@ -5007,17 +5200,21 @@ class drawutilssvg {
5007
5200
  * <pre> [ point1, point1_startControl, point2_endControl, point2, point2_startControl, point3_endControl, point3, ... pointN_endControl, pointN ]</pre>
5008
5201
  *
5009
5202
  * @method cubicBezierPath
5010
- * @param {Vertex[]} path - The cubic bezier path as described above.
5203
+ * @param {XYCoords[]} path - The cubic bezier path as described above.
5011
5204
  * @param {string} color - The CSS colot to draw the path with.
5012
5205
  * @param {number=1} lineWidth - (optional) The line width to use.
5206
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5207
+ *
5013
5208
  * @return {void}
5014
5209
  * @instance
5015
5210
  * @memberof drawutilssvg
5016
5211
  */
5017
- cubicBezierPath(path, color, lineWidth) {
5212
+ cubicBezierPath(path, color, lineWidth, strokeOptions) {
5018
5213
  const node = this.makeNode("path");
5019
- if (!path || path.length == 0)
5214
+ this.applyStrokeOpts(node, strokeOptions);
5215
+ if (!path || path.length == 0) {
5020
5216
  return node;
5217
+ }
5021
5218
  // Draw curve
5022
5219
  const d = ["M", this._x(path[0].x), this._y(path[0].y)];
5023
5220
  // Draw curve path
@@ -5054,20 +5251,20 @@ class drawutilssvg {
5054
5251
  * Draw a handle line (with a light grey).
5055
5252
  *
5056
5253
  * @method handleLine
5057
- * @param {Vertex} startPoint - The start point to draw the handle at.
5058
- * @param {Vertex} endPoint - The end point to draw the handle at.
5254
+ * @param {XYCoords} startPoint - The start point to draw the handle at.
5255
+ * @param {XYCoords} endPoint - The end point to draw the handle at.
5059
5256
  * @return {void}
5060
5257
  * @instance
5061
5258
  * @memberof drawutilssvg
5062
5259
  */
5063
5260
  handleLine(startPoint, endPoint) {
5064
- this.line(startPoint, endPoint, "rgb(192,192,192)");
5261
+ this.line(startPoint, endPoint, "rgb(128,128,128,0.5)");
5065
5262
  }
5066
5263
  /**
5067
5264
  * Draw a 1x1 dot with the specified (CSS-) color.
5068
5265
  *
5069
5266
  * @method dot
5070
- * @param {Vertex} p - The position to draw the dot at.
5267
+ * @param {XYCoords} p - The position to draw the dot at.
5071
5268
  * @param {string} color - The CSS color to draw the dot with.
5072
5269
  * @return {void}
5073
5270
  * @instance
@@ -5075,13 +5272,17 @@ class drawutilssvg {
5075
5272
  */
5076
5273
  dot(p, color) {
5077
5274
  const node = this.makeNode("line");
5275
+ node.setAttribute("x1", `${this._x(p.x)}`);
5276
+ node.setAttribute("y1", `${this._y(p.y)}`);
5277
+ node.setAttribute("x2", `${this._x(p.x)}`);
5278
+ node.setAttribute("y2", `${this._y(p.y)}`);
5078
5279
  return this._bindFillDraw(node, "dot", color, 1);
5079
5280
  }
5080
5281
  /**
5081
5282
  * Draw the given point with the specified (CSS-) color and radius 3.
5082
5283
  *
5083
5284
  * @method point
5084
- * @param {Vertex} p - The position to draw the point at.
5285
+ * @param {XYCoords} p - The position to draw the point at.
5085
5286
  * @param {string} color - The CSS color to draw the point with.
5086
5287
  * @return {void}
5087
5288
  * @instance
@@ -5101,17 +5302,20 @@ class drawutilssvg {
5101
5302
  * Note that if the x- and y- scales are different the result will be an ellipse rather than a circle.
5102
5303
  *
5103
5304
  * @method circle
5104
- * @param {Vertex} center - The center of the circle.
5305
+ * @param {XYCoords} center - The center of the circle.
5105
5306
  * @param {number} radius - The radius of the circle.
5106
5307
  * @param {string} color - The CSS color to draw the circle with.
5107
5308
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
5309
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5310
+ *
5108
5311
  * @return {void}
5109
5312
  * @instance
5110
5313
  * @memberof drawutilssvg
5111
5314
  */
5112
- circle(center, radius, color, lineWidth) {
5315
+ circle(center, radius, color, lineWidth, strokeOptions) {
5113
5316
  // Todo: draw ellipse when scalex!=scaley
5114
5317
  const node = this.makeNode("circle");
5318
+ this.applyStrokeOpts(node, strokeOptions);
5115
5319
  node.setAttribute("cx", `${this._x(center.x)}`);
5116
5320
  node.setAttribute("cy", `${this._y(center.y)}`);
5117
5321
  node.setAttribute("r", `${radius * this.scale.x}`); // y?
@@ -5121,17 +5325,20 @@ class drawutilssvg {
5121
5325
  * Draw a circular arc (section of a circle) with the given CSS color.
5122
5326
  *
5123
5327
  * @method circleArc
5124
- * @param {Vertex} center - The center of the circle.
5328
+ * @param {XYCoords} center - The center of the circle.
5125
5329
  * @param {number} radius - The radius of the circle.
5126
5330
  * @param {number} startAngle - The angle to start at.
5127
5331
  * @param {number} endAngle - The angle to end at.
5128
5332
  * @param {string} color - The CSS color to draw the circle with.
5333
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5334
+ *
5129
5335
  * @return {void}
5130
5336
  * @instance
5131
5337
  * @memberof drawutilssvg
5132
5338
  */
5133
- circleArc(center, radius, startAngle, endAngle, color, lineWidth) {
5339
+ circleArc(center, radius, startAngle, endAngle, color, lineWidth, strokeOptions) {
5134
5340
  const node = this.makeNode("path");
5341
+ this.applyStrokeOpts(node, strokeOptions);
5135
5342
  const arcData = CircleSector.circleSectorUtils.describeSVGArc(this._x(center.x), this._y(center.y), radius * this.scale.x, // y?
5136
5343
  startAngle, endAngle);
5137
5344
  node.setAttribute("d", arcData.join(" "));
@@ -5141,21 +5348,24 @@ class drawutilssvg {
5141
5348
  * Draw an ellipse with the specified (CSS-) color and thw two radii.
5142
5349
  *
5143
5350
  * @method ellipse
5144
- * @param {Vertex} center - The center of the ellipse.
5351
+ * @param {XYCoords} center - The center of the ellipse.
5145
5352
  * @param {number} radiusX - The radius of the ellipse.
5146
5353
  * @param {number} radiusY - The radius of the ellipse.
5147
5354
  * @param {string} color - The CSS color to draw the ellipse with.
5148
5355
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
5149
5356
  * @param {number=} rotation - (optional, default=0) The rotation of the ellipse.
5357
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5358
+ *
5150
5359
  * @return {void}
5151
5360
  * @instance
5152
5361
  * @memberof drawutilssvg
5153
5362
  */
5154
- ellipse(center, radiusX, radiusY, color, lineWidth, rotation) {
5363
+ ellipse(center, radiusX, radiusY, color, lineWidth, rotation, strokeOptions) {
5155
5364
  if (typeof rotation === "undefined") {
5156
5365
  rotation = 0.0;
5157
5366
  }
5158
5367
  const node = this.makeNode("ellipse");
5368
+ this.applyStrokeOpts(node, strokeOptions);
5159
5369
  node.setAttribute("cx", `${this._x(center.x)}`);
5160
5370
  node.setAttribute("cy", `${this._y(center.y)}`);
5161
5371
  node.setAttribute("rx", `${radiusX * this.scale.x}`);
@@ -5171,15 +5381,18 @@ class drawutilssvg {
5171
5381
  *
5172
5382
  * @method square
5173
5383
  * @param {XYCoords} center - The center of the square.
5174
- * @param {Vertex} size - The size of the square.
5384
+ * @param {number} size - The size of the square.
5175
5385
  * @param {string} color - The CSS color to draw the square with.
5176
5386
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
5177
- * @return {void}
5387
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5388
+ *
5389
+ * @return {SVGElement}
5178
5390
  * @instance
5179
5391
  * @memberof drawutilssvg
5180
5392
  */
5181
- square(center, size, color, lineWidth) {
5393
+ square(center, size, color, lineWidth, strokeOptions) {
5182
5394
  const node = this.makeNode("rectangle");
5395
+ this.applyStrokeOpts(node, strokeOptions);
5183
5396
  node.setAttribute("x", `${this._x(center.x - size / 2.0)}`);
5184
5397
  node.setAttribute("y", `${this._y(center.y - size / 2.0)}`);
5185
5398
  node.setAttribute("width", `${size * this.scale.x}`);
@@ -5194,9 +5407,15 @@ class drawutilssvg {
5194
5407
  * @param {number} height - The height of the rectangle.
5195
5408
  * @param {string} color - The color to use.
5196
5409
  * @param {number=1} lineWidth - (optional) The line with to use (default is 1).
5410
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5411
+ *
5412
+ * @return {SVGElement}
5413
+ * @instance
5414
+ * @memberof drawutilssvg
5197
5415
  **/
5198
- rect(position, width, height, color, lineWidth) {
5416
+ rect(position, width, height, color, lineWidth, strokeOptions) {
5199
5417
  const node = this.makeNode("rect");
5418
+ this.applyStrokeOpts(node, strokeOptions);
5200
5419
  node.setAttribute("x", `${this._x(position.x)}`);
5201
5420
  node.setAttribute("y", `${this._y(position.y)}`);
5202
5421
  node.setAttribute("width", `${width * this.scale.x}`);
@@ -5207,7 +5426,7 @@ class drawutilssvg {
5207
5426
  * Draw a grid of horizontal and vertical lines with the given (CSS-) color.
5208
5427
  *
5209
5428
  * @method grid
5210
- * @param {Vertex} center - The center of the grid.
5429
+ * @param {XYCoords} center - The center of the grid.
5211
5430
  * @param {number} width - The total width of the grid (width/2 each to the left and to the right).
5212
5431
  * @param {number} height - The total height of the grid (height/2 each to the top and to the bottom).
5213
5432
  * @param {number} sizeX - The horizontal grid size.
@@ -5241,7 +5460,7 @@ class drawutilssvg {
5241
5460
  * This works analogue to the grid() function
5242
5461
  *
5243
5462
  * @method raster
5244
- * @param {Vertex} center - The center of the raster.
5463
+ * @param {XYCoords} center - The center of the raster.
5245
5464
  * @param {number} width - The total width of the raster (width/2 each to the left and to the right).
5246
5465
  * @param {number} height - The total height of the raster (height/2 each to the top and to the bottom).
5247
5466
  * @param {number} sizeX - The horizontal raster size.
@@ -5274,8 +5493,8 @@ class drawutilssvg {
5274
5493
  * as even shaped diamonds.
5275
5494
  *
5276
5495
  * @method diamondHandle
5277
- * @param {Vertex} center - The center of the diamond.
5278
- * @param {Vertex} size - The x/y-size of the diamond.
5496
+ * @param {XYCoords} center - The center of the diamond.
5497
+ * @param {number} size - The x/y-size of the diamond.
5279
5498
  * @param {string} color - The CSS color to draw the diamond with.
5280
5499
  * @return {void}
5281
5500
  * @instance
@@ -5309,8 +5528,8 @@ class drawutilssvg {
5309
5528
  * as even shaped squares.
5310
5529
  *
5311
5530
  * @method squareHandle
5312
- * @param {Vertex} center - The center of the square.
5313
- * @param {Vertex} size - The x/y-size of the square.
5531
+ * @param {XYCoords} center - The center of the square.
5532
+ * @param {XYCoords} size - The x/y-size of the square.
5314
5533
  * @param {string} color - The CSS color to draw the square with.
5315
5534
  * @return {void}
5316
5535
  * @instance
@@ -5332,7 +5551,7 @@ class drawutilssvg {
5332
5551
  * as even shaped circles.
5333
5552
  *
5334
5553
  * @method circleHandle
5335
- * @param {Vertex} center - The center of the circle.
5554
+ * @param {XYCoords} center - The center of the circle.
5336
5555
  * @param {number} radius - The radius of the circle.
5337
5556
  * @param {string} color - The CSS color to draw the circle with.
5338
5557
  * @return {void}
@@ -5431,18 +5650,22 @@ class drawutilssvg {
5431
5650
  * Draw a polygon line (alternative function to the polygon).
5432
5651
  *
5433
5652
  * @method polyline
5434
- * @param {Vertex[]} vertices - The polygon vertices to draw.
5653
+ * @param {XYCoords[]} vertices - The polygon vertices to draw.
5435
5654
  * @param {boolan} isOpen - If true the polyline will not be closed at its end.
5436
5655
  * @param {string} color - The CSS color to draw the polygon with.
5437
5656
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
5657
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
5658
+ *
5438
5659
  * @return {void}
5439
5660
  * @instance
5440
5661
  * @memberof drawutilssvg
5441
5662
  */
5442
- polyline(vertices, isOpen, color, lineWidth) {
5663
+ polyline(vertices, isOpen, color, lineWidth, strokeOptions) {
5443
5664
  const node = this.makeNode("path");
5444
- if (vertices.length == 0)
5665
+ this.applyStrokeOpts(node, strokeOptions);
5666
+ if (vertices.length == 0) {
5445
5667
  return node;
5668
+ }
5446
5669
  // Draw curve
5447
5670
  const d = ["M", this._x(vertices[0].x), this._y(vertices[0].y)];
5448
5671
  var n = vertices.length;
@@ -5545,12 +5768,16 @@ class drawutilssvg {
5545
5768
  * @param {string=null} color - (optional) The color to draw this path with (default is null).
5546
5769
  * @param {number=1} lineWidth - (optional) the line width to use (default is 1).
5547
5770
  * @param {boolean=false} options.inplace - (optional) If set to true then path transforamtions (scale and translate) will be done in-place in the array. This can boost the performance.
5771
+ * @param {number=} options.dashOffset - (optional) `See StrokeOptions`.
5772
+ * @param {number=[]} options.dashArray - (optional) `See StrokeOptions`.
5773
+ *
5548
5774
  * @instance
5549
5775
  * @memberof drawutils
5550
5776
  * @return {R} An instance representing the drawn path.
5551
5777
  */
5552
5778
  path(pathData, color, lineWidth, options) {
5553
5779
  const node = this.makeNode("path");
5780
+ this.applyStrokeOpts(node, options);
5554
5781
  // Transform the path: in-place (fast) or copy (slower)
5555
5782
  const d = options && options.inplace ? pathData : drawutilssvg.copyPathData(pathData);
5556
5783
  drawutilssvg.transformPathData(d, this.offset, this.scale);
@@ -5576,7 +5803,7 @@ class drawutilssvg {
5576
5803
  }
5577
5804
  // Add a covering rect with the given background color
5578
5805
  this.curId = "background";
5579
- this.curClassName = undefined;
5806
+ this.curClassName = null; // undefined;
5580
5807
  const node = this.makeNode("rect");
5581
5808
  // For some strange reason SVG rotation transforms use degrees instead of radians
5582
5809
  // Note that the background does not scale with the zoom level (always covers full element)
@@ -5588,7 +5815,7 @@ class drawutilssvg {
5588
5815
  this._bindFillDraw(node, this.curId, null, null);
5589
5816
  node.setAttribute("fill", typeof color === "undefined" ? "none" : color);
5590
5817
  // Clear the current ID again
5591
- this.curId = undefined;
5818
+ this.curId = null; // undefined;
5592
5819
  }
5593
5820
  /**
5594
5821
  * A private helper function to clear all SVG nodes from the &gt;g> node.
@@ -5792,6 +6019,126 @@ class drawutilssvg {
5792
6019
  }
5793
6020
  } // END while
5794
6021
  } // END transformPathData
6022
+ static nodeSupportsLineDash(nodeName) {
6023
+ return ["line", "path", "circle", "ellipse", "rectangle", "rect"].includes(nodeName);
6024
+ }
6025
+ /**
6026
+ * Creates a basic <line> node with start and end coordinates. The created node will not
6027
+ * be bound to any root node.
6028
+ *
6029
+ * @private
6030
+ * @method makeLineNode
6031
+ * @param {XYCoords} zA - The line's start position.
6032
+ * @param {XYCoords} zB - The line's start position.
6033
+ * @param {string} color - The CSS color to draw the point with.
6034
+ * @param {number=1} lineWidth - (optional) The line width to use.
6035
+ * @param {StrokeOptions=} strokeOptions - (optional) Additional stroke options to use.
6036
+ * @param {string=} classNameOverride - (optional) If nothing is passed the default classname 'path' will be used.
6037
+ * @return {SVGLineElement}
6038
+ * @instance
6039
+ * @memberof drawutilssvg
6040
+ */
6041
+ makeLineNode(zA, zB, color, lineWidth, strokeOptions, classNameOverride) {
6042
+ const line = this.makeNode("line");
6043
+ line.setAttribute("x1", `${this._x(zA.x)}`);
6044
+ line.setAttribute("y1", `${this._y(zA.y)}`);
6045
+ line.setAttribute("x2", `${this._x(zB.x)}`);
6046
+ line.setAttribute("y2", `${this._y(zB.y)}`);
6047
+ this._configureNode(line, classNameOverride !== null && classNameOverride !== void 0 ? classNameOverride : "line", this.fillShapes, color, lineWidth || 1, strokeOptions);
6048
+ return line;
6049
+ }
6050
+ /**
6051
+ * Creates a basic <path> node with given path string data. The created node will not
6052
+ * be bound to any root node.
6053
+ *
6054
+ * @private
6055
+ * @method makePathNode
6056
+ * @param {string} pathString - The path data (must be a valid path data string).
6057
+ * @param {string} color - The CSS color to draw the point with.
6058
+ * @param {number=1} lineWidth - (optional) The line width to use.
6059
+ * @param {StrokeOptions=} strokeOptions - (optional) Additional stroke options to use.
6060
+ * @param {string=} classNameOverride - (optional) If nothing is passed the default classname 'path' will be used.
6061
+ * @return {SVGPathElement}
6062
+ * @instance
6063
+ * @memberof drawutilssvg
6064
+ */
6065
+ makePathNode(pathString, color, lineWidth, strokeOptions, classNameOverride) {
6066
+ const path = this.makeNode("path");
6067
+ path.setAttribute("d", pathString);
6068
+ this._configureNode(path, classNameOverride !== null && classNameOverride !== void 0 ? classNameOverride : "path", this.fillShapes, color, lineWidth || 1, strokeOptions);
6069
+ return path;
6070
+ }
6071
+ /**
6072
+ * Creates a basic arrow head node (<path> node) at the end of the given line coordinates. The created node will not
6073
+ * be bound to any root node.
6074
+ *
6075
+ * @private
6076
+ * @method makeArrowHeadNode
6077
+ * @param {string} pathString - The path data (must be a valid path data string).
6078
+ * @param {string} color - The CSS color to draw the point with.
6079
+ * @param {number=1} lineWidth - (optional) The line width to use.
6080
+ * @param {number=8} headLength - (optional) The length of the arrow head; if none is specified then the head will be 8 absolute units long.
6081
+ * @param {StrokeOptions=} strokeOptions - (optional) Additional stroke options to use.
6082
+ * @param {XYCoords=} arrowHeadBasePositionBuffer - (optional) If not null, then this position will contain the arrow head's start point (after execution). Some sort of OUT variable.
6083
+ * @return {SVGPathElement}
6084
+ * @instance
6085
+ * @memberof drawutilssvg
6086
+ */
6087
+ makeArrowHeadNode(zA, zB, color, lineWidth, headLength = 8, strokeOptions, arrowHeadBasePositionBuffer) {
6088
+ var vertices = Vector.utils.buildArrowHead(zA, zB, headLength, this.scale.x, this.scale.y);
6089
+ const d = ["M", this.offset.x + vertices[0].x, this.offset.y + vertices[0].y];
6090
+ if (arrowHeadBasePositionBuffer) {
6091
+ arrowHeadBasePositionBuffer.x = vertices[0].x / this.scale.x;
6092
+ arrowHeadBasePositionBuffer.y = vertices[0].y / this.scale.y;
6093
+ }
6094
+ for (var i = 1; i <= vertices.length; i++) {
6095
+ d.push("L");
6096
+ // Note: only use offset here (the vertices are already scaled)
6097
+ d.push(this.offset.x + vertices[i % vertices.length].x);
6098
+ d.push(this.offset.y + vertices[i % vertices.length].y);
6099
+ }
6100
+ const node = this.makePathNode(d.join(" "), color, lineWidth, strokeOptions, "arrowhead");
6101
+ return node;
6102
+ }
6103
+ /**
6104
+ * Creates a basic cubic Bézier path node (<path> node) with the given cubic Bézier data. The created node will not
6105
+ * be bound to any root node.
6106
+ *
6107
+ * @private
6108
+ * @method makeCubicBezierNode
6109
+ * @param {XYCoords} startPoint - The start point of the cubic Bézier curve
6110
+ * @param {XYCoords} endPoint - The end point the cubic Bézier curve.
6111
+ * @param {XYCoords} startControlPoint - The start control point the cubic Bézier curve.
6112
+ * @param {XYCoords} endControlPoint - The end control point the cubic Bézier curve.
6113
+ * @param {string} color - The CSS color to draw the point with.
6114
+ * @param {number=1} lineWidth - (optional) The line width to use.
6115
+ * @param {StrokeOptions=} strokeOptions - (optional) Additional stroke options to use.
6116
+ * @param {string=} classNameOverride - (optional) If nothing is passed the default classname 'path' will be used.
6117
+ * @param {XYCoords=} arrowHeadBasePositionBuffer - (optional) If not null, then this position will contain the arrow head's start point (after execution). Some sort of OUT variable.
6118
+ * @return {SVGPathElement}
6119
+ * @instance
6120
+ * @memberof drawutilssvg
6121
+ */
6122
+ makeCubicBezierNode(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth, strokeOptions) {
6123
+ if (startPoint instanceof CubicBezierCurve) {
6124
+ return this.cubicBezier(startPoint.startPoint, startPoint.endPoint, startPoint.startControlPoint, startPoint.endControlPoint, color, lineWidth);
6125
+ }
6126
+ // Draw curve
6127
+ const d = [
6128
+ "M",
6129
+ this._x(startPoint.x),
6130
+ this._y(startPoint.y),
6131
+ "C",
6132
+ this._x(startControlPoint.x),
6133
+ this._y(startControlPoint.y),
6134
+ this._x(endControlPoint.x),
6135
+ this._y(endControlPoint.y),
6136
+ this._x(endPoint.x),
6137
+ this._y(endPoint.y)
6138
+ ];
6139
+ const node = this.makePathNode(d.join(" "), color, lineWidth, strokeOptions, "cubicBezier");
6140
+ return node;
6141
+ }
5795
6142
  }
5796
6143
  drawutilssvg.HEAD_XML = [
5797
6144
  '<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
@@ -5850,7 +6197,15 @@ drawutilssvg.HEAD_XML = [
5850
6197
  * @modified 2022-08-23 Fixed a type issue in the `setConfiguration` function.
5851
6198
  * @modified 2022-08-23 Fixed a type issue in the `path` function.
5852
6199
  * @modified 2023-02-10 The methods `setCurrentClassName` and `setCurrentId` also accept `null` now.
5853
- * @version 1.12.4
6200
+ * @modified 2023-09-29 Removed unused method stub for texturedPoly helper function (cleanup).
6201
+ * @modified 2023-09-29 Downgrading all `Vertex` param type to the more generic `XYCoords` type in these render functions: line, arrow, texturedPoly, cubicBezier, cubicBezierPath, handle, handleLine, dot, point, circle, circleArc, ellipse, grid, raster.
6202
+ * @modified 2023-09-29 Added the `headLength` parameter to the 'DrawLib.arrow()` function.
6203
+ * @modified 2023-09-29 Added the `arrowHead(...)` function to the 'DrawLib.arrow()` interface.
6204
+ * @modified 2023-09-29 Added the `cubicBezierArrow(...)` function to the 'DrawLib.arrow()` interface.
6205
+ * @modified 2023-09-29 Added the `lineDashes` attribute.
6206
+ * @modified 2023-09-30 Adding `strokeOptions` param to these draw function: line, arrow, cubicBezierArrow, cubicBezier, cubicBezierPath, circle, circleArc, ellipse, square, rect, polygon, polyline.
6207
+ * @modified 2023-10-07 Adding the optional `arrowHeadBasePositionBuffer` param to the arrowHead(...) method.
6208
+ * @version 1.13.0
5854
6209
  **/
5855
6210
  // Todo: rename this class to Drawutils?
5856
6211
  /**
@@ -5872,10 +6227,47 @@ class drawutils {
5872
6227
  **/
5873
6228
  constructor(context, fillShapes) {
5874
6229
  this.ctx = context;
6230
+ // this.lineDash = [];
5875
6231
  this.offset = new Vertex(0, 0);
5876
6232
  this.scale = new Vertex(1, 1);
5877
6233
  this.fillShapes = fillShapes;
5878
6234
  }
6235
+ /**
6236
+ * A private helper method to apply stroke options to the current
6237
+ * context.
6238
+ * @param {StrokeOptions=} strokeOptions -
6239
+ */
6240
+ applyStrokeOpts(strokeOptions) {
6241
+ var _a, _b;
6242
+ this.ctx.setLineDash(((_a = strokeOptions === null || strokeOptions === void 0 ? void 0 : strokeOptions.dashArray) !== null && _a !== void 0 ? _a : []).map((dashArrayElem) => {
6243
+ // Note assume scale.x === scale.y
6244
+ // Invariant scale makes funny stuff anyway.
6245
+ return dashArrayElem * this.scale.x;
6246
+ }));
6247
+ this.ctx.lineDashOffset = ((_b = strokeOptions === null || strokeOptions === void 0 ? void 0 : strokeOptions.dashOffset) !== null && _b !== void 0 ? _b : 0) * this.scale.x;
6248
+ }
6249
+ // +---------------------------------------------------------------------------------
6250
+ // | This is the final helper function for drawing and filling stuff. It is not
6251
+ // | intended to be used from the outside.
6252
+ // |
6253
+ // | When in draw mode it draws the current shape.
6254
+ // | When in fill mode it fills the current shape.
6255
+ // |
6256
+ // | This function is usually only called internally.
6257
+ // |
6258
+ // | @param color A stroke/fill color to use.
6259
+ // +-------------------------------
6260
+ // TODO: convert this to a STATIC function.
6261
+ _fillOrDraw(color) {
6262
+ if (this.fillShapes) {
6263
+ this.ctx.fillStyle = color;
6264
+ this.ctx.fill();
6265
+ }
6266
+ else {
6267
+ this.ctx.strokeStyle = color;
6268
+ this.ctx.stroke();
6269
+ }
6270
+ }
5879
6271
  /**
5880
6272
  * Called before each draw cycle.
5881
6273
  * @param {UID=} uid - (optional) A UID identifying the currently drawn element(s).
@@ -5906,6 +6298,20 @@ class drawutils {
5906
6298
  setConfiguration(configuration) {
5907
6299
  this.ctx.globalCompositeOperation = configuration.blendMode || "source-over";
5908
6300
  }
6301
+ // /**
6302
+ // * Set or clear the line-dash configuration. Pass `null` for un-dashed lines.
6303
+ // *
6304
+ // * See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray
6305
+ // * and https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
6306
+ // * for how line dashes work.
6307
+ // *
6308
+ // * @method
6309
+ // * @param {Array<number> lineDashes - The line-dash array configuration.
6310
+ // * @returns {void}
6311
+ // */
6312
+ // setLineDash(lineDash: Array<number>) {
6313
+ // this.lineDash = lineDash;
6314
+ // }
5909
6315
  /**
5910
6316
  * This method shouled be called each time the currently drawn `Drawable` changes.
5911
6317
  * It is used by some libraries for identifying elemente on re-renders.
@@ -5932,17 +6338,20 @@ class drawutils {
5932
6338
  * Draw the line between the given two points with the specified (CSS-) color.
5933
6339
  *
5934
6340
  * @method line
5935
- * @param {Vertex} zA - The start point of the line.
5936
- * @param {Vertex} zB - The end point of the line.
6341
+ * @param {XYCoords} zA - The start point of the line.
6342
+ * @param {XYCoords} zB - The end point of the line.
5937
6343
  * @param {string} color - Any valid CSS color string.
5938
6344
  * @param {number} lineWidth? - [optional] The line's width.
6345
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6346
+ *
5939
6347
  * @return {void}
5940
6348
  * @instance
5941
6349
  * @memberof drawutils
5942
6350
  **/
5943
- line(zA, zB, color, lineWidth) {
6351
+ line(zA, zB, color, lineWidth, strokeOptions) {
5944
6352
  this.ctx.save();
5945
6353
  this.ctx.beginPath();
6354
+ this.applyStrokeOpts(strokeOptions);
5946
6355
  this.ctx.moveTo(this.offset.x + zA.x * this.scale.x, this.offset.y + zA.y * this.scale.y);
5947
6356
  this.ctx.lineTo(this.offset.x + zB.x * this.scale.x, this.offset.y + zB.y * this.scale.y);
5948
6357
  this.ctx.strokeStyle = color;
@@ -5954,22 +6363,74 @@ class drawutils {
5954
6363
  * Draw a line and an arrow at the end (zB) of the given line with the specified (CSS-) color.
5955
6364
  *
5956
6365
  * @method arrow
5957
- * @param {Vertex} zA - The start point of the arrow-line.
5958
- * @param {Vertex} zB - The end point of the arrow-line.
6366
+ * @param {XYCoords} zA - The start point of the arrow-line.
6367
+ * @param {XYCoords} zB - The end point of the arrow-line.
5959
6368
  * @param {string} color - Any valid CSS color string.
5960
6369
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
6370
+ * @param {headLength=8} headLength - (optional) The length of the arrow head (default is 8 units).
6371
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6372
+ *
5961
6373
  * @return {void}
5962
6374
  * @instance
5963
6375
  * @memberof drawutils
5964
6376
  **/
5965
- arrow(zA, zB, color, lineWidth) {
5966
- var headlen = 8; // length of head in pixels
5967
- // var vertices = PlotBoilerplate.utils.buildArrowHead( zA, zB, headlen, this.scale.x, this.scale.y );
5968
- // var vertices : Array<Vertex> = Vertex.utils.buildArrowHead( zA, zB, headlen, this.scale.x, this.scale.y );
6377
+ arrow(zA, zB, color, lineWidth, headLength = 8, strokeOptions) {
6378
+ const arrowHeadBasePosition = new Vertex(0, 0);
6379
+ this.arrowHead(zA, zB, color, lineWidth, headLength, undefined, arrowHeadBasePosition); // Will NOT use dash configuration
6380
+ this.line(zA, arrowHeadBasePosition, color, lineWidth, strokeOptions); // Will use dash configuration
6381
+ }
6382
+ /**
6383
+ * Draw a cubic Bézier curve and and an arrow at the end (endControlPoint) of the given line width the specified (CSS-) color and arrow size.
6384
+ *
6385
+ * @method cubicBezierArrow
6386
+ * @param {XYCoords} startPoint - The start point of the cubic Bézier curve
6387
+ * @param {XYCoords} endPoint - The end point the cubic Bézier curve.
6388
+ * @param {XYCoords} startControlPoint - The start control point the cubic Bézier curve.
6389
+ * @param {XYCoords} endControlPoint - The end control point the cubic Bézier curve.
6390
+ * @param {string} color - The CSS color to draw the curve with.
6391
+ * @param {number} lineWidth - (optional) The line width to use.
6392
+ * @param {headLength=8} headLength - (optional) The length of the arrow head (default is 8 units).
6393
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6394
+ *
6395
+ * @return {void}
6396
+ * @instance
6397
+ * @memberof DrawLib
6398
+ */
6399
+ cubicBezierArrow(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth, headLength, strokeOptions) {
6400
+ const arrowHeadBasePosition = new Vertex(0, 0);
6401
+ // Will NOT use dash configuration
6402
+ this.arrowHead(endControlPoint, endPoint, color, lineWidth, headLength, undefined, arrowHeadBasePosition);
6403
+ const diff = arrowHeadBasePosition.difference(endPoint);
6404
+ // Will use dash configuration
6405
+ this.cubicBezier(startPoint, { x: endPoint.x - diff.x, y: endPoint.y - diff.y }, startControlPoint, { x: endControlPoint.x - diff.x, y: endControlPoint.y - diff.y }, color, lineWidth, strokeOptions);
6406
+ }
6407
+ /**
6408
+ * Draw just an arrow head a the end of an imaginary line (zB) of the given line width the specified (CSS-) color and size.
6409
+ *
6410
+ * @method arrow
6411
+ * @param {XYCoords} zA - The start point of the arrow-line.
6412
+ * @param {XYCoords} zB - The end point of the arrow-line.
6413
+ * @param {string} color - Any valid CSS color string.
6414
+ * @param {number=1} lineWidth - (optional) The line width to use; default is 1.
6415
+ * @param {number=8} headLength - (optional) The length of the arrow head (default is 8 pixels).
6416
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6417
+ * @param {XYCoords=} arrowHeadBasePositionBuffer - (optional) If not null, then this position will contain the arrow head's start point (after execution). Some sort of OUT variable.
6418
+ *
6419
+ * @return {void}
6420
+ * @instance
6421
+ * @memberof DrawLib
6422
+ **/
6423
+ arrowHead(zA, zB, color, lineWidth, headLength = 8, strokeOptions, arrowHeadBasePositionBuffer) {
6424
+ // var headLength: number = 8; // length of head in pixels
5969
6425
  this.ctx.save();
5970
6426
  this.ctx.beginPath();
5971
- var vertices = Vertex.utils.buildArrowHead(zA, zB, headlen, this.scale.x, this.scale.y);
5972
- this.ctx.moveTo(this.offset.x + zA.x * this.scale.x, this.offset.y + zA.y * this.scale.y);
6427
+ this.applyStrokeOpts(strokeOptions);
6428
+ var vertices = Vector.utils.buildArrowHead(zA, zB, headLength, this.scale.x, this.scale.y);
6429
+ if (arrowHeadBasePositionBuffer) {
6430
+ arrowHeadBasePositionBuffer.x = vertices[0].x / this.scale.x;
6431
+ arrowHeadBasePositionBuffer.y = vertices[0].y / this.scale.y;
6432
+ }
6433
+ this.ctx.moveTo(this.offset.x + vertices[0].x, this.offset.y + vertices[0].y);
5973
6434
  for (var i = 0; i < vertices.length; i++) {
5974
6435
  this.ctx.lineTo(this.offset.x + vertices[i].x, this.offset.y + vertices[i].y);
5975
6436
  }
@@ -5985,8 +6446,8 @@ class drawutils {
5985
6446
  *
5986
6447
  * @method image
5987
6448
  * @param {Image} image - The image object to draw.
5988
- * @param {Vertex} position - The position to draw the the upper left corner at.
5989
- * @param {Vertex} size - The x/y-size to draw the image with.
6449
+ * @param {XYCoords} position - The position to draw the the upper left corner at.
6450
+ * @param {XYCoords} size - The x/y-size to draw the image with.
5990
6451
  * @param {number=0.0} alpha - (optional, default=0.0) The transparency (1.0=opaque, 0.0=transparent).
5991
6452
  * @return {void}
5992
6453
  * @instance
@@ -6015,7 +6476,7 @@ class drawutils {
6015
6476
  * @param {Image} textureImage - The image object to draw.
6016
6477
  * @param {Bounds} textureSize - The texture size to use; these are the original bounds to map the polygon vertices to.
6017
6478
  * @param {Polygon} polygon - The polygon to use as clip path.
6018
- * @param {Vertex} polygonPosition - The polygon's position (relative), measured at the bounding box's center.
6479
+ * @param {XYCoords} polygonPosition - The polygon's position (relative), measured at the bounding box's center.
6019
6480
  * @param {number} rotation - The rotation to use for the polygon (and for the texture).
6020
6481
  * @param {XYCoords={x:0,y:0}} rotationCenter - (optional) The rotational center; default is center of bounding box.
6021
6482
  * @return {void}
@@ -6024,10 +6485,9 @@ class drawutils {
6024
6485
  **/
6025
6486
  texturedPoly(textureImage, textureSize, polygon, polygonPosition, rotation) {
6026
6487
  var basePolygonBounds = polygon.getBounds();
6027
- var targetCenterDifference = polygonPosition.clone().difference(basePolygonBounds.getCenter());
6028
- // var rotationalOffset = rotationCenter ? polygonPosition.difference(rotationCenter) : { x: 0, y: 0 };
6029
- // var rotationalOffset = { x: 0, y: 0 };
6030
- basePolygonBounds.getCenter().sub(targetCenterDifference);
6488
+ // var targetCenterDifference = polygonPosition.clone().difference(basePolygonBounds.getCenter());
6489
+ new Vertex(polygonPosition.x, polygonPosition.y).difference(basePolygonBounds.getCenter());
6490
+ // var tileCenter = basePolygonBounds.getCenter().sub(targetCenterDifference);
6031
6491
  // Get the position offset of the polygon
6032
6492
  var targetTextureSize = new Vertex(textureSize.width, textureSize.height);
6033
6493
  // var targetTextureOffset = new Vertex(-textureSize.width / 2, -textureSize.height / 2).sub(targetCenterDifference);
@@ -6050,52 +6510,83 @@ class drawutils {
6050
6510
  );
6051
6511
  this.ctx.restore();
6052
6512
  }
6053
- _texturedPoly(textureImage, textureSize, polygon, polygonPosition, rotation, rotationCenter = { x: 0, y: 0 }) {
6054
- var basePolygonBounds = polygon.getBounds();
6055
- var targetCenterDifference = polygonPosition.clone().difference(basePolygonBounds.getCenter());
6056
- var rotationalOffset = rotationCenter ? polygonPosition.difference(rotationCenter) : { x: 0, y: 0 };
6057
- // var rotationalOffset = { x: 0, y: 0 };
6058
- var tileCenter = basePolygonBounds.getCenter().sub(targetCenterDifference);
6059
- // Get the position offset of the polygon
6060
- var targetTextureSize = new Vertex(textureSize.width, textureSize.height);
6061
- var targetTextureOffset = new Vertex(-textureSize.width / 2, -textureSize.height / 2).sub(targetCenterDifference);
6062
- this.ctx.save();
6063
- // this.ctx.translate(
6064
- // this.offset.x + (tileCenter.x - rotationalOffset.x * 0 + targetTextureOffset.x * 0.0) * this.scale.x,
6065
- // this.offset.y + (tileCenter.y - rotationalOffset.y * 0 + targetTextureOffset.y * 0.0) * this.scale.y
6066
- // );
6067
- this.ctx.translate(this.offset.x + (tileCenter.x - rotationalOffset.x * 0 + targetTextureOffset.x * 0.0) * this.scale.x, this.offset.y + (tileCenter.y - rotationalOffset.y * 0 + targetTextureOffset.y * 0.0) * this.scale.y);
6068
- this.ctx.rotate(rotation);
6069
- drawutils.helpers.clipPoly(this.ctx, {
6070
- x: (-targetCenterDifference.x * 1 - tileCenter.x - rotationalOffset.x) * this.scale.x,
6071
- y: (-targetCenterDifference.y * 1 - tileCenter.y - rotationalOffset.y) * this.scale.y
6072
- }, this.scale, polygon.vertices);
6073
- this.ctx.drawImage(textureImage, 0, 0, textureImage.naturalWidth - 1, // There is this horrible Safari bug (fixed in newer versions)
6513
+ /*
6514
+ _texturedPoly(
6515
+ textureImage: HTMLImageElement,
6516
+ textureSize: Bounds,
6517
+ polygon: Polygon,
6518
+ polygonPosition: XYCoords,
6519
+ rotation: number,
6520
+ rotationCenter: XYCoords = { x: 0, y: 0 }
6521
+ ): void {
6522
+ var basePolygonBounds = polygon.getBounds();
6523
+ var targetCenterDifference = polygonPosition.clone().difference(basePolygonBounds.getCenter());
6524
+ var rotationalOffset = rotationCenter ? polygonPosition.difference(rotationCenter) : { x: 0, y: 0 };
6525
+ // var rotationalOffset = { x: 0, y: 0 };
6526
+ var tileCenter = basePolygonBounds.getCenter().sub(targetCenterDifference);
6527
+
6528
+ // Get the position offset of the polygon
6529
+ var targetTextureSize = new Vertex(textureSize.width, textureSize.height);
6530
+ var targetTextureOffset = new Vertex(-textureSize.width / 2, -textureSize.height / 2).sub(targetCenterDifference);
6531
+
6532
+ this.ctx.save();
6533
+
6534
+ // this.ctx.translate(
6535
+ // this.offset.x + (tileCenter.x - rotationalOffset.x * 0 + targetTextureOffset.x * 0.0) * this.scale.x,
6536
+ // this.offset.y + (tileCenter.y - rotationalOffset.y * 0 + targetTextureOffset.y * 0.0) * this.scale.y
6537
+ // );
6538
+ this.ctx.translate(
6539
+ this.offset.x + (tileCenter.x - rotationalOffset.x * 0 + targetTextureOffset.x * 0.0) * this.scale.x,
6540
+ this.offset.y + (tileCenter.y - rotationalOffset.y * 0 + targetTextureOffset.y * 0.0) * this.scale.y
6541
+ );
6542
+ this.ctx.rotate(rotation);
6543
+
6544
+ drawutils.helpers.clipPoly(
6545
+ this.ctx,
6546
+ {
6547
+ x: (-targetCenterDifference.x * 1 - tileCenter.x - rotationalOffset.x) * this.scale.x,
6548
+ y: (-targetCenterDifference.y * 1 - tileCenter.y - rotationalOffset.y) * this.scale.y
6549
+ },
6550
+ this.scale,
6551
+ polygon.vertices
6552
+ );
6553
+ this.ctx.drawImage(
6554
+ textureImage,
6555
+ 0,
6556
+ 0,
6557
+ textureImage.naturalWidth - 1, // There is this horrible Safari bug (fixed in newer versions)
6074
6558
  textureImage.naturalHeight - 1, // To avoid errors substract 1 here.
6075
- (-polygonPosition.x + targetTextureOffset.x * 1 - rotationalOffset.x * 1) * this.scale.x, (-polygonPosition.y + targetTextureOffset.y * 1 - rotationalOffset.y * 1) * this.scale.y, targetTextureSize.x * this.scale.x, targetTextureSize.y * this.scale.y);
6076
- // const scaledTextureSize = new Bounds(
6077
- // new Vertex(
6078
- // -polygonPosition.x + targetTextureOffset.x - rotationalOffset.x,
6079
- // -polygonPosition.y + targetTextureOffset.y - rotationalOffset.y
6080
- // ).scaleXY(this.scale, rotationCenter),
6081
- // new Vertex(
6082
- // -polygonPosition.x + targetTextureOffset.x - rotationalOffset.x + targetTextureSize.x,
6083
- // -polygonPosition.y + targetTextureOffset.y - rotationalOffset.y + targetTextureSize.y
6084
- // ).scaleXY(this.scale, rotationCenter)
6085
- // );
6086
- // this.ctx.drawImage(
6087
- // textureImage,
6088
- // 0,
6089
- // 0,
6090
- // textureImage.naturalWidth - 1, // There is this horrible Safari bug (fixed in newer versions)
6091
- // textureImage.naturalHeight - 1, // To avoid errors substract 1 here.
6092
- // scaledTextureSize.min.x,
6093
- // scaledTextureSize.min.y,
6094
- // scaledTextureSize.width,
6095
- // scaledTextureSize.height
6096
- // );
6097
- this.ctx.restore();
6559
+ (-polygonPosition.x + targetTextureOffset.x * 1 - rotationalOffset.x * 1) * this.scale.x,
6560
+ (-polygonPosition.y + targetTextureOffset.y * 1 - rotationalOffset.y * 1) * this.scale.y,
6561
+ targetTextureSize.x * this.scale.x,
6562
+ targetTextureSize.y * this.scale.y
6563
+ );
6564
+
6565
+ // const scaledTextureSize = new Bounds(
6566
+ // new Vertex(
6567
+ // -polygonPosition.x + targetTextureOffset.x - rotationalOffset.x,
6568
+ // -polygonPosition.y + targetTextureOffset.y - rotationalOffset.y
6569
+ // ).scaleXY(this.scale, rotationCenter),
6570
+ // new Vertex(
6571
+ // -polygonPosition.x + targetTextureOffset.x - rotationalOffset.x + targetTextureSize.x,
6572
+ // -polygonPosition.y + targetTextureOffset.y - rotationalOffset.y + targetTextureSize.y
6573
+ // ).scaleXY(this.scale, rotationCenter)
6574
+ // );
6575
+ // this.ctx.drawImage(
6576
+ // textureImage,
6577
+ // 0,
6578
+ // 0,
6579
+ // textureImage.naturalWidth - 1, // There is this horrible Safari bug (fixed in newer versions)
6580
+ // textureImage.naturalHeight - 1, // To avoid errors substract 1 here.
6581
+ // scaledTextureSize.min.x,
6582
+ // scaledTextureSize.min.y,
6583
+ // scaledTextureSize.width,
6584
+ // scaledTextureSize.height
6585
+ // );
6586
+
6587
+ this.ctx.restore();
6098
6588
  }
6589
+ */
6099
6590
  /**
6100
6591
  * Draw a rectangle.
6101
6592
  *
@@ -6104,10 +6595,16 @@ class drawutils {
6104
6595
  * @param {number} height - The height of the rectangle.
6105
6596
  * @param {string} color - The color to use.
6106
6597
  * @param {number=1} lineWidth - (optional) The line with to use (default is 1).
6598
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6599
+ *
6600
+ * @return {void}
6601
+ * @instance
6602
+ * @memberof drawutils
6107
6603
  **/
6108
- rect(position, width, height, color, lineWidth) {
6604
+ rect(position, width, height, color, lineWidth, strokeOptions) {
6109
6605
  this.ctx.save();
6110
6606
  this.ctx.beginPath();
6607
+ this.applyStrokeOpts(strokeOptions);
6111
6608
  this.ctx.moveTo(this.offset.x + position.x * this.scale.x, this.offset.y + position.y * this.scale.y);
6112
6609
  this.ctx.lineTo(this.offset.x + (position.x + width) * this.scale.x, this.offset.y + position.y * this.scale.y);
6113
6610
  this.ctx.lineTo(this.offset.x + (position.x + width) * this.scale.x, this.offset.y + (position.y + height) * this.scale.y);
@@ -6118,43 +6615,23 @@ class drawutils {
6118
6615
  this._fillOrDraw(color);
6119
6616
  this.ctx.restore();
6120
6617
  }
6121
- // +---------------------------------------------------------------------------------
6122
- // | This is the final helper function for drawing and filling stuff. It is not
6123
- // | intended to be used from the outside.
6124
- // |
6125
- // | When in draw mode it draws the current shape.
6126
- // | When in fill mode it fills the current shape.
6127
- // |
6128
- // | This function is usually only called internally.
6129
- // |
6130
- // | @param color A stroke/fill color to use.
6131
- // +-------------------------------
6132
- // TODO: convert this to a STATIC function.
6133
- _fillOrDraw(color) {
6134
- if (this.fillShapes) {
6135
- this.ctx.fillStyle = color;
6136
- this.ctx.fill();
6137
- }
6138
- else {
6139
- this.ctx.strokeStyle = color;
6140
- this.ctx.stroke();
6141
- }
6142
- }
6143
6618
  /**
6144
6619
  * Draw the given (cubic) bézier curve.
6145
6620
  *
6146
6621
  * @method cubicBezier
6147
- * @param {Vertex} startPoint - The start point of the cubic Bézier curve
6148
- * @param {Vertex} endPoint - The end point the cubic Bézier curve.
6149
- * @param {Vertex} startControlPoint - The start control point the cubic Bézier curve.
6150
- * @param {Vertex} endControlPoint - The end control point the cubic Bézier curve.
6622
+ * @param {XYCoords} startPoint - The start point of the cubic Bézier curve
6623
+ * @param {XYCoords} endPoint - The end point the cubic Bézier curve.
6624
+ * @param {XYCoords} startControlPoint - The start control point the cubic Bézier curve.
6625
+ * @param {XYCoords} endControlPoint - The end control point the cubic Bézier curve.
6151
6626
  * @param {string} color - The CSS color to draw the curve with.
6152
6627
  * @param {number} lineWidth - (optional) The line width to use.
6628
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6629
+ *
6153
6630
  * @return {void}
6154
6631
  * @instance
6155
6632
  * @memberof drawutils
6156
6633
  */
6157
- cubicBezier(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth) {
6634
+ cubicBezier(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth, strokeOptions) {
6158
6635
  if (startPoint instanceof CubicBezierCurve) {
6159
6636
  this.cubicBezier(startPoint.startPoint, startPoint.endPoint, startPoint.startControlPoint, startPoint.endControlPoint, color, lineWidth);
6160
6637
  return;
@@ -6162,6 +6639,7 @@ class drawutils {
6162
6639
  // Draw curve
6163
6640
  this.ctx.save();
6164
6641
  this.ctx.beginPath();
6642
+ this.applyStrokeOpts(strokeOptions);
6165
6643
  this.ctx.moveTo(this.offset.x + startPoint.x * this.scale.x, this.offset.y + startPoint.y * this.scale.y);
6166
6644
  this.ctx.bezierCurveTo(this.offset.x + startControlPoint.x * this.scale.x, this.offset.y + startControlPoint.y * this.scale.y, this.offset.x + endControlPoint.x * this.scale.x, this.offset.y + endControlPoint.y * this.scale.y, this.offset.x + endPoint.x * this.scale.x, this.offset.y + endPoint.y * this.scale.y);
6167
6645
  //this.ctx.closePath();
@@ -6173,19 +6651,22 @@ class drawutils {
6173
6651
  * Draw the given (quadratic) bézier curve.
6174
6652
  *
6175
6653
  * @method quadraticBezier
6176
- * @param {Vertex} startPoint - The start point of the cubic Bézier curve
6177
- * @param {Vertex} controlPoint - The control point the cubic Bézier curve.
6178
- * @param {Vertex} endPoint - The end control point the cubic Bézier curve.
6654
+ * @param {XYCoords} startPoint - The start point of the cubic Bézier curve
6655
+ * @param {XYCoords} controlPoint - The control point the cubic Bézier curve.
6656
+ * @param {XYCoords} endPoint - The end control point the cubic Bézier curve.
6179
6657
  * @param {string} color - The CSS color to draw the curve with.
6180
6658
  * @param {number|string} lineWidth - (optional) The line width to use.
6659
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6660
+ *
6181
6661
  * @return {void}
6182
6662
  * @instance
6183
6663
  * @memberof drawutils
6184
6664
  */
6185
- quadraticBezier(startPoint, controlPoint, endPoint, color, lineWidth) {
6665
+ quadraticBezier(startPoint, controlPoint, endPoint, color, lineWidth, strokeOptions) {
6186
6666
  // Draw curve
6187
6667
  this.ctx.save();
6188
6668
  this.ctx.beginPath();
6669
+ this.applyStrokeOpts(strokeOptions);
6189
6670
  this.ctx.moveTo(this.offset.x + startPoint.x * this.scale.x, this.offset.y + startPoint.y * this.scale.y);
6190
6671
  this.ctx.quadraticCurveTo(this.offset.x + controlPoint.x * this.scale.x, this.offset.y + controlPoint.y * this.scale.y, this.offset.x + endPoint.x * this.scale.x, this.offset.y + endPoint.y * this.scale.y);
6191
6672
  this.ctx.lineWidth = lineWidth || 2;
@@ -6200,22 +6681,26 @@ class drawutils {
6200
6681
  * <pre> [ point1, point1_startControl, point2_endControl, point2, point2_startControl, point3_endControl, point3, ... pointN_endControl, pointN ]</pre>
6201
6682
  *
6202
6683
  * @method cubicBezierPath
6203
- * @param {Vertex[]} path - The cubic bezier path as described above.
6684
+ * @param {XYCoords[]} path - The cubic bezier path as described above.
6204
6685
  * @param {string} color - The CSS colot to draw the path with.
6205
6686
  * @param {number=1} lineWidth - (optional) The line width to use.
6687
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6688
+ *
6206
6689
  * @return {void}
6207
6690
  * @instance
6208
6691
  * @memberof drawutils
6209
6692
  */
6210
- cubicBezierPath(path, color, lineWidth) {
6211
- if (!path || path.length == 0)
6693
+ cubicBezierPath(path, color, lineWidth, strokeOptions) {
6694
+ if (!path || path.length == 0) {
6212
6695
  return;
6696
+ }
6213
6697
  // Draw curve
6214
6698
  this.ctx.save();
6215
6699
  this.ctx.beginPath();
6216
6700
  var endPoint;
6217
6701
  var startControlPoint;
6218
6702
  var endControlPoint;
6703
+ this.applyStrokeOpts(strokeOptions);
6219
6704
  this.ctx.moveTo(this.offset.x + path[0].x * this.scale.x, this.offset.y + path[0].y * this.scale.y);
6220
6705
  for (var i = 1; i < path.length; i += 3) {
6221
6706
  startControlPoint = path[i];
@@ -6234,8 +6719,8 @@ class drawutils {
6234
6719
  * The colors for this are fixed and cannot be specified.
6235
6720
  *
6236
6721
  * @method handle
6237
- * @param {Vertex} startPoint - The start of the handle.
6238
- * @param {Vertex} endPoint - The end point of the handle.
6722
+ * @param {XYCoords} startPoint - The start of the handle.
6723
+ * @param {XYCoords} endPoint - The end point of the handle.
6239
6724
  * @return {void}
6240
6725
  * @instance
6241
6726
  * @memberof drawutils
@@ -6250,21 +6735,21 @@ class drawutils {
6250
6735
  * Draw a handle line (with a light grey).
6251
6736
  *
6252
6737
  * @method handleLine
6253
- * @param {Vertex} startPoint - The start point to draw the handle at.
6254
- * @param {Vertex} endPoint - The end point to draw the handle at.
6738
+ * @param {XYCoords} startPoint - The start point to draw the handle at.
6739
+ * @param {XYCoords} endPoint - The end point to draw the handle at.
6255
6740
  * @return {void}
6256
6741
  * @instance
6257
6742
  * @memberof drawutils
6258
6743
  */
6259
6744
  handleLine(startPoint, endPoint) {
6260
6745
  // Draw handle lines
6261
- this.line(startPoint, endPoint, "rgb(192,192,192)");
6746
+ this.line(startPoint, endPoint, "rgba(128,128,128, 0.5)", undefined);
6262
6747
  }
6263
6748
  /**
6264
6749
  * Draw a 1x1 dot with the specified (CSS-) color.
6265
6750
  *
6266
6751
  * @method dot
6267
- * @param {Vertex} p - The position to draw the dot at.
6752
+ * @param {XYCoords} p - The position to draw the dot at.
6268
6753
  * @param {string} color - The CSS color to draw the dot with.
6269
6754
  * @return {void}
6270
6755
  * @instance
@@ -6273,6 +6758,7 @@ class drawutils {
6273
6758
  dot(p, color) {
6274
6759
  this.ctx.save();
6275
6760
  this.ctx.beginPath();
6761
+ this.ctx.setLineDash([]); // Clear line-dash settings
6276
6762
  this.ctx.moveTo(Math.round(this.offset.x + this.scale.x * p.x), Math.round(this.offset.y + this.scale.y * p.y));
6277
6763
  this.ctx.lineTo(Math.round(this.offset.x + this.scale.x * p.x + 1), Math.round(this.offset.y + this.scale.y * p.y + 1));
6278
6764
  this.ctx.closePath();
@@ -6284,7 +6770,7 @@ class drawutils {
6284
6770
  * Draw the given point with the specified (CSS-) color and radius 3.
6285
6771
  *
6286
6772
  * @method point
6287
- * @param {Vertex} p - The position to draw the point at.
6773
+ * @param {XYCoords} p - The position to draw the point at.
6288
6774
  * @param {string} color - The CSS color to draw the point with.
6289
6775
  * @return {void}
6290
6776
  * @instance
@@ -6292,6 +6778,7 @@ class drawutils {
6292
6778
  */
6293
6779
  point(p, color) {
6294
6780
  var radius = 3;
6781
+ this.ctx.setLineDash([]); // Clear line-dash settings
6295
6782
  this.ctx.beginPath();
6296
6783
  this.ctx.arc(this.offset.x + p.x * this.scale.x, this.offset.y + p.y * this.scale.y, radius, 0, 2 * Math.PI, false);
6297
6784
  this.ctx.closePath();
@@ -6304,15 +6791,18 @@ class drawutils {
6304
6791
  * Note that if the x- and y- scales are different the result will be an ellipse rather than a circle.
6305
6792
  *
6306
6793
  * @method circle
6307
- * @param {Vertex} center - The center of the circle.
6794
+ * @param {XYCoords} center - The center of the circle.
6308
6795
  * @param {number} radius - The radius of the circle.
6309
6796
  * @param {string} color - The CSS color to draw the circle with.
6310
6797
  * @param {number} lineWidth - The line width (optional, default=1).
6798
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6799
+ *
6311
6800
  * @return {void}
6312
6801
  * @instance
6313
6802
  * @memberof drawutils
6314
6803
  */
6315
- circle(center, radius, color, lineWidth) {
6804
+ circle(center, radius, color, lineWidth, strokeOptions) {
6805
+ this.applyStrokeOpts(strokeOptions);
6316
6806
  this.ctx.beginPath();
6317
6807
  this.ctx.ellipse(this.offset.x + center.x * this.scale.x, this.offset.y + center.y * this.scale.y, radius * this.scale.x, radius * this.scale.y, 0.0, 0.0, Math.PI * 2);
6318
6808
  this.ctx.closePath();
@@ -6320,24 +6810,28 @@ class drawutils {
6320
6810
  this._fillOrDraw(color);
6321
6811
  }
6322
6812
  /**
6323
- * Draw a circular arc (section of a circle) with the given CSS color.
6324
- *
6325
- * @method circleArc
6326
- * @param {Vertex} center - The center of the circle.
6327
- * @param {number} radius - The radius of the circle.
6328
- * @param {number} startAngle - The angle to start at.
6329
- * @param {number} endAngle - The angle to end at.
6330
- * @param {string=#000000} color - The CSS color to draw the circle with.
6331
- * @param {number=1} lineWidth - The line width to use
6332
- // * @param {boolean=false} options.asSegment - If `true` then no beginPath and no draw will be applied (as part of larger path).
6333
- * @return {void}
6334
- * @instance
6335
- * @memberof drawutils
6336
- */
6813
+ * Draw a circular arc (section of a circle) with the given CSS color.
6814
+ *
6815
+ * @method circleArc
6816
+ * @param {XYCoords} center - The center of the circle.
6817
+ * @param {number} radius - The radius of the circle.
6818
+ * @param {number} startAngle - The angle to start at.
6819
+ * @param {number} endAngle - The angle to end at.
6820
+ * @param {string=#000000} color - The CSS color to draw the circle with.
6821
+ * @param {number=1} lineWidth - The line width to use
6822
+ * @param {boolean=false} options.asSegment - If `true` then no beginPath and no draw will be applied (as part of larger path).
6823
+ * @param {number=} options.dashOffset - (optional) `See StrokeOptions`.
6824
+ * @param {number=[]} options.dashArray - (optional) `See StrokeOptions`.
6825
+ *
6826
+ * @return {void}
6827
+ * @instance
6828
+ * @memberof drawutils
6829
+ */
6337
6830
  circleArc(center, radius, startAngle, endAngle, color, lineWidth, options) {
6338
6831
  if (!options || !options.asSegment) {
6339
6832
  this.ctx.beginPath();
6340
6833
  }
6834
+ this.applyStrokeOpts(options);
6341
6835
  this.ctx.ellipse(this.offset.x + center.x * this.scale.x, this.offset.y + center.y * this.scale.y, radius * this.scale.x, radius * this.scale.y, 0.0, startAngle, endAngle, false);
6342
6836
  if (!options || !options.asSegment) {
6343
6837
  // this.ctx.closePath();
@@ -6349,20 +6843,23 @@ class drawutils {
6349
6843
  * Draw an ellipse with the specified (CSS-) color and thw two radii.
6350
6844
  *
6351
6845
  * @method ellipse
6352
- * @param {Vertex} center - The center of the ellipse.
6846
+ * @param {XYCoords} center - The center of the ellipse.
6353
6847
  * @param {number} radiusX - The radius of the ellipse.
6354
6848
  * @param {number} radiusY - The radius of the ellipse.
6355
6849
  * @param {string} color - The CSS color to draw the ellipse with.
6356
6850
  * @param {number} lineWidth=1 - An optional line width param (default is 1).
6357
6851
  * @param {number=} rotation - (optional, default=0) The rotation of the ellipse.
6852
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6853
+ *
6358
6854
  * @return {void}
6359
6855
  * @instance
6360
6856
  * @memberof drawutils
6361
6857
  */
6362
- ellipse(center, radiusX, radiusY, color, lineWidth, rotation) {
6858
+ ellipse(center, radiusX, radiusY, color, lineWidth, rotation, strokeOptions) {
6363
6859
  if (typeof rotation === "undefined") {
6364
6860
  rotation = 0.0;
6365
6861
  }
6862
+ this.applyStrokeOpts(strokeOptions);
6366
6863
  this.ctx.beginPath();
6367
6864
  this.ctx.ellipse(this.offset.x + center.x * this.scale.x, this.offset.y + center.y * this.scale.y, radiusX * this.scale.x, radiusY * this.scale.y, rotation, 0.0, Math.PI * 2);
6368
6865
  this.ctx.closePath();
@@ -6379,11 +6876,14 @@ class drawutils {
6379
6876
  * @param {number} size - The size of the square.
6380
6877
  * @param {string} color - The CSS color to draw the square with.
6381
6878
  * @param {number} lineWidth - The line with to use (optional, default is 1).
6879
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
6880
+ *
6382
6881
  * @return {void}
6383
6882
  * @instance
6384
6883
  * @memberof drawutils
6385
6884
  */
6386
- square(center, size, color, lineWidth) {
6885
+ square(center, size, color, lineWidth, strokeOptions) {
6886
+ this.applyStrokeOpts(strokeOptions);
6387
6887
  this.ctx.beginPath();
6388
6888
  this.ctx.rect(this.offset.x + (center.x - size / 2.0) * this.scale.x, this.offset.y + (center.y - size / 2.0) * this.scale.y, size * this.scale.x, size * this.scale.y);
6389
6889
  this.ctx.closePath();
@@ -6394,7 +6894,7 @@ class drawutils {
6394
6894
  * Draw a grid of horizontal and vertical lines with the given (CSS-) color.
6395
6895
  *
6396
6896
  * @method grid
6397
- * @param {Vertex} center - The center of the grid.
6897
+ * @param {XYCoords} center - The center of the grid.
6398
6898
  * @param {number} width - The total width of the grid (width/2 each to the left and to the right).
6399
6899
  * @param {number} height - The total height of the grid (height/2 each to the top and to the bottom).
6400
6900
  * @param {number} sizeX - The horizontal grid size.
@@ -6405,6 +6905,7 @@ class drawutils {
6405
6905
  * @memberof drawutils
6406
6906
  */
6407
6907
  grid(center, width, height, sizeX, sizeY, color) {
6908
+ this.ctx.setLineDash([]); // Clear line-dash settings
6408
6909
  this.ctx.beginPath();
6409
6910
  var yMin = -Math.ceil((height * 0.5) / sizeY) * sizeY;
6410
6911
  var yMax = height / 2;
@@ -6429,7 +6930,7 @@ class drawutils {
6429
6930
  * This works analogue to the grid() function
6430
6931
  *
6431
6932
  * @method raster
6432
- * @param {Vertex} center - The center of the raster.
6933
+ * @param {XYCoords} center - The center of the raster.
6433
6934
  * @param {number} width - The total width of the raster (width/2 each to the left and to the right).
6434
6935
  * @param {number} height - The total height of the raster (height/2 each to the top and to the bottom).
6435
6936
  * @param {number} sizeX - The horizontal raster size.
@@ -6441,6 +6942,7 @@ class drawutils {
6441
6942
  */
6442
6943
  raster(center, width, height, sizeX, sizeY, color) {
6443
6944
  this.ctx.save();
6945
+ this.ctx.setLineDash([]); // Clear line-dash settings
6444
6946
  this.ctx.beginPath();
6445
6947
  for (var x = -Math.ceil((width * 0.5) / sizeX) * sizeX; x < width / 2; x += sizeX) {
6446
6948
  for (var y = -Math.ceil((height * 0.5) / sizeY) * sizeY; y < height / 2; y += sizeY) {
@@ -6465,14 +6967,15 @@ class drawutils {
6465
6967
  * as even shaped diamonds.
6466
6968
  *
6467
6969
  * @method diamondHandle
6468
- * @param {Vertex} center - The center of the diamond.
6469
- * @param {Vertex} size - The x/y-size of the diamond.
6970
+ * @param {XYCoords} center - The center of the diamond.
6971
+ * @param {number} size - The x/y-size of the diamond.
6470
6972
  * @param {string} color - The CSS color to draw the diamond with.
6471
6973
  * @return {void}
6472
6974
  * @instance
6473
6975
  * @memberof drawutils
6474
6976
  */
6475
6977
  diamondHandle(center, size, color) {
6978
+ this.ctx.setLineDash([]); // Clear line-dash settings
6476
6979
  this.ctx.beginPath();
6477
6980
  this.ctx.moveTo(this.offset.x + center.x * this.scale.x - size / 2.0, this.offset.y + center.y * this.scale.y);
6478
6981
  this.ctx.lineTo(this.offset.x + center.x * this.scale.x, this.offset.y + center.y * this.scale.y - size / 2.0);
@@ -6490,14 +6993,15 @@ class drawutils {
6490
6993
  * as even shaped squares.
6491
6994
  *
6492
6995
  * @method squareHandle
6493
- * @param {Vertex} center - The center of the square.
6494
- * @param {Vertex} size - The x/y-size of the square.
6996
+ * @param {XYCoords} center - The center of the square.
6997
+ * @param {number} size - The x/y-size of the square.
6495
6998
  * @param {string} color - The CSS color to draw the square with.
6496
6999
  * @return {void}
6497
7000
  * @instance
6498
7001
  * @memberof drawutils
6499
7002
  */
6500
7003
  squareHandle(center, size, color) {
7004
+ this.ctx.setLineDash([]); // Clear line-dash settings
6501
7005
  this.ctx.beginPath();
6502
7006
  this.ctx.rect(this.offset.x + center.x * this.scale.x - size / 2.0, this.offset.y + center.y * this.scale.y - size / 2.0, size, size);
6503
7007
  this.ctx.closePath();
@@ -6512,7 +7016,7 @@ class drawutils {
6512
7016
  * as even shaped circles.
6513
7017
  *
6514
7018
  * @method circleHandle
6515
- * @param {Vertex} center - The center of the circle.
7019
+ * @param {XYCoords} center - The center of the circle.
6516
7020
  * @param {number} radius - The radius of the circle.
6517
7021
  * @param {string} color - The CSS color to draw the circle with.
6518
7022
  * @return {void}
@@ -6521,6 +7025,7 @@ class drawutils {
6521
7025
  */
6522
7026
  circleHandle(center, radius, color) {
6523
7027
  radius = radius || 3;
7028
+ this.ctx.setLineDash([]); // Clear line-dash settings
6524
7029
  this.ctx.beginPath();
6525
7030
  this.ctx.arc(this.offset.x + center.x * this.scale.x, this.offset.y + center.y * this.scale.y, radius, 0, 2 * Math.PI, false);
6526
7031
  this.ctx.closePath();
@@ -6543,6 +7048,7 @@ class drawutils {
6543
7048
  */
6544
7049
  crosshair(center, radius, color, lineWidth) {
6545
7050
  this.ctx.save();
7051
+ this.ctx.setLineDash([]); // Clear line-dash settings
6546
7052
  this.ctx.beginPath();
6547
7053
  this.ctx.moveTo(this.offset.x + center.x * this.scale.x - radius, this.offset.y + center.y * this.scale.y);
6548
7054
  this.ctx.lineTo(this.offset.x + center.x * this.scale.x + radius, this.offset.y + center.y * this.scale.y);
@@ -6570,6 +7076,7 @@ class drawutils {
6570
7076
  */
6571
7077
  cross(center, radius, color, lineWidth) {
6572
7078
  this.ctx.save();
7079
+ this.ctx.setLineDash([]); // Clear line-dash settings
6573
7080
  this.ctx.beginPath();
6574
7081
  this.ctx.moveTo(this.offset.x + center.x * this.scale.x - radius, this.offset.y + center.y * this.scale.y - radius);
6575
7082
  this.ctx.lineTo(this.offset.x + center.x * this.scale.x + radius, this.offset.y + center.y * this.scale.y + radius);
@@ -6588,30 +7095,35 @@ class drawutils {
6588
7095
  * @param {Polygon} polygon - The polygon to draw.
6589
7096
  * @param {string} color - The CSS color to draw the polygon with.
6590
7097
  * @param {string} lineWidth - The line width to use.
7098
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
7099
+ *
6591
7100
  * @return {void}
6592
7101
  * @instance
6593
7102
  * @memberof drawutils
6594
7103
  */
6595
- polygon(polygon, color, lineWidth) {
6596
- this.polyline(polygon.vertices, polygon.isOpen, color, lineWidth);
7104
+ polygon(polygon, color, lineWidth, strokeOptions) {
7105
+ this.polyline(polygon.vertices, polygon.isOpen, color, lineWidth, strokeOptions);
6597
7106
  }
6598
7107
  /**
6599
7108
  * Draw a polygon line (alternative function to the polygon).
6600
7109
  *
6601
7110
  * @method polyline
6602
- * @param {Vertex[]} vertices - The polygon vertices to draw.
7111
+ * @param {XYCoords[]} vertices - The polygon vertices to draw.
6603
7112
  * @param {boolan} isOpen - If true the polyline will not be closed at its end.
6604
7113
  * @param {string} color - The CSS color to draw the polygon with.
6605
7114
  * @param {number} lineWidth - The line width (default is 1.0);
7115
+ * @param {StrokeOptions=} strokeOptions - (optional) Stroke settings to use.
7116
+ *
6606
7117
  * @return {void}
6607
7118
  * @instance
6608
7119
  * @memberof drawutils
6609
7120
  */
6610
- polyline(vertices, isOpen, color, lineWidth) {
7121
+ polyline(vertices, isOpen, color, lineWidth, strokeOptions) {
6611
7122
  if (vertices.length <= 1) {
6612
7123
  return;
6613
7124
  }
6614
7125
  this.ctx.save();
7126
+ this.applyStrokeOpts(strokeOptions);
6615
7127
  this.ctx.beginPath();
6616
7128
  this.ctx.lineWidth = (lineWidth || 1.0) * this.scale.x;
6617
7129
  this.ctx.moveTo(this.offset.x + vertices[0].x * this.scale.x, this.offset.y + vertices[0].y * this.scale.y);
@@ -6723,6 +7235,8 @@ class drawutils {
6723
7235
  * @param {string=null} color - (optional) The color to draw this path with (default is null).
6724
7236
  * @param {number=1} lineWidth - (optional) the line width to use (default is 1).
6725
7237
  * @param {boolean=false} options.inplace - (optional) If set to true then path transforamtions (scale and translate) will be done in-place in the array. This can boost the performance.
7238
+ * @param {number=} options.dashOffset - (optional) `See StrokeOptions`.
7239
+ * @param {number=[]} options.dashArray - (optional) `See StrokeOptions`.
6726
7240
  * @instance
6727
7241
  * @memberof drawutils
6728
7242
  * @return {R} An instance representing the drawn path.
@@ -6734,6 +7248,7 @@ class drawutils {
6734
7248
  this.ctx.strokeStyle = color;
6735
7249
  }
6736
7250
  this.ctx.lineWidth = lineWidth || 1;
7251
+ this.applyStrokeOpts(options);
6737
7252
  if (this.fillShapes) {
6738
7253
  if (color) {
6739
7254
  this.ctx.fillStyle = color;
@@ -6790,7 +7305,12 @@ drawutils.helpers = {
6790
7305
  * @modified 2022-03-27 Added the `texturedPoly` function.
6791
7306
  * @modified 2022-07-26 Adding `alpha` to the `image(...)` function.
6792
7307
  * @modified 2023-02-10 The methods `setCurrentClassName` and `setCurrentId` also accept `null` now.
6793
- * @version 0.0.9
7308
+ * @modified 2023-09-29 Downgrading all `Vertex` param type to the more generic `XYCoords` type in these render functions: line, arrow, texturedPoly, cubicBezier, cubicBezierPath, handle, handleLine, dot, point, circle, circleArc, ellipse, grid, raster.
7309
+ * @modified 2023-09-29 Added the `headLength` parameter to the 'DrawLib.arrow()` function.
7310
+ * @modified 2023-09-29 Added the `arrowHead(...)` function to the 'DrawLib.arrow()` interface.
7311
+ * @modified 2023-09-29 Added the `cubicBezierArrow(...)` function to the 'DrawLib.arrow()` interface.
7312
+ * @modified 2023-09-29 Added the `lineDashes` attribute.
7313
+ * @version 0.0.10
6794
7314
  **/
6795
7315
  /**
6796
7316
  * @classdesc A wrapper class for basic drawing operations. This is the WebGL
@@ -6884,6 +7404,20 @@ class drawutilsgl {
6884
7404
  setConfiguration(configuration) {
6885
7405
  // TODO
6886
7406
  }
7407
+ // /**
7408
+ // * Set or clear the line-dash configuration. Pass `null` for un-dashed lines.
7409
+ // *
7410
+ // * See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray
7411
+ // * and https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
7412
+ // * for how line dashes work.
7413
+ // *
7414
+ // * @method
7415
+ // * @param {Array<number> lineDashes - The line-dash array configuration.
7416
+ // * @returns {void}
7417
+ // */
7418
+ // setLineDash(lineDashes: Array<number>) {
7419
+ // // TODO
7420
+ // }
6887
7421
  /**
6888
7422
  * This method shouled be called each time the currently drawn `Drawable` changes.
6889
7423
  * It is used by some libraries for identifying elemente on re-renders.
@@ -6911,8 +7445,8 @@ class drawutilsgl {
6911
7445
  * Draw the line between the given two points with the specified (CSS-) color.
6912
7446
  *
6913
7447
  * @method line
6914
- * @param {Vertex} zA - The start point of the line.
6915
- * @param {Vertex} zB - The end point of the line.
7448
+ * @param {XYCoords} zA - The start point of the line.
7449
+ * @param {XYCoords} zB - The end point of the line.
6916
7450
  * @param {string} color - Any valid CSS color string.
6917
7451
  * @return {void}
6918
7452
  * @instance
@@ -6961,9 +7495,10 @@ class drawutilsgl {
6961
7495
  * Draw a line and an arrow at the end (zB) of the given line with the specified (CSS-) color.
6962
7496
  *
6963
7497
  * @method arrow
6964
- * @param {Vertex} zA - The start point of the arrow-line.
6965
- * @param {Vertex} zB - The end point of the arrow-line.
7498
+ * @param {XYCoords} zA - The start point of the arrow-line.
7499
+ * @param {XYCoords} zB - The end point of the arrow-line.
6966
7500
  * @param {string} color - Any valid CSS color string.
7501
+ * @param {headLength=8} headLength - (optional) The length of the arrow head (default is 8 units).
6967
7502
  * @return {void}
6968
7503
  * @instance
6969
7504
  * @memberof drawutils
@@ -6971,6 +7506,41 @@ class drawutilsgl {
6971
7506
  arrow(zA, zB, color) {
6972
7507
  // NOT YET IMPLEMENTED
6973
7508
  }
7509
+ /**
7510
+ * Draw a cubic Bézier curve and and an arrow at the end (endControlPoint) of the given line width the specified (CSS-) color and arrow size.
7511
+ *
7512
+ * @method cubicBezierArrow
7513
+ * @param {XYCoords} startPoint - The start point of the cubic Bézier curve
7514
+ * @param {XYCoords} endPoint - The end point the cubic Bézier curve.
7515
+ * @param {XYCoords} startControlPoint - The start control point the cubic Bézier curve.
7516
+ * @param {XYCoords} endControlPoint - The end control point the cubic Bézier curve.
7517
+ * @param {string} color - The CSS color to draw the curve with.
7518
+ * @param {number} lineWidth - (optional) The line width to use.
7519
+ * @param {headLength=8} headLength - (optional) The length of the arrow head (default is 8 units).
7520
+ *
7521
+ * @return {void}
7522
+ * @instance
7523
+ * @memberof DrawLib
7524
+ */
7525
+ cubicBezierArrow(startPoint, endPoint, startControlPoint, endControlPoint, color, lineWidth, headLength) {
7526
+ // NOT YET IMPLEMENTED
7527
+ }
7528
+ /**
7529
+ * Draw just an arrow head a the end of an imaginary line (zB) of the given line width the specified (CSS-) color and size.
7530
+ *
7531
+ * @method arrow
7532
+ * @param {XYCoords} zA - The start point of the arrow-line.
7533
+ * @param {XYCoords} zB - The end point of the arrow-line.
7534
+ * @param {string} color - Any valid CSS color string.
7535
+ * @param {number=1} lineWidth - (optional) The line width to use; default is 1.
7536
+ * @param {number=8} headLength - (optional) The length of the arrow head (default is 8 pixels).
7537
+ * @return {void}
7538
+ * @instance
7539
+ * @memberof DrawLib
7540
+ **/
7541
+ arrowHead(zA, zB, color, lineWidth, headLength) {
7542
+ // NOT YET IMPLEMENTED
7543
+ }
6974
7544
  /**
6975
7545
  * Draw an image at the given position with the given size.<br>
6976
7546
  * <br>
@@ -6978,8 +7548,8 @@ class drawutilsgl {
6978
7548
  *
6979
7549
  * @method image
6980
7550
  * @param {Image} image - The image object to draw.
6981
- * @param {Vertex} position - The position to draw the the upper left corner at.
6982
- * @param {Vertex} size - The x/y-size to draw the image with.
7551
+ * @param {XYCoords} position - The position to draw the the upper left corner at.
7552
+ * @param {XYCoords} size - The x/y-size to draw the image with.
6983
7553
  * @param {number=0.0} alpha - (optional, default=0.0) The transparency (0.0=opaque, 1.0=transparent).
6984
7554
  * @return {void}
6985
7555
  * @instance
@@ -6997,7 +7567,7 @@ class drawutilsgl {
6997
7567
  * @param {Image} textureImage - The image object to draw.
6998
7568
  * @param {Bounds} textureSize - The texture size to use; these are the original bounds to map the polygon vertices to.
6999
7569
  * @param {Polygon} polygon - The polygon to use as clip path.
7000
- * @param {Vertex} polygonPosition - The polygon's position (relative), measured at the bounding box's center.
7570
+ * @param {XYCoords} polygonPosition - The polygon's position (relative), measured at the bounding box's center.
7001
7571
  * @param {number} rotation - The rotation to use for the polygon (and for the texture).
7002
7572
  * @return {void}
7003
7573
  * @instance
@@ -7024,10 +7594,10 @@ class drawutilsgl {
7024
7594
  * Draw the given (cubic) bézier curve.
7025
7595
  *
7026
7596
  * @method cubicBezier
7027
- * @param {Vertex} startPoint - The start point of the cubic Bézier curve
7028
- * @param {Vertex} endPoint - The end point the cubic Bézier curve.
7029
- * @param {Vertex} startControlPoint - The start control point the cubic Bézier curve.
7030
- * @param {Vertex} endControlPoint - The end control point the cubic Bézier curve.
7597
+ * @param {XYCoords} startPoint - The start point of the cubic Bézier curve
7598
+ * @param {XYCoords} endPoint - The end point the cubic Bézier curve.
7599
+ * @param {XYCoords} startControlPoint - The start control point the cubic Bézier curve.
7600
+ * @param {VertXYCoordsex} endControlPoint - The end control point the cubic Bézier curve.
7031
7601
  * @param {string} color - The CSS color to draw the curve with.
7032
7602
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
7033
7603
  * @return {void}
@@ -7045,7 +7615,7 @@ class drawutilsgl {
7045
7615
  * <pre> [ point1, point1_startControl, point2_endControl, point2, point2_startControl, point3_endControl, point3, ... pointN_endControl, pointN ]</pre>
7046
7616
  *
7047
7617
  * @method cubicBezierPath
7048
- * @param {Vertex[]} path - The cubic bezier path as described above.
7618
+ * @param {XYCoords[]} path - The cubic bezier path as described above.
7049
7619
  * @param {string} color - The CSS colot to draw the path with.
7050
7620
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
7051
7621
  * @return {void}
@@ -7061,8 +7631,8 @@ class drawutilsgl {
7061
7631
  * The colors for this are fixed and cannot be specified.
7062
7632
  *
7063
7633
  * @method handle
7064
- * @param {Vertex} startPoint - The start of the handle.
7065
- * @param {Vertex} endPoint - The end point of the handle.
7634
+ * @param {XYCoords} startPoint - The start of the handle.
7635
+ * @param {XYCoords} endPoint - The end point of the handle.
7066
7636
  * @return {void}
7067
7637
  * @instance
7068
7638
  * @memberof drawutils
@@ -7074,8 +7644,8 @@ class drawutilsgl {
7074
7644
  * Draw a handle line (with a light grey).
7075
7645
  *
7076
7646
  * @method handleLine
7077
- * @param {Vertex} startPoint - The start point to draw the handle at.
7078
- * @param {Vertex} endPoint - The end point to draw the handle at.
7647
+ * @param {XYCoords} startPoint - The start point to draw the handle at.
7648
+ * @param {XYCoords} endPoint - The end point to draw the handle at.
7079
7649
  * @return {void}
7080
7650
  * @instance
7081
7651
  * @memberof drawutils
@@ -7087,7 +7657,7 @@ class drawutilsgl {
7087
7657
  * Draw a 1x1 dot with the specified (CSS-) color.
7088
7658
  *
7089
7659
  * @method dot
7090
- * @param {Vertex} p - The position to draw the dot at.
7660
+ * @param {XYCoords} p - The position to draw the dot at.
7091
7661
  * @param {string} color - The CSS color to draw the dot with.
7092
7662
  * @return {void}
7093
7663
  * @instance
@@ -7100,7 +7670,7 @@ class drawutilsgl {
7100
7670
  * Draw the given point with the specified (CSS-) color and radius 3.
7101
7671
  *
7102
7672
  * @method point
7103
- * @param {Vertex} p - The position to draw the point at.
7673
+ * @param {XYCoords} p - The position to draw the point at.
7104
7674
  * @param {string} color - The CSS color to draw the point with.
7105
7675
  * @return {void}
7106
7676
  * @instance
@@ -7115,7 +7685,7 @@ class drawutilsgl {
7115
7685
  * Note that if the x- and y- scales are different the result will be an ellipse rather than a circle.
7116
7686
  *
7117
7687
  * @method circle
7118
- * @param {Vertex} center - The center of the circle.
7688
+ * @param {XYCoords} center - The center of the circle.
7119
7689
  * @param {number} radius - The radius of the circle.
7120
7690
  * @param {string} color - The CSS color to draw the circle with.
7121
7691
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
@@ -7130,7 +7700,7 @@ class drawutilsgl {
7130
7700
  * Draw a circular arc (section of a circle) with the given CSS color.
7131
7701
  *
7132
7702
  * @method circleArc
7133
- * @param {Vertex} center - The center of the circle.
7703
+ * @param {XYCoords} center - The center of the circle.
7134
7704
  * @param {number} radius - The radius of the circle.
7135
7705
  * @param {number} startAngle - The angle to start at.
7136
7706
  * @param {number} endAngle - The angle to end at.
@@ -7146,7 +7716,7 @@ class drawutilsgl {
7146
7716
  * Draw an ellipse with the specified (CSS-) color and thw two radii.
7147
7717
  *
7148
7718
  * @method ellipse
7149
- * @param {Vertex} center - The center of the ellipse.
7719
+ * @param {XYCoords} center - The center of the ellipse.
7150
7720
  * @param {number} radiusX - The radius of the ellipse.
7151
7721
  * @param {number} radiusY - The radius of the ellipse.
7152
7722
  * @param {string} color - The CSS color to draw the ellipse with.
@@ -7166,7 +7736,7 @@ class drawutilsgl {
7166
7736
  *
7167
7737
  * @method square
7168
7738
  * @param {XYCords} center - The center of the square.
7169
- * @param {Vertex} size - The size of the square.
7739
+ * @param {number} size - The size of the square.
7170
7740
  * @param {string} color - The CSS color to draw the square with.
7171
7741
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
7172
7742
  * @return {void}
@@ -7192,7 +7762,7 @@ class drawutilsgl {
7192
7762
  * Draw a grid of horizontal and vertical lines with the given (CSS-) color.
7193
7763
  *
7194
7764
  * @method grid
7195
- * @param {Vertex} center - The center of the grid.
7765
+ * @param {XYCoords} center - The center of the grid.
7196
7766
  * @param {number} width - The total width of the grid (width/2 each to the left and to the right).
7197
7767
  * @param {number} height - The total height of the grid (height/2 each to the top and to the bottom).
7198
7768
  * @param {number} sizeX - The horizontal grid size.
@@ -7211,7 +7781,7 @@ class drawutilsgl {
7211
7781
  * This works analogue to the grid() function
7212
7782
  *
7213
7783
  * @method raster
7214
- * @param {Vertex} center - The center of the raster.
7784
+ * @param {XYCoords} center - The center of the raster.
7215
7785
  * @param {number} width - The total width of the raster (width/2 each to the left and to the right).
7216
7786
  * @param {number} height - The total height of the raster (height/2 each to the top and to the bottom).
7217
7787
  * @param {number} sizeX - The horizontal raster size.
@@ -7232,8 +7802,8 @@ class drawutilsgl {
7232
7802
  * as even shaped diamonds.
7233
7803
  *
7234
7804
  * @method diamondHandle
7235
- * @param {Vertex} center - The center of the diamond.
7236
- * @param {Vertex} size - The x/y-size of the diamond.
7805
+ * @param {XYCoords} center - The center of the diamond.
7806
+ * @param {number} size - The x/y-size of the diamond.
7237
7807
  * @param {string} color - The CSS color to draw the diamond with.
7238
7808
  * @return {void}
7239
7809
  * @instance
@@ -7250,8 +7820,8 @@ class drawutilsgl {
7250
7820
  * as even shaped squares.
7251
7821
  *
7252
7822
  * @method squareHandle
7253
- * @param {Vertex} center - The center of the square.
7254
- * @param {Vertex} size - The x/y-size of the square.
7823
+ * @param {XYCoords} center - The center of the square.
7824
+ * @param {number} size - The x/y-size of the square.
7255
7825
  * @param {string} color - The CSS color to draw the square with.
7256
7826
  * @return {void}
7257
7827
  * @instance
@@ -7268,7 +7838,7 @@ class drawutilsgl {
7268
7838
  * as even shaped circles.
7269
7839
  *
7270
7840
  * @method circleHandle
7271
- * @param {Vertex} center - The center of the circle.
7841
+ * @param {XYCoords} center - The center of the circle.
7272
7842
  * @param {number} radius - The radius of the circle.
7273
7843
  * @param {string} color - The CSS color to draw the circle with.
7274
7844
  * @return {void}
@@ -7364,7 +7934,7 @@ class drawutilsgl {
7364
7934
  * Draw a polygon line (alternative function to the polygon).
7365
7935
  *
7366
7936
  * @method polyline
7367
- * @param {Vertex[]} vertices - The polygon vertices to draw.
7937
+ * @param {XYCoords[]} vertices - The polygon vertices to draw.
7368
7938
  * @param {boolan} isOpen - If true the polyline will not be closed at its end.
7369
7939
  * @param {string} color - The CSS color to draw the polygon with.
7370
7940
  * @param {number=} lineWidth - (optional) The line width to use; default is 1.
@@ -8961,7 +9531,8 @@ class PBImage {
8961
9531
  * @author Ikaros Kappler
8962
9532
  * @date 2021-11-16
8963
9533
  * @modified 2022-02-02 Added the `destroy` method.
8964
- * @version 1.1.0
9534
+ * @modified 2023-09-25 Fixed a type error in the constructor. Nothing vital.
9535
+ * @version 1.1.1
8965
9536
  **/
8966
9537
  /**
8967
9538
  * @classdesc A simple text element: position, fontSize, fontFamily, color, textAlign, lineHeight and rotation.
@@ -8992,14 +9563,14 @@ class PBText {
8992
9563
  this.uid = UIDGenerator.next();
8993
9564
  this.text = text;
8994
9565
  this.anchor = anchor !== null && anchor !== void 0 ? anchor : new Vertex();
8995
- this.color = options.color;
8996
- this.fontFamily = options.fontFamily;
8997
- this.fontSize = options.fontSize;
8998
- this.fontStyle = options.fontStyle;
8999
- this.fontWeight = options.fontWeight;
9000
- this.lineHeight = options.lineHeight;
9001
- this.textAlign = options.textAlign;
9002
- this.rotation = options.rotation;
9566
+ this.color = options === null || options === void 0 ? void 0 : options.color;
9567
+ this.fontFamily = options === null || options === void 0 ? void 0 : options.fontFamily;
9568
+ this.fontSize = options === null || options === void 0 ? void 0 : options.fontSize;
9569
+ this.fontStyle = options === null || options === void 0 ? void 0 : options.fontStyle;
9570
+ this.fontWeight = options === null || options === void 0 ? void 0 : options.fontWeight;
9571
+ this.lineHeight = options === null || options === void 0 ? void 0 : options.lineHeight;
9572
+ this.textAlign = options === null || options === void 0 ? void 0 : options.textAlign;
9573
+ this.rotation = options === null || options === void 0 ? void 0 : options.rotation;
9003
9574
  }
9004
9575
  /**
9005
9576
  * This function should invalidate any installed listeners and invalidate this object.
@@ -9719,7 +10290,7 @@ class VEllipse {
9719
10290
  else {
9720
10291
  let startTangent = this.tangentAt(curAngle);
9721
10292
  let endTangent = this.tangentAt(nextAngle);
9722
- // Find intersection
10293
+ // Find intersection (ignore that the result might be null in some extreme cases)
9723
10294
  let intersection = startTangent.intersection(endTangent);
9724
10295
  // What if intersection is undefined?
9725
10296
  // --> This *can* not happen if segmentCount > 2 and height and width of the ellipse are not zero.
@@ -10159,7 +10730,8 @@ VEllipseSector.ellipseSectorUtils = {
10159
10730
  * @modified 2023-02-10 Fixing an issue of the `style.position` setting when `fitToParent=true` from `absolute` to `static` (default).
10160
10731
  * @modified 2023-02-10 Cleaning up most type errors in the main class (mostly null checks).
10161
10732
  * @modified 2023-02-10 Adding `enableZoom` and `enablePan` (both default true) to have the option to disable these functions.
10162
- * @version 1.17.2
10733
+ * @modified 2023-09-29 Adding proper dicionary key and value types to the params of `PlotBoilerplate.utils.safeMergeByKeys` (was `object` before).
10734
+ * @version 1.17.3
10163
10735
  *
10164
10736
  * @file PlotBoilerplate
10165
10737
  * @fileoverview The main class.
@@ -10277,7 +10849,7 @@ class PlotBoilerplate {
10277
10849
  draggable: true,
10278
10850
  visible: true
10279
10851
  };
10280
- if (typeof config.canvas == "undefined") {
10852
+ if (typeof config.canvas === "undefined") {
10281
10853
  throw "No canvas specified.";
10282
10854
  }
10283
10855
  /**
@@ -10369,6 +10941,10 @@ class PlotBoilerplate {
10369
10941
  fill: true
10370
10942
  }
10371
10943
  },
10944
+ // bezierPath: {
10945
+ // color: "#0022a8",
10946
+ // lineWidth: 1
10947
+ // },
10372
10948
  polygon: {
10373
10949
  color: "#0022a8",
10374
10950
  lineWidth: 1
@@ -10428,7 +11004,10 @@ class PlotBoilerplate {
10428
11004
  // +-------------------------------
10429
11005
  this.grid = new Grid(new Vertex(0, 0), new Vertex(50, 50));
10430
11006
  this.canvasSize = { width: PlotBoilerplate.DEFAULT_CANVAS_WIDTH, height: PlotBoilerplate.DEFAULT_CANVAS_HEIGHT };
10431
- const canvasElement = typeof config.canvas == "string" ? document.querySelector(config.canvas) : config.canvas;
11007
+ const canvasElement = typeof config.canvas === "string" ? document.querySelector(config.canvas) : config.canvas;
11008
+ if (typeof canvasElement === "undefined") {
11009
+ throw `Cannot initialize PlotBoilerplate with a null canvas (element "${config.canvas} not found).`;
11010
+ }
10432
11011
  // Which renderer to use: Canvas2D, WebGL (experimental) or SVG?
10433
11012
  if (canvasElement.tagName.toLowerCase() === "canvas") {
10434
11013
  this.canvas = canvasElement;
@@ -11076,10 +11655,10 @@ class PlotBoilerplate {
11076
11655
  if (this.drawConfig.drawBezierHandleLines && this.drawConfig.drawHandleLines) {
11077
11656
  draw.setCurrentId(`${d.uid}_l0`);
11078
11657
  draw.setCurrentClassName(`${d.className}-start-line`);
11079
- draw.line(d.bezierCurves[c].startPoint, d.bezierCurves[c].startControlPoint, this.drawConfig.bezier.handleLine.color, this.drawConfig.bezier.handleLine.lineWidth);
11658
+ draw.handleLine(d.bezierCurves[c].startPoint, d.bezierCurves[c].startControlPoint);
11080
11659
  draw.setCurrentId(`${d.uid}_l1`);
11081
11660
  draw.setCurrentClassName(`${d.className}-end-line`);
11082
- draw.line(d.bezierCurves[c].endPoint, d.bezierCurves[c].endControlPoint, this.drawConfig.bezier.handleLine.color, this.drawConfig.bezier.handleLine.lineWidth);
11661
+ draw.handleLine(d.bezierCurves[c].endPoint, d.bezierCurves[c].endControlPoint);
11083
11662
  }
11084
11663
  curveIndex++;
11085
11664
  } // END for
@@ -11102,11 +11681,11 @@ class PlotBoilerplate {
11102
11681
  draw.setCurrentId(`${d.uid}_e0`);
11103
11682
  draw.setCurrentClassName(`${d.className}-v-line`);
11104
11683
  // draw.line( d.center.clone().add(0,d.axis.y-d.center.y), d.axis, '#c8c8c8' );
11105
- draw.line(d.center.clone().add(0, d.signedRadiusV()).rotate(d.rotation, d.center), d.axis, "#c8c8c8");
11684
+ draw.handleLine(d.center.clone().add(0, d.signedRadiusV()).rotate(d.rotation, d.center), d.axis); // , "#c8c8c8");
11106
11685
  draw.setCurrentId(`${d.uid}_e1`);
11107
11686
  draw.setCurrentClassName(`${d.className}-h-line`);
11108
11687
  // draw.line( d.center.clone().add(d.axis.x-d.center.x,0), d.axis, '#c8c8c8' );
11109
- draw.line(d.center.clone().add(d.signedRadiusH(), 0).rotate(d.rotation, d.center), d.axis, "#c8c8c8");
11688
+ draw.handleLine(d.center.clone().add(d.signedRadiusH(), 0).rotate(d.rotation, d.center), d.axis); // , "#c8c8c8");
11110
11689
  }
11111
11690
  draw.setCurrentId(d.uid);
11112
11691
  draw.setCurrentClassName(`${d.className}`);
@@ -12042,22 +12621,35 @@ PlotBoilerplate.utils = {
12042
12621
  **/
12043
12622
  safeMergeByKeys: (base, extension) => {
12044
12623
  for (var k in extension) {
12045
- if (!extension.hasOwnProperty(k))
12624
+ if (!extension.hasOwnProperty(k)) {
12046
12625
  continue;
12626
+ }
12047
12627
  if (base.hasOwnProperty(k)) {
12048
- var typ = typeof base[k];
12628
+ const typ = typeof base[k];
12629
+ const extVal = extension[k];
12049
12630
  try {
12050
- if (typ == "boolean")
12051
- base[k] = !!JSON.parse(extension[k]);
12052
- else if (typ == "number")
12053
- base[k] = JSON.parse(extension[k]) * 1;
12054
- else if (typ == "function" && typeof extension[k] == "function")
12631
+ if (typ == "boolean") {
12632
+ if (typeof extVal === "string")
12633
+ base[k] = Boolean(!!JSON.parse(extVal));
12634
+ else
12635
+ base[k] = extVal;
12636
+ }
12637
+ else if (typ == "number") {
12638
+ if (typeof extVal === "string")
12639
+ base[k] = Number(JSON.parse(extVal) * 1);
12640
+ else
12641
+ base[k] = extension[k];
12642
+ }
12643
+ else if (typ == "function" && typeof extVal == "function") {
12055
12644
  base[k] = extension[k];
12056
- else
12645
+ }
12646
+ else {
12647
+ // Probably a sting
12057
12648
  base[k] = extension[k];
12649
+ }
12058
12650
  }
12059
12651
  catch (e) {
12060
- console.error("error in key ", k, extension[k], e);
12652
+ console.error("error in key ", k, extVal, e);
12061
12653
  }
12062
12654
  }
12063
12655
  else {
@@ -12066,6 +12658,31 @@ PlotBoilerplate.utils = {
12066
12658
  }
12067
12659
  return base;
12068
12660
  },
12661
+ /*
12662
+ __safeMergeByKeys: <KeyType extends string | number | symbol, ValueType extends boolean | number | string | Function>(
12663
+ base: Record<KeyType, ValueType>,
12664
+ extension: Record<KeyType, string>
12665
+ ): Record<KeyType, ValueType> => {
12666
+ for (var k in extension) {
12667
+ if (!extension.hasOwnProperty(k)) continue;
12668
+ if (base.hasOwnProperty(k)) {
12669
+ var typ = typeof base[k];
12670
+ try {
12671
+ if (typ == "boolean") base[k] = !!JSON.parse(extension[k]);
12672
+ else if (typ == "number") base[k] = JSON.parse(extension[k]) * 1;
12673
+ else if (typ == "function" && typeof extension[k] == "function") base[k] = extension[k];
12674
+ else base[k] = extension[k];
12675
+ } catch (e) {
12676
+ console.error("error in key ", k, extension[k], e);
12677
+ }
12678
+ } else {
12679
+ base[k] = extension[k];
12680
+ }
12681
+ }
12682
+ return base;
12683
+ },
12684
+ *()
12685
+
12069
12686
  /**
12070
12687
  * A helper function to scale elements (usually the canvas) using CSS.
12071
12688
  *