@joint/core 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +376 -0
  2. package/README.md +49 -0
  3. package/dist/geometry.js +6486 -0
  4. package/dist/geometry.min.js +8 -0
  5. package/dist/joint.d.ts +5536 -0
  6. package/dist/joint.js +39629 -0
  7. package/dist/joint.min.js +8 -0
  8. package/dist/joint.nowrap.js +39626 -0
  9. package/dist/joint.nowrap.min.js +8 -0
  10. package/dist/vectorizer.js +9135 -0
  11. package/dist/vectorizer.min.js +8 -0
  12. package/dist/version.mjs +3 -0
  13. package/index.js +3 -0
  14. package/joint.mjs +27 -0
  15. package/package.json +192 -0
  16. package/src/V/annotation.mjs +0 -0
  17. package/src/V/index.mjs +2642 -0
  18. package/src/anchors/index.mjs +123 -0
  19. package/src/config/index.mjs +12 -0
  20. package/src/connectionPoints/index.mjs +202 -0
  21. package/src/connectionStrategies/index.mjs +73 -0
  22. package/src/connectors/curve.mjs +553 -0
  23. package/src/connectors/index.mjs +6 -0
  24. package/src/connectors/jumpover.mjs +452 -0
  25. package/src/connectors/normal.mjs +12 -0
  26. package/src/connectors/rounded.mjs +17 -0
  27. package/src/connectors/smooth.mjs +44 -0
  28. package/src/connectors/straight.mjs +110 -0
  29. package/src/dia/Cell.mjs +945 -0
  30. package/src/dia/CellView.mjs +1316 -0
  31. package/src/dia/Element.mjs +519 -0
  32. package/src/dia/ElementView.mjs +859 -0
  33. package/src/dia/Graph.mjs +1112 -0
  34. package/src/dia/HighlighterView.mjs +319 -0
  35. package/src/dia/Link.mjs +565 -0
  36. package/src/dia/LinkView.mjs +2207 -0
  37. package/src/dia/Paper.mjs +3171 -0
  38. package/src/dia/PaperLayer.mjs +75 -0
  39. package/src/dia/ToolView.mjs +69 -0
  40. package/src/dia/ToolsView.mjs +128 -0
  41. package/src/dia/attributes/calc.mjs +128 -0
  42. package/src/dia/attributes/connection.mjs +75 -0
  43. package/src/dia/attributes/defs.mjs +76 -0
  44. package/src/dia/attributes/eval.mjs +64 -0
  45. package/src/dia/attributes/index.mjs +69 -0
  46. package/src/dia/attributes/legacy.mjs +148 -0
  47. package/src/dia/attributes/offset.mjs +53 -0
  48. package/src/dia/attributes/props.mjs +30 -0
  49. package/src/dia/attributes/shape.mjs +92 -0
  50. package/src/dia/attributes/text.mjs +180 -0
  51. package/src/dia/index.mjs +13 -0
  52. package/src/dia/layers/GridLayer.mjs +176 -0
  53. package/src/dia/ports.mjs +874 -0
  54. package/src/elementTools/Control.mjs +153 -0
  55. package/src/elementTools/HoverConnect.mjs +37 -0
  56. package/src/elementTools/index.mjs +5 -0
  57. package/src/env/index.mjs +43 -0
  58. package/src/g/bezier.mjs +175 -0
  59. package/src/g/curve.mjs +956 -0
  60. package/src/g/ellipse.mjs +245 -0
  61. package/src/g/extend.mjs +64 -0
  62. package/src/g/geometry.helpers.mjs +58 -0
  63. package/src/g/index.mjs +17 -0
  64. package/src/g/intersection.mjs +511 -0
  65. package/src/g/line.bearing.mjs +30 -0
  66. package/src/g/line.length.mjs +5 -0
  67. package/src/g/line.mjs +356 -0
  68. package/src/g/line.squaredLength.mjs +10 -0
  69. package/src/g/path.mjs +2260 -0
  70. package/src/g/point.mjs +375 -0
  71. package/src/g/points.mjs +247 -0
  72. package/src/g/polygon.mjs +51 -0
  73. package/src/g/polyline.mjs +523 -0
  74. package/src/g/rect.mjs +556 -0
  75. package/src/g/types.mjs +10 -0
  76. package/src/highlighters/addClass.mjs +27 -0
  77. package/src/highlighters/index.mjs +5 -0
  78. package/src/highlighters/list.mjs +111 -0
  79. package/src/highlighters/mask.mjs +220 -0
  80. package/src/highlighters/opacity.mjs +17 -0
  81. package/src/highlighters/stroke.mjs +100 -0
  82. package/src/layout/index.mjs +4 -0
  83. package/src/layout/ports/port.mjs +188 -0
  84. package/src/layout/ports/portLabel.mjs +224 -0
  85. package/src/linkAnchors/index.mjs +76 -0
  86. package/src/linkTools/Anchor.mjs +235 -0
  87. package/src/linkTools/Arrowhead.mjs +103 -0
  88. package/src/linkTools/Boundary.mjs +48 -0
  89. package/src/linkTools/Button.mjs +121 -0
  90. package/src/linkTools/Connect.mjs +85 -0
  91. package/src/linkTools/HoverConnect.mjs +161 -0
  92. package/src/linkTools/Segments.mjs +393 -0
  93. package/src/linkTools/Vertices.mjs +253 -0
  94. package/src/linkTools/helpers.mjs +33 -0
  95. package/src/linkTools/index.mjs +8 -0
  96. package/src/mvc/Collection.mjs +560 -0
  97. package/src/mvc/Data.mjs +46 -0
  98. package/src/mvc/Dom/Dom.mjs +587 -0
  99. package/src/mvc/Dom/Event.mjs +130 -0
  100. package/src/mvc/Dom/animations.mjs +122 -0
  101. package/src/mvc/Dom/events.mjs +69 -0
  102. package/src/mvc/Dom/index.mjs +13 -0
  103. package/src/mvc/Dom/methods.mjs +392 -0
  104. package/src/mvc/Dom/props.mjs +77 -0
  105. package/src/mvc/Dom/vars.mjs +5 -0
  106. package/src/mvc/Events.mjs +337 -0
  107. package/src/mvc/Listener.mjs +33 -0
  108. package/src/mvc/Model.mjs +239 -0
  109. package/src/mvc/View.mjs +323 -0
  110. package/src/mvc/ViewBase.mjs +182 -0
  111. package/src/mvc/index.mjs +9 -0
  112. package/src/mvc/mvcUtils.mjs +90 -0
  113. package/src/polyfills/array.js +4 -0
  114. package/src/polyfills/base64.js +68 -0
  115. package/src/polyfills/index.mjs +5 -0
  116. package/src/polyfills/number.js +3 -0
  117. package/src/polyfills/string.js +3 -0
  118. package/src/polyfills/typedArray.js +47 -0
  119. package/src/routers/index.mjs +6 -0
  120. package/src/routers/manhattan.mjs +856 -0
  121. package/src/routers/metro.mjs +91 -0
  122. package/src/routers/normal.mjs +6 -0
  123. package/src/routers/oneSide.mjs +60 -0
  124. package/src/routers/orthogonal.mjs +323 -0
  125. package/src/routers/rightAngle.mjs +1056 -0
  126. package/src/shapes/index.mjs +3 -0
  127. package/src/shapes/standard.mjs +755 -0
  128. package/src/util/cloneCells.mjs +67 -0
  129. package/src/util/getRectPoint.mjs +65 -0
  130. package/src/util/index.mjs +5 -0
  131. package/src/util/svgTagTemplate.mjs +110 -0
  132. package/src/util/util.mjs +1754 -0
  133. package/src/util/utilHelpers.mjs +2402 -0
  134. package/src/util/wrappers.mjs +56 -0
  135. package/types/geometry.d.ts +815 -0
  136. package/types/index.d.ts +53 -0
  137. package/types/joint.d.ts +4391 -0
  138. package/types/joint.head.d.ts +12 -0
  139. package/types/vectorizer.d.ts +327 -0
@@ -0,0 +1,956 @@
1
+ import { Point } from './point.mjs';
2
+ import { Rect } from './rect.mjs';
3
+ import { Line } from './line.mjs';
4
+ import { Polyline } from './polyline.mjs';
5
+ import { types } from './types.mjs';
6
+
7
+ const {
8
+ abs,
9
+ sqrt,
10
+ min,
11
+ max,
12
+ pow
13
+ } = Math;
14
+
15
+ export const Curve = function(p1, p2, p3, p4) {
16
+
17
+ if (!(this instanceof Curve)) {
18
+ return new Curve(p1, p2, p3, p4);
19
+ }
20
+
21
+ if (p1 instanceof Curve) {
22
+ return new Curve(p1.start, p1.controlPoint1, p1.controlPoint2, p1.end);
23
+ }
24
+
25
+ this.start = new Point(p1);
26
+ this.controlPoint1 = new Point(p2);
27
+ this.controlPoint2 = new Point(p3);
28
+ this.end = new Point(p4);
29
+ };
30
+
31
+ // Curve passing through points.
32
+ // Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx).
33
+ // @param {array} points Array of points through which the smooth line will go.
34
+ // @return {array} curves.
35
+ Curve.throughPoints = (function() {
36
+
37
+ // Get open-ended Bezier Spline Control Points.
38
+ // @param knots Input Knot Bezier spline points (At least two points!).
39
+ // @param firstControlPoints Output First Control points. Array of knots.length - 1 length.
40
+ // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length.
41
+ function getCurveControlPoints(knots) {
42
+
43
+ var firstControlPoints = [];
44
+ var secondControlPoints = [];
45
+ var n = knots.length - 1;
46
+ var i;
47
+
48
+ // Special case: Bezier curve should be a straight line.
49
+ if (n == 1) {
50
+ // 3P1 = 2P0 + P3
51
+ firstControlPoints[0] = new Point(
52
+ (2 * knots[0].x + knots[1].x) / 3,
53
+ (2 * knots[0].y + knots[1].y) / 3
54
+ );
55
+
56
+ // P2 = 2P1 – P0
57
+ secondControlPoints[0] = new Point(
58
+ 2 * firstControlPoints[0].x - knots[0].x,
59
+ 2 * firstControlPoints[0].y - knots[0].y
60
+ );
61
+
62
+ return [firstControlPoints, secondControlPoints];
63
+ }
64
+
65
+ // Calculate first Bezier control points.
66
+ // Right hand side vector.
67
+ var rhs = [];
68
+
69
+ // Set right hand side X values.
70
+ for (i = 1; i < n - 1; i++) {
71
+ rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
72
+ }
73
+
74
+ rhs[0] = knots[0].x + 2 * knots[1].x;
75
+ rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0;
76
+
77
+ // Get first control points X-values.
78
+ var x = getFirstControlPoints(rhs);
79
+
80
+ // Set right hand side Y values.
81
+ for (i = 1; i < n - 1; ++i) {
82
+ rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
83
+ }
84
+
85
+ rhs[0] = knots[0].y + 2 * knots[1].y;
86
+ rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0;
87
+
88
+ // Get first control points Y-values.
89
+ var y = getFirstControlPoints(rhs);
90
+
91
+ // Fill output arrays.
92
+ for (i = 0; i < n; i++) {
93
+ // First control point.
94
+ firstControlPoints.push(new Point(x[i], y[i]));
95
+
96
+ // Second control point.
97
+ if (i < n - 1) {
98
+ secondControlPoints.push(new Point(
99
+ 2 * knots [i + 1].x - x[i + 1],
100
+ 2 * knots[i + 1].y - y[i + 1]
101
+ ));
102
+
103
+ } else {
104
+ secondControlPoints.push(new Point(
105
+ (knots[n].x + x[n - 1]) / 2,
106
+ (knots[n].y + y[n - 1]) / 2
107
+ ));
108
+ }
109
+ }
110
+
111
+ return [firstControlPoints, secondControlPoints];
112
+ }
113
+
114
+ // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
115
+ // @param rhs Right hand side vector.
116
+ // @return Solution vector.
117
+ function getFirstControlPoints(rhs) {
118
+
119
+ var n = rhs.length;
120
+ // `x` is a solution vector.
121
+ var x = [];
122
+ var tmp = [];
123
+ var b = 2.0;
124
+
125
+ x[0] = rhs[0] / b;
126
+
127
+ // Decomposition and forward substitution.
128
+ for (var i = 1; i < n; i++) {
129
+ tmp[i] = 1 / b;
130
+ b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
131
+ x[i] = (rhs[i] - x[i - 1]) / b;
132
+ }
133
+
134
+ for (i = 1; i < n; i++) {
135
+ // Backsubstitution.
136
+ x[n - i - 1] -= tmp[n - i] * x[n - i];
137
+ }
138
+
139
+ return x;
140
+ }
141
+
142
+ return function(points) {
143
+
144
+ if (!points || (Array.isArray(points) && points.length < 2)) {
145
+ throw new Error('At least 2 points are required');
146
+ }
147
+
148
+ var controlPoints = getCurveControlPoints(points);
149
+
150
+ var curves = [];
151
+ var n = controlPoints[0].length;
152
+ for (var i = 0; i < n; i++) {
153
+
154
+ var controlPoint1 = new Point(controlPoints[0][i].x, controlPoints[0][i].y);
155
+ var controlPoint2 = new Point(controlPoints[1][i].x, controlPoints[1][i].y);
156
+
157
+ curves.push(new Curve(points[i], controlPoint1, controlPoint2, points[i + 1]));
158
+ }
159
+
160
+ return curves;
161
+ };
162
+ })();
163
+
164
+ Curve.prototype = {
165
+
166
+ type: types.Curve,
167
+
168
+ // Returns a bbox that tightly envelops the curve.
169
+ bbox: function() {
170
+
171
+ var start = this.start;
172
+ var controlPoint1 = this.controlPoint1;
173
+ var controlPoint2 = this.controlPoint2;
174
+ var end = this.end;
175
+
176
+ var x0 = start.x;
177
+ var y0 = start.y;
178
+ var x1 = controlPoint1.x;
179
+ var y1 = controlPoint1.y;
180
+ var x2 = controlPoint2.x;
181
+ var y2 = controlPoint2.y;
182
+ var x3 = end.x;
183
+ var y3 = end.y;
184
+
185
+ var points = new Array(); // local extremes
186
+ var tvalues = new Array(); // t values of local extremes
187
+ var bounds = [new Array(), new Array()];
188
+
189
+ var a, b, c, t;
190
+ var t1, t2;
191
+ var b2ac, sqrtb2ac;
192
+
193
+ for (var i = 0; i < 2; ++i) {
194
+
195
+ if (i === 0) {
196
+ b = 6 * x0 - 12 * x1 + 6 * x2;
197
+ a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
198
+ c = 3 * x1 - 3 * x0;
199
+
200
+ } else {
201
+ b = 6 * y0 - 12 * y1 + 6 * y2;
202
+ a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
203
+ c = 3 * y1 - 3 * y0;
204
+ }
205
+
206
+ if (abs(a) < 1e-12) { // Numerical robustness
207
+ if (abs(b) < 1e-12) { // Numerical robustness
208
+ continue;
209
+ }
210
+
211
+ t = -c / b;
212
+ if ((0 < t) && (t < 1)) tvalues.push(t);
213
+
214
+ continue;
215
+ }
216
+
217
+ b2ac = b * b - 4 * c * a;
218
+ sqrtb2ac = sqrt(b2ac);
219
+
220
+ if (b2ac < 0) continue;
221
+
222
+ t1 = (-b + sqrtb2ac) / (2 * a);
223
+ if ((0 < t1) && (t1 < 1)) tvalues.push(t1);
224
+
225
+ t2 = (-b - sqrtb2ac) / (2 * a);
226
+ if ((0 < t2) && (t2 < 1)) tvalues.push(t2);
227
+ }
228
+
229
+ var j = tvalues.length;
230
+ var jlen = j;
231
+ var mt;
232
+ var x, y;
233
+
234
+ while (j--) {
235
+ t = tvalues[j];
236
+ mt = 1 - t;
237
+
238
+ x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
239
+ bounds[0][j] = x;
240
+
241
+ y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
242
+ bounds[1][j] = y;
243
+
244
+ points[j] = { X: x, Y: y };
245
+ }
246
+
247
+ tvalues[jlen] = 0;
248
+ tvalues[jlen + 1] = 1;
249
+
250
+ points[jlen] = { X: x0, Y: y0 };
251
+ points[jlen + 1] = { X: x3, Y: y3 };
252
+
253
+ bounds[0][jlen] = x0;
254
+ bounds[1][jlen] = y0;
255
+
256
+ bounds[0][jlen + 1] = x3;
257
+ bounds[1][jlen + 1] = y3;
258
+
259
+ tvalues.length = jlen + 2;
260
+ bounds[0].length = jlen + 2;
261
+ bounds[1].length = jlen + 2;
262
+ points.length = jlen + 2;
263
+
264
+ var left = min.apply(null, bounds[0]);
265
+ var top = min.apply(null, bounds[1]);
266
+ var right = max.apply(null, bounds[0]);
267
+ var bottom = max.apply(null, bounds[1]);
268
+
269
+ return new Rect(left, top, (right - left), (bottom - top));
270
+ },
271
+
272
+ clone: function() {
273
+
274
+ return new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
275
+ },
276
+
277
+ // Returns the point on the curve closest to point `p`
278
+ closestPoint: function(p, opt) {
279
+
280
+ return this.pointAtT(this.closestPointT(p, opt));
281
+ },
282
+
283
+ closestPointLength: function(p, opt) {
284
+
285
+ opt = opt || {};
286
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
287
+ var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
288
+ var localOpt = { precision: precision, subdivisions: subdivisions };
289
+
290
+ return this.lengthAtT(this.closestPointT(p, localOpt), localOpt);
291
+ },
292
+
293
+ closestPointNormalizedLength: function(p, opt) {
294
+
295
+ opt = opt || {};
296
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
297
+ var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
298
+ var localOpt = { precision: precision, subdivisions: subdivisions };
299
+
300
+ var cpLength = this.closestPointLength(p, localOpt);
301
+ if (!cpLength) return 0;
302
+
303
+ var length = this.length(localOpt);
304
+ if (length === 0) return 0;
305
+
306
+ return cpLength / length;
307
+ },
308
+
309
+ // Returns `t` of the point on the curve closest to point `p`
310
+ closestPointT: function(p, opt) {
311
+
312
+ opt = opt || {};
313
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
314
+ var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
315
+ // does not use localOpt
316
+
317
+ // identify the subdivision that contains the point:
318
+ var investigatedSubdivision;
319
+ var investigatedSubdivisionStartT; // assume that subdivisions are evenly spaced
320
+ var investigatedSubdivisionEndT;
321
+ var distFromStart; // distance of point from start of baseline
322
+ var distFromEnd; // distance of point from end of baseline
323
+ var chordLength; // distance between start and end of the subdivision
324
+ var minSumDist; // lowest observed sum of the two distances
325
+ var n = subdivisions.length;
326
+ var subdivisionSize = (n ? (1 / n) : 0);
327
+ for (var i = 0; i < n; i++) {
328
+
329
+ var currentSubdivision = subdivisions[i];
330
+
331
+ var startDist = currentSubdivision.start.distance(p);
332
+ var endDist = currentSubdivision.end.distance(p);
333
+ var sumDist = startDist + endDist;
334
+
335
+ // check that the point is closest to current subdivision and not any other
336
+ if (!minSumDist || (sumDist < minSumDist)) {
337
+ investigatedSubdivision = currentSubdivision;
338
+
339
+ investigatedSubdivisionStartT = i * subdivisionSize;
340
+ investigatedSubdivisionEndT = (i + 1) * subdivisionSize;
341
+
342
+ distFromStart = startDist;
343
+ distFromEnd = endDist;
344
+
345
+ chordLength = currentSubdivision.start.distance(currentSubdivision.end);
346
+
347
+ minSumDist = sumDist;
348
+ }
349
+ }
350
+
351
+ var precisionRatio = pow(10, -precision);
352
+
353
+ // recursively divide investigated subdivision:
354
+ // until distance between baselinePoint and closest path endpoint is within 10^(-precision)
355
+ // then return the closest endpoint of that final subdivision
356
+ while (true) {
357
+
358
+ // check if we have reached at least one required observed precision
359
+ // - calculated as: the difference in distances from point to start and end divided by the distance
360
+ // - note that this function is not monotonic = it doesn't converge stably but has "teeth"
361
+ // - the function decreases while one of the endpoints is fixed but "jumps" whenever we switch
362
+ // - this criterion works well for points lying far away from the curve
363
+ var startPrecisionRatio = (distFromStart ? (abs(distFromStart - distFromEnd) / distFromStart) : 0);
364
+ var endPrecisionRatio = (distFromEnd ? (abs(distFromStart - distFromEnd) / distFromEnd) : 0);
365
+ var hasRequiredPrecision = ((startPrecisionRatio < precisionRatio) || (endPrecisionRatio < precisionRatio));
366
+
367
+ // check if we have reached at least one required minimal distance
368
+ // - calculated as: the subdivision chord length multiplied by precisionRatio
369
+ // - calculation is relative so it will work for arbitrarily large/small curves and their subdivisions
370
+ // - this is a backup criterion that works well for points lying "almost at" the curve
371
+ var hasMinimalStartDistance = (distFromStart ? (distFromStart < (chordLength * precisionRatio)) : true);
372
+ var hasMinimalEndDistance = (distFromEnd ? (distFromEnd < (chordLength * precisionRatio)) : true);
373
+ var hasMinimalDistance = (hasMinimalStartDistance || hasMinimalEndDistance);
374
+
375
+ // do we stop now?
376
+ if (hasRequiredPrecision || hasMinimalDistance) {
377
+ return ((distFromStart <= distFromEnd) ? investigatedSubdivisionStartT : investigatedSubdivisionEndT);
378
+ }
379
+
380
+ // otherwise, set up for next iteration
381
+ var divided = investigatedSubdivision.divide(0.5);
382
+ subdivisionSize /= 2;
383
+
384
+ var startDist1 = divided[0].start.distance(p);
385
+ var endDist1 = divided[0].end.distance(p);
386
+ var sumDist1 = startDist1 + endDist1;
387
+
388
+ var startDist2 = divided[1].start.distance(p);
389
+ var endDist2 = divided[1].end.distance(p);
390
+ var sumDist2 = startDist2 + endDist2;
391
+
392
+ if (sumDist1 <= sumDist2) {
393
+ investigatedSubdivision = divided[0];
394
+
395
+ investigatedSubdivisionEndT -= subdivisionSize; // subdivisionSize was already halved
396
+
397
+ distFromStart = startDist1;
398
+ distFromEnd = endDist1;
399
+
400
+ } else {
401
+ investigatedSubdivision = divided[1];
402
+
403
+ investigatedSubdivisionStartT += subdivisionSize; // subdivisionSize was already halved
404
+
405
+ distFromStart = startDist2;
406
+ distFromEnd = endDist2;
407
+ }
408
+ }
409
+ },
410
+
411
+ closestPointTangent: function(p, opt) {
412
+
413
+ return this.tangentAtT(this.closestPointT(p, opt));
414
+ },
415
+
416
+ // Returns `true` if the area surrounded by the curve contains the point `p`.
417
+ // Implements the even-odd algorithm (self-intersections are "outside").
418
+ // Closes open curves (always imagines a closing segment).
419
+ // Precision may be adjusted by passing an `opt` object.
420
+ containsPoint: function(p, opt) {
421
+
422
+ var polyline = this.toPolyline(opt);
423
+ return polyline.containsPoint(p);
424
+ },
425
+
426
+ // Divides the curve into two at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided.
427
+ // For a function that uses `t`, use Curve.divideAtT().
428
+ divideAt: function(ratio, opt) {
429
+
430
+ if (ratio <= 0) return this.divideAtT(0);
431
+ if (ratio >= 1) return this.divideAtT(1);
432
+
433
+ var t = this.tAt(ratio, opt);
434
+
435
+ return this.divideAtT(t);
436
+ },
437
+
438
+ // Divides the curve into two at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
439
+ divideAtLength: function(length, opt) {
440
+
441
+ var t = this.tAtLength(length, opt);
442
+
443
+ return this.divideAtT(t);
444
+ },
445
+
446
+ // Divides the curve into two at point defined by `t` between 0 and 1.
447
+ // Using de Casteljau's algorithm (http://math.stackexchange.com/a/317867).
448
+ // Additional resource: https://pomax.github.io/bezierinfo/#decasteljau
449
+ divideAtT: function(t) {
450
+
451
+ var start = this.start;
452
+ var controlPoint1 = this.controlPoint1;
453
+ var controlPoint2 = this.controlPoint2;
454
+ var end = this.end;
455
+
456
+ // shortcuts for `t` values that are out of range
457
+ if (t <= 0) {
458
+ return [
459
+ new Curve(start, start, start, start),
460
+ new Curve(start, controlPoint1, controlPoint2, end)
461
+ ];
462
+ }
463
+
464
+ if (t >= 1) {
465
+ return [
466
+ new Curve(start, controlPoint1, controlPoint2, end),
467
+ new Curve(end, end, end, end)
468
+ ];
469
+ }
470
+
471
+ var dividerPoints = this.getSkeletonPoints(t);
472
+
473
+ var startControl1 = dividerPoints.startControlPoint1;
474
+ var startControl2 = dividerPoints.startControlPoint2;
475
+ var divider = dividerPoints.divider;
476
+ var dividerControl1 = dividerPoints.dividerControlPoint1;
477
+ var dividerControl2 = dividerPoints.dividerControlPoint2;
478
+
479
+ // return array with two new curves
480
+ return [
481
+ new Curve(start, startControl1, startControl2, divider),
482
+ new Curve(divider, dividerControl1, dividerControl2, end)
483
+ ];
484
+ },
485
+
486
+ // Returns the distance between the curve's start and end points.
487
+ endpointDistance: function() {
488
+
489
+ return this.start.distance(this.end);
490
+ },
491
+
492
+ // Checks whether two curves are exactly the same.
493
+ equals: function(c) {
494
+
495
+ return !!c &&
496
+ this.start.x === c.start.x &&
497
+ this.start.y === c.start.y &&
498
+ this.controlPoint1.x === c.controlPoint1.x &&
499
+ this.controlPoint1.y === c.controlPoint1.y &&
500
+ this.controlPoint2.x === c.controlPoint2.x &&
501
+ this.controlPoint2.y === c.controlPoint2.y &&
502
+ this.end.x === c.end.x &&
503
+ this.end.y === c.end.y;
504
+ },
505
+
506
+ // Returns five helper points necessary for curve division.
507
+ getSkeletonPoints: function(t) {
508
+
509
+ var start = this.start;
510
+ var control1 = this.controlPoint1;
511
+ var control2 = this.controlPoint2;
512
+ var end = this.end;
513
+
514
+ // shortcuts for `t` values that are out of range
515
+ if (t <= 0) {
516
+ return {
517
+ startControlPoint1: start.clone(),
518
+ startControlPoint2: start.clone(),
519
+ divider: start.clone(),
520
+ dividerControlPoint1: control1.clone(),
521
+ dividerControlPoint2: control2.clone()
522
+ };
523
+ }
524
+
525
+ if (t >= 1) {
526
+ return {
527
+ startControlPoint1: control1.clone(),
528
+ startControlPoint2: control2.clone(),
529
+ divider: end.clone(),
530
+ dividerControlPoint1: end.clone(),
531
+ dividerControlPoint2: end.clone()
532
+ };
533
+ }
534
+
535
+ var midpoint1 = (new Line(start, control1)).pointAt(t);
536
+ var midpoint2 = (new Line(control1, control2)).pointAt(t);
537
+ var midpoint3 = (new Line(control2, end)).pointAt(t);
538
+
539
+ var subControl1 = (new Line(midpoint1, midpoint2)).pointAt(t);
540
+ var subControl2 = (new Line(midpoint2, midpoint3)).pointAt(t);
541
+
542
+ var divider = (new Line(subControl1, subControl2)).pointAt(t);
543
+
544
+ var output = {
545
+ startControlPoint1: midpoint1,
546
+ startControlPoint2: subControl1,
547
+ divider: divider,
548
+ dividerControlPoint1: subControl2,
549
+ dividerControlPoint2: midpoint3
550
+ };
551
+
552
+ return output;
553
+ },
554
+
555
+ // Returns a list of curves whose flattened length is better than `opt.precision`.
556
+ // That is, observed difference in length between recursions is less than 10^(-3) = 0.001 = 0.1%
557
+ // (Observed difference is not real precision, but close enough as long as special cases are covered)
558
+ // As a rule of thumb, increasing `precision` by 1 requires 2 more iterations (= levels of division operations)
559
+ // - Precision 0 (endpointDistance) - 0 iterations => total of 2^0 - 1 = 0 operations (1 subdivision)
560
+ // - Precision 1 (<10% error) - 2 iterations => total of 2^2 - 1 = 3 operations (4 subdivisions)
561
+ // - Precision 2 (<1% error) - 4 iterations => total of 2^4 - 1 = 15 operations requires 4 division operations on all elements (15 operations total) (16 subdivisions)
562
+ // - Precision 3 (<0.1% error) - 6 iterations => total of 2^6 - 1 = 63 operations - acceptable when drawing (64 subdivisions)
563
+ // - Precision 4 (<0.01% error) - 8 iterations => total of 2^8 - 1 = 255 operations - high resolution, can be used to interpolate `t` (256 subdivisions)
564
+ // (Variation of 1 recursion worse or better is possible depending on the curve, doubling/halving the number of operations accordingly)
565
+ getSubdivisions: function(opt) {
566
+
567
+ opt = opt || {};
568
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
569
+ // not using opt.subdivisions
570
+ // not using localOpt
571
+
572
+ var start = this.start;
573
+ var control1 = this.controlPoint1;
574
+ var control2 = this.controlPoint2;
575
+ var end = this.end;
576
+
577
+ var subdivisions = [new Curve(start, control1, control2, end)];
578
+ if (precision === 0) return subdivisions;
579
+
580
+ // special case #1: point-like curves
581
+ // - no need to calculate subdivisions, they would all be identical
582
+ var isPoint = !this.isDifferentiable();
583
+ if (isPoint) return subdivisions;
584
+
585
+ var previousLength = this.endpointDistance();
586
+
587
+ var precisionRatio = pow(10, -precision);
588
+
589
+ // special case #2: sine-like curves may have the same observed length in iteration 0 and 1 - skip iteration 1
590
+ // - not a problem for further iterations because cubic curves cannot have more than two local extrema
591
+ // - (i.e. cubic curves cannot intersect the baseline more than once)
592
+ // - therefore starting from iteration = 2 ensures that subsequent iterations do not produce sampling with equal length
593
+ // - (unless it's a straight-line curve, see below)
594
+ var minIterations = 2; // = 2*1
595
+
596
+ // special case #3: straight-line curves have the same observed length in all iterations
597
+ // - this causes observed precision ratio to always be 0 (= lower than `precisionRatio`, which is our exit condition)
598
+ // - we enforce the expected number of iterations = 2 * precision
599
+ var isLine = ((control1.cross(start, end) === 0) && (control2.cross(start, end) === 0));
600
+ if (isLine) {
601
+ minIterations = (2 * precision);
602
+ }
603
+
604
+ // recursively divide curve at `t = 0.5`
605
+ // until we reach `minIterations`
606
+ // and until the difference between observed length at subsequent iterations is lower than `precision`
607
+ var iteration = 0;
608
+ while (true) {
609
+ iteration += 1;
610
+
611
+ // divide all subdivisions
612
+ var newSubdivisions = [];
613
+ var numSubdivisions = subdivisions.length;
614
+ for (var i = 0; i < numSubdivisions; i++) {
615
+
616
+ var currentSubdivision = subdivisions[i];
617
+ var divided = currentSubdivision.divide(0.5); // dividing at t = 0.5 (not at middle length!)
618
+ newSubdivisions.push(divided[0], divided[1]);
619
+ }
620
+
621
+ // measure new length
622
+ var length = 0;
623
+ var numNewSubdivisions = newSubdivisions.length;
624
+ for (var j = 0; j < numNewSubdivisions; j++) {
625
+
626
+ var currentNewSubdivision = newSubdivisions[j];
627
+ length += currentNewSubdivision.endpointDistance();
628
+ }
629
+
630
+ // check if we have reached minimum number of iterations
631
+ if (iteration >= minIterations) {
632
+
633
+ // check if we have reached required observed precision
634
+ var observedPrecisionRatio = ((length !== 0) ? ((length - previousLength) / length) : 0);
635
+ if (observedPrecisionRatio < precisionRatio) {
636
+ return newSubdivisions;
637
+ }
638
+ }
639
+
640
+ // otherwise, set up for next iteration
641
+ subdivisions = newSubdivisions;
642
+ previousLength = length;
643
+ }
644
+ },
645
+
646
+ isDifferentiable: function() {
647
+
648
+ var start = this.start;
649
+ var control1 = this.controlPoint1;
650
+ var control2 = this.controlPoint2;
651
+ var end = this.end;
652
+
653
+ return !(start.equals(control1) && control1.equals(control2) && control2.equals(end));
654
+ },
655
+
656
+ // Returns flattened length of the curve with precision better than `opt.precision`; or using `opt.subdivisions` provided.
657
+ length: function(opt) {
658
+
659
+ opt = opt || {};
660
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSubdivisions() call
661
+ var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
662
+ // not using localOpt
663
+
664
+ var length = 0;
665
+ var n = subdivisions.length;
666
+ for (var i = 0; i < n; i++) {
667
+
668
+ var currentSubdivision = subdivisions[i];
669
+ length += currentSubdivision.endpointDistance();
670
+ }
671
+
672
+ return length;
673
+ },
674
+
675
+ // Returns distance along the curve up to `t` with precision better than requested `opt.precision`. (Not using `opt.subdivisions`.)
676
+ lengthAtT: function(t, opt) {
677
+
678
+ if (t <= 0) return 0;
679
+
680
+ opt = opt || {};
681
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
682
+ // not using opt.subdivisions
683
+ // not using localOpt
684
+
685
+ var subCurve = this.divide(t)[0];
686
+ var subCurveLength = subCurve.length({ precision: precision });
687
+
688
+ return subCurveLength;
689
+ },
690
+
691
+ // Returns point at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided.
692
+ // Mirrors Line.pointAt() function.
693
+ // For a function that tracks `t`, use Curve.pointAtT().
694
+ pointAt: function(ratio, opt) {
695
+
696
+ if (ratio <= 0) return this.start.clone();
697
+ if (ratio >= 1) return this.end.clone();
698
+
699
+ var t = this.tAt(ratio, opt);
700
+
701
+ return this.pointAtT(t);
702
+ },
703
+
704
+ // Returns point at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
705
+ pointAtLength: function(length, opt) {
706
+
707
+ var t = this.tAtLength(length, opt);
708
+
709
+ return this.pointAtT(t);
710
+ },
711
+
712
+ // Returns the point at provided `t` between 0 and 1.
713
+ // `t` does not track distance along curve as it does in Line objects.
714
+ // Non-linear relationship, speeds up and slows down as curve warps!
715
+ // For linear length-based solution, use Curve.pointAt().
716
+ pointAtT: function(t) {
717
+
718
+ if (t <= 0) return this.start.clone();
719
+ if (t >= 1) return this.end.clone();
720
+
721
+ return this.getSkeletonPoints(t).divider;
722
+ },
723
+
724
+ // Default precision
725
+ PRECISION: 3,
726
+
727
+ round: function(precision) {
728
+
729
+ this.start.round(precision);
730
+ this.controlPoint1.round(precision);
731
+ this.controlPoint2.round(precision);
732
+ this.end.round(precision);
733
+ return this;
734
+ },
735
+
736
+ scale: function(sx, sy, origin) {
737
+
738
+ this.start.scale(sx, sy, origin);
739
+ this.controlPoint1.scale(sx, sy, origin);
740
+ this.controlPoint2.scale(sx, sy, origin);
741
+ this.end.scale(sx, sy, origin);
742
+ return this;
743
+ },
744
+
745
+ // Returns a tangent line at requested `ratio` with precision better than requested `opt.precision`; or using `opt.subdivisions` provided.
746
+ tangentAt: function(ratio, opt) {
747
+
748
+ if (!this.isDifferentiable()) return null;
749
+
750
+ if (ratio < 0) ratio = 0;
751
+ else if (ratio > 1) ratio = 1;
752
+
753
+ var t = this.tAt(ratio, opt);
754
+
755
+ return this.tangentAtT(t);
756
+ },
757
+
758
+ // Returns a tangent line at requested `length` with precision better than requested `opt.precision`; or using `opt.subdivisions` provided.
759
+ tangentAtLength: function(length, opt) {
760
+
761
+ if (!this.isDifferentiable()) return null;
762
+
763
+ var t = this.tAtLength(length, opt);
764
+
765
+ return this.tangentAtT(t);
766
+ },
767
+
768
+ // Returns a tangent line at requested `t`.
769
+ tangentAtT: function(t) {
770
+
771
+ if (!this.isDifferentiable()) return null;
772
+
773
+ if (t < 0) t = 0;
774
+ else if (t > 1) t = 1;
775
+
776
+ var skeletonPoints = this.getSkeletonPoints(t);
777
+
778
+ var p1 = skeletonPoints.startControlPoint2;
779
+ var p2 = skeletonPoints.dividerControlPoint1;
780
+
781
+ var tangentStart = skeletonPoints.divider;
782
+
783
+ var tangentLine = new Line(p1, p2);
784
+ tangentLine.translate(tangentStart.x - p1.x, tangentStart.y - p1.y); // move so that tangent line starts at the point requested
785
+
786
+ return tangentLine;
787
+ },
788
+
789
+ // Returns `t` at requested `ratio` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
790
+ tAt: function(ratio, opt) {
791
+
792
+ if (ratio <= 0) return 0;
793
+ if (ratio >= 1) return 1;
794
+
795
+ opt = opt || {};
796
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
797
+ var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
798
+ var localOpt = { precision: precision, subdivisions: subdivisions };
799
+
800
+ var curveLength = this.length(localOpt);
801
+ var length = curveLength * ratio;
802
+
803
+ return this.tAtLength(length, localOpt);
804
+ },
805
+
806
+ // Returns `t` at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
807
+ // Uses `precision` to approximate length within `precision` (always underestimates)
808
+ // Then uses a binary search to find the `t` of a subdivision endpoint that is close (within `precision`) to the `length`, if the curve was as long as approximated
809
+ // As a rule of thumb, increasing `precision` by 1 causes the algorithm to go 2^(precision - 1) deeper
810
+ // - Precision 0 (chooses one of the two endpoints) - 0 levels
811
+ // - Precision 1 (chooses one of 5 points, <10% error) - 1 level
812
+ // - Precision 2 (<1% error) - 3 levels
813
+ // - Precision 3 (<0.1% error) - 7 levels
814
+ // - Precision 4 (<0.01% error) - 15 levels
815
+ tAtLength: function(length, opt) {
816
+
817
+ var fromStart = true;
818
+ if (length < 0) {
819
+ fromStart = false; // negative lengths mean start calculation from end point
820
+ length = -length; // absolute value
821
+ }
822
+
823
+ opt = opt || {};
824
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
825
+ var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
826
+ var localOpt = { precision: precision, subdivisions: subdivisions };
827
+
828
+ // identify the subdivision that contains the point at requested `length`:
829
+ var investigatedSubdivision;
830
+ var investigatedSubdivisionStartT; // assume that subdivisions are evenly spaced
831
+ var investigatedSubdivisionEndT;
832
+ //var baseline; // straightened version of subdivision to investigate
833
+ //var baselinePoint; // point on the baseline that is the requested distance away from start
834
+ var baselinePointDistFromStart; // distance of baselinePoint from start of baseline
835
+ var baselinePointDistFromEnd; // distance of baselinePoint from end of baseline
836
+ var l = 0; // length so far
837
+ var n = subdivisions.length;
838
+ var subdivisionSize = 1 / n;
839
+ for (var i = 0; i < n; i++) {
840
+ var index = (fromStart ? i : (n - 1 - i));
841
+
842
+ var currentSubdivision = subdivisions[i];
843
+ var d = currentSubdivision.endpointDistance(); // length of current subdivision
844
+
845
+ if (length <= (l + d)) {
846
+ investigatedSubdivision = currentSubdivision;
847
+
848
+ investigatedSubdivisionStartT = index * subdivisionSize;
849
+ investigatedSubdivisionEndT = (index + 1) * subdivisionSize;
850
+
851
+ baselinePointDistFromStart = (fromStart ? (length - l) : ((d + l) - length));
852
+ baselinePointDistFromEnd = (fromStart ? ((d + l) - length) : (length - l));
853
+
854
+ break;
855
+ }
856
+
857
+ l += d;
858
+ }
859
+
860
+ if (!investigatedSubdivision) return (fromStart ? 1 : 0); // length requested is out of range - return maximum t
861
+ // note that precision affects what length is recorded
862
+ // (imprecise measurements underestimate length by up to 10^(-precision) of the precise length)
863
+ // e.g. at precision 1, the length may be underestimated by up to 10% and cause this function to return 1
864
+
865
+ var curveLength = this.length(localOpt);
866
+
867
+ var precisionRatio = pow(10, -precision);
868
+
869
+ // recursively divide investigated subdivision:
870
+ // until distance between baselinePoint and closest path endpoint is within 10^(-precision)
871
+ // then return the closest endpoint of that final subdivision
872
+ while (true) {
873
+
874
+ // check if we have reached required observed precision
875
+ var observedPrecisionRatio;
876
+
877
+ observedPrecisionRatio = ((curveLength !== 0) ? (baselinePointDistFromStart / curveLength) : 0);
878
+ if (observedPrecisionRatio < precisionRatio) return investigatedSubdivisionStartT;
879
+ observedPrecisionRatio = ((curveLength !== 0) ? (baselinePointDistFromEnd / curveLength) : 0);
880
+ if (observedPrecisionRatio < precisionRatio) return investigatedSubdivisionEndT;
881
+
882
+ // otherwise, set up for next iteration
883
+ var newBaselinePointDistFromStart;
884
+ var newBaselinePointDistFromEnd;
885
+
886
+ var divided = investigatedSubdivision.divide(0.5);
887
+ subdivisionSize /= 2;
888
+
889
+ var baseline1Length = divided[0].endpointDistance();
890
+ var baseline2Length = divided[1].endpointDistance();
891
+
892
+ if (baselinePointDistFromStart <= baseline1Length) { // point at requested length is inside divided[0]
893
+ investigatedSubdivision = divided[0];
894
+
895
+ investigatedSubdivisionEndT -= subdivisionSize; // sudivisionSize was already halved
896
+
897
+ newBaselinePointDistFromStart = baselinePointDistFromStart;
898
+ newBaselinePointDistFromEnd = baseline1Length - newBaselinePointDistFromStart;
899
+
900
+ } else { // point at requested length is inside divided[1]
901
+ investigatedSubdivision = divided[1];
902
+
903
+ investigatedSubdivisionStartT += subdivisionSize; // subdivisionSize was already halved
904
+
905
+ newBaselinePointDistFromStart = baselinePointDistFromStart - baseline1Length;
906
+ newBaselinePointDistFromEnd = baseline2Length - newBaselinePointDistFromStart;
907
+ }
908
+
909
+ baselinePointDistFromStart = newBaselinePointDistFromStart;
910
+ baselinePointDistFromEnd = newBaselinePointDistFromEnd;
911
+ }
912
+ },
913
+
914
+ // Returns an array of points that represents the curve when flattened, up to `opt.precision`; or using `opt.subdivisions` provided.
915
+ // Flattened length is no more than 10^(-precision) away from real curve length.
916
+ toPoints: function(opt) {
917
+
918
+ opt = opt || {};
919
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSubdivisions() call
920
+ var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
921
+ // not using localOpt
922
+
923
+ var points = [subdivisions[0].start.clone()];
924
+ var n = subdivisions.length;
925
+ for (var i = 0; i < n; i++) {
926
+
927
+ var currentSubdivision = subdivisions[i];
928
+ points.push(currentSubdivision.end.clone());
929
+ }
930
+
931
+ return points;
932
+ },
933
+
934
+ // Returns a polyline that represents the curve when flattened, up to `opt.precision`; or using `opt.subdivisions` provided.
935
+ // Flattened length is no more than 10^(-precision) away from real curve length.
936
+ toPolyline: function(opt) {
937
+
938
+ return new Polyline(this.toPoints(opt));
939
+ },
940
+
941
+ toString: function() {
942
+
943
+ return this.start + ' ' + this.controlPoint1 + ' ' + this.controlPoint2 + ' ' + this.end;
944
+ },
945
+
946
+ translate: function(tx, ty) {
947
+
948
+ this.start.translate(tx, ty);
949
+ this.controlPoint1.translate(tx, ty);
950
+ this.controlPoint2.translate(tx, ty);
951
+ this.end.translate(tx, ty);
952
+ return this;
953
+ }
954
+ };
955
+
956
+ Curve.prototype.divide = Curve.prototype.divideAtT;