@khanacademy/kmath 1.0.0 → 2.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.
package/dist/index.js CHANGED
@@ -1,21 +1,44 @@
1
- import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
2
- import _ from 'underscore';
3
- import { approximateEqual, approximateDeepEqual } from '@khanacademy/perseus-core';
4
- import $ from 'jquery';
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var perseusUtils = require('@khanacademy/perseus-utils');
6
+ var _ = require('underscore');
7
+ var perseusCore = require('@khanacademy/perseus-core');
8
+ var $ = require('jquery');
9
+
10
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
+
12
+ var ___default = /*#__PURE__*/_interopDefaultCompat(_);
13
+ var $__default = /*#__PURE__*/_interopDefaultCompat($);
14
+
15
+ // This file is processed by a Rollup plugin (replace) to inject the production
16
+ // version number during the release build.
17
+ // In dev, you'll never see the version number.
5
18
 
6
19
  const libName = "@khanacademy/kmath";
7
- const libVersion = "1.0.0";
8
- addLibraryVersionToPerseusDebug(libName, libVersion);
20
+ const libVersion = "2.0.0";
21
+ perseusUtils.addLibraryVersionToPerseusDebug(libName, libVersion);
22
+
23
+ /**
24
+ * Number Utils
25
+ * A number is a js-number, e.g. 5.12
26
+ */
9
27
 
10
28
  const DEFAULT_TOLERANCE = 1e-9;
29
+
30
+ // TODO: Should this just be Number.Epsilon
11
31
  const EPSILON = Math.pow(2, -42);
12
32
  function is$2(x) {
13
- return _.isNumber(x) && !_.isNaN(x);
33
+ return ___default.default.isNumber(x) && !___default.default.isNaN(x);
14
34
  }
15
35
  function equal$4(x, y, tolerance) {
36
+ // Checking for undefined makes this function behave nicely
37
+ // with vectors of different lengths that are _.zip'd together
16
38
  if (x == null || y == null) {
17
39
  return x === y;
18
40
  }
41
+ // We check === here so that +/-Infinity comparisons work correctly
19
42
  if (x === y) {
20
43
  return true;
21
44
  }
@@ -24,16 +47,21 @@ function equal$4(x, y, tolerance) {
24
47
  }
25
48
  return Math.abs(x - y) < tolerance;
26
49
  }
27
- function sign$1(x, tolerance) {
50
+ function sign$1(x, tolerance) /* Should be: 0 | 1 | -1 */{
28
51
  return equal$4(x, 0, tolerance) ? 0 : Math.abs(x) / x;
29
52
  }
30
53
  function isInteger(num, tolerance) {
31
54
  return equal$4(Math.round(num), num, tolerance);
32
55
  }
56
+
57
+ // Round a number to a certain number of decimal places
33
58
  function round$2(num, precision) {
34
59
  const factor = Math.pow(10, precision);
35
60
  return Math.round(num * factor) / factor;
36
61
  }
62
+
63
+ // Round num to the nearest multiple of increment
64
+ // i.e. roundTo(83, 5) -> 85
37
65
  function roundTo$2(num, increment) {
38
66
  return Math.round(num / increment) * increment;
39
67
  }
@@ -43,7 +71,26 @@ function floorTo$2(num, increment) {
43
71
  function ceilTo$2(num, increment) {
44
72
  return Math.ceil(num / increment) * increment;
45
73
  }
46
- function toFraction(decimal, tolerance = EPSILON, maxDenominator = 1000) {
74
+
75
+ /**
76
+ * toFraction
77
+ *
78
+ * Returns a [numerator, denominator] array rational representation
79
+ * of `decimal`
80
+ *
81
+ * See http://en.wikipedia.org/wiki/Continued_fraction for implementation
82
+ * details
83
+ *
84
+ * toFraction(4/8) => [1, 2]
85
+ * toFraction(0.66) => [33, 50]
86
+ * toFraction(0.66, 0.01) => [2/3]
87
+ * toFraction(283 + 1/3) => [850, 3]
88
+ */
89
+ function toFraction(decimal) {
90
+ let tolerance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : EPSILON;
91
+ let maxDenominator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1000;
92
+ // Initialize everything to compute successive terms of
93
+ // continued-fraction approximations via recurrence relation
47
94
  let n = [1, 0];
48
95
  let d = [0, 1];
49
96
  let a = Math.floor(decimal);
@@ -57,6 +104,9 @@ function toFraction(decimal, tolerance = EPSILON, maxDenominator = 1000) {
57
104
  a = Math.floor(1 / rem);
58
105
  rem = 1 / rem - a;
59
106
  }
107
+
108
+ // We failed to find a nice rational representation,
109
+ // so return an irrational "fraction"
60
110
  return [decimal, 1];
61
111
  }
62
112
 
@@ -75,13 +125,21 @@ var number = /*#__PURE__*/Object.freeze({
75
125
  toFraction: toFraction
76
126
  });
77
127
 
128
+ /**
129
+ * Vector Utils
130
+ * A vector is an array of numbers e.g. [0, 3, 4].
131
+ */
132
+
78
133
  function arraySum(array) {
79
134
  return array.reduce((memo, arg) => memo + arg, 0);
80
135
  }
81
136
  function arrayProduct(array) {
82
137
  return array.reduce((memo, arg) => memo * arg, 1);
83
138
  }
84
- function zip(...arrays) {
139
+ function zip() {
140
+ for (var _len = arguments.length, arrays = new Array(_len), _key = 0; _key < _len; _key++) {
141
+ arrays[_key] = arguments[_key];
142
+ }
85
143
  const n = Math.min(...arrays.map(a => a.length));
86
144
  const results = [];
87
145
  for (let i = 0; i < n; i++) {
@@ -92,6 +150,16 @@ function zip(...arrays) {
92
150
  function map(pair, f) {
93
151
  return [f(pair[0], 0), f(pair[1], 1)];
94
152
  }
153
+
154
+ /**
155
+ * Checks if the given vector contains only numbers and, optionally, is of the
156
+ * right dimension (length).
157
+ *
158
+ * is([1, 2, 3]) -> true
159
+ * is([1, "Hello", 3]) -> false
160
+ * is([1, 2, 3], 1) -> false
161
+ */
162
+
95
163
  function is$1(vec, dimension) {
96
164
  if (!Array.isArray(vec)) {
97
165
  return false;
@@ -101,30 +169,46 @@ function is$1(vec, dimension) {
101
169
  }
102
170
  return vec.every(is$2);
103
171
  }
172
+
173
+ // Normalize to a unit vector
104
174
  function normalize(v) {
105
175
  return scale(v, 1 / length(v));
106
176
  }
177
+
178
+ // Length/magnitude of a vector
107
179
  function length(v) {
108
180
  return Math.sqrt(dot(v, v));
109
181
  }
182
+ // Dot product of two vectors
110
183
  function dot(a, b) {
111
184
  const zipped = zip(a, b);
112
185
  const multiplied = zipped.map(arrayProduct);
113
186
  return arraySum(multiplied);
114
187
  }
115
- function add$1(...vecs) {
116
- const zipped = zip(...vecs);
188
+
189
+ /* vector-add multiple [x, y] coords/vectors
190
+ *
191
+ * add([1, 2], [3, 4]) -> [4, 6]
192
+ */
193
+ function add$1() {
194
+ const zipped = zip(...arguments);
195
+ // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
117
196
  return zipped.map(arraySum);
118
197
  }
119
198
  function subtract(v1, v2) {
199
+ // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
120
200
  return zip(v1, v2).map(dim => dim[0] - dim[1]);
121
201
  }
122
202
  function negate(v) {
203
+ // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
123
204
  return v.map(x => {
124
205
  return -x;
125
206
  });
126
207
  }
208
+
209
+ // Scale a vector
127
210
  function scale(v1, scalar) {
211
+ // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
128
212
  return v1.map(x => {
129
213
  return x * scalar;
130
214
  });
@@ -133,6 +217,9 @@ function equal$3(v1, v2, tolerance) {
133
217
  return v1.length === v2.length && zip(v1, v2).every(pair => equal$4(pair[0], pair[1], tolerance));
134
218
  }
135
219
  function codirectional(v1, v2, tolerance) {
220
+ // The origin is trivially codirectional with all other vectors.
221
+ // This gives nice semantics for codirectionality between points when
222
+ // comparing their difference vectors.
136
223
  if (equal$4(length(v1), 0, tolerance) || equal$4(length(v2), 0, tolerance)) {
137
224
  return true;
138
225
  }
@@ -143,24 +230,48 @@ function codirectional(v1, v2, tolerance) {
143
230
  function collinear$1(v1, v2, tolerance) {
144
231
  return codirectional(v1, v2, tolerance) || codirectional(v1, negate(v2), tolerance);
145
232
  }
233
+
234
+ // TODO(jeremy) These coordinate conversion functions really only handle 2D points (ie. [number, number])
235
+
236
+ // Convert a cartesian coordinate into a radian polar coordinate
146
237
  function polarRadFromCart$1(v) {
147
238
  const radius = length(v);
148
239
  let theta = Math.atan2(v[1], v[0]);
240
+
241
+ // Convert angle range from [-pi, pi] to [0, 2pi]
149
242
  if (theta < 0) {
150
243
  theta += 2 * Math.PI;
151
244
  }
152
245
  return [radius, theta];
153
246
  }
154
- function polarDegFromCart$1(v) {
247
+
248
+ // Converts a cartesian coordinate into a degree polar coordinate
249
+ function polarDegFromCart$1(v) /* TODO: convert to tuple/Point */{
155
250
  const polar = polarRadFromCart$1(v);
156
251
  return [polar[0], polar[1] * 180 / Math.PI];
157
252
  }
158
- function cartFromPolarRad$1(radius, theta = 0) {
253
+
254
+ /* Convert a polar coordinate into a cartesian coordinate
255
+ *
256
+ * Examples:
257
+ * cartFromPolarRad(5, Math.PI)
258
+ */
259
+ function cartFromPolarRad$1(radius) /* TODO: convert to tuple/Point */{
260
+ let theta = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
159
261
  return [radius * Math.cos(theta), radius * Math.sin(theta)];
160
262
  }
161
- function cartFromPolarDeg$1(radius, theta = 0) {
263
+
264
+ /* Convert a polar coordinate into a cartesian coordinate
265
+ *
266
+ * Examples:
267
+ * cartFromPolarDeg(5, 30)
268
+ */
269
+ function cartFromPolarDeg$1(radius) {
270
+ let theta = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
162
271
  return cartFromPolarRad$1(radius, theta * Math.PI / 180);
163
272
  }
273
+
274
+ // Rotate vector
164
275
  function rotateRad$1(v, theta) {
165
276
  const polar = polarRadFromCart$1(v);
166
277
  const angle = polar[1] + theta;
@@ -171,26 +282,39 @@ function rotateDeg$1(v, theta) {
171
282
  const angle = polar[1] + theta;
172
283
  return cartFromPolarDeg$1(polar[0], angle);
173
284
  }
285
+
286
+ // Angle between two vectors
174
287
  function angleRad(v1, v2) {
175
288
  return Math.acos(dot(v1, v2) / (length(v1) * length(v2)));
176
289
  }
177
290
  function angleDeg(v1, v2) {
178
291
  return angleRad(v1, v2) * 180 / Math.PI;
179
292
  }
293
+
294
+ // Vector projection of v1 onto v2
180
295
  function projection(v1, v2) {
181
296
  const scalar = dot(v1, v2) / dot(v2, v2);
182
297
  return scale(v2, scalar);
183
298
  }
299
+
300
+ // Round each number to a certain number of decimal places
184
301
  function round$1(vec, precision) {
302
+ // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
185
303
  return vec.map((elem, i) => round$2(elem, precision[i] || precision));
186
304
  }
305
+
306
+ // Round each number to the nearest increment
307
+
187
308
  function roundTo$1(vec, increment) {
309
+ // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
188
310
  return vec.map((elem, i) => roundTo$2(elem, increment[i] || increment));
189
311
  }
190
312
  function floorTo$1(vec, increment) {
313
+ // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
191
314
  return vec.map((elem, i) => floorTo$2(elem, increment[i] || increment));
192
315
  }
193
316
  function ceilTo$1(vec, increment) {
317
+ // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
194
318
  return vec.map((elem, i) => ceilTo$2(elem, increment[i] || increment));
195
319
  }
196
320
 
@@ -224,6 +348,15 @@ var vector$1 = /*#__PURE__*/Object.freeze({
224
348
  zip: zip
225
349
  });
226
350
 
351
+ /**
352
+ * Point Utils
353
+ * A point is an array of two numbers e.g. [0, 0].
354
+ */
355
+
356
+
357
+ // A point, in 2D, 3D, or nD space.
358
+
359
+ // Rotate point (around origin unless a center is specified)
227
360
  function rotateRad(point, theta, center) {
228
361
  if (center === undefined) {
229
362
  return rotateRad$1(point, theta);
@@ -238,9 +371,13 @@ function rotateDeg(point, theta, center) {
238
371
  return add$1(center, rotateDeg$1(subtract(point, center), theta));
239
372
  }
240
373
  }
374
+
375
+ // Distance between two points
241
376
  function distanceToPoint$1(point1, point2) {
242
377
  return length(subtract(point1, point2));
243
378
  }
379
+
380
+ // Distance between point and line
244
381
  function distanceToLine(point, line) {
245
382
  const lv = subtract(line[1], line[0]);
246
383
  const pv = subtract(point, line[0]);
@@ -248,6 +385,8 @@ function distanceToLine(point, line) {
248
385
  const distancePv = subtract(projectedPv, pv);
249
386
  return length(distancePv);
250
387
  }
388
+
389
+ // Reflect point over line
251
390
  function reflectOverLine(point, line) {
252
391
  const lv = subtract(line[1], line[0]);
253
392
  const pv = subtract(point, line[0]);
@@ -255,7 +394,17 @@ function reflectOverLine(point, line) {
255
394
  const reflectedPv = subtract(scale(projectedPv, 2), pv);
256
395
  return add$1(line[0], reflectedPv);
257
396
  }
258
- function compare(point1, point2, equalityTolerance) {
397
+
398
+ /**
399
+ * Compares two points, returning -1, 0, or 1, for use with
400
+ * Array.prototype.sort
401
+ *
402
+ * Note: This technically doesn't satisfy the total-ordering
403
+ * requirements of Array.prototype.sort unless equalityTolerance
404
+ * is 0. In some cases very close points that compare within a
405
+ * few equalityTolerances could appear in the wrong order.
406
+ */
407
+ function compare(point1, point2, equalityTolerance) /* TODO: convert to -1 | 0 | 1 type */{
259
408
  if (point1.length !== point2.length) {
260
409
  return point1.length - point2.length;
261
410
  }
@@ -266,15 +415,23 @@ function compare(point1, point2, equalityTolerance) {
266
415
  }
267
416
  return 0;
268
417
  }
418
+
419
+ // Check if a value is a point
269
420
  const is = is$1;
421
+
422
+ // Add and subtract vector(s)
270
423
  const addVector = add$1;
271
424
  const addVectors = add$1;
272
425
  const subtractVector = subtract;
273
426
  const equal$2 = equal$3;
427
+
428
+ // Convert from cartesian to polar and back
274
429
  const polarRadFromCart = polarRadFromCart$1;
275
430
  const polarDegFromCart = polarDegFromCart$1;
276
431
  const cartFromPolarRad = cartFromPolarRad$1;
277
432
  const cartFromPolarDeg = cartFromPolarDeg$1;
433
+
434
+ // Rounding
278
435
  const round = round$1;
279
436
  const roundTo = roundTo$1;
280
437
  const floorTo = floorTo$1;
@@ -303,6 +460,11 @@ var point = /*#__PURE__*/Object.freeze({
303
460
  subtractVector: subtractVector
304
461
  });
305
462
 
463
+ /**
464
+ * Line Utils
465
+ * A line is an array of two points e.g. [[-5, 0], [5, 0]].
466
+ */
467
+
306
468
  function distanceToPoint(line, point$1) {
307
469
  return distanceToLine(point$1, line);
308
470
  }
@@ -313,14 +475,20 @@ function midpoint(line) {
313
475
  return [(line[0][0] + line[1][0]) / 2, (line[0][1] + line[1][1]) / 2];
314
476
  }
315
477
  function equal$1(line1, line2, tolerance) {
478
+ // TODO: A nicer implementation might just check collinearity of
479
+ // vectors using underscore magick
480
+ // Compare the directions of the lines
316
481
  const v1 = subtract(line1[1], line1[0]);
317
482
  const v2 = subtract(line2[1], line2[0]);
318
483
  if (!collinear$1(v1, v2, tolerance)) {
319
484
  return false;
320
485
  }
486
+ // If the start point is the same for the two lines, then they are the same
321
487
  if (equal$2(line1[0], line2[0])) {
322
488
  return true;
323
489
  }
490
+ // Make sure that the direction to get from line1 to
491
+ // line2 is the same as the direction of the lines
324
492
  const line1ToLine2Vector = subtract(line2[0], line1[0]);
325
493
  return collinear$1(v1, line1ToLine2Vector, tolerance);
326
494
  }
@@ -333,7 +501,15 @@ var line = /*#__PURE__*/Object.freeze({
333
501
  reflectPoint: reflectPoint
334
502
  });
335
503
 
504
+ /**
505
+ * Ray Utils
506
+ * A ray (→) is an array of an endpoint and another point along the ray.
507
+ * For example, [[0, 0], [1, 0]] is the ray starting at the origin and
508
+ * traveling along the positive x-axis.
509
+ */
510
+
336
511
  function equal(ray1, ray2, tolerance) {
512
+ // Compare the directions of the rays
337
513
  const v1 = subtract(ray1[1], ray1[0]);
338
514
  const v2 = subtract(ray2[1], ray2[0]);
339
515
  const sameOrigin = equal$2(ray1[0], ray2[0]);
@@ -347,9 +523,11 @@ var ray = /*#__PURE__*/Object.freeze({
347
523
  });
348
524
 
349
525
  const KhanMath = {
526
+ // Simplify formulas before display
350
527
  cleanMath: function (expr) {
351
528
  return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
352
529
  },
530
+ // Bound a number by 1e-6 and 1e20 to avoid exponents after toString
353
531
  bound: function (num) {
354
532
  if (num === 0) {
355
533
  return num;
@@ -367,12 +545,17 @@ const KhanMath = {
367
545
  },
368
546
  getGCD: function (a, b) {
369
547
  if (arguments.length > 2) {
548
+ // TODO(kevinb): rewrite using rest args instead of arguments
549
+ // eslint-disable-next-line prefer-rest-params
370
550
  const rest = [].slice.call(arguments, 1);
551
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
371
552
  return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
372
553
  }
373
554
  let mod;
374
555
  a = Math.abs(a);
375
556
  b = Math.abs(b);
557
+
558
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
376
559
  while (b) {
377
560
  mod = a % b;
378
561
  a = b;
@@ -382,7 +565,10 @@ const KhanMath = {
382
565
  },
383
566
  getLCM: function (a, b) {
384
567
  if (arguments.length > 2) {
568
+ // TODO(kevinb): rewrite using rest args instead of arguments
569
+ // eslint-disable-next-line prefer-rest-params
385
570
  const rest = [].slice.call(arguments, 1);
571
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
386
572
  return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
387
573
  }
388
574
  return Math.abs(a * b) / KhanMath.getGCD(a, b);
@@ -393,7 +579,8 @@ const KhanMath = {
393
579
  return false;
394
580
  }
395
581
  if (n < 101) {
396
- return !!$.grep(KhanMath.primes, function (p, i) {
582
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
583
+ return !!$__default.default.grep(KhanMath.primes, function (p, i) {
397
584
  return Math.abs(p - n) <= 0.5;
398
585
  }).length;
399
586
  }
@@ -407,6 +594,7 @@ const KhanMath = {
407
594
  }
408
595
  return true;
409
596
  },
597
+ // @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
410
598
  getPrimeFactorization: function (number) {
411
599
  if (number === 1) {
412
600
  return [];
@@ -417,24 +605,39 @@ const KhanMath = {
417
605
  const maxf = Math.sqrt(number);
418
606
  for (let f = 2; f <= maxf; f++) {
419
607
  if (number % f === 0) {
420
- return $.merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
608
+ return $__default.default.merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
421
609
  }
422
610
  }
423
611
  },
612
+ // Round a number to the nearest increment
613
+ // E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
614
+ // num = 45, return 60.
424
615
  roundToNearest: function (increment, num) {
425
616
  return Math.round(num / increment) * increment;
426
617
  },
618
+ // Round a number to a certain number of decimal places
427
619
  roundTo: function (precision, num) {
428
620
  const factor = Math.pow(10, precision).toFixed(5);
621
+ // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'number'. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
429
622
  return Math.round((num * factor).toFixed(5)) / factor;
430
623
  },
624
+ /**
625
+ * Return a string of num rounded to a fixed precision decimal places,
626
+ * with an approx symbol if num had to be rounded, and trailing 0s
627
+ */
431
628
  toFixedApprox: function (num, precision) {
629
+ // TODO(aria): Make this locale-dependent
432
630
  const fixedStr = num.toFixed(precision);
433
631
  if (equal$4(+fixedStr, num)) {
434
632
  return fixedStr;
435
633
  }
436
634
  return "\\approx " + fixedStr;
437
635
  },
636
+ /**
637
+ * Return a string of num rounded to precision decimal places, with an
638
+ * approx symbol if num had to be rounded, but no trailing 0s if it was
639
+ * not rounded.
640
+ */
438
641
  roundToApprox: function (num, precision) {
439
642
  const fixed = KhanMath.roundTo(precision, num);
440
643
  if (equal$4(fixed, num)) {
@@ -442,6 +645,11 @@ const KhanMath = {
442
645
  }
443
646
  return KhanMath.toFixedApprox(num, precision);
444
647
  },
648
+ // toFraction(4/8) => [1, 2]
649
+ // toFraction(0.666) => [333, 500]
650
+ // toFraction(0.666, 0.001) => [2, 3]
651
+ //
652
+ // tolerance can't be bigger than 1, sorry
445
653
  toFraction: function (decimal, tolerance) {
446
654
  if (tolerance == null) {
447
655
  tolerance = Math.pow(2, -46);
@@ -462,6 +670,8 @@ const KhanMath = {
462
670
  let hiD = 1;
463
671
  let midN = 1;
464
672
  let midD = 2;
673
+
674
+ // eslint-disable-next-line no-constant-condition
465
675
  while (true) {
466
676
  if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
467
677
  return [midN, midD];
@@ -477,8 +687,11 @@ const KhanMath = {
477
687
  midD = loD + hiD;
478
688
  }
479
689
  },
690
+ // Returns the format (string) of a given numeric string
691
+ // Note: purposively more inclusive than answer-types' predicate.forms
692
+ // That is, it is not necessarily true that interpreted input are numeric
480
693
  getNumericFormat: function (text) {
481
- text = $.trim(text);
694
+ text = $__default.default.trim(text);
482
695
  text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
483
696
  if (text.match(/^[+-]?\d+$/)) {
484
697
  return "integer";
@@ -498,12 +711,13 @@ const KhanMath = {
498
711
  }
499
712
  return null;
500
713
  },
714
+ // Returns a string of the number in a specified format
501
715
  toNumericString: function (number$1, format) {
502
716
  if (number$1 == null) {
503
717
  return "";
504
718
  }
505
719
  if (number$1 === 0) {
506
- return "0";
720
+ return "0"; // otherwise it might end up as 0% or 0pi
507
721
  }
508
722
  if (format === "percent") {
509
723
  return number$1 * 100 + "%";
@@ -518,21 +732,25 @@ const KhanMath = {
518
732
  return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
519
733
  }
520
734
  }
521
- if (_(["proper", "improper", "mixed", "fraction"]).contains(format)) {
735
+ if (___default.default(["proper", "improper", "mixed", "fraction"]).contains(format)) {
522
736
  const fraction = toFraction(number$1);
523
737
  const numerator = Math.abs(fraction[0]);
524
738
  const denominator = fraction[1];
525
739
  const sign = number$1 < 0 ? "-" : "";
526
740
  if (denominator === 1) {
527
- return sign + numerator;
741
+ return sign + numerator; // for integers, irrational, d > 1000
528
742
  }
529
743
  if (format === "mixed") {
530
744
  const modulus = numerator % denominator;
531
745
  const integer = (numerator - modulus) / denominator;
532
- return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
533
- }
746
+ return sign + (
747
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
748
+ integer ? integer + " " : "") + modulus + "/" + denominator;
749
+ } // otherwise proper, improper, or fraction
534
750
  return sign + numerator + "/" + denominator;
535
751
  }
752
+
753
+ // otherwise (decimal, float, long long)
536
754
  return String(number$1);
537
755
  }
538
756
  };
@@ -543,24 +761,40 @@ function add(a, b) {
543
761
  return a + b;
544
762
  }
545
763
 
764
+ /**
765
+ * A collection of geomtry-related utility functions
766
+ */
767
+
768
+
769
+ // This should really be a readonly tuple of [number, number]
770
+
771
+ // Given a number, return whether it is positive (1), negative (-1), or zero (0)
546
772
  function sign(val) {
547
- if (approximateEqual(val, 0)) {
773
+ if (perseusCore.approximateEqual(val, 0)) {
548
774
  return 0;
549
775
  }
550
776
  return val > 0 ? 1 : -1;
551
777
  }
778
+
779
+ // Determine whether three points are collinear (0), for a clockwise turn (negative),
780
+ // or counterclockwise turn (positive)
552
781
  function ccw(a, b, c) {
553
782
  return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
554
783
  }
555
784
  function collinear(a, b, c) {
556
- return approximateEqual(ccw(a, b, c), 0);
785
+ return perseusCore.approximateEqual(ccw(a, b, c), 0);
557
786
  }
787
+
788
+ // Given rect bounding points A and B, whether point C is inside the rect
558
789
  function pointInRect(a, b, c) {
559
790
  return c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) && c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]);
560
791
  }
792
+
793
+ // Whether line segment AB intersects line segment CD
794
+ // http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
561
795
  function intersects(ab, cd) {
562
796
  const triplets = [[ab[0], ab[1], cd[0]], [ab[0], ab[1], cd[1]], [cd[0], cd[1], ab[0]], [cd[0], cd[1], ab[1]]];
563
- const orientations = _.map(triplets, function (triplet) {
797
+ const orientations = ___default.default.map(triplets, function (triplet) {
564
798
  return sign(ccw(...triplet));
565
799
  });
566
800
  if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
@@ -573,14 +807,21 @@ function intersects(ab, cd) {
573
807
  }
574
808
  return false;
575
809
  }
810
+
811
+ // Whether any two sides of a polygon intersect each other
576
812
  function polygonSidesIntersect(vertices) {
577
813
  for (let i = 0; i < vertices.length; i++) {
578
814
  for (let k = i + 1; k < vertices.length; k++) {
815
+ // If any two vertices are the same point, sides overlap
579
816
  if (equal$2(vertices[i], vertices[k])) {
580
817
  return true;
581
818
  }
819
+
820
+ // Find the other end of the sides starting at vertices i and k
582
821
  const iNext = (i + 1) % vertices.length;
583
822
  const kNext = (k + 1) % vertices.length;
823
+
824
+ // Adjacent sides always intersect (at the vertex); skip those
584
825
  if (iNext === k || kNext === i) {
585
826
  continue;
586
827
  }
@@ -594,16 +835,20 @@ function polygonSidesIntersect(vertices) {
594
835
  return false;
595
836
  }
596
837
  function vector(a, b) {
597
- return _.map(_.zip(a, b), function (pair) {
838
+ return ___default.default.map(___default.default.zip(a, b), function (pair) {
598
839
  return pair[0] - pair[1];
599
840
  });
600
841
  }
601
842
  function reverseVector(vector) {
602
843
  return [-vector[0], -vector[1]];
603
844
  }
845
+
846
+ // Returns whether connecting the given sequence of `points` forms a clockwise
847
+ // path (assuming a closed loop, where the last point connects back to the
848
+ // first).
604
849
  function clockwise(points) {
605
- const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
606
- const areas = _.map(segments, function (segment) {
850
+ const segments = ___default.default.zip(points, points.slice(1).concat(points.slice(0, 1)));
851
+ const areas = ___default.default.map(segments, function (segment) {
607
852
  const p1 = segment[0];
608
853
  const p2 = segment[1];
609
854
  return (p2[0] - p1[0]) * (p2[1] + p1[1]);
@@ -611,36 +856,44 @@ function clockwise(points) {
611
856
  return sum(areas) > 0;
612
857
  }
613
858
  function magnitude(v) {
614
- return Math.sqrt(_.reduce(v, function (memo, el) {
859
+ return Math.sqrt(___default.default.reduce(v, function (memo, el) {
860
+ // @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
615
861
  return memo + Math.pow(el, 2);
616
862
  }, 0));
617
863
  }
618
864
  function dotProduct(a, b) {
619
- return _.reduce(_.zip(a, b), function (memo, pair) {
865
+ return ___default.default.reduce(___default.default.zip(a, b), function (memo, pair) {
620
866
  return memo + pair[0] * pair[1];
621
867
  }, 0);
622
868
  }
623
869
  function sideLengths(coords) {
624
- const segments = _.zip(coords, rotate(coords));
870
+ const segments = ___default.default.zip(coords, rotate(coords));
625
871
  return segments.map(function (segment) {
872
+ // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
626
873
  return magnitude(vector(...segment));
627
874
  });
628
875
  }
876
+
877
+ // Based on http://math.stackexchange.com/a/151149
629
878
  function angleMeasures(coords) {
630
- const triplets = _.zip(rotate(coords, -1), coords, rotate(coords, 1));
631
- const offsets = _.map(triplets, function (triplet) {
879
+ const triplets = ___default.default.zip(rotate(coords, -1), coords, rotate(coords, 1));
880
+ const offsets = ___default.default.map(triplets, function (triplet) {
632
881
  const p = vector(triplet[1], triplet[0]);
633
882
  const q = vector(triplet[2], triplet[1]);
883
+ // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'Coord'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
634
884
  const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
885
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
635
886
  return sign(ccw(...triplet)) > 0 ? raw : -raw;
636
887
  });
637
- const sum = _.reduce(offsets, function (memo, arg) {
888
+ const sum = ___default.default.reduce(offsets, function (memo, arg) {
638
889
  return memo + arg;
639
890
  }, 0);
640
- return _.map(offsets, function (offset) {
891
+ return ___default.default.map(offsets, function (offset) {
641
892
  return sum > 0 ? Math.PI - offset : Math.PI + offset;
642
893
  });
643
894
  }
895
+
896
+ // Whether two polygons are similar (or if specified, congruent)
644
897
  function similar(coords1, coords2, tolerance) {
645
898
  if (coords1.length !== coords2.length) {
646
899
  return false;
@@ -653,22 +906,30 @@ function similar(coords1, coords2, tolerance) {
653
906
  for (let i = 0; i < 2 * n; i++) {
654
907
  let angles = angles2.slice();
655
908
  let sides = sides2.slice();
909
+
910
+ // Reverse angles and sides to allow matching reflected polygons
656
911
  if (i >= n) {
657
912
  angles.reverse();
658
913
  sides.reverse();
914
+ // Since sides are calculated from two coordinates,
915
+ // simply reversing results in an off by one error
916
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
659
917
  sides = rotate(sides, 1);
660
918
  }
919
+
920
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
661
921
  angles = rotate(angles, i);
922
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
662
923
  sides = rotate(sides, i);
663
- if (approximateDeepEqual(angles1, angles)) {
664
- const sidePairs = _.zip(sides1, sides);
665
- const factors = _.map(sidePairs, function (pair) {
924
+ if (perseusCore.approximateDeepEqual(angles1, angles)) {
925
+ const sidePairs = ___default.default.zip(sides1, sides);
926
+ const factors = ___default.default.map(sidePairs, function (pair) {
666
927
  return pair[0] / pair[1];
667
928
  });
668
- const same = _.all(factors, function (factor) {
669
- return approximateEqual(factors[0], factor);
929
+ const same = ___default.default.all(factors, function (factor) {
930
+ return perseusCore.approximateEqual(factors[0], factor);
670
931
  });
671
- const congruentEnough = _.all(sidePairs, function (pair) {
932
+ const congruentEnough = ___default.default.all(sidePairs, function (pair) {
672
933
  return equal$4(pair[0], pair[1], tolerance);
673
934
  });
674
935
  if (same && congruentEnough) {
@@ -678,21 +939,32 @@ function similar(coords1, coords2, tolerance) {
678
939
  }
679
940
  return false;
680
941
  }
942
+
943
+ // Given triangle with sides ABC return angle opposite side C in degrees
681
944
  function lawOfCosines(a, b, c) {
682
945
  return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
683
946
  }
684
- function canonicalSineCoefficients([amplitude, angularFrequency, phase, verticalOffset]) {
947
+ function canonicalSineCoefficients(_ref) {
948
+ let [amplitude, angularFrequency, phase, verticalOffset] = _ref;
949
+ // For a curve of the form f(x) = a * Sin(b * x - c) + d,
950
+ // this function ensures that a, b > 0, and c is its
951
+ // smallest possible positive value.
952
+
953
+ // Guarantee a > 0
685
954
  if (amplitude < 0) {
686
955
  amplitude *= -1;
687
956
  angularFrequency *= -1;
688
957
  phase *= -1;
689
958
  }
690
959
  const period = 2 * Math.PI;
960
+ // Guarantee b > 0
691
961
  if (angularFrequency < 0) {
692
962
  angularFrequency *= -1;
693
963
  phase *= -1;
694
964
  phase += period / 2;
695
965
  }
966
+
967
+ // Guarantee c is smallest possible positive value
696
968
  while (phase > 0) {
697
969
  phase -= period;
698
970
  }
@@ -701,19 +973,26 @@ function canonicalSineCoefficients([amplitude, angularFrequency, phase, vertical
701
973
  }
702
974
  return [amplitude, angularFrequency, phase, verticalOffset];
703
975
  }
976
+
977
+ // e.g. rotate([1, 2, 3]) -> [2, 3, 1]
704
978
  function rotate(array, n) {
705
979
  n = typeof n === "undefined" ? 1 : n % array.length;
706
980
  return array.slice(n).concat(array.slice(0, n));
707
981
  }
708
982
  function getLineEquation(first, second) {
709
- if (approximateEqual(first[0], second[0])) {
983
+ if (perseusCore.approximateEqual(first[0], second[0])) {
710
984
  return "x = " + first[0].toFixed(3);
711
985
  }
712
986
  const m = (second[1] - first[1]) / (second[0] - first[0]);
713
987
  const b = first[1] - m * first[0];
714
988
  return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
715
989
  }
716
- function getLineIntersection(firstPoints, secondPoints) {
990
+
991
+ // Stolen from the wikipedia article
992
+ // http://en.wikipedia.org/wiki/Line-line_intersection
993
+ function getLineIntersection(
994
+ // TODO(LP-10725): update these to be 2-tuples
995
+ firstPoints, secondPoints) {
717
996
  const x1 = firstPoints[0][0];
718
997
  const y1 = firstPoints[0][1];
719
998
  const x2 = firstPoints[1][0];
@@ -724,6 +1003,7 @@ function getLineIntersection(firstPoints, secondPoints) {
724
1003
  const y4 = secondPoints[1][1];
725
1004
  const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
726
1005
  if (Math.abs(determinant) < 1e-9) {
1006
+ // Lines are parallel
727
1007
  return null;
728
1008
  }
729
1009
  const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
@@ -760,16 +1040,25 @@ var geometry = /*#__PURE__*/Object.freeze({
760
1040
  vector: vector
761
1041
  });
762
1042
 
1043
+ // This file contains helper functions for working with angles.
1044
+
763
1045
  function convertDegreesToRadians(degrees) {
764
1046
  return degrees / 180 * Math.PI;
765
1047
  }
766
1048
  function convertRadiansToDegrees(radians) {
767
1049
  const degree = radians / Math.PI * 180;
1050
+ // Account for floating point errors.
768
1051
  return Number(degree.toPrecision(15));
769
1052
  }
770
- function calculateAngleInDegrees([x, y]) {
1053
+
1054
+ // Returns a value between -180 and 180, inclusive. The angle is measured
1055
+ // between the positive x-axis and the given vector.
1056
+ function calculateAngleInDegrees(_ref) {
1057
+ let [x, y] = _ref;
771
1058
  return Math.atan2(y, x) * 180 / Math.PI;
772
1059
  }
1060
+
1061
+ // Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
773
1062
  function polar(r, th) {
774
1063
  if (typeof r === "number") {
775
1064
  r = [r, r];
@@ -777,19 +1066,37 @@ function polar(r, th) {
777
1066
  th = th * Math.PI / 180;
778
1067
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
779
1068
  }
1069
+ // This function calculates the angle between two points and an optional vertex.
1070
+ // If the vertex is not provided, the angle is measured between the two points.
1071
+ // This does not account for reflex angles or clockwise position.
780
1072
  const getAngleFromVertex = (point, vertex) => {
781
1073
  const x = point[0] - vertex[0];
782
1074
  const y = point[1] - vertex[1];
1075
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
783
1076
  if (!x && !y) {
784
1077
  return 0;
785
1078
  }
786
1079
  return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
787
1080
  };
788
- const getClockwiseAngle = (coords, allowReflexAngles = false) => {
1081
+
1082
+ // This function calculates the clockwise angle between three points,
1083
+ // and is used to generate the labels and equation strings of the
1084
+ // current angle for the interactive graph.
1085
+ const getClockwiseAngle = function (coords) {
1086
+ let allowReflexAngles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
789
1087
  const coordsCopy = [...coords];
1088
+ // The coords are saved as [point1, vertex, point2] in the interactive graph
790
1089
  const areClockwise = clockwise([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
1090
+
1091
+ // We may need to reverse the coordinates if we allow
1092
+ // reflex angles and the points are not in clockwise order.
791
1093
  const shouldReverseCoords = !areClockwise || allowReflexAngles;
1094
+
1095
+ // Reverse the coordinates accordingly to ensure the angle is calculated correctly
792
1096
  const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
1097
+
1098
+ // Calculate the angles between the two points and get the difference
1099
+ // between the two angles to get the clockwise angle.
793
1100
  const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
794
1101
  const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
795
1102
  const angle = (startAngle + 360 - endAngle) % 360;
@@ -806,21 +1113,30 @@ var angles = /*#__PURE__*/Object.freeze({
806
1113
  polar: polar
807
1114
  });
808
1115
 
1116
+ // TODO: there's another, very similar getSinusoidCoefficients function
1117
+ // they should probably be merged
809
1118
  function getSinusoidCoefficients(coords) {
1119
+ // It's assumed that p1 is the root and p2 is the first peak
810
1120
  const p1 = coords[0];
811
1121
  const p2 = coords[1];
1122
+
1123
+ // Resulting coefficients are canonical for this sine curve
812
1124
  const amplitude = p2[1] - p1[1];
813
1125
  const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
814
1126
  const phase = p1[0] * angularFrequency;
815
1127
  const verticalOffset = p1[1];
816
1128
  return [amplitude, angularFrequency, phase, verticalOffset];
817
1129
  }
1130
+ // TODO: there's another, very similar getQuadraticCoefficients function
1131
+ // they should probably be merged
818
1132
  function getQuadraticCoefficients(coords) {
819
1133
  const p1 = coords[0];
820
1134
  const p2 = coords[1];
821
1135
  const p3 = coords[2];
822
1136
  const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
823
1137
  if (denom === 0) {
1138
+ // Many of the callers assume that the return value is always defined.
1139
+ // @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
824
1140
  return;
825
1141
  }
826
1142
  const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
@@ -835,5 +1151,15 @@ var coefficients = /*#__PURE__*/Object.freeze({
835
1151
  getSinusoidCoefficients: getSinusoidCoefficients
836
1152
  });
837
1153
 
838
- export { KhanMath, angles, coefficients, geometry, libVersion, line, number, point, ray, sum, vector$1 as vector };
1154
+ exports.KhanMath = KhanMath;
1155
+ exports.angles = angles;
1156
+ exports.coefficients = coefficients;
1157
+ exports.geometry = geometry;
1158
+ exports.libVersion = libVersion;
1159
+ exports.line = line;
1160
+ exports.number = number;
1161
+ exports.point = point;
1162
+ exports.ray = ray;
1163
+ exports.sum = sum;
1164
+ exports.vector = vector$1;
839
1165
  //# sourceMappingURL=index.js.map