diff --git a/example/complex.ts b/example/complex.ts index 6b14368..c6ced5d 100644 --- a/example/complex.ts +++ b/example/complex.ts @@ -6,7 +6,7 @@ import { INTERPOLATORS } from "./color"; import { saveFrames, saveVideo } from "./util.ts"; import { Complex } from "../src"; -const ENABLE_CAPTURE = true; +const ENABLE_CAPTURE = false; const { canvas, p, h2, h3 } = van.tags; const { math, msup, mrow, mo, mi, mn } = van.tagsNS( @@ -56,7 +56,7 @@ function Mandelbrot() { }; const PARAMS = { ...DEFAULT_PARAMS }; - const canv = canvas({ + let canv: HTMLCanvasElement | OffscreenCanvas = canvas({ width: WIDTH, height: HEIGHT, class: "grabbable", @@ -87,10 +87,12 @@ function Mandelbrot() { zoom: PARAMS.zoom, stride: PARAMS.stride, iterations: PARAMS.iterations, + exponent: PARAMS.mandel_exponent, + c: PARAMS.julia_c, }); }, }); - const ctx = canv.getContext("2d")!; + let ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D = canv.getContext("2d")!; const pane = new Pane({ title: "Parameters" }); const tab = pane.addTab({ @@ -100,13 +102,13 @@ function Mandelbrot() { label: "c", x: { min: -1, max: 1 }, y: { min: -1, max: 1 }, - }); + }).on("change", draw); tab.pages[1].addBinding(PARAMS, "mandel_exponent", { label: "exponent", min: 2, max: 10, step: 1, - }); + }).on("change", draw); tab.on("select", draw); pane.addBlade({ view: "separator" }); pane.addBinding(PARAMS, "iterations", { @@ -114,20 +116,20 @@ function Mandelbrot() { max: 100, step: 1, format: (v) => v.toFixed(0), - }); + }).on("change", draw); pane.addBinding(PARAMS, "stride", { min: 1, max: 100, step: 1, format: (v) => v.toFixed(0), - }); + }).on("change", draw); pane.addBinding(PARAMS, "scale", { options: Object.keys(INTERPOLATORS).reduce( (acc, key) => ({ ...acc, [key]: key }), {} ), - }); - pane.addBinding(PARAMS, "invert"); + }).on("change", draw); + pane.addBinding(PARAMS, "invert").on("change", draw); pane.addBlade({ view: "separator" }); pane.addButton({ title: "Reset" }).on("click", reset); @@ -148,65 +150,104 @@ function Mandelbrot() { // PARAMS.julia_c = { x: -0.1838235, y: -0.6681985 }; // } + // { + // tab.pages[1].selected = true; + // tab.pages[0].selected = false; + // PARAMS.iterations = 100; + // PARAMS.stride = 1; + // PARAMS.offset = { x: -19.754255103824594, y: 22.3536824833637 }; + // PARAMS.zoom = 0.18129028535257674; + // PARAMS.mandel_exponent = 2; + // PARAMS.scale = "Spectral"; + // PARAMS.invert = true; + // } + + // { + // tab.pages[0].selected = true; + // tab.pages[1].selected = false; + // PARAMS.iterations = 100; + // PARAMS.stride = 1; + // PARAMS.offset = { x: -3.1557671462338437, y: 0.45462455886752356 }; + // PARAMS.zoom = 0.04624599826929624; + // PARAMS.scale = "RdYlBu"; + // PARAMS.invert = true; + // PARAMS.julia_c = { x: -0.22058823529411764, y: -0.7270220588235294 }; + // } + { - tab.pages[1].selected = true; - tab.pages[0].selected = false; - PARAMS.iterations = 100; + tab.pages[0].selected = true; + tab.pages[1].selected = false; + PARAMS.iterations = 200; PARAMS.stride = 1; - PARAMS.offset = { x: -19.754255103824594, y: 22.3536824833637 }; - PARAMS.zoom = 0.18129028535257674; - PARAMS.mandel_exponent = 2; - PARAMS.scale = "Magma"; + PARAMS.offset = { x: -0.08185745207194693, y: 0.02633283452359185 }; + PARAMS.zoom = 0.002475803738526237; + PARAMS.scale = "Earthen"; + PARAMS.invert = true; + PARAMS.julia_c = { x: -0.20588235294117652, y: 0.8318014705882353 }; } + draw(); + canv = new OffscreenCanvas(WIDTH, HEIGHT); + ctx = canv.getContext("2d")!; saveVideo(canv, () => { draw(); const { x, y } = PARAMS.offset; PARAMS.zoom *= ZOOM_SCALE; PARAMS.offset = { x: x * ZOOM_SCALE, y: y * ZOOM_SCALE }; - }, undefined, { fps: 10, quality: 1 }); + }, undefined, { fps: 30, count: 600, quality: 1 }); }); } - pane.on("change", draw); + function mandelbrot(iterations: number, scale: number, exponent: number) { + return function(x: number, y: number) { + const c = new Complex( + (x / WIDTH) * scale - scale / 2, + (y / HEIGHT) * scale - scale / 2 + ); + let z = Complex.from(c); - function divergesIn(x: number, y: number) { - const scale = 4 / PARAMS.zoom; - const c = new Complex( - (x / WIDTH) * scale - scale / 2, - (y / HEIGHT) * scale - scale / 2 - ); - let z = Complex.from(c); - - if (tab.pages[1].selected) { - // MANDELBROT - for (let i = 0; i < PARAMS.iterations; i++) { - z = z.pow(PARAMS.mandel_exponent).add(c); + for (let i = 0; i < iterations; i++) { + z = z.pow(exponent).add(c); if (Math.sqrt(z.real ** 2 + z.imaginary ** 2) > 2) { return i; } } - } else { - // JULIA + + return iterations; + } + } + + function julia(iterations: number, scale: number, c: Complex) { + return function(x: number, y: number) { + let z = new Complex( + (x / WIDTH) * scale - scale / 2, + (y / HEIGHT) * scale - scale / 2 + ); + for (let i = 0; i < PARAMS.iterations; i++) { - z = z.pow(2).add(new Complex(PARAMS.julia_c.x, PARAMS.julia_c.y)); + z = z.pow(2).add(c); if (Math.sqrt(z.real ** 2 + z.imaginary ** 2) > 2) { return i; } } + return iterations; } - return PARAMS.iterations; } function draw() { const { x: offsetX, y: offsetY } = PARAMS.offset; const interpolate = INTERPOLATORS[PARAMS.scale]; + const scale = 4 / PARAMS.zoom; + const algo = tab.pages[0].selected + ? julia(PARAMS.iterations, scale, new Complex(PARAMS.julia_c.x, PARAMS.julia_c.y)) + : mandelbrot(PARAMS.iterations, scale, PARAMS.mandel_exponent); + let i: number; for (let y = 0; y < HEIGHT; y += PARAMS.stride) { for (let x = 0; x < WIDTH; x += PARAMS.stride) { - const i = divergesIn( + i = algo( x + offsetX + PARAMS.stride / 2, y + offsetY + PARAMS.stride / 2 ); diff --git a/example/package.json b/example/package.json index e5722eb..3bd9a6c 100644 --- a/example/package.json +++ b/example/package.json @@ -13,13 +13,13 @@ "@ffmpeg/ffmpeg": "^0.12.10", "@ffmpeg/types": "^0.12.2", "@ffmpeg/util": "^0.12.1", + "@tweakpane/core": "^2.0.3", "@tweakpane/plugin-essentials": "^0.2.1", "d3-color": "^3.1.0", "d3-scale-chromatic": "^3.0.0", "tweakpane": "^4.0.3" }, "devDependencies": { - "@tweakpane/core": "^2.0.3", "@types/d3-color": "^3.1.3", "@types/d3-scale-chromatic": "^3.0.3" } diff --git a/example/pnpm-lock.yaml b/example/pnpm-lock.yaml index 4783037..74682f9 100644 --- a/example/pnpm-lock.yaml +++ b/example/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@ffmpeg/util': specifier: ^0.12.1 version: 0.12.1 + '@tweakpane/core': + specifier: ^2.0.3 + version: 2.0.3 '@tweakpane/plugin-essentials': specifier: ^0.2.1 version: 0.2.1(tweakpane@4.0.3) @@ -28,9 +31,6 @@ dependencies: version: 4.0.3 devDependencies: - '@tweakpane/core': - specifier: ^2.0.3 - version: 2.0.3 '@types/d3-color': specifier: ^3.1.3 version: 3.1.3 @@ -59,7 +59,7 @@ packages: /@tweakpane/core@2.0.3: resolution: {integrity: sha512-qHci4XA1Wngpwy8IzsLh5JEdscz8aDti/9YhyOaq01si+cgNDaZfwzTtXdn1+xTxSnCM+pW4Zb2/4eqn+K1ATw==} - dev: true + dev: false /@tweakpane/plugin-essentials@0.2.1(tweakpane@4.0.3): resolution: {integrity: sha512-VbFU1/uD+CJNFQdfLXUOLjeG5HyUZH97Ox9CxmyVetg1hqjVun3C83HAGFULyhKzl8tSgii8jr304r8QpdHwzQ==} diff --git a/example/tsconfig.json b/example/tsconfig.json new file mode 100644 index 0000000..8335008 --- /dev/null +++ b/example/tsconfig.json @@ -0,0 +1,30 @@ +{ + "include": ["src/"], + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["ESNext", "DOM, DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "dist/", + "resolveJsonModule": true, + "rootDir": "src/", + "skipLibCheck": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "sourceMap": true, + "strict": true, + "target": "ESNext" + } +} diff --git a/example/util.ts b/example/util.ts index 2c37117..b1c8178 100644 --- a/example/util.ts +++ b/example/util.ts @@ -76,8 +76,8 @@ export function saveFrames( } export async function saveVideo( - canvas: HTMLCanvasElement, - tick: (canvas: HTMLCanvasElement, f: number, dt: number) => void, + canvas: HTMLCanvasElement | OffscreenCanvas, + tick: (canvas: HTMLCanvasElement | OffscreenCanvas, f: number, dt: number) => void, filename?: string, options: VideoOptions = {} ) { @@ -89,8 +89,8 @@ export async function saveVideo( const fileName = filename ?? `output.${fileType}`; const ffmpeg = await (async () => { - const BASE_URL = ""; - // const BASE_URL = "https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm"; + // const BASE_URL = "/kdim"; + const BASE_URL = "https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm"; const ffmpeg = new FFmpeg(); await ffmpeg.load({ coreURL: await toBlobURL(`${BASE_URL}/ffmpeg-core.js`, 'text/javascript'), @@ -102,7 +102,7 @@ export async function saveVideo( async function writeFrame(): Promise { return new Promise((resolve, reject) => { - canvas.toBlob( + canvas instanceof HTMLCanvasElement ? canvas.toBlob( async (blob) => { if (!blob) { reject(new Error("Unable to create Blob from canvas")); @@ -110,7 +110,12 @@ export async function saveVideo( const data = await fetchFile(blob!); await ffmpeg.writeFile(`${f}.png`, data); resolve(); - }, "png", frameOptions.quality); + }, "png", frameOptions.quality) : (async () => { + const blob = await canvas.convertToBlob(frameOptions); + const data = await fetchFile(blob); + await ffmpeg.writeFile(`${f}.png`, data); + resolve(); + })(); }); } diff --git a/example/vite.config.ts b/example/vite.config.ts index 834e70d..88a41f7 100644 --- a/example/vite.config.ts +++ b/example/vite.config.ts @@ -5,6 +5,6 @@ export default defineConfig({ // dynamicImportVarsOptions: { // exclude: [] // } }, - // optimizeDeps: { exclude: ["worker.js"] }, + optimizeDeps: { exclude: ["@ffmpeg/ffmpeg", "@ffmpeg/util"] }, base: "https://rektdeckard.github.io/kdim/", }); diff --git a/package.json b/package.json index 7d10a12..b842763 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kdim", - "version": "0.6.5", + "version": "0.6.6", "description": "Utility data stuctures, math, and types for JS", "author": { "name": "Tobias Fried", @@ -38,9 +38,10 @@ "build": "vite build && tsc --emitDeclarationOnly", "test": "vitest", "coverage": "vitest run --coverage", - "ex": "vite serve ./example", + "ex": "vite serve --force ./example", "ex:serve": "pnpm ex", "ex:build": "vite build ./example", + "ex:preview": "pnpm ex:build && vite preview ./example", "ex:deploy": "pnpm ex:build && gh-pages -d ./example/dist" }, "devDependencies": { diff --git a/src/data/KDTree.ts b/src/data/KDTree.ts index 6b05afb..251a0e4 100644 --- a/src/data/KDTree.ts +++ b/src/data/KDTree.ts @@ -70,9 +70,7 @@ export class KDTree implements Iterable> { this.#dimensions = point.length; } else if (this.#dimensions !== point.length) { throw new TypeError( - `Point [${point}] has ${point.length} dimensions, but should have ${ - this.#dimensions - }` + `Point [${point}] has ${point.length} dimensions, but should have ${this.#dimensions}` ); } diff --git a/src/data/builtin.ts b/src/data/builtin.ts index 92836bf..ca2679e 100644 --- a/src/data/builtin.ts +++ b/src/data/builtin.ts @@ -11,25 +11,31 @@ export class ArrayTools { } static unzip(tuples: [A, B][]): [A[], B[]] { - return tuples.reduce<[A[], B[]]>((ab, [a, b]) => { - ab[0].push(a); - ab[1].push(b); - return ab; - }, [[], []]); + return tuples.reduce<[A[], B[]]>( + (ab, [a, b]) => { + ab[0].push(a); + ab[1].push(b); + return ab; + }, + [[], []] + ); } static zip3(a: A[], b: B[], c: C[]): [A, B, C][] { - if (a.length !== b.length || a.length !== c.length) throw new Error("Unequal array lengths"); + if (a.length !== b.length || a.length !== c.length) + throw new Error("Unequal array lengths"); return a.map((ai, i) => [ai, b[i], c[i]]); } static unzip3(tuples: [A, B, C][]): [A[], B[], C[]] { - return tuples.reduce<[A[], B[], C[]]>((abc, [a, b, c]) => { - abc[0].push(a); - abc[1].push(b); - abc[2].push(c); - return abc; - }, [[], [], []]); + return tuples.reduce<[A[], B[], C[]]>( + (abc, [a, b, c]) => { + abc[0].push(a); + abc[1].push(b); + abc[2].push(c); + return abc; + }, + [[], [], []] + ); } } - diff --git a/src/math/complex.ts b/src/math/complex.ts index 50d576e..cbf9e87 100644 --- a/src/math/complex.ts +++ b/src/math/complex.ts @@ -12,20 +12,12 @@ export class Complex Abs, Eq<[Complex | Number]> { - #r: number; - #i: number; + real: number; + imaginary: number; constructor(real: number = 0, imaginary: number = 0) { - this.#r = real; - this.#i = imaginary; - } - - get real() { - return this.#r; - } - - get imaginary(): number { - return this.#i; + this.real = real; + this.imaginary = imaginary; } static from(init: N): Complex { @@ -91,8 +83,8 @@ export class Complex } abs(): Complex { - if (Math.sign(this.#r) === Math.sign(this.#i)) return this; - return new Complex(Math.abs(this.#r), Math.abs(this.#i)); + if (Math.sign(this.real) === Math.sign(this.imaginary)) return this; + return new Complex(Math.abs(this.real), Math.abs(this.imaginary)); } eq(...other: [Number | Complex]): boolean { diff --git a/src/math/hash.ts b/src/math/hash.ts index e62ae34..d639e40 100644 --- a/src/math/hash.ts +++ b/src/math/hash.ts @@ -104,12 +104,12 @@ export function typeHasher( }; return { - dispatch: function (value: any): string { + dispatch: function(value: any): string { const type: Type = value === null ? "null" : typeof value; // @ts-ignore return this[("_" + type) as PropertyHasher](value); }, - _object: function (object: Record) { + _object: function(object: Record) { const pattern = /\[object (.*)\]/i; const objString = Object.prototype.toString.call(object); const test = pattern.exec(objString); @@ -160,7 +160,7 @@ export function typeHasher( write("object:" + keys.length + ":"); const self = this; - return keys.forEach(function (key) { + return keys.forEach(function(key) { self.dispatch(key); write(":"); self.dispatch(object[key]); @@ -168,7 +168,7 @@ export function typeHasher( }); } }, - _array: function (arr: Array, unordered: boolean = false): any { + _array: function(arr: Array, unordered: boolean = false): any { const self = this; write("array:" + arr.length + ":"); if (!unordered || arr.length <= 1) { @@ -202,95 +202,95 @@ export function typeHasher( entries.sort(); return this._array(entries, false); }, - _date: function (date: Date) { + _date: function(date: Date) { return write("date:" + date.toJSON()); }, - _symbol: function (sym: Symbol) { + _symbol: function(sym: Symbol) { return write("symbol:" + sym.toString()); }, - _error: function (err: Error) { + _error: function(err: Error) { return write("error:" + err.toString()); }, - _boolean: function (bool: boolean) { + _boolean: function(bool: boolean) { return write("bool:" + bool.toString()); }, - _string: function (string: string) { + _string: function(string: string) { write("string:" + string.length + ":"); write(string.toString()); }, - _function: function (fn: Function) { + _function: function(fn: Function) { write("fn:"); this.dispatch(fn.toString()); }, - _number: function (number: number) { + _number: function(number: number) { return write("number:" + number.toString()); }, - _xml: function (xml: string) { + _xml: function(xml: string) { return write("xml:" + xml.toString()); }, - _null: function () { + _null: function() { return write("Null"); }, - _undefined: function () { + _undefined: function() { return write("Undefined"); }, - _regexp: function (regex: RegExp) { + _regexp: function(regex: RegExp) { return write("regex:" + regex.toString()); }, - _uint8array: function (arr: Array) { + _uint8array: function(arr: Array) { write("uint8array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _uint8clampedarray: function (arr: Uint8ClampedArray) { + _uint8clampedarray: function(arr: Uint8ClampedArray) { write("uint8clampedarray:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _int8array: function (arr: Int8Array) { + _int8array: function(arr: Int8Array) { write("int8array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _uint16array: function (arr: Uint16Array) { + _uint16array: function(arr: Uint16Array) { write("uint16array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _int16array: function (arr: Int16Array) { + _int16array: function(arr: Int16Array) { write("int16array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _uint32array: function (arr: Uint32Array) { + _uint32array: function(arr: Uint32Array) { write("uint32array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _int32array: function (arr: Int32Array) { + _int32array: function(arr: Int32Array) { write("int32array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _float32array: function (arr: Float32Array) { + _float32array: function(arr: Float32Array) { write("float32array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _float64array: function (arr: Float64Array) { + _float64array: function(arr: Float64Array) { write("float64array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, - _arraybuffer: function (arr: ArrayBuffer) { + _arraybuffer: function(arr: ArrayBuffer) { write("arraybuffer:"); return this.dispatch(new Uint8Array(arr)); }, - _url: function (url: URL) { + _url: function(url: URL) { return write("url:" + url.toString()); }, - _map: function (map: Map) { + _map: function(map: Map) { write("map:"); var arr = Array.from(map); return this._array(arr, options.unorderedSets !== false); }, - _set: function (set: Set) { + _set: function(set: Set) { write("set:"); var arr = Array.from(set); return this._array(arr, options.unorderedSets !== false); }, - _file: function (file: File) { + _file: function(file: File) { write("file:"); return this.dispatch([ file.name, @@ -299,65 +299,65 @@ export function typeHasher( file.lastModified, ]); }, - _blob: function () { + _blob: function() { throw Error("Hashing Blob objects is currently not supported."); }, - _domwindow: function () { + _domwindow: function() { return write("domwindow"); }, - _bigint: function (number: BigInt) { + _bigint: function(number: BigInt) { return write("bigint:" + number.toString()); }, /* Node.js standard native objects */ - _process: function () { + _process: function() { return write("process"); }, - _timer: function () { + _timer: function() { return write("timer"); }, - _pipe: function () { + _pipe: function() { return write("pipe"); }, - _tcp: function () { + _tcp: function() { return write("tcp"); }, - _udp: function () { + _udp: function() { return write("udp"); }, - _tty: function () { + _tty: function() { return write("tty"); }, - _statwatcher: function () { + _statwatcher: function() { return write("statwatcher"); }, - _securecontext: function () { + _securecontext: function() { return write("securecontext"); }, - _connection: function () { + _connection: function() { return write("connection"); }, - _zlib: function () { + _zlib: function() { return write("zlib"); }, - _context: function () { + _context: function() { return write("context"); }, - _nodescript: function () { + _nodescript: function() { return write("nodescript"); }, - _httpparser: function () { + _httpparser: function() { return write("httpparser"); }, - _dataview: function () { + _dataview: function() { return write("dataview"); }, - _signal: function () { + _signal: function() { return write("signal"); }, - _fsevent: function () { + _fsevent: function() { return write("fsevent"); }, - _tlswrap: function () { + _tlswrap: function() { return write("tlswrap"); }, } as const; diff --git a/src/math/matrix.ts b/src/math/matrix.ts index 2861b24..ad6bff4 100644 --- a/src/math/matrix.ts +++ b/src/math/matrix.ts @@ -24,10 +24,10 @@ type MatrixResult< > = I extends number ? Matrix : I extends MatrixOperand - ? N extends O - ? Matrix - : never - : never; + ? N extends O + ? Matrix + : never + : never; export type MTXOptions = { format?: "coordinate" | "array"; @@ -390,8 +390,8 @@ export class Matrix other instanceof Matrix ? other : Matrix.isMatrixLike(other) - ? new Matrix(other) - : null; + ? new Matrix(other) + : null; if (!otherMatrix) { throw new Error("Argument is not matrix-like."); diff --git a/src/math/rational.ts b/src/math/rational.ts index 3c4fc4a..cd06eb6 100644 --- a/src/math/rational.ts +++ b/src/math/rational.ts @@ -86,8 +86,8 @@ export class Rational return base instanceof Rational ? base : typeof base === "string" - ? Rational.parse(base) - : new Rational(base, opt); + ? Rational.parse(base) + : new Rational(base, opt); } static parse(fraction: string): Rational { @@ -275,8 +275,8 @@ export class Rational format === "nospace" ? "/" : format === "unicode" - ? FRACTION_SLASH - : " / "; + ? FRACTION_SLASH + : " / "; if (!mixed) { return `${this.numerator}${separator}${this.denominator}`; diff --git a/src/math/statistics.ts b/src/math/statistics.ts index cae6e41..cea5595 100644 --- a/src/math/statistics.ts +++ b/src/math/statistics.ts @@ -192,14 +192,14 @@ export class Statistics { const sqdv = ( typeof mean === "number" ? data.map((v) => { - return Math.pow((v as number) - mean, 2); - }) + return Math.pow((v as number) - mean, 2); + }) : data.map((v) => { - const dv = (v as ArithmeticObject).sub( - mean as T - ) as ArithmeticObject; - return dv.mul(dv as T); - }) + const dv = (v as ArithmeticObject).sub( + mean as T + ) as ArithmeticObject; + return dv.mul(dv as T); + }) ) as T[]; return Statistics.mean(sqdv); @@ -457,7 +457,9 @@ export class Statistics { * @param data An array of tuples of [x, y] coordinates of numbers or object number types * @returns The covariance, or `undefined` if less than 2 samples */ - static covariance>(data: [x: T, y: T][]): T | undefined { + static covariance>( + data: [x: T, y: T][] + ): T | undefined { const n = data.length; if (n < 2) return; @@ -473,18 +475,28 @@ export class Statistics { const my = Statistics.mean(ys); if (my === undefined) return; - const dxs = xs.map((x) => typeof x === "number" ? x - (mx as number) : x.sub(mx)); - const dys = ys.map((y) => typeof y === "number" ? y - (my as number) : y.sub(my)); + const dxs = xs.map((x) => + typeof x === "number" ? x - (mx as number) : x.sub(mx) + ); + const dys = ys.map((y) => + typeof y === "number" ? y - (my as number) : y.sub(my) + ); const psum = dxs.reduce((acc, dx, i) => { const dy = dys[i]; - const product = typeof dy === "number" ? dy * (dx as number) : dy.mul(dx as typeof dy); - return typeof product === "number" ? product + (acc as number) : product.add(acc as typeof product); + const product = + typeof dy === "number" ? dy * (dx as number) : dy.mul(dx as typeof dy); + return typeof product === "number" + ? product + (acc as number) + : product.add(acc as typeof product); }, 0); return (typeof psum === "number" ? psum / (n - 1) : psum.div(n - 1)) as T; } - static pcc>(data: [x: T, y: T][], type: "sample" | "population" = "sample"): T | undefined { + static pcc>( + data: [x: T, y: T][], + type: "sample" | "population" = "sample" + ): T | undefined { if (type === "population") { const cov = Statistics.covariance(data); if (cov === undefined) return; @@ -492,7 +504,9 @@ export class Statistics { const [xsd, ysd] = ArrayTools.unzip(data).map(Statistics.sd); if (xsd === undefined || ysd === undefined) return; - return typeof cov === "number" ? ((cov / (+xsd * +ysd)) as T) : cov.div((xsd as ArithmeticObject).mul(ysd)); + return typeof cov === "number" + ? ((cov / (+xsd * +ysd)) as T) + : cov.div((xsd as ArithmeticObject).mul(ysd)); } else { const n = data.length; if (n < 2) return; @@ -511,26 +525,40 @@ export class Statistics { if (typeof mx === "number" && typeof my === "number") { const dxs = (xs as number[]).map((x) => x - mx); - const sdx = dxs.reduce((acc, d) => (d ** 2) + acc, 0); + const sdx = dxs.reduce((acc, d) => d ** 2 + acc, 0); const dys = (ys as number[]).map((y) => y - my); - const sdy = dys.reduce((acc, d) => (d ** 2) + acc, 0); + const sdy = dys.reduce((acc, d) => d ** 2 + acc, 0); const psum = dxs.reduce((acc, dx, i) => { const dy = dys[i]; - return acc + (dy * dx); + return acc + dy * dx; }, 0); - return psum / (Math.sqrt(sdx) * Math.sqrt(sdy)) as T; + return (psum / (Math.sqrt(sdx) * Math.sqrt(sdy))) as T; } else if (typeof mx !== "number" && typeof my !== "number") { - const dxs = (xs as ArithmeticObject[]).map((x) => x.sub(mx)) as ArithmeticObject[]; - const sdx = dxs.reduce>((acc, d) => (d.pow(2) as ArithmeticObject).add(acc as number), 0); - const dys = (ys as ArithmeticObject[]).map((y) => y.sub(my)) as ArithmeticObject[]; - const sdy = dys.reduce>((acc, d) => (d.pow(2) as ArithmeticObject).add(acc as number), 0); + const dxs = (xs as ArithmeticObject[]).map((x) => + x.sub(mx) + ) as ArithmeticObject[]; + const sdx = dxs.reduce>( + (acc, d) => (d.pow(2) as ArithmeticObject).add(acc as number), + 0 + ); + const dys = (ys as ArithmeticObject[]).map((y) => + y.sub(my) + ) as ArithmeticObject[]; + const sdy = dys.reduce>( + (acc, d) => (d.pow(2) as ArithmeticObject).add(acc as number), + 0 + ); const psum = dxs.reduce>((acc, dx, i) => { const dy = dys[i]; return (dy.mul(dx as T) as ArithmeticObject).add(acc as T); }, 0); - return ((psum as any).div(((sdx as any).pow(1 / 2) as ArithmeticObject).mul((sdy as any).pow(1 / 2)))); + return (psum as any).div( + ((sdx as any).pow(1 / 2) as ArithmeticObject).mul( + (sdy as any).pow(1 / 2) + ) + ); } else { throw new Error("Unreachable!"); } diff --git a/test/math/statistics.test.ts b/test/math/statistics.test.ts index d5d9415..2790616 100644 --- a/test/math/statistics.test.ts +++ b/test/math/statistics.test.ts @@ -326,7 +326,7 @@ describe("Statistics", () => { ).toStrictEqual([0, 20, 55, 77.25, 100]); }); - it("calculates percentiles of object number types", () => { }); + it("calculates percentiles of object number types", () => {}); }); describe("summary", () => { @@ -458,7 +458,7 @@ describe("Statistics", () => { [new Float(12.76), new Float(7.06)], [new Float(12.35), new Float(6.81)], [new Float(12.43), new Float(6.88)], - [new Float(12.70), new Float(6.98)], + [new Float(12.7), new Float(6.98)], [new Float(13.09), new Float(7.35)], ]; expect(Statistics.covariance(data)!.valueOf()).toBeCloseTo(0.0604, 4); @@ -471,7 +471,7 @@ describe("Statistics", () => { [2, 1], [4, 3], [6, 7], - [8, 13] + [8, 13], ]; expect(Statistics.pcc(data)).toBeCloseTo(0.976, 3); }); @@ -483,7 +483,7 @@ describe("Statistics", () => { [new Float(40), new Float(5000)], [new Float(50), new Float(7500)], ]; - expect(Statistics.pcc(data)!.valueOf()).toBeCloseTo(0.994, 3) + expect(Statistics.pcc(data)!.valueOf()).toBeCloseTo(0.994, 3); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 2ccfa7f..cc118bf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "esModuleInterop": true, "isolatedModules": true, "jsx": "react-jsx", - "lib": ["ESNext", "DOM"], + "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "node", "noFallthroughCasesInSwitch": true,