├── .arcconfig ├── .arclint ├── .flowconfig ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __tests__ ├── line.js ├── number.js ├── point.js └── vector.js ├── babel.config.json ├── build └── kmath.js ├── flow-typed └── npm │ └── jest_v27.x.x.js ├── index.js ├── line.js ├── lint_blacklist.txt ├── logo.js ├── logo.svg ├── number.js ├── package.json ├── point.js ├── ray.js ├── vector.js ├── webpack.config.js └── yarn.lock /.arcconfig: -------------------------------------------------------------------------------- 1 | { 2 | "conduit_uri": "https://phabricator.khanacademy.org/", 3 | "lint.engine": "ArcanistConfigurationDrivenLintEngine" 4 | } 5 | -------------------------------------------------------------------------------- /.arclint: -------------------------------------------------------------------------------- 1 | { 2 | "linters": { 3 | "khan-linter": { 4 | "type": "script-and-regex", 5 | "script-and-regex.script": "ka-lint --always-exit-0 --blacklist=yes --propose-arc-fixes", 6 | "script-and-regex.regex": "\/^((?P[^:]*):(?P\\d+):((?P\\d+):)? (?P((?PE)|(?PW))\\S+) (?P[^\\x00]*)(\\x00(?P[^\\x00]*)\\x00(?P[^\\x00]*)\\x00)?)|(?PSKIPPING.*)$\/m" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/resolve/test/* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | [lints] 9 | 10 | [options] 11 | 12 | [strict] 13 | 14 | [version] 15 | 0.167.1 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .arcconfig 2 | 3 | flow-typed/npm/ 4 | .vscode/ 5 | build/ 6 | coverage/ 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "bracketSpacing": false 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // We use Flow (this disables TypeScript support) 3 | "javascript.validate.enable": false 4 | } 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to kmath 2 | 3 | ## CLA 4 | 5 | In order to contribute to kmath, you must first sign the CLA, found at www.khanacademy.org/r/cla 6 | 7 | ## License 8 | 9 | kmath is licenced under the [MIT License](http://opensource.org/licenses/MIT). 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Khan Academy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 🚚 _**As of April 2022 this repo is no longer the home of `kmath`. The contents and development activity have moved into the Perseus repo [here](https://github.com/Khan/perseus/tree/main/packages/kmath).**_ 3 | 4 | # kmath 5 | 6 | Javascript Numeric Math Utilities 7 | 8 | ## Overview 9 | 10 | kmath is a collection of Javascript utility functions for performing 11 | numeric (rather than algebraic) math that isn't built into Javascript, 12 | especially geometric calculations. 13 | 14 | For example, some computations are easy to express using vectors, but 15 | difficult to express in terms of raw real number variables. kmath lets 16 | you write vector-, point-, line-, or ray-based math naturally, and 17 | also has some real number utility methods that might be useful if 18 | you're writing a math-heavy Javascript application. 19 | 20 | kmath emphasizes simplicity and interoperability in the interfaces 21 | provided. All kmath interfaces use standard Javascript types, usually 22 | numbers, arrays of numbers, or arrays of arrays of numbers. This means 23 | interoping with other systems (other libraries, or sending kmath types 24 | over json) is easy, but kmath doesn't have it's own object-oriented 25 | types, which may be inconvenient (for example, in debugging, you'll 26 | just see Arrays rather than, for example, Vectors). 27 | 28 | kmath also focuses on being a high quality library of a few functions, 29 | rather than a large library of many functions which are less unified. 30 | 31 | kmath emphasizes simplicity in design over performance, when those two 32 | are at odds. If you are writing code in an inner loop in an allocation- 33 | sensitive environment, kmath might not be for you. Each method is 34 | pure and allocates a new array for the return value. 35 | 36 | ## Getting started 37 | 38 | After cloning or downloading kmath, you can install it by running 39 | `npm install` or `make install`. 40 | 41 | To play around with the available interfaces, you can load kmath 42 | into a Node repl: 43 | 44 | $ node 45 | > var kmath = require("kmath"); 46 | > kmath.vector.add([1, 2], [3, 4]) 47 | [4, 6] 48 | 49 | ## Usage overview 50 | 51 | kmath has 5 basic types: 52 | 53 | - number 54 | - vector 55 | - point 56 | - ray 57 | - line 58 | 59 | Each has its own representation: 60 | 61 | - number: a js number (i.e. `5`) 62 | - vector: a js array of numbers (i.e. `[5, 6]`) 63 | - point: a js array of numbers (i.e. `[5, 6]`) 64 | - ray: a js array of two points (i.e. `[[5, 6], [1, 2]]`) 65 | - line: a js array of two points (i.e. `[[5, 6], [1, 2]]`) 66 | 67 | kmath functions usually take an argument of the corresponding type as 68 | the first parameter, and other parameters afterwards. For example, to 69 | rotate the point `[1, 1]` by 90 degrees around `[0, 0]`, one might use: 70 | 71 | kmath.point.rotateDeg([1, 1], 90, [0, 0]) 72 | 73 | Documentation for specific functions for each type is provided below. 74 | 75 | ## kmath.number 76 | 77 | #### `number.DEFAULT_TOLERANCE === 1e-9` 78 | 79 | The default tolerance to kmath functions. 80 | 81 | #### `number.EPSILON === Math.pow(2, -42)` 82 | 83 | A small number. Not machine epsilon, but a reasonable amount of error 84 | more than generally accrued by doing repeated floating point math. 85 | 86 | #### `number.is(maybeANumber)` 87 | 88 | Returns true if the argument is a javascript number. 89 | 90 | #### `number.equal(number1, number2, [tolerance])` 91 | 92 | Compares whether number1 and number2 are equal to each other, within 93 | a difference of tolerance, which defaults to `number.DEFAULT_TOLERANCE`. 94 | 95 | #### `number.sign(aNumber, [tolerance])` 96 | 97 | Returns 0 if the number is equal to 0 within `tolerance`, or -1 if the 98 | number is less than 0, or 1 if the number is greater than 0. 99 | 100 | #### `number.isInteger(aNumber, tolerance)` 101 | 102 | Returns true if `aNumber` is within `tolerance` difference of an integer. 103 | `tolerance` defaults to `number.DEFAULT_TOLERANCE`. 104 | 105 | #### `number.round(aNumber, precision)` 106 | 107 | Rounds `aNumber` to `precision` decimal places. 108 | 109 | #### `number.roundTo(aNumber, increment)` 110 | 111 | Rounds `aNumber` to the nearest `increment`. 112 | 113 | For example, `number.roundTo(1.4, 0.5)` would return `1.5` 114 | 115 | #### `number.floorTo(aNumber, increment)` 116 | 117 | Returns the nearest multiple of `increment` that is no greater than 118 | `aNumber`. 119 | 120 | #### `number.ceilTo(aNumber, increment)` 121 | 122 | Returns the nearest multiple of `increment` that is no smaller than 123 | `aNumber`. 124 | 125 | #### `number.toFraction(decimal, [tolerance], [maxDenominator])` 126 | 127 | Returns an array containing two elements, `[n, d]`, a numerator and 128 | denominator representing a fraction `n/d` that is within `tolerance` 129 | of `decimal`. 130 | 131 | If no fraction with a denominator less than or equal to `maxDenominator` 132 | is found, `[decimal, 1]` is returned. 133 | 134 | `tolerance` defaults to `number.EPSILON`. `maxDenominator` defaults to 135 | `1000`. 136 | 137 | ## kmath.vector 138 | 139 | #### `vector.is(maybeAVector, [dimension])` 140 | 141 | Returns true if `maybeAVector` is an array of numbers. If `dimension` is specified, 142 | only returns true if `maybeAVector` is a vector with dimension `dimension`. 143 | 144 | #### `vector.equal(v1, v2, [tolerance])` 145 | 146 | Returns true if `v1` and `v2` are equal within `tolerance`. If `tolerance` 147 | is not specified, `kmath.number.DEFAULT_TOLERANCE` is used. Each dimension 148 | is compared individually, rather than comparing length and direction. 149 | 150 | If `v1` and `v2` have different lengths, this function returns `false`. 151 | 152 | kmath.vector.equal([1, 2], [1, 3]) 153 | => false 154 | kmath.vector.equal([1, 2], [1, 2, 3]) 155 | => false 156 | kmath.vector.equal([1, 2], [1, 2]) 157 | => true 158 | 159 | #### `vector.codirectional(v1, v2, [tolerance])` 160 | 161 | Returns true if `v1` and `v2` have the same direction within `tolerance`. 162 | If `tolerance` is unspecified, `kmath.number.DEFAULT_TOLERANCE` is used. 163 | Note that tolerance is checked after normalization. 164 | 165 | If `v1` and `v2` are different lengths, this function returns `false`, 166 | regardless of whether they are codirectional in the existing dimensions. 167 | 168 | This function defines the zero-vector as trivially codirectional with 169 | every vector. 170 | 171 | kmath.vector.codirectional([1, 2], [2, 4]) 172 | => true 173 | kmath.vector.codirectinoal([1, 2], [0, 0]) 174 | => true 175 | kmath.vector.codirectional([1, 2], [1, 2, 0]) 176 | => false 177 | kmath.vector.codirectional([1, 2], [-2, -4]) 178 | => false 179 | 180 | #### `vector.colinear(v1, v2, [tolerance])` 181 | 182 | Returns true if `v1` and `v2` lie along the same line, regardless of 183 | direction. This is equivalent to either `v1` and `v2` being codirectional, 184 | or `v1` and `-v2` being codirectional, or whether there is some 185 | `scaleFactor` such that `vector.equal(vector.scale(v1, scaleFactor), v2)` 186 | is true. 187 | 188 | kmath.vector.colinear([1, 2], [-2, -4]) 189 | => true 190 | 191 | #### `vector.normalize(v)` 192 | 193 | Scales the cartesian vector `v` to be `1` unit long. 194 | 195 | #### `vector.length(v)` 196 | 197 | Returns the length of the cartesian vector `v`. 198 | 199 | #### `vector.dot(v1, v2)` 200 | 201 | Returns the dot product of cartesian vectors `v1` and `v2`. 202 | 203 | #### `vector.add(*vectors)` 204 | 205 | Adds multiple cartesian vectors together. 206 | 207 | kmath.vector.add([1, 2], [3, 4]) 208 | => [4, 6] 209 | 210 | #### `vector.subtract(v1, v2)` 211 | 212 | Subtracts the cartesian vector `v2` from the cartesian vector `v1`. 213 | 214 | kmath.vector.subtract([4, 6], [1, 2]) 215 | => [3, 4] 216 | 217 | #### `vector.negate(v)` 218 | 219 | Negates the cartesian vector `v`. This has the same effect as scaling 220 | `v` by `-1` or reversing the direction of `v`. 221 | 222 | #### `vector.scale(v, scalar)` 223 | 224 | Scales the cartesian vector `v` by `scalar`. 225 | 226 | kmath.vector.scale([1, 2], 2) 227 | => [2, 4] 228 | 229 | #### `vector.polarRadFromCart(v)` 230 | 231 | Returns a polar vector `[length, angle]` from the two-dimensional cartesian 232 | vector `v`, where `angle` is measured in radians. 233 | 234 | kmath.vector.polarRadFromCart([1, 1]) 235 | => [1.4142135623730951, 0.7853981633974483] 236 | 237 | #### `vector.polarDegFromCart(v)` 238 | 239 | Returns a polar vector `[length, angle]` from the two-dimensional cartesian 240 | vector `v`, where `angle` is measured in degrees. 241 | 242 | kmath.vector.polarDegFromCart([0, 2]) 243 | => [2, 90] 244 | 245 | #### `vector.cartFromPolarRad(polar)` or `vector.cartFromPolarRad(length, angle)` 246 | 247 | Returns a two-dimensional cartesian vector from the polar vector input, 248 | where the input angle is measured in radians. 249 | 250 | kmath.vector.cartFromPolarRad([2, Math.PI]) 251 | => [-2, 0] // Note: a very small nonzero number is actually returned here, 252 | // due to numerical inaccuracy. 253 | kmath.vector.cartFromPolarRad(Math.pow(2, 0.5), Math.PI/4) 254 | => [1, 1] 255 | 256 | #### `vector.cartFromPolarDeg(polar)` or `vector.cartFromPolarDeg(length, angle)` 257 | 258 | Returns a two-dimensional cartesian vector from the polar vector input, 259 | where the input angle is measured in degrees. 260 | 261 | kmath.vector.cartFromPolarRad([2, 90]) 262 | => [-2, 0] // Note: a very small nonzero number is actually returned here, 263 | // due to numerical inaccuracy. 264 | kmath.vector.cartFromPolarRad(Math.pow(2, 0.5), 45) 265 | => [1, 1] 266 | 267 | #### `vector.rotateRad(v, angle)` 268 | 269 | Returns the rotation of the cartesian vector `v` by `angle` radians. 270 | 271 | #### `vector.rotateDeg(v, angle)` 272 | 273 | Returns the rotation of the cartesian vector `v` by `angle` degrees. 274 | 275 | #### `vector.angleRad(v1, v2)` 276 | 277 | Returns the angle between the directions of cartesian vectors 278 | `v1` and `v2` in radians. 279 | 280 | #### `vector.angleDeg(v1, v2)` 281 | 282 | Returns the angle between the directions of cartesian vectors 283 | `v1` and `v2` in radians. 284 | 285 | #### `vector.projection(v1, v2)` 286 | 287 | Returns the projection of `v1` along the direction of `v2` 288 | 289 | #### `vector.round(v, precision)` 290 | 291 | Rounds each dimension of `v` to `precision` decimal places. 292 | 293 | #### `vector.roundTo(v, increment)` 294 | 295 | Rounds each dimension of `v` to the nearest `increment`. 296 | 297 | #### `vector.floorTo(v, increment)` 298 | 299 | Floors each dimension of `v` to the nearest `increment`. 300 | 301 | #### `vector.ceilTo(v, increment)` 302 | 303 | Ceils each dimension of `v` to the nearest `increment`. 304 | 305 | ## kmath.point 306 | 307 | #### `point.is(maybeAPoint, [length])` 308 | 309 | Returns true if `maybeAPoint` is an array of numbers. If length is specified, 310 | only returns true if `maybeAPoint` is a point of dimension `length`. 311 | 312 | #### `point.equal(p1, p2, [tolerance])` 313 | 314 | Returns true if `p1` and `p2` are equal within `tolerance`. If `tolerance` 315 | is not specified, `kmath.number.DEFAULT_TOLERANCE` is used. Each dimension 316 | is compared individually. 317 | 318 | If `p1` and `p2` have different lengths, this function returns `false`. 319 | 320 | kmath.point.equal([1, 2], [1, 3]) 321 | => false 322 | kmath.point.equal([1, 2], [1, 2, 3]) 323 | => false 324 | kmath.point.equal([1, 2], [1, 2]) 325 | => true 326 | 327 | #### `point.addVector(p, *vectors)` or `point.addVectors(p, *vectors)` 328 | 329 | Returns the point created by adding the cartesian vectors `*vectors` 330 | to the cartesian point `p`. 331 | 332 | #### `point.subtractVector(p, v)` 333 | 334 | Returns the point created by subtracting the cartesian vectors `v` 335 | to the cartesian point `p`. 336 | 337 | #### `point.distanceToPoint(p1, p2)` 338 | 339 | Returns the distance between `p1` and `p2`. 340 | 341 | #### `point.distanceToLine(p, theLine)` 342 | 343 | Returns the distance between `p` and the line `theLine`. 344 | 345 | For example, to find the distance from the origin to the 346 | `y = 5` line, one could write: 347 | 348 | kmath.point.distanceToLine([0, 0], [[-1, 5], [1, 5]]) 349 | => 5 350 | 351 | #### `point.reflectOverLine(p, theLine)` 352 | 353 | Returns the reflection of `p` over the line `theLine`. 354 | 355 | For example, to reflect the origin over the line `y = 5`, 356 | one could write: 357 | 358 | kmath.point.reflectOverLine([0, 0], [[-1, 5], [1, 5]]) 359 | => [0, 10] 360 | 361 | #### `point.compare(p1, p2, [equalityTolerance])` 362 | 363 | Compares two points, returning -1, 0, or 1, for use with 364 | Array.prototype.sort 365 | 366 | Note: This technically doesn't satisfy the total-ordering 367 | requirements of Array.prototype.sort unless equalityTolerance 368 | is 0. In some cases very close points that compare within a 369 | few equalityTolerances could appear in the wrong order. 370 | 371 | #### `point.polarRadFromCart(p)` 372 | 373 | Returns a polar point `[length, angle]` from the two-dimensional cartesian 374 | point `v`, where `angle` is measured in radians. 375 | 376 | kmath.point.polarRadFromCart([1, 1]) 377 | => [1.4142135623730951, 0.7853981633974483] 378 | 379 | #### `point.polarDegFromCart(p)` 380 | 381 | Returns a polar point `[length, angle]` from the two-dimensional cartesian 382 | point `v`, where `angle` is measured in degrees. 383 | 384 | kmath.point.polarDegFromCart([0, 2]) 385 | => [2, 90] 386 | 387 | #### `point.cartFromPolarRad(polar)` or `point.cartFromPolarRad(length, angle)` 388 | 389 | Returns a two-dimensional cartesian point from the polar point input, 390 | where the input angle is measured in radians. 391 | 392 | kmath.point.cartFromPolarRad([2, Math.PI]) 393 | => [-2, 0] // Note: a very small nonzero number is actually returned here, 394 | // due to numerical inaccuracy. 395 | kmath.point.cartFromPolarRad(Math.pow(2, 0.5), Math.PI/4) 396 | => [1, 1] 397 | 398 | #### `point.cartFromPolarDeg(polar)` or `point.cartFromPolarDeg(length, angle)` 399 | 400 | Returns a two-dimensional cartesian point from the polar point input, 401 | where the input angle is measured in degrees. 402 | 403 | kmath.point.cartFromPolarRad([2, 90]) 404 | => [-2, 0] // Note: a very small nonzero number is actually returned here, 405 | // due to numerical inaccuracy. 406 | kmath.point.cartFromPolarRad(Math.pow(2, 0.5), 45) 407 | => [1, 1] 408 | 409 | #### `point.rotateRad(p, angle, center)` 410 | 411 | Returns the rotation of the two-dimensional point `v` by `angle` radians 412 | around the point `center` 413 | 414 | #### `point.rotateDeg(p, angle, center)` 415 | 416 | Returns the rotation of the two-dimensional point `v` by `angle` degrees 417 | around the point `center`. 418 | 419 | #### `point.round(p, precision)` 420 | 421 | Rounds each dimension of `p` to `precision` decimal places. 422 | 423 | #### `point.roundTo(p, increment)` 424 | 425 | Rounds each dimension of `p` to the nearest `increment`. 426 | 427 | #### `point.floorTo(p, increment)` 428 | 429 | Floors each dimension of `p` to the nearest `increment`. 430 | 431 | #### `point.ceilTo(p, increment)` 432 | 433 | Ceils each dimension of `p` to the nearest `increment`. 434 | 435 | ## kmath.ray 436 | 437 | #### `ray.equal(r1, r2, [tolerance])` 438 | 439 | Returns true if rays `r1` and `r2` are equal within `tolerance`. 440 | 441 | If unspecified, `tolerance` defaults to `kmath.number.DEFAULT_TOLERANCE`. 442 | 443 | ## kmath.line 444 | 445 | #### `line.equal(line1, line2, [tolerance])` 446 | 447 | Returns true if lines `line1` and `line2` are equal within `tolerance`. 448 | 449 | If unspecified, `tolerance` defaults to `kmath.number.DEFAULT_TOLERANCE`. 450 | 451 | #### `line.midpoint(theLine)` 452 | 453 | Returns the midpoint of the line `theLine`. 454 | 455 | kmath.line.midpoint([[0, 5], [5, 0]]) 456 | => [2.5, 2.5] 457 | 458 | #### `line.distanceToPoint(theLine, p)` 459 | 460 | Returns the distance between `theLine` and point `p`. 461 | 462 | #### `line.reflectPoint(theLine, p)` 463 | 464 | Returns the reflection of `p` over `theLine`. 465 | 466 | ## License 467 | 468 | MIT. See the LICENSE file for more information. 469 | -------------------------------------------------------------------------------- /__tests__/line.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import * as line from "../line.js"; 4 | 5 | describe("kline", function () { 6 | it("finds distance to point", () => { 7 | const result = line.distanceToPoint( 8 | [ 9 | [-5, 0], 10 | [5, 0], 11 | ], 12 | [0, 5] 13 | ); 14 | expect(result).toBe(5); 15 | }); 16 | 17 | it("reflects a point", () => { 18 | const result = line.reflectPoint( 19 | [ 20 | [-5, -5], 21 | [5, 5], 22 | ], 23 | [0, 5] 24 | ); 25 | expect(result).toEqual([5, 0]); 26 | }); 27 | 28 | it("finds the midpoint", () => { 29 | const result = line.midpoint([ 30 | [-5, -5], 31 | [5, 5], 32 | ]); 33 | expect(result).toEqual([0, 0]); 34 | }); 35 | 36 | it("two identical lines should be equal", function () { 37 | var result = line.equal( 38 | [ 39 | [1, 1], 40 | [3, 3], 41 | ], 42 | [ 43 | [1, 1], 44 | [3, 3], 45 | ] 46 | ); 47 | expect(result).toBe(true); 48 | }); 49 | 50 | it("two parallel lines should not be equal", function () { 51 | var result = line.equal( 52 | [ 53 | [1, 1], 54 | [3, 3], 55 | ], 56 | [ 57 | [1, 2], 58 | [3, 4], 59 | ] 60 | ); 61 | expect(result).toBe(false); 62 | }); 63 | 64 | it("two intersecting lines should not be equal", function () { 65 | var result = line.equal( 66 | [ 67 | [1, 1], 68 | [3, 3], 69 | ], 70 | [ 71 | [1, 1], 72 | [3, 4], 73 | ] 74 | ); 75 | expect(result).toBe(false); 76 | }); 77 | 78 | it("two collinear lines should be equal #1", function () { 79 | var result = line.equal( 80 | [ 81 | [1, 1], 82 | [3, 3], 83 | ], 84 | [ 85 | [0, 0], 86 | [5, 5], 87 | ] 88 | ); 89 | expect(result).toBe(true); 90 | }); 91 | 92 | it("two collinear lines should be equal #2", function () { 93 | var result = line.equal( 94 | [ 95 | [4, 4], 96 | [5, 5], 97 | ], 98 | [ 99 | [0, 0], 100 | [1, 1], 101 | ] 102 | ); 103 | expect(result).toBe(true); 104 | }); 105 | 106 | it("two collinear lines should be equal #3", function () { 107 | var result = line.equal( 108 | [ 109 | [0, 0], 110 | [1, 1], 111 | ], 112 | [ 113 | [3, 3], 114 | [6, 6], 115 | ] 116 | ); 117 | expect(result).toBe(true); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /__tests__/number.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import * as number from "../number.js"; 4 | 5 | describe("knumber", function () { 6 | it.each([3, Math.PI, 6.28, 5e10, 1 / 0])("is a number: %s", (num) => { 7 | expect(number.is(num)).toBe(true); 8 | }); 9 | 10 | it.each(["10", 0 / 0, NaN])("is not a number:%s", (num) => { 11 | expect(number.is(num)).toBe(false); 12 | }); 13 | 14 | it("two equal numbers should be equal", function () { 15 | var result = number.equal(1 / 3, (1 / 90) * 30); 16 | expect(result).toBe(true); 17 | }); 18 | 19 | it("two different numbers should not be equal", function () { 20 | var result = number.equal(1 / 3, 1.333333); 21 | expect(result).toBe(false); 22 | }); 23 | 24 | it("Infinity should equal Infinity", function () { 25 | var result = number.equal( 26 | Number.POSITIVE_INFINITY, 27 | Number.POSITIVE_INFINITY 28 | ); 29 | expect(result).toBe(true); 30 | }); 31 | 32 | it("+Infinity should not equal -Infinity", function () { 33 | var result = number.equal( 34 | Number.POSITIVE_INFINITY, 35 | Number.NEGATIVE_INFINITY 36 | ); 37 | expect(result).toBe(false); 38 | }); 39 | 40 | it("sign(0) should be 0", function () { 41 | expect(number.sign(0)).toBe(0); 42 | }); 43 | 44 | it("sign(-0.0) should be 0", function () { 45 | expect(number.sign(-0.0)).toBe(0); 46 | }); 47 | 48 | it("sign(3.2) should be 1", function () { 49 | expect(number.sign(3.2)).toBe(1); 50 | }); 51 | 52 | it("sign(-2.8) should be -1", function () { 53 | expect(number.sign(-2.8)).toBe(-1); 54 | }); 55 | 56 | it("isInteger(-2.8) should be false", function () { 57 | expect(number.isInteger(-2.8)).toBe(false); 58 | }); 59 | 60 | it("isInteger(-2) should be true", function () { 61 | expect(number.isInteger(-2)).toBe(true); 62 | }); 63 | 64 | it("isInteger(10.0) should be true", () => { 65 | expect(number.isInteger(10)).toBe(true); 66 | }); 67 | 68 | it("rounds to correct precision", () => { 69 | expect(number.round(0.06793, 4)).toBe(0.0679); 70 | expect(number.round(0.06793, 3)).toBe(0.068); 71 | }); 72 | 73 | it("rounds to correct interval", () => { 74 | expect(number.roundTo(83, 5)).toBe(85); 75 | expect(number.roundTo(2.3, 0.5)).toBe(2.5); 76 | }); 77 | 78 | it("floors to the correct interval", () => { 79 | expect(number.floorTo(83, 5)).toBe(80); 80 | expect(number.floorTo(2.3, 0.5)).toBe(2); 81 | }); 82 | 83 | it("ceils to the correct interval", () => { 84 | expect(number.ceilTo(81, 5)).toBe(85); 85 | expect(number.ceilTo(2.1, 0.5)).toBe(2.5); 86 | }); 87 | 88 | it("toFraction(-2) should be -2/1", function () { 89 | expect(number.toFraction(-2)).toStrictEqual([-2, 1]); 90 | }); 91 | 92 | it("toFraction(-2.5) should be -5/2", function () { 93 | expect(number.toFraction(-2.5)).toStrictEqual([-5, 2]); 94 | }); 95 | 96 | it("toFraction(2/3) should be 2/3", function () { 97 | expect(number.toFraction(2 / 3)).toStrictEqual([2, 3]); 98 | }); 99 | 100 | it("toFraction(283.33...) should be 850/3", function () { 101 | expect(number.toFraction(283 + 1 / 3)).toStrictEqual([850, 3]); 102 | }); 103 | 104 | it("toFraction(0) should be 0/1", function () { 105 | expect(number.toFraction(0)).toStrictEqual([0, 1]); 106 | }); 107 | 108 | it("toFraction(pi) should be pi/1", function () { 109 | expect(number.toFraction(Math.PI)).toStrictEqual([Math.PI, 1]); 110 | }); 111 | 112 | it("toFraction(0.66) should be 33/50", function () { 113 | expect(number.toFraction(0.66)).toStrictEqual([33, 50]); 114 | }); 115 | 116 | it("toFraction(0.66, 0.01) should be 2/3", function () { 117 | expect(number.toFraction(0.66, 0.01)).toStrictEqual([2, 3]); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /__tests__/point.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import * as point from "../point.js"; 4 | 5 | describe("kpoint", function () { 6 | it("point.compare should return positive if the first element is larger", function () { 7 | var result = point.compare([5, 2], [3, 4]); 8 | expect(result).toBeGreaterThan(0); 9 | }); 10 | 11 | it("point.compare should return negative if the first element is smaller", function () { 12 | var result = point.compare([2, 2], [4, 0]); 13 | expect(result).toBeLessThan(0); 14 | }); 15 | 16 | it("point.compare should return positive if the second element is larger", function () { 17 | var result = point.compare([5, 2], [5, 1]); 18 | expect(result).toBeGreaterThan(0); 19 | }); 20 | 21 | it("point.compare should return negative if the second element is smaller", function () { 22 | var result = point.compare([2, 2], [2, 4]); 23 | expect(result).toBeLessThan(0); 24 | }); 25 | 26 | it("point.compare should return positive if the third element is larger", function () { 27 | var result = point.compare([5, 3, -2], [5, 3, -4]); 28 | expect(result).toBeGreaterThan(0); 29 | }); 30 | 31 | it("point.compare should return negative if the third element is smaller", function () { 32 | var result = point.compare([2, -1, -4], [2, -1, -2]); 33 | expect(result).toBeLessThan(0); 34 | }); 35 | 36 | it("point.compare should return 0 if the vectors are equal", function () { 37 | var result = point.compare([2, 4, 3], [2, 4, 3]); 38 | expect(result).toBe(0); 39 | }); 40 | 41 | it("point.compare should return negative if v1 is shorter than v2", function () { 42 | var result = point.compare([2, 4], [2, 4, 3]); 43 | expect(result).toBeLessThan(0); 44 | }); 45 | 46 | it("point.compare should return positive if v1 is longer than v2", function () { 47 | var result = point.compare([2, 4, -2], [2, 2]); 48 | expect(result).toBeGreaterThan(0); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /__tests__/vector.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import * as vector from "../vector.js"; 4 | 5 | describe("kvector", function () { 6 | it("vector.add should add two 2D vectors", function () { 7 | var result = vector.add([1, 2], [3, 4]); 8 | expect(result).toStrictEqual([4, 6]); 9 | }); 10 | 11 | it("vector.add should add two 3D vectors", function () { 12 | var result = vector.add([1, 2, 3], [4, 5, 6]); 13 | expect(result).toStrictEqual([5, 7, 9]); 14 | }); 15 | 16 | it("vector.add should add three 2D vectors", function () { 17 | var result = vector.add([1, 2], [3, 4], [5, 6]); 18 | expect(result).toStrictEqual([9, 12]); 19 | }); 20 | 21 | it("vector.subtract should subtract two 2D vectors", function () { 22 | var result = vector.subtract([1, 2], [3, 4]); 23 | expect(result).toStrictEqual([-2, -2]); 24 | }); 25 | 26 | it("vector.subtract should subtract two 3D vectors", function () { 27 | var result = vector.subtract([1, 2, 3], [4, 5, 6]); 28 | expect(result).toStrictEqual([-3, -3, -3]); 29 | }); 30 | 31 | it("vector.dot should take the dot product of 2 2D vectors", function () { 32 | var result = vector.dot([1, 2], [3, 4]); 33 | expect(result).toBe(3 + 8); 34 | }); 35 | 36 | it("vector.dot should take the dot product of 2 3D vectors", function () { 37 | var result = vector.dot([1, 2, 3], [4, 5, 6]); 38 | expect(result).toBe(4 + 10 + 18); 39 | }); 40 | 41 | it("vector.scale should scale a 2D vector", function () { 42 | var result = vector.scale([4, 2], 0.5); 43 | expect(result).toStrictEqual([2, 1]); 44 | }); 45 | 46 | it("vector.scale should scale a 3D vector", function () { 47 | var result = vector.scale([1, 2, 3], 2); 48 | expect(result).toStrictEqual([2, 4, 6]); 49 | }); 50 | 51 | it("vector.length should take the length of a 2D vector", function () { 52 | var result = vector.length([3, 4]); 53 | expect(result).toBe(5); 54 | }); 55 | 56 | it("vector.length should take the length of a 3D vector", function () { 57 | var result = vector.length([4, 0, 3]); 58 | expect(result).toBe(5); 59 | }); 60 | 61 | it("vector.equal should return true on two equal 3D vectors", function () { 62 | var result = vector.equal([6, 3, 4], [6, 3, 4]); 63 | expect(result).toBe(true); 64 | }); 65 | 66 | it("vector.equal should return false on two inequal 3D vectors", function () { 67 | var result = vector.equal([6, 3, 4], [6, 4, 4]); 68 | expect(result).toBe(false); 69 | }); 70 | 71 | it("vector.equal should return false on a 2D and 3D vector", function () { 72 | var result = vector.equal([6, 4], [6, 4, 4]); 73 | expect(result).toBe(false); 74 | }); 75 | 76 | it("vector.equal should return false on a 2D and 3D vector", function () { 77 | var result = vector.equal([6, 3, 4], [6, 3]); 78 | expect(result).toBe(false); 79 | }); 80 | 81 | it("vector.equal should return false on a 2D and 3D vector with a trailing 0", function () { 82 | var result = vector.equal([6, 3, 0], [6, 3]); 83 | expect(result).toBe(false); 84 | }); 85 | 86 | it( 87 | "vector.collinear should return true on two collinear vectors of " + 88 | "the same magnitude but different direction", 89 | function () { 90 | var result = vector.collinear([3, 3], [-3, -3]); 91 | expect(result).toBe(true); 92 | } 93 | ); 94 | 95 | it( 96 | "vector.collinear should return true on two collinear vectors of " + 97 | "different magnitudes", 98 | function () { 99 | var result = vector.collinear([2, 1], [6, 3]); 100 | expect(result).toBe(true); 101 | } 102 | ); 103 | 104 | it("vector.collinear should return false on non-collinear vectors", function () { 105 | var result = vector.collinear([1, 2], [-1, 2]); 106 | expect(result).toBe(false); 107 | }); 108 | 109 | it("vector.negate of [-2, 2] is [2, -2]", function () { 110 | var result = vector.negate([-2, 2]); 111 | expect(result).toStrictEqual([2, -2]); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": "commonjs" 7 | } 8 | ] 9 | ], 10 | "plugins": ["@babel/plugin-transform-flow-strip-types"] 11 | } 12 | -------------------------------------------------------------------------------- /build/kmath.js: -------------------------------------------------------------------------------- 1 | !function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r(require("underscore")):"function"==typeof define&&define.amd?define(["underscore"],r):"object"==typeof exports?exports.kmath=r(require("underscore")):t.kmath=r(t._)}(self,(function(t){return(()=>{"use strict";var r={45:(t,r,e)=>{t.exports={number:e(414),vector:e(670),point:e(368),line:e(35),ray:e(450)}},35:(t,r,e)=>{function n(t){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n(t)}Object.defineProperty(r,"__esModule",{value:!0}),r.distanceToPoint=function(t,r){return o.distanceToLine(r,t)},r.equal=function(t,r,e){var n=u.subtract(t[1],t[0]),a=u.subtract(r[1],r[0]);if(!u.collinear(n,a,e))return!1;if(o.equal(t[0],r[0]))return!0;var i=u.subtract(r[0],t[0]);return u.collinear(n,i,e)},r.midpoint=function(t){return[(t[0][0]+t[1][0])/2,(t[0][1]+t[1][1])/2]},r.reflectPoint=function(t,r){return o.reflectOverLine(r,t)};var o=i(e(368)),u=i(e(670));function a(t){if("function"!=typeof WeakMap)return null;var r=new WeakMap,e=new WeakMap;return(a=function(t){return t?e:r})(t)}function i(t,r){if(!r&&t&&t.__esModule)return t;if(null===t||"object"!==n(t)&&"function"!=typeof t)return{default:t};var e=a(r);if(e&&e.has(t))return e.get(t);var o={},u=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in t)if("default"!==i&&Object.prototype.hasOwnProperty.call(t,i)){var f=u?Object.getOwnPropertyDescriptor(t,i):null;f&&(f.get||f.set)?Object.defineProperty(o,i,f):o[i]=t[i]}return o.default=t,e&&e.set(t,o),o}},414:(t,r,e)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.EPSILON=r.DEFAULT_TOLERANCE=void 0,r.ceilTo=function(t,r){return Math.ceil(t/r)*r},r.equal=a,r.floorTo=function(t,r){return Math.floor(t/r)*r},r.is=function(t){return o.default.isNumber(t)&&!o.default.isNaN(t)},r.isInteger=function(t,r){return a(Math.round(t),t,r)},r.round=function(t,r){var e=Math.pow(10,r);return Math.round(t*e)/e},r.roundTo=function(t,r){return Math.round(t/r)*r},r.sign=function(t,r){return a(t,0,r)?0:Math.abs(t)/t},r.toFraction=function(t){for(var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:u,e=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1e3,n=[1,0],o=[0,1],i=Math.floor(t),f=t-i;o[0]<=e;){if(a(n[0]/o[0],t,r))return[n[0],o[0]];n=[i*n[0]+n[1],n[0]],o=[i*o[0]+o[1],o[0]],f=1/f-(i=Math.floor(1/f))}return[t,1]};var n,o=(n=e(38))&&n.__esModule?n:{default:n};r.DEFAULT_TOLERANCE=1e-9;var u=Math.pow(2,-42);function a(t,r,e){return null==t||null==r?t===r:t===r||(null==e&&(e=1e-9),Math.abs(t-r){function n(t){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n(t)}Object.defineProperty(r,"__esModule",{value:!0}),r.ceilTo=r.cartFromPolarRad=r.cartFromPolarDeg=r.addVectors=r.addVector=void 0,r.compare=function(t,r,e){if(t.length!==r.length)return t.length-r.length;for(var n=0;n{function n(t){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n(t)}Object.defineProperty(r,"__esModule",{value:!0}),r.equal=function(t,r,e){var n=o.subtract(t[1],t[0]),a=o.subtract(r[1],r[0]),i=u.equal(t[0],r[0]),f=o.codirectional(n,a,e);return i&&f};var o=i(e(670)),u=i(e(368));function a(t){if("function"!=typeof WeakMap)return null;var r=new WeakMap,e=new WeakMap;return(a=function(t){return t?e:r})(t)}function i(t,r){if(!r&&t&&t.__esModule)return t;if(null===t||"object"!==n(t)&&"function"!=typeof t)return{default:t};var e=a(r);if(e&&e.has(t))return e.get(t);var o={},u=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in t)if("default"!==i&&Object.prototype.hasOwnProperty.call(t,i)){var f=u?Object.getOwnPropertyDescriptor(t,i):null;f&&(f.get||f.set)?Object.defineProperty(o,i,f):o[i]=t[i]}return o.default=t,e&&e.set(t,o),o}},670:(t,r,e)=>{function n(t){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n(t)}Object.defineProperty(r,"__esModule",{value:!0}),r.add=function(){for(var t=arguments.length,r=new Array(t),e=0;e{r.exports=t}},e={};return function t(n){var o=e[n];if(void 0!==o)return o.exports;var u=e[n]={exports:{}};return r[n](u,u.exports,t),u.exports}(45)})()})); -------------------------------------------------------------------------------- /flow-typed/npm/jest_v27.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 5ddcf688200e3506308fdcfa78ca48d9 2 | // flow-typed version: 644a595e77/jest_v27.x.x/flow_>=v0.134.x 3 | 4 | type JestMockFn, TReturn> = { 5 | (...args: TArguments): TReturn, 6 | /** 7 | * An object for introspecting mock calls 8 | */ 9 | mock: { 10 | /** 11 | * An array that represents all calls that have been made into this mock 12 | * function. Each call is represented by an array of arguments that were 13 | * passed during the call. 14 | */ 15 | calls: Array, 16 | /** 17 | * An array that contains all the object instances that have been 18 | * instantiated from this mock function. 19 | */ 20 | instances: Array, 21 | /** 22 | * An array that contains all the object results that have been 23 | * returned by this mock function call 24 | */ 25 | results: Array<{ 26 | isThrow: boolean, 27 | value: TReturn, 28 | ... 29 | }>, 30 | ... 31 | }, 32 | /** 33 | * Resets all information stored in the mockFn.mock.calls and 34 | * mockFn.mock.instances arrays. Often this is useful when you want to clean 35 | * up a mock's usage data between two assertions. 36 | */ 37 | mockClear(): void, 38 | /** 39 | * Resets all information stored in the mock. This is useful when you want to 40 | * completely restore a mock back to its initial state. 41 | */ 42 | mockReset(): void, 43 | /** 44 | * Removes the mock and restores the initial implementation. This is useful 45 | * when you want to mock functions in certain test cases and restore the 46 | * original implementation in others. Beware that mockFn.mockRestore only 47 | * works when mock was created with jest.spyOn. Thus you have to take care of 48 | * restoration yourself when manually assigning jest.fn(). 49 | */ 50 | mockRestore(): void, 51 | /** 52 | * Accepts a function that should be used as the implementation of the mock. 53 | * The mock itself will still record all calls that go into and instances 54 | * that come from itself -- the only difference is that the implementation 55 | * will also be executed when the mock is called. 56 | */ 57 | mockImplementation( 58 | fn: (...args: TArguments) => TReturn 59 | ): JestMockFn, 60 | /** 61 | * Accepts a function that will be used as an implementation of the mock for 62 | * one call to the mocked function. Can be chained so that multiple function 63 | * calls produce different results. 64 | */ 65 | mockImplementationOnce( 66 | fn: (...args: TArguments) => TReturn 67 | ): JestMockFn, 68 | /** 69 | * Accepts a string to use in test result output in place of "jest.fn()" to 70 | * indicate which mock function is being referenced. 71 | */ 72 | mockName(name: string): JestMockFn, 73 | /** 74 | * Just a simple sugar function for returning `this` 75 | */ 76 | mockReturnThis(): void, 77 | /** 78 | * Accepts a value that will be returned whenever the mock function is called. 79 | */ 80 | mockReturnValue(value: TReturn): JestMockFn, 81 | /** 82 | * Sugar for only returning a value once inside your mock 83 | */ 84 | mockReturnValueOnce(value: TReturn): JestMockFn, 85 | /** 86 | * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value)) 87 | */ 88 | mockResolvedValue(value: TReturn): JestMockFn>, 89 | /** 90 | * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) 91 | */ 92 | mockResolvedValueOnce( 93 | value: TReturn 94 | ): JestMockFn>, 95 | /** 96 | * Sugar for jest.fn().mockImplementation(() => Promise.reject(value)) 97 | */ 98 | mockRejectedValue(value: TReturn): JestMockFn>, 99 | /** 100 | * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value)) 101 | */ 102 | mockRejectedValueOnce(value: TReturn): JestMockFn>, 103 | ... 104 | }; 105 | 106 | type JestAsymmetricEqualityType = { 107 | /** 108 | * A custom Jasmine equality tester 109 | */ 110 | asymmetricMatch(value: mixed): boolean, 111 | ... 112 | }; 113 | 114 | type JestCallsType = { 115 | allArgs(): mixed, 116 | all(): mixed, 117 | any(): boolean, 118 | count(): number, 119 | first(): mixed, 120 | mostRecent(): mixed, 121 | reset(): void, 122 | ... 123 | }; 124 | 125 | type JestClockType = { 126 | install(): void, 127 | mockDate(date: Date): void, 128 | tick(milliseconds?: number): void, 129 | uninstall(): void, 130 | ... 131 | }; 132 | 133 | type JestMatcherResult = { 134 | message?: string | (() => string), 135 | pass: boolean, 136 | ... 137 | }; 138 | 139 | type JestMatcher = ( 140 | received: any, 141 | ...actual: Array 142 | ) => JestMatcherResult | Promise; 143 | 144 | type JestPromiseType = { 145 | /** 146 | * Use rejects to unwrap the reason of a rejected promise so any other 147 | * matcher can be chained. If the promise is fulfilled the assertion fails. 148 | */ 149 | rejects: JestExpectType, 150 | /** 151 | * Use resolves to unwrap the value of a fulfilled promise so any other 152 | * matcher can be chained. If the promise is rejected the assertion fails. 153 | */ 154 | resolves: JestExpectType, 155 | ... 156 | }; 157 | 158 | /** 159 | * Jest allows functions and classes to be used as test names in test() and 160 | * describe() 161 | */ 162 | type JestTestName = string | Function; 163 | 164 | /** 165 | * Plugin: jest-styled-components 166 | */ 167 | 168 | type JestStyledComponentsMatcherValue = 169 | | string 170 | | JestAsymmetricEqualityType 171 | | RegExp 172 | | typeof undefined; 173 | 174 | type JestStyledComponentsMatcherOptions = { 175 | media?: string, 176 | modifier?: string, 177 | supports?: string, 178 | ... 179 | }; 180 | 181 | type JestStyledComponentsMatchersType = { 182 | toHaveStyleRule( 183 | property: string, 184 | value: JestStyledComponentsMatcherValue, 185 | options?: JestStyledComponentsMatcherOptions 186 | ): void, 187 | ... 188 | }; 189 | 190 | /** 191 | * Plugin: jest-enzyme 192 | */ 193 | type EnzymeMatchersType = { 194 | // 5.x 195 | toBeEmpty(): void, 196 | toBePresent(): void, 197 | // 6.x 198 | toBeChecked(): void, 199 | toBeDisabled(): void, 200 | toBeEmptyRender(): void, 201 | toContainMatchingElement(selector: string): void, 202 | toContainMatchingElements(n: number, selector: string): void, 203 | toContainExactlyOneMatchingElement(selector: string): void, 204 | toContainReact(element: React$Element): void, 205 | toExist(): void, 206 | toHaveClassName(className: string): void, 207 | toHaveHTML(html: string): void, 208 | toHaveProp: ((propKey: string, propValue?: any) => void) & 209 | ((props: { ... }) => void), 210 | toHaveRef(refName: string): void, 211 | toHaveState: ((stateKey: string, stateValue?: any) => void) & 212 | ((state: { ... }) => void), 213 | toHaveStyle: ((styleKey: string, styleValue?: any) => void) & 214 | ((style: { ... }) => void), 215 | toHaveTagName(tagName: string): void, 216 | toHaveText(text: string): void, 217 | toHaveValue(value: any): void, 218 | toIncludeText(text: string): void, 219 | toMatchElement( 220 | element: React$Element, 221 | options?: {| ignoreProps?: boolean, verbose?: boolean |} 222 | ): void, 223 | toMatchSelector(selector: string): void, 224 | // 7.x 225 | toHaveDisplayName(name: string): void, 226 | ... 227 | }; 228 | 229 | // DOM testing library extensions (jest-dom) 230 | // https://github.com/testing-library/jest-dom 231 | type DomTestingLibraryType = { 232 | /** 233 | * @deprecated 234 | */ 235 | toBeInTheDOM(container?: HTMLElement): void, 236 | 237 | // 4.x 238 | toBeInTheDocument(): void, 239 | toBeVisible(): void, 240 | toBeEmpty(): void, 241 | toBeDisabled(): void, 242 | toBeEnabled(): void, 243 | toBeInvalid(): void, 244 | toBeRequired(): void, 245 | toBeValid(): void, 246 | toContainElement(element: HTMLElement | null): void, 247 | toContainHTML(htmlText: string): void, 248 | toHaveAttribute(attr: string, value?: any): void, 249 | toHaveClass(...classNames: string[]): void, 250 | toHaveFocus(): void, 251 | toHaveFormValues(expectedValues: { [name: string]: any, ... }): void, 252 | toHaveStyle(css: string | { [name: string]: any, ... }): void, 253 | toHaveTextContent( 254 | text: string | RegExp, 255 | options?: {| normalizeWhitespace: boolean |} 256 | ): void, 257 | toHaveValue(value?: string | string[] | number): void, 258 | 259 | // 5.x 260 | toHaveDisplayValue(value: string | string[]): void, 261 | toBeChecked(): void, 262 | toBeEmptyDOMElement(): void, 263 | toBePartiallyChecked(): void, 264 | toHaveDescription(text: string | RegExp): void, 265 | ... 266 | }; 267 | 268 | // Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers 269 | type JestJQueryMatchersType = { 270 | toExist(): void, 271 | toHaveLength(len: number): void, 272 | toHaveId(id: string): void, 273 | toHaveClass(className: string): void, 274 | toHaveTag(tag: string): void, 275 | toHaveAttr(key: string, val?: any): void, 276 | toHaveProp(key: string, val?: any): void, 277 | toHaveText(text: string | RegExp): void, 278 | toHaveData(key: string, val?: any): void, 279 | toHaveValue(val: any): void, 280 | toHaveCss(css: { [key: string]: any, ... }): void, 281 | toBeChecked(): void, 282 | toBeDisabled(): void, 283 | toBeEmpty(): void, 284 | toBeHidden(): void, 285 | toBeSelected(): void, 286 | toBeVisible(): void, 287 | toBeFocused(): void, 288 | toBeInDom(): void, 289 | toBeMatchedBy(sel: string): void, 290 | toHaveDescendant(sel: string): void, 291 | toHaveDescendantWithText(sel: string, text: string | RegExp): void, 292 | ... 293 | }; 294 | 295 | // Jest Extended Matchers: https://github.com/jest-community/jest-extended 296 | type JestExtendedMatchersType = { 297 | /** 298 | * Note: Currently unimplemented 299 | * Passing assertion 300 | * 301 | * @param {String} message 302 | */ 303 | // pass(message: string): void; 304 | 305 | /** 306 | * Note: Currently unimplemented 307 | * Failing assertion 308 | * 309 | * @param {String} message 310 | */ 311 | // fail(message: string): void; 312 | 313 | /** 314 | * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. 315 | */ 316 | toBeEmpty(): void, 317 | /** 318 | * Use .toBeOneOf when checking if a value is a member of a given Array. 319 | * @param {Array.<*>} members 320 | */ 321 | toBeOneOf(members: any[]): void, 322 | /** 323 | * Use `.toBeNil` when checking a value is `null` or `undefined`. 324 | */ 325 | toBeNil(): void, 326 | /** 327 | * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. 328 | * @param {Function} predicate 329 | */ 330 | toSatisfy(predicate: (n: any) => boolean): void, 331 | /** 332 | * Use `.toBeArray` when checking if a value is an `Array`. 333 | */ 334 | toBeArray(): void, 335 | /** 336 | * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. 337 | * @param {Number} x 338 | */ 339 | toBeArrayOfSize(x: number): void, 340 | /** 341 | * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. 342 | * @param {Array.<*>} members 343 | */ 344 | toIncludeAllMembers(members: any[]): void, 345 | /** 346 | * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. 347 | * @param {Array.<*>} members 348 | */ 349 | toIncludeAnyMembers(members: any[]): void, 350 | /** 351 | * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array. 352 | * @param {Function} predicate 353 | */ 354 | toSatisfyAll(predicate: (n: any) => boolean): void, 355 | /** 356 | * Use `.toBeBoolean` when checking if a value is a `Boolean`. 357 | */ 358 | toBeBoolean(): void, 359 | /** 360 | * Use `.toBeTrue` when checking a value is equal (===) to `true`. 361 | */ 362 | toBeTrue(): void, 363 | /** 364 | * Use `.toBeFalse` when checking a value is equal (===) to `false`. 365 | */ 366 | toBeFalse(): void, 367 | /** 368 | * Use .toBeDate when checking if a value is a Date. 369 | */ 370 | toBeDate(): void, 371 | /** 372 | * Use `.toBeFunction` when checking if a value is a `Function`. 373 | */ 374 | toBeFunction(): void, 375 | /** 376 | * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. 377 | * 378 | * Note: Required Jest version >22 379 | * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same 380 | * 381 | * @param {Mock} mock 382 | */ 383 | toHaveBeenCalledBefore(mock: JestMockFn): void, 384 | /** 385 | * Use `.toBeNumber` when checking if a value is a `Number`. 386 | */ 387 | toBeNumber(): void, 388 | /** 389 | * Use `.toBeNaN` when checking a value is `NaN`. 390 | */ 391 | toBeNaN(): void, 392 | /** 393 | * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. 394 | */ 395 | toBeFinite(): void, 396 | /** 397 | * Use `.toBePositive` when checking if a value is a positive `Number`. 398 | */ 399 | toBePositive(): void, 400 | /** 401 | * Use `.toBeNegative` when checking if a value is a negative `Number`. 402 | */ 403 | toBeNegative(): void, 404 | /** 405 | * Use `.toBeEven` when checking if a value is an even `Number`. 406 | */ 407 | toBeEven(): void, 408 | /** 409 | * Use `.toBeOdd` when checking if a value is an odd `Number`. 410 | */ 411 | toBeOdd(): void, 412 | /** 413 | * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). 414 | * 415 | * @param {Number} start 416 | * @param {Number} end 417 | */ 418 | toBeWithin(start: number, end: number): void, 419 | /** 420 | * Use `.toBeObject` when checking if a value is an `Object`. 421 | */ 422 | toBeObject(): void, 423 | /** 424 | * Use `.toContainKey` when checking if an object contains the provided key. 425 | * 426 | * @param {String} key 427 | */ 428 | toContainKey(key: string): void, 429 | /** 430 | * Use `.toContainKeys` when checking if an object has all of the provided keys. 431 | * 432 | * @param {Array.} keys 433 | */ 434 | toContainKeys(keys: string[]): void, 435 | /** 436 | * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. 437 | * 438 | * @param {Array.} keys 439 | */ 440 | toContainAllKeys(keys: string[]): void, 441 | /** 442 | * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. 443 | * 444 | * @param {Array.} keys 445 | */ 446 | toContainAnyKeys(keys: string[]): void, 447 | /** 448 | * Use `.toContainValue` when checking if an object contains the provided value. 449 | * 450 | * @param {*} value 451 | */ 452 | toContainValue(value: any): void, 453 | /** 454 | * Use `.toContainValues` when checking if an object contains all of the provided values. 455 | * 456 | * @param {Array.<*>} values 457 | */ 458 | toContainValues(values: any[]): void, 459 | /** 460 | * Use `.toContainAllValues` when checking if an object only contains all of the provided values. 461 | * 462 | * @param {Array.<*>} values 463 | */ 464 | toContainAllValues(values: any[]): void, 465 | /** 466 | * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. 467 | * 468 | * @param {Array.<*>} values 469 | */ 470 | toContainAnyValues(values: any[]): void, 471 | /** 472 | * Use `.toContainEntry` when checking if an object contains the provided entry. 473 | * 474 | * @param {Array.} entry 475 | */ 476 | toContainEntry(entry: [string, string]): void, 477 | /** 478 | * Use `.toContainEntries` when checking if an object contains all of the provided entries. 479 | * 480 | * @param {Array.>} entries 481 | */ 482 | toContainEntries(entries: [string, string][]): void, 483 | /** 484 | * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. 485 | * 486 | * @param {Array.>} entries 487 | */ 488 | toContainAllEntries(entries: [string, string][]): void, 489 | /** 490 | * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. 491 | * 492 | * @param {Array.>} entries 493 | */ 494 | toContainAnyEntries(entries: [string, string][]): void, 495 | /** 496 | * Use `.toBeExtensible` when checking if an object is extensible. 497 | */ 498 | toBeExtensible(): void, 499 | /** 500 | * Use `.toBeFrozen` when checking if an object is frozen. 501 | */ 502 | toBeFrozen(): void, 503 | /** 504 | * Use `.toBeSealed` when checking if an object is sealed. 505 | */ 506 | toBeSealed(): void, 507 | /** 508 | * Use `.toBeString` when checking if a value is a `String`. 509 | */ 510 | toBeString(): void, 511 | /** 512 | * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. 513 | * 514 | * @param {String} string 515 | */ 516 | toEqualCaseInsensitive(string: string): void, 517 | /** 518 | * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. 519 | * 520 | * @param {String} prefix 521 | */ 522 | toStartWith(prefix: string): void, 523 | /** 524 | * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. 525 | * 526 | * @param {String} suffix 527 | */ 528 | toEndWith(suffix: string): void, 529 | /** 530 | * Use `.toInclude` when checking if a `String` includes the given `String` substring. 531 | * 532 | * @param {String} substring 533 | */ 534 | toInclude(substring: string): void, 535 | /** 536 | * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. 537 | * 538 | * @param {String} substring 539 | * @param {Number} times 540 | */ 541 | toIncludeRepeated(substring: string, times: number): void, 542 | /** 543 | * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. 544 | * 545 | * @param {Array.} substring 546 | */ 547 | toIncludeMultiple(substring: string[]): void, 548 | ... 549 | }; 550 | 551 | // Diffing snapshot utility for Jest (snapshot-diff) 552 | // https://github.com/jest-community/snapshot-diff 553 | type SnapshotDiffType = { 554 | /** 555 | * Compare the difference between the actual in the `expect()` 556 | * vs the object inside `valueB` with some extra options. 557 | */ 558 | toMatchDiffSnapshot( 559 | valueB: any, 560 | options?: {| 561 | expand?: boolean, 562 | colors?: boolean, 563 | contextLines?: number, 564 | stablePatchmarks?: boolean, 565 | aAnnotation?: string, 566 | bAnnotation?: string, 567 | |}, 568 | testName?: string 569 | ): void, 570 | ... 571 | }; 572 | 573 | interface JestExpectType { 574 | not: JestExpectType & 575 | EnzymeMatchersType & 576 | DomTestingLibraryType & 577 | JestJQueryMatchersType & 578 | JestStyledComponentsMatchersType & 579 | JestExtendedMatchersType & 580 | SnapshotDiffType; 581 | /** 582 | * If you have a mock function, you can use .lastCalledWith to test what 583 | * arguments it was last called with. 584 | */ 585 | lastCalledWith(...args: Array): void; 586 | /** 587 | * toBe just checks that a value is what you expect. It uses === to check 588 | * strict equality. 589 | */ 590 | toBe(value: any): void; 591 | /** 592 | * Use .toBeCalledWith to ensure that a mock function was called with 593 | * specific arguments. 594 | */ 595 | toBeCalledWith(...args: Array): void; 596 | /** 597 | * Using exact equality with floating point numbers is a bad idea. Rounding 598 | * means that intuitive things fail. 599 | */ 600 | toBeCloseTo(num: number, delta: any): void; 601 | /** 602 | * Use .toBeDefined to check that a variable is not undefined. 603 | */ 604 | toBeDefined(): void; 605 | /** 606 | * Use .toBeFalsy when you don't care what a value is, you just want to 607 | * ensure a value is false in a boolean context. 608 | */ 609 | toBeFalsy(): void; 610 | /** 611 | * To compare floating point numbers, you can use toBeGreaterThan. 612 | */ 613 | toBeGreaterThan(number: number): void; 614 | /** 615 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 616 | */ 617 | toBeGreaterThanOrEqual(number: number): void; 618 | /** 619 | * To compare floating point numbers, you can use toBeLessThan. 620 | */ 621 | toBeLessThan(number: number): void; 622 | /** 623 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 624 | */ 625 | toBeLessThanOrEqual(number: number): void; 626 | /** 627 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 628 | * class. 629 | */ 630 | toBeInstanceOf(cls: Class<*>): void; 631 | /** 632 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 633 | * nicer. 634 | */ 635 | toBeNull(): void; 636 | /** 637 | * Use .toBeTruthy when you don't care what a value is, you just want to 638 | * ensure a value is true in a boolean context. 639 | */ 640 | toBeTruthy(): void; 641 | /** 642 | * Use .toBeUndefined to check that a variable is undefined. 643 | */ 644 | toBeUndefined(): void; 645 | /** 646 | * Use .toContain when you want to check that an item is in a list. For 647 | * testing the items in the list, this uses ===, a strict equality check. 648 | */ 649 | toContain(item: any): void; 650 | /** 651 | * Use .toContainEqual when you want to check that an item is in a list. For 652 | * testing the items in the list, this matcher recursively checks the 653 | * equality of all fields, rather than checking for object identity. 654 | */ 655 | toContainEqual(item: any): void; 656 | /** 657 | * Use .toEqual when you want to check that two objects have the same value. 658 | * This matcher recursively checks the equality of all fields, rather than 659 | * checking for object identity. 660 | */ 661 | toEqual(value: any): void; 662 | /** 663 | * Use .toHaveBeenCalled to ensure that a mock function got called. 664 | */ 665 | toHaveBeenCalled(): void; 666 | toBeCalled(): void; 667 | /** 668 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 669 | * number of times. 670 | */ 671 | toHaveBeenCalledTimes(number: number): void; 672 | toBeCalledTimes(number: number): void; 673 | /** 674 | * 675 | */ 676 | toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; 677 | nthCalledWith(nthCall: number, ...args: Array): void; 678 | /** 679 | * 680 | */ 681 | toHaveReturned(): void; 682 | toReturn(): void; 683 | /** 684 | * 685 | */ 686 | toHaveReturnedTimes(number: number): void; 687 | toReturnTimes(number: number): void; 688 | /** 689 | * 690 | */ 691 | toHaveReturnedWith(value: any): void; 692 | toReturnWith(value: any): void; 693 | /** 694 | * 695 | */ 696 | toHaveLastReturnedWith(value: any): void; 697 | lastReturnedWith(value: any): void; 698 | /** 699 | * 700 | */ 701 | toHaveNthReturnedWith(nthCall: number, value: any): void; 702 | nthReturnedWith(nthCall: number, value: any): void; 703 | /** 704 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 705 | * specific arguments. 706 | */ 707 | toHaveBeenCalledWith(...args: Array): void; 708 | toBeCalledWith(...args: Array): void; 709 | /** 710 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 711 | * with specific arguments. 712 | */ 713 | toHaveBeenLastCalledWith(...args: Array): void; 714 | lastCalledWith(...args: Array): void; 715 | /** 716 | * Check that an object has a .length property and it is set to a certain 717 | * numeric value. 718 | */ 719 | toHaveLength(number: number): void; 720 | /** 721 | * 722 | */ 723 | toHaveProperty(propPath: string | $ReadOnlyArray, value?: any): void; 724 | /** 725 | * Use .toMatch to check that a string matches a regular expression or string. 726 | */ 727 | toMatch(regexpOrString: RegExp | string): void; 728 | /** 729 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 730 | */ 731 | toMatchObject(object: Object | Array): void; 732 | /** 733 | * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. 734 | */ 735 | toStrictEqual(value: any): void; 736 | /** 737 | * This ensures that an Object matches the most recent snapshot. 738 | */ 739 | toMatchSnapshot(propertyMatchers?: any, name?: string): void; 740 | /** 741 | * This ensures that an Object matches the most recent snapshot. 742 | */ 743 | toMatchSnapshot(name: string): void; 744 | 745 | toMatchInlineSnapshot(snapshot?: string): void; 746 | toMatchInlineSnapshot(propertyMatchers?: any, snapshot?: string): void; 747 | /** 748 | * Use .toThrow to test that a function throws when it is called. 749 | * If you want to test that a specific error gets thrown, you can provide an 750 | * argument to toThrow. The argument can be a string for the error message, 751 | * a class for the error, or a regex that should match the error. 752 | * 753 | * Alias: .toThrowError 754 | */ 755 | toThrow(message?: string | Error | Class | RegExp): void; 756 | toThrowError(message?: string | Error | Class | RegExp): void; 757 | /** 758 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 759 | * matching the most recent snapshot when it is called. 760 | */ 761 | toThrowErrorMatchingSnapshot(): void; 762 | toThrowErrorMatchingInlineSnapshot(snapshot?: string): void; 763 | } 764 | 765 | type JestObjectType = { 766 | /** 767 | * Disables automatic mocking in the module loader. 768 | * 769 | * After this method is called, all `require()`s will return the real 770 | * versions of each module (rather than a mocked version). 771 | */ 772 | disableAutomock(): JestObjectType, 773 | /** 774 | * An un-hoisted version of disableAutomock 775 | */ 776 | autoMockOff(): JestObjectType, 777 | /** 778 | * Enables automatic mocking in the module loader. 779 | */ 780 | enableAutomock(): JestObjectType, 781 | /** 782 | * An un-hoisted version of enableAutomock 783 | */ 784 | autoMockOn(): JestObjectType, 785 | /** 786 | * Clears the mock.calls and mock.instances properties of all mocks. 787 | * Equivalent to calling .mockClear() on every mocked function. 788 | */ 789 | clearAllMocks(): JestObjectType, 790 | /** 791 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 792 | * mocked function. 793 | */ 794 | resetAllMocks(): JestObjectType, 795 | /** 796 | * Restores all mocks back to their original value. 797 | */ 798 | restoreAllMocks(): JestObjectType, 799 | /** 800 | * Removes any pending timers from the timer system. 801 | */ 802 | clearAllTimers(): void, 803 | /** 804 | * Returns the number of fake timers still left to run. 805 | */ 806 | getTimerCount(): number, 807 | /** 808 | * Set the current system time used by fake timers. 809 | * Simulates a user changing the system clock while your program is running. 810 | * It affects the current time but it does not in itself cause 811 | * e.g. timers to fire; they will fire exactly as they would have done 812 | * without the call to jest.setSystemTime(). 813 | */ 814 | setSystemTime(now?: number | Date): void, 815 | /** 816 | * The same as `mock` but not moved to the top of the expectation by 817 | * babel-jest. 818 | */ 819 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 820 | /** 821 | * The same as `unmock` but not moved to the top of the expectation by 822 | * babel-jest. 823 | */ 824 | dontMock(moduleName: string): JestObjectType, 825 | /** 826 | * Returns a new, unused mock function. Optionally takes a mock 827 | * implementation. 828 | */ 829 | fn, TReturn>( 830 | implementation?: (...args: TArguments) => TReturn 831 | ): JestMockFn, 832 | /** 833 | * Determines if the given function is a mocked function. 834 | */ 835 | isMockFunction(fn: Function): boolean, 836 | /** 837 | * Alias of `createMockFromModule`. 838 | */ 839 | genMockFromModule(moduleName: string): any, 840 | /** 841 | * Given the name of a module, use the automatic mocking system to generate a 842 | * mocked version of the module for you. 843 | */ 844 | createMockFromModule(moduleName: string): any, 845 | /** 846 | * Mocks a module with an auto-mocked version when it is being required. 847 | * 848 | * The second argument can be used to specify an explicit module factory that 849 | * is being run instead of using Jest's automocking feature. 850 | * 851 | * The third argument can be used to create virtual mocks -- mocks of modules 852 | * that don't exist anywhere in the system. 853 | */ 854 | mock( 855 | moduleName: string, 856 | moduleFactory?: any, 857 | options?: Object 858 | ): JestObjectType, 859 | /** 860 | * Returns the actual module instead of a mock, bypassing all checks on 861 | * whether the module should receive a mock implementation or not. 862 | */ 863 | requireActual(m: $Flow$ModuleRef | string): T, 864 | /** 865 | * Returns a mock module instead of the actual module, bypassing all checks 866 | * on whether the module should be required normally or not. 867 | */ 868 | requireMock(moduleName: string): any, 869 | /** 870 | * Resets the module registry - the cache of all required modules. This is 871 | * useful to isolate modules where local state might conflict between tests. 872 | */ 873 | resetModules(): JestObjectType, 874 | /** 875 | * Creates a sandbox registry for the modules that are loaded inside the 876 | * callback function. This is useful to isolate specific modules for every 877 | * test so that local module state doesn't conflict between tests. 878 | */ 879 | isolateModules(fn: () => void): JestObjectType, 880 | /** 881 | * Exhausts the micro-task queue (usually interfaced in node via 882 | * process.nextTick). 883 | */ 884 | runAllTicks(): void, 885 | /** 886 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 887 | * setInterval(), and setImmediate()). 888 | */ 889 | runAllTimers(): void, 890 | /** 891 | * Exhausts all tasks queued by setImmediate(). 892 | */ 893 | runAllImmediates(): void, 894 | /** 895 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 896 | * or setInterval() and setImmediate()). 897 | */ 898 | advanceTimersByTime(msToRun: number): void, 899 | /** 900 | * Executes only the macro-tasks that are currently pending (i.e., only the 901 | * tasks that have been queued by setTimeout() or setInterval() up to this 902 | * point) 903 | */ 904 | runOnlyPendingTimers(): void, 905 | /** 906 | * Explicitly supplies the mock object that the module system should return 907 | * for the specified module. Note: It is recommended to use jest.mock() 908 | * instead. 909 | */ 910 | setMock(moduleName: string, moduleExports: any): JestObjectType, 911 | /** 912 | * Indicates that the module system should never return a mocked version of 913 | * the specified module from require() (e.g. that it should always return the 914 | * real module). 915 | */ 916 | unmock(moduleName: string): JestObjectType, 917 | /** 918 | * Instructs Jest to use fake versions of the standard timer functions 919 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 920 | * setImmediate and clearImmediate). 921 | */ 922 | useFakeTimers(mode?: 'modern' | 'legacy'): JestObjectType, 923 | /** 924 | * Instructs Jest to use the real versions of the standard timer functions. 925 | */ 926 | useRealTimers(): JestObjectType, 927 | /** 928 | * Creates a mock function similar to jest.fn but also tracks calls to 929 | * object[methodName]. 930 | */ 931 | spyOn( 932 | object: Object, 933 | methodName: string, 934 | accessType?: 'get' | 'set' 935 | ): JestMockFn, 936 | /** 937 | * Set the default timeout interval for tests and before/after hooks in milliseconds. 938 | * Note: The default timeout interval is 5 seconds if this method is not called. 939 | */ 940 | setTimeout(timeout: number): JestObjectType, 941 | ... 942 | }; 943 | 944 | type JestSpyType = { calls: JestCallsType, ... }; 945 | 946 | type JestDoneFn = {| 947 | (error?: Error): void, 948 | fail: (error: Error) => void, 949 | |}; 950 | 951 | /** Runs this function after every test inside this context */ 952 | declare function afterEach( 953 | fn: (done: JestDoneFn) => ?Promise, 954 | timeout?: number 955 | ): void; 956 | /** Runs this function before every test inside this context */ 957 | declare function beforeEach( 958 | fn: (done: JestDoneFn) => ?Promise, 959 | timeout?: number 960 | ): void; 961 | /** Runs this function after all tests have finished inside this context */ 962 | declare function afterAll( 963 | fn: (done: JestDoneFn) => ?Promise, 964 | timeout?: number 965 | ): void; 966 | /** Runs this function before any tests have started inside this context */ 967 | declare function beforeAll( 968 | fn: (done: JestDoneFn) => ?Promise, 969 | timeout?: number 970 | ): void; 971 | 972 | /** A context for grouping tests together */ 973 | declare var describe: { 974 | /** 975 | * Creates a block that groups together several related tests in one "test suite" 976 | */ 977 | (name: JestTestName, fn: () => void): void, 978 | /** 979 | * Only run this describe block 980 | */ 981 | only(name: JestTestName, fn: () => void): void, 982 | /** 983 | * Skip running this describe block 984 | */ 985 | skip(name: JestTestName, fn: () => void): void, 986 | /** 987 | * each runs this test against array of argument arrays per each run 988 | * 989 | * @param {table} table of Test 990 | */ 991 | each( 992 | ...table: Array | mixed> | [Array, string] 993 | ): ( 994 | name: JestTestName, 995 | fn?: (...args: Array) => ?Promise, 996 | timeout?: number 997 | ) => void, 998 | ... 999 | }; 1000 | 1001 | /** An individual test unit */ 1002 | declare var it: { 1003 | /** 1004 | * An individual test unit 1005 | * 1006 | * @param {JestTestName} Name of Test 1007 | * @param {Function} Test 1008 | * @param {number} Timeout for the test, in milliseconds. 1009 | */ 1010 | ( 1011 | name: JestTestName, 1012 | fn?: (done: JestDoneFn) => ?Promise, 1013 | timeout?: number 1014 | ): void, 1015 | /** 1016 | * Only run this test 1017 | * 1018 | * @param {JestTestName} Name of Test 1019 | * @param {Function} Test 1020 | * @param {number} Timeout for the test, in milliseconds. 1021 | */ 1022 | only: {| 1023 | ( 1024 | name: JestTestName, 1025 | fn?: (done: JestDoneFn) => ?Promise, 1026 | timeout?: number 1027 | ): void, 1028 | each( 1029 | ...table: Array | mixed> | [Array, string] 1030 | ): ( 1031 | name: JestTestName, 1032 | fn?: (...args: Array) => ?Promise, 1033 | timeout?: number 1034 | ) => void, 1035 | |}, 1036 | /** 1037 | * Skip running this test 1038 | * 1039 | * @param {JestTestName} Name of Test 1040 | * @param {Function} Test 1041 | * @param {number} Timeout for the test, in milliseconds. 1042 | */ 1043 | skip: {| 1044 | ( 1045 | name: JestTestName, 1046 | fn?: (done: JestDoneFn) => ?Promise, 1047 | timeout?: number 1048 | ): void, 1049 | each( 1050 | ...table: Array | mixed> | [Array, string] 1051 | ): ( 1052 | name: JestTestName, 1053 | fn?: (...args: Array) => ?Promise, 1054 | timeout?: number 1055 | ) => void, 1056 | |}, 1057 | /** 1058 | * Highlight planned tests in the summary output 1059 | * 1060 | * @param {String} Name of Test to do 1061 | */ 1062 | todo(name: string): void, 1063 | /** 1064 | * Run the test concurrently 1065 | * 1066 | * @param {JestTestName} Name of Test 1067 | * @param {Function} Test 1068 | * @param {number} Timeout for the test, in milliseconds. 1069 | */ 1070 | concurrent( 1071 | name: JestTestName, 1072 | fn?: (done: JestDoneFn) => ?Promise, 1073 | timeout?: number 1074 | ): void, 1075 | /** 1076 | * each runs this test against array of argument arrays per each run 1077 | * 1078 | * @param {table} table of Test 1079 | */ 1080 | each( 1081 | ...table: Array | mixed> | [Array, string] 1082 | ): ( 1083 | name: JestTestName, 1084 | fn?: (...args: Array) => ?Promise, 1085 | timeout?: number 1086 | ) => void, 1087 | ... 1088 | }; 1089 | 1090 | declare function fit( 1091 | name: JestTestName, 1092 | fn: (done: JestDoneFn) => ?Promise, 1093 | timeout?: number 1094 | ): void; 1095 | /** An individual test unit */ 1096 | declare var test: typeof it; 1097 | /** A disabled group of tests */ 1098 | declare var xdescribe: typeof describe; 1099 | /** A focused group of tests */ 1100 | declare var fdescribe: typeof describe; 1101 | /** A disabled individual test */ 1102 | declare var xit: typeof it; 1103 | /** A disabled individual test */ 1104 | declare var xtest: typeof it; 1105 | 1106 | type JestPrettyFormatColors = { 1107 | comment: { 1108 | close: string, 1109 | open: string, 1110 | ... 1111 | }, 1112 | content: { 1113 | close: string, 1114 | open: string, 1115 | ... 1116 | }, 1117 | prop: { 1118 | close: string, 1119 | open: string, 1120 | ... 1121 | }, 1122 | tag: { 1123 | close: string, 1124 | open: string, 1125 | ... 1126 | }, 1127 | value: { 1128 | close: string, 1129 | open: string, 1130 | ... 1131 | }, 1132 | ... 1133 | }; 1134 | 1135 | type JestPrettyFormatIndent = (string) => string; 1136 | type JestPrettyFormatRefs = Array; 1137 | type JestPrettyFormatPrint = (any) => string; 1138 | type JestPrettyFormatStringOrNull = string | null; 1139 | 1140 | type JestPrettyFormatOptions = {| 1141 | callToJSON: boolean, 1142 | edgeSpacing: string, 1143 | escapeRegex: boolean, 1144 | highlight: boolean, 1145 | indent: number, 1146 | maxDepth: number, 1147 | min: boolean, 1148 | plugins: JestPrettyFormatPlugins, 1149 | printFunctionName: boolean, 1150 | spacing: string, 1151 | theme: {| 1152 | comment: string, 1153 | content: string, 1154 | prop: string, 1155 | tag: string, 1156 | value: string, 1157 | |}, 1158 | |}; 1159 | 1160 | type JestPrettyFormatPlugin = { 1161 | print: ( 1162 | val: any, 1163 | serialize: JestPrettyFormatPrint, 1164 | indent: JestPrettyFormatIndent, 1165 | opts: JestPrettyFormatOptions, 1166 | colors: JestPrettyFormatColors 1167 | ) => string, 1168 | test: (any) => boolean, 1169 | ... 1170 | }; 1171 | 1172 | type JestPrettyFormatPlugins = Array; 1173 | 1174 | /** The expect function is used every time you want to test a value */ 1175 | declare var expect: { 1176 | /** The object that you want to make assertions against */ 1177 | ( 1178 | value: any 1179 | ): JestExpectType & 1180 | JestPromiseType & 1181 | EnzymeMatchersType & 1182 | DomTestingLibraryType & 1183 | JestJQueryMatchersType & 1184 | JestStyledComponentsMatchersType & 1185 | JestExtendedMatchersType & 1186 | SnapshotDiffType, 1187 | /** Add additional Jasmine matchers to Jest's roster */ 1188 | extend(matchers: { [name: string]: JestMatcher, ... }): void, 1189 | /** Add a module that formats application-specific data structures. */ 1190 | addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, 1191 | assertions(expectedAssertions: number): void, 1192 | hasAssertions(): void, 1193 | any(value: mixed): JestAsymmetricEqualityType, 1194 | anything(): any, 1195 | arrayContaining(value: Array): Array, 1196 | objectContaining(value: Object): Object, 1197 | /** Matches any received string that contains the exact expected string. */ 1198 | stringContaining(value: string): string, 1199 | stringMatching(value: string | RegExp): string, 1200 | not: { 1201 | arrayContaining: (value: $ReadOnlyArray) => Array, 1202 | objectContaining: (value: { ... }) => Object, 1203 | stringContaining: (value: string) => string, 1204 | stringMatching: (value: string | RegExp) => string, 1205 | ... 1206 | }, 1207 | ... 1208 | }; 1209 | 1210 | // TODO handle return type 1211 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 1212 | declare function spyOn(value: mixed, method: string): Object; 1213 | 1214 | /** Holds all functions related to manipulating test runner */ 1215 | declare var jest: JestObjectType; 1216 | 1217 | /** 1218 | * The global Jasmine object, this is generally not exposed as the public API, 1219 | * using features inside here could break in later versions of Jest. 1220 | */ 1221 | declare var jasmine: { 1222 | DEFAULT_TIMEOUT_INTERVAL: number, 1223 | any(value: mixed): JestAsymmetricEqualityType, 1224 | anything(): any, 1225 | arrayContaining(value: Array): Array, 1226 | clock(): JestClockType, 1227 | createSpy(name: string): JestSpyType, 1228 | createSpyObj( 1229 | baseName: string, 1230 | methodNames: Array 1231 | ): { [methodName: string]: JestSpyType, ... }, 1232 | objectContaining(value: Object): Object, 1233 | stringMatching(value: string): string, 1234 | ... 1235 | }; 1236 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | number: require("./number.js"), 3 | vector: require("./vector.js"), 4 | point: require("./point.js"), 5 | line: require("./line.js"), 6 | ray: require("./ray.js"), 7 | }; 8 | -------------------------------------------------------------------------------- /line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Line Utils 3 | * A line is an array of two points e.g. [[-5, 0], [5, 0]]. 4 | * 5 | * @flow 6 | */ 7 | 8 | import * as kpoint from "./point.js"; 9 | import * as kvector from "./vector.js"; 10 | 11 | import type {Point} from "./point.js"; 12 | 13 | export type Line = [Point, Point]; 14 | 15 | export function distanceToPoint(line: Line, point: Point): number { 16 | return kpoint.distanceToLine(point, line); 17 | } 18 | 19 | export function reflectPoint( 20 | line: Line, 21 | point: Point 22 | ): $ReadOnlyArray /* TODO: convert to Point */ { 23 | return kpoint.reflectOverLine(point, line); 24 | } 25 | 26 | export function midpoint(line: Line): Point { 27 | return [(line[0][0] + line[1][0]) / 2, (line[0][1] + line[1][1]) / 2]; 28 | } 29 | 30 | export function equal(line1: Line, line2: Line, tolerance?: number): boolean { 31 | // TODO: A nicer implementation might just check collinearity of 32 | // vectors using underscore magick 33 | // Compare the directions of the lines 34 | var v1 = kvector.subtract(line1[1], line1[0]); 35 | var v2 = kvector.subtract(line2[1], line2[0]); 36 | if (!kvector.collinear(v1, v2, tolerance)) { 37 | return false; 38 | } 39 | // If the start point is the same for the two lines, then they are the same 40 | if (kpoint.equal(line1[0], line2[0])) { 41 | return true; 42 | } 43 | // Make sure that the direction to get from line1 to 44 | // line2 is the same as the direction of the lines 45 | var line1ToLine2Vector = kvector.subtract(line2[0], line1[0]); 46 | return kvector.collinear(v1, line1ToLine2Vector, tolerance); 47 | } 48 | -------------------------------------------------------------------------------- /lint_blacklist.txt: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /logo.js: -------------------------------------------------------------------------------- 1 | // This file describes the graphie source code of the kmath logo 2 | // currently used on khan.github.io. 3 | // 4 | // Also located at http://ka-perseus-graphie.s3.amazonaws.com/42ef3cbadc3e6464124533191728c3c5c55c7355.svg 5 | 6 | init({ 7 | range: [ 8 | [0, 10], 9 | [0, 10], 10 | ], 11 | scale: 40, 12 | }); 13 | 14 | ellipse(5, 5, 5, { 15 | stroke: null, 16 | fill: GREEN, 17 | }); 18 | 19 | line([2, 5], [8.5, 5], { 20 | stroke: "WHITE", 21 | fill: "WHITE", 22 | strokeWidth: 25, 23 | arrows: "->", 24 | }); 25 | 26 | line([5, 2], [5, 8], { 27 | stroke: "WHITE", 28 | fill: "WHITE", 29 | strokeWidth: 25, 30 | }); 31 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Number Utils 3 | * A number is a js-number, e.g. 5.12 4 | * 5 | * @flow 6 | */ 7 | 8 | import _ from "underscore"; 9 | 10 | export const DEFAULT_TOLERANCE: number = 1e-9; 11 | 12 | // TODO: Should this just be Number.Epsilon 13 | export const EPSILON: number = Math.pow(2, -42); 14 | 15 | export function is(x: any): boolean { 16 | return _.isNumber(x) && !_.isNaN(x); 17 | } 18 | 19 | export function equal(x: number, y: number, tolerance?: number): boolean { 20 | // Checking for undefined makes this function behave nicely 21 | // with vectors of different lengths that are _.zip'd together 22 | if (x == null || y == null) { 23 | return x === y; 24 | } 25 | // We check === here so that +/-Infinity comparisons work correctly 26 | if (x === y) { 27 | return true; 28 | } 29 | if (tolerance == null) { 30 | tolerance = DEFAULT_TOLERANCE; 31 | } 32 | return Math.abs(x - y) < tolerance; 33 | } 34 | 35 | export function sign( 36 | x: number, 37 | tolerance?: number 38 | ): number /* Should be: 0 | 1 | -1 */ { 39 | return equal(x, 0, tolerance) ? 0 : Math.abs(x) / x; 40 | } 41 | 42 | export function isInteger(num: number, tolerance?: number): boolean { 43 | return equal(Math.round(num), num, tolerance); 44 | } 45 | 46 | // Round a number to a certain number of decimal places 47 | export function round(num: number, precision: number): number { 48 | var factor = Math.pow(10, precision); 49 | return Math.round(num * factor) / factor; 50 | } 51 | 52 | // Round num to the nearest multiple of increment 53 | // i.e. roundTo(83, 5) -> 85 54 | export function roundTo(num: number, increment: number): number { 55 | return Math.round(num / increment) * increment; 56 | } 57 | 58 | export function floorTo(num: number, increment: number): number { 59 | return Math.floor(num / increment) * increment; 60 | } 61 | 62 | export function ceilTo(num: number, increment: number): number { 63 | return Math.ceil(num / increment) * increment; 64 | } 65 | 66 | /** 67 | * toFraction 68 | * 69 | * Returns a [numerator, denominator] array rational representation 70 | * of `decimal` 71 | * 72 | * See http://en.wikipedia.org/wiki/Continued_fraction for implementation 73 | * details 74 | * 75 | * toFraction(4/8) => [1, 2] 76 | * toFraction(0.66) => [33, 50] 77 | * toFraction(0.66, 0.01) => [2/3] 78 | * toFraction(283 + 1/3) => [850, 3] 79 | */ 80 | export function toFraction( 81 | decimal: number, 82 | tolerance: number = EPSILON, // can't be 0 83 | max_denominator: number = 1000 84 | ): [number, number] { 85 | // Initialize everything to compute successive terms of 86 | // continued-fraction approximations via recurrence relation 87 | var n = [1, 0], 88 | d = [0, 1]; 89 | var a = Math.floor(decimal), 90 | t; 91 | var rem = decimal - a; 92 | 93 | while (d[0] <= max_denominator) { 94 | if (equal(n[0] / d[0], decimal, tolerance)) { 95 | return [n[0], d[0]]; 96 | } 97 | n = [a * n[0] + n[1], n[0]]; 98 | d = [a * d[0] + d[1], d[0]]; 99 | a = Math.floor(1 / rem); 100 | rem = 1 / rem - a; 101 | } 102 | 103 | // We failed to find a nice rational representation, 104 | // so return an irrational "fraction" 105 | return [decimal, 1]; 106 | } 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kmath", 3 | "version": "0.0.2", 4 | "description": "Khan Academy's Javascript Numeric Math Utilities", 5 | "main": "index.js", 6 | "scripts": { 7 | "format": "prettier --write .", 8 | "test": "jest", 9 | "build": "webpack --mode production" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/Khan/kmath.git" 14 | }, 15 | "keywords": [ 16 | "math" 17 | ], 18 | "author": "Khan Academy", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/Khan/kmath/issues" 22 | }, 23 | "homepage": "https://github.com/Khan/kmath", 24 | "devDependencies": { 25 | "@babel/core": "^7.16.7", 26 | "@babel/plugin-transform-flow-strip-types": "^7.16.7", 27 | "@babel/preset-env": "^7.16.8", 28 | "babel-loader": "^8.2.3", 29 | "flow-bin": "0.167.1", 30 | "jest": "^27.4.7", 31 | "prettier": "^2.5.1", 32 | "webpack": "^5.66.0", 33 | "webpack-cli": "^4.9.1" 34 | }, 35 | "dependencies": { 36 | "underscore": "1.4.4" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /point.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Point Utils 3 | * A point is an array of two numbers e.g. [0, 0]. 4 | * 5 | * @flow 6 | */ 7 | 8 | import _ from "underscore"; 9 | 10 | import * as kvector from "./vector.js"; 11 | import * as knumber from "./number.js"; 12 | 13 | // A point, in 2D, 3D, or nD space. 14 | export type Point = $ReadOnlyArray; 15 | 16 | // Rotate point (around origin unless a center is specified) 17 | export function rotateRad(point: Point, theta: number, center: Point): Point { 18 | if (center === undefined) { 19 | return kvector.rotateRad(point, theta); 20 | } else { 21 | return kvector.add( 22 | center, 23 | kvector.rotateRad(kvector.subtract(point, center), theta) 24 | ); 25 | } 26 | } 27 | 28 | export function rotateDeg(point: Point, theta: number, center: Point): Point { 29 | if (center === undefined) { 30 | return kvector.rotateDeg(point, theta); 31 | } else { 32 | return kvector.add( 33 | center, 34 | kvector.rotateDeg(kvector.subtract(point, center), theta) 35 | ); 36 | } 37 | } 38 | 39 | // Distance between two points 40 | export function distanceToPoint(point1: Point, point2: Point): number { 41 | return kvector.length(kvector.subtract(point1, point2)); 42 | } 43 | 44 | // Distance between point and line 45 | export function distanceToLine(point: Point, line: [Point, Point]): number { 46 | var lv = kvector.subtract(line[1], line[0]); 47 | var pv = kvector.subtract(point, line[0]); 48 | var projectedPv = kvector.projection(pv, lv); 49 | var distancePv = kvector.subtract(projectedPv, pv); 50 | return kvector.length(distancePv); 51 | } 52 | 53 | // Reflect point over line 54 | export function reflectOverLine( 55 | point: Point, 56 | line: [Point, Point] 57 | ): $ReadOnlyArray /* TODO: convert to Point */ { 58 | var lv = kvector.subtract(line[1], line[0]); 59 | var pv = kvector.subtract(point, line[0]); 60 | var projectedPv = kvector.projection(pv, lv); 61 | var reflectedPv = kvector.subtract(kvector.scale(projectedPv, 2), pv); 62 | return kvector.add(line[0], reflectedPv); 63 | } 64 | 65 | /** 66 | * Compares two points, returning -1, 0, or 1, for use with 67 | * Array.prototype.sort 68 | * 69 | * Note: This technically doesn't satisfy the total-ordering 70 | * requirements of Array.prototype.sort unless equalityTolerance 71 | * is 0. In some cases very close points that compare within a 72 | * few equalityTolerances could appear in the wrong order. 73 | */ 74 | export function compare( 75 | point1: Point, 76 | point2: Point, 77 | equalityTolerance?: number 78 | ): number /* TODO: convert to -1 | 0 | 1 type */ { 79 | if (point1.length !== point2.length) { 80 | return point1.length - point2.length; 81 | } 82 | for (var i = 0; i < point1.length; i++) { 83 | if (!knumber.equal(point1[i], point2[i], equalityTolerance)) { 84 | return point1[i] - point2[i]; 85 | } 86 | } 87 | return 0; 88 | } 89 | 90 | // Check if a value is a point 91 | export const is = kvector.is; 92 | 93 | // Add and subtract vector(s) 94 | export const addVector = kvector.add; 95 | export const addVectors = kvector.add; 96 | export const subtractVector = kvector.subtract; 97 | export const equal = kvector.equal; 98 | 99 | // Convert from cartesian to polar and back 100 | export const polarRadFromCart = kvector.polarRadFromCart; 101 | export const polarDegFromCart = kvector.polarDegFromCart; 102 | export const cartFromPolarRad = kvector.cartFromPolarRad; 103 | export const cartFromPolarDeg = kvector.cartFromPolarDeg; 104 | 105 | // Rounding 106 | export const round = kvector.round; 107 | export const roundTo = kvector.roundTo; 108 | export const floorTo = kvector.floorTo; 109 | export const ceilTo = kvector.ceilTo; 110 | -------------------------------------------------------------------------------- /ray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ray Utils 3 | * A ray (→) is an array of an endpoint and another point along the ray. 4 | * For example, [[0, 0], [1, 0]] is the ray starting at the origin and 5 | * traveling along the positive x-axis. 6 | * 7 | * @flow 8 | */ 9 | 10 | import * as kvector from "./vector.js"; 11 | import * as kpoint from "./point.js"; 12 | 13 | import type {Point} from "./point"; 14 | 15 | export type Ray = [Point, Point]; 16 | 17 | export function equal(ray1: Ray, ray2: Ray, tolerance: number): boolean { 18 | // Compare the directions of the rays 19 | var v1 = kvector.subtract(ray1[1], ray1[0]); 20 | var v2 = kvector.subtract(ray2[1], ray2[0]); 21 | 22 | var sameOrigin = kpoint.equal(ray1[0], ray2[0]); 23 | var codirectional = kvector.codirectional(v1, v2, tolerance); 24 | 25 | return sameOrigin && codirectional; 26 | } 27 | -------------------------------------------------------------------------------- /vector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vector Utils 3 | * A vector is an array of numbers e.g. [0, 3, 4]. 4 | * 5 | * @flow 6 | */ 7 | 8 | import _ from "underscore"; 9 | import * as knumber from "./number.js"; 10 | 11 | function arraySum(array: $ReadOnlyArray): number { 12 | return array.reduce((memo, arg) => memo + arg, 0); 13 | } 14 | 15 | function arrayProduct(array: $ReadOnlyArray): number { 16 | return array.reduce((memo, arg) => memo * arg, 1); 17 | } 18 | 19 | export function is(vec: $ReadOnlyArray, dimension: number): boolean { 20 | if (!_.isArray(vec)) { 21 | return false; 22 | } 23 | if (dimension !== undefined && vec.length !== dimension) { 24 | return false; 25 | } 26 | return vec.every(knumber.is); 27 | } 28 | 29 | // Normalize to a unit vector 30 | export function normalize(v: $ReadOnlyArray): $ReadOnlyArray { 31 | return scale(v, 1 / length(v)); 32 | } 33 | 34 | // Length/magnitude of a vector 35 | export function length(v: $ReadOnlyArray): number { 36 | return Math.sqrt(dot(v, v)); 37 | } 38 | // Dot product of two vectors 39 | export function dot( 40 | a: $ReadOnlyArray, 41 | b: $ReadOnlyArray 42 | ): number { 43 | var zipped = _.zip(a, b); 44 | var multiplied = zipped.map(arrayProduct); 45 | return arraySum(multiplied); 46 | } 47 | 48 | /* vector-add multiple [x, y] coords/vectors 49 | * 50 | * add([1, 2], [3, 4]) -> [4, 6] 51 | */ 52 | export function add( 53 | ...vecs: $ReadOnlyArray<$ReadOnlyArray> 54 | ): $ReadOnlyArray { 55 | var zipped = _.zip(...vecs); 56 | return zipped.map(arraySum); 57 | } 58 | 59 | export function subtract( 60 | v1: $ReadOnlyArray, 61 | v2: $ReadOnlyArray 62 | ): $ReadOnlyArray { 63 | return _.zip(v1, v2).map((dim) => dim[0] - dim[1]); 64 | } 65 | 66 | export function negate(v: $ReadOnlyArray): $ReadOnlyArray { 67 | return v.map((x) => { 68 | return -x; 69 | }); 70 | } 71 | 72 | // Scale a vector 73 | export function scale( 74 | v1: $ReadOnlyArray, 75 | scalar: number 76 | ): $ReadOnlyArray { 77 | return v1.map((x) => { 78 | return x * scalar; 79 | }); 80 | } 81 | 82 | export function equal( 83 | v1: $ReadOnlyArray, 84 | v2: $ReadOnlyArray, 85 | tolerance?: number 86 | ): boolean { 87 | // _.zip will nicely deal with the lengths, going through 88 | // the length of the longest vector. knumber.equal then 89 | // returns false for any number compared to the undefined 90 | // passed in if one of the vectors is shorter. 91 | return _.zip(v1, v2).every((pair) => 92 | knumber.equal(pair[0], pair[1], tolerance) 93 | ); 94 | } 95 | 96 | export function codirectional( 97 | v1: $ReadOnlyArray, 98 | v2: $ReadOnlyArray, 99 | tolerance?: number 100 | ): boolean { 101 | // The origin is trivially codirectional with all other vectors. 102 | // This gives nice semantics for codirectionality between points when 103 | // comparing their difference vectors. 104 | if ( 105 | knumber.equal(length(v1), 0, tolerance) || 106 | knumber.equal(length(v2), 0, tolerance) 107 | ) { 108 | return true; 109 | } 110 | 111 | v1 = normalize(v1); 112 | v2 = normalize(v2); 113 | 114 | return equal(v1, v2, tolerance); 115 | } 116 | 117 | export function collinear( 118 | v1: $ReadOnlyArray, 119 | v2: $ReadOnlyArray, 120 | tolerance?: number 121 | ): boolean { 122 | return ( 123 | codirectional(v1, v2, tolerance) || 124 | codirectional(v1, negate(v2), tolerance) 125 | ); 126 | } 127 | 128 | // Convert a cartesian coordinate into a radian polar coordinate 129 | export function polarRadFromCart( 130 | v: $ReadOnlyArray 131 | ): $ReadOnlyArray { 132 | var radius = length(v); 133 | var theta = Math.atan2(v[1], v[0]); 134 | 135 | // Convert angle range from [-pi, pi] to [0, 2pi] 136 | if (theta < 0) { 137 | theta += 2 * Math.PI; 138 | } 139 | 140 | return [radius, theta]; 141 | } 142 | 143 | // Converts a cartesian coordinate into a degree polar coordinate 144 | export function polarDegFromCart( 145 | v: $ReadOnlyArray 146 | ): $ReadOnlyArray /* TODO: convert to tuple/Point */ { 147 | var polar = polarRadFromCart(v); 148 | return [polar[0], (polar[1] * 180) / Math.PI]; 149 | } 150 | 151 | /* Convert a polar coordinate into a cartesian coordinate 152 | * 153 | * Examples: 154 | * cartFromPolarRad(5, Math.PI) 155 | * cartFromPolarRad([5, Math.PI]) 156 | */ 157 | export function cartFromPolarRad( 158 | radius: number, 159 | theta?: number = 0 160 | ): $ReadOnlyArray /* TODO: convert to tuple/Point */ { 161 | return [radius * Math.cos(theta), radius * Math.sin(theta)]; 162 | } 163 | 164 | /* Convert a polar coordinate into a cartesian coordinate 165 | * 166 | * Examples: 167 | * cartFromPolarDeg(5, 30) 168 | * cartFromPolarDeg([5, 30]) 169 | */ 170 | export function cartFromPolarDeg( 171 | radius: number, 172 | theta?: number = 0 173 | ): $ReadOnlyArray { 174 | return cartFromPolarRad(radius, (theta * Math.PI) / 180); 175 | } 176 | 177 | // Rotate vector 178 | export function rotateRad( 179 | v: $ReadOnlyArray, 180 | theta: number 181 | ): $ReadOnlyArray { 182 | var polar = polarRadFromCart(v); 183 | var angle = polar[1] + theta; 184 | return cartFromPolarRad(polar[0], angle); 185 | } 186 | 187 | export function rotateDeg( 188 | v: $ReadOnlyArray, 189 | theta: number 190 | ): $ReadOnlyArray { 191 | var polar = polarDegFromCart(v); 192 | var angle = polar[1] + theta; 193 | return cartFromPolarDeg(polar[0], angle); 194 | } 195 | 196 | // Angle between two vectors 197 | export function angleRad( 198 | v1: $ReadOnlyArray, 199 | v2: $ReadOnlyArray 200 | ): number { 201 | return Math.acos(dot(v1, v2) / (length(v1) * length(v2))); 202 | } 203 | 204 | export function angleDeg( 205 | v1: $ReadOnlyArray, 206 | v2: $ReadOnlyArray 207 | ): number { 208 | return (angleRad(v1, v2) * 180) / Math.PI; 209 | } 210 | 211 | // Vector projection of v1 onto v2 212 | export function projection( 213 | v1: $ReadOnlyArray, 214 | v2: $ReadOnlyArray 215 | ): $ReadOnlyArray { 216 | var scalar = dot(v1, v2) / dot(v2, v2); 217 | return scale(v2, scalar); 218 | } 219 | 220 | // Round each number to a certain number of decimal places 221 | export function round( 222 | vec: $ReadOnlyArray, 223 | precision: $ReadOnlyArray | number 224 | ): $ReadOnlyArray { 225 | return vec.map((elem, i) => 226 | // $FlowFixMe[prop-missing] 227 | // $FlowFixMe[incompatible-call] 228 | knumber.round(elem, precision[i] || precision) 229 | ); 230 | } 231 | 232 | // Round each number to the nearest increment 233 | export function roundTo( 234 | vec: $ReadOnlyArray, 235 | increment: $ReadOnlyArray | number 236 | ): $ReadOnlyArray { 237 | return vec.map((elem, i) => 238 | // $FlowFixMe[prop-missing] 239 | // $FlowFixMe[incompatible-call] 240 | knumber.roundTo(elem, increment[i] || increment) 241 | ); 242 | } 243 | 244 | export function floorTo( 245 | vec: $ReadOnlyArray, 246 | increment: $ReadOnlyArray | number 247 | ): $ReadOnlyArray { 248 | return vec.map((elem, i) => 249 | // $FlowFixMe[prop-missing] 250 | // $FlowFixMe[incompatible-call] 251 | knumber.floorTo(elem, increment[i] || increment) 252 | ); 253 | } 254 | 255 | export function ceilTo( 256 | vec: $ReadOnlyArray, 257 | increment: $ReadOnlyArray | number 258 | ): $ReadOnlyArray { 259 | return vec.map((elem, i) => 260 | // $FlowFixMe[prop-missing] 261 | // $FlowFixMe[incompatible-call] 262 | knumber.ceilTo(elem, increment[i] || increment) 263 | ); 264 | } 265 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./index.js", 5 | output: { 6 | path: path.join(__dirname, "build"), 7 | filename: "kmath.js", 8 | library: "kmath", 9 | libraryTarget: "umd", 10 | }, 11 | externals: { 12 | underscore: { 13 | commonjs: "underscore", 14 | commonjs2: "underscore", 15 | amd: "underscore", 16 | root: "_", 17 | }, 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | use: {loader: "babel-loader"}, 25 | }, 26 | ], 27 | }, 28 | }; 29 | --------------------------------------------------------------------------------