@zushah/chalkboard 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,17 @@
1
+ /*
2
+ Chalkboard
3
+ Version 3.0.2 Euler
4
+ Released April 13th, 2026
5
+ Authored by Zushah: https://www.github.com/Zushah
6
+ Licensed under MPL-2.0: https://opensource.org/license/mpl-2-0
7
+ Repository: https://www.github.com/Zushah/Chalkboard
8
+ Website: https://zushah.github.io/Chalkboard
9
+ */
10
+ /*
11
+ This Source Code Form is subject to the terms of the
12
+ Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
13
+ with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
14
+ */
1
15
  "use strict";
2
16
  var Chalkboard;
3
17
  (function (Chalkboard) {
@@ -83,7 +97,15 @@ var Chalkboard;
83
97
  };
84
98
  Chalkboard.CONTEXT = typeof window !== "undefined" ? "ctx" : "0";
85
99
  Chalkboard.E = (exponent = 1) => {
86
- return Math.pow(Math.pow(10, 1 / Math.log(10)), exponent);
100
+ if (exponent === 0)
101
+ return 1;
102
+ if (exponent === 1)
103
+ return 2.718281828459045;
104
+ const LN2 = 0.6931471805599453, INV_LN2 = 1.4426950408889634;
105
+ const k = Math.round(exponent * INV_LN2);
106
+ const r = exponent - k * LN2, r2 = r * r, r3 = r2 * r, r4 = r3 * r, r5 = r4 * r, r6 = r5 * r, r7 = r6 * r, r8 = r7 * r, r9 = r8 * r, r10 = r9 * r;
107
+ const exp_r = 1 + r + r2 / 2 + r3 / 6 + r4 / 24 + r5 / 120 + r6 / 720 + r7 / 5040 + r8 / 40320 + r9 / 362880 + r10 / 3628800;
108
+ return exp_r * (2 ** k);
87
109
  };
88
110
  Chalkboard.I = (exponent = 1) => {
89
111
  if (exponent % 4 === 0)
@@ -97,13 +119,36 @@ var Chalkboard;
97
119
  return Chalkboard.comp.init(0, 0);
98
120
  };
99
121
  Chalkboard.PI = (coefficient = 1) => {
100
- return coefficient * 4 * (4 * Math.atan(1 / 5) - Math.atan(1 / 239));
122
+ let a = 1.0, b = Math.sqrt(0.5), t = 0.25, p = 1.0;
123
+ let aNext = (a + b) * 0.5, bNext = Math.sqrt(a * b);
124
+ t -= p * (a - aNext) * (a - aNext);
125
+ a = aNext;
126
+ b = bNext;
127
+ p *= 2.0;
128
+ aNext = (a + b) * 0.5;
129
+ bNext = Math.sqrt(a * b);
130
+ t -= p * (a - aNext) * (a - aNext);
131
+ a = aNext;
132
+ b = bNext;
133
+ p *= 2.0;
134
+ aNext = (a + b) * 0.5;
135
+ bNext = Math.sqrt(a * b);
136
+ t -= p * (a - aNext) * (a - aNext);
137
+ a = aNext;
138
+ b = bNext;
139
+ p *= 2.0;
140
+ aNext = (a + b) * 0.5;
141
+ bNext = Math.sqrt(a * b);
142
+ t -= p * (a - aNext) * (a - aNext);
143
+ a = aNext;
144
+ b = bNext;
145
+ return coefficient * (((a + b) * (a + b)) / (4.0 * t));
101
146
  };
102
147
  Chalkboard.REGISTER = (name, func) => {
103
148
  Chalkboard.REGISTRY[name] = func;
104
149
  };
105
150
  Chalkboard.REGISTRY = {};
106
- Chalkboard.VERSION = "3.0.0";
151
+ Chalkboard.VERSION = "3.0.1";
107
152
  Chalkboard.VERSIONALIAS = "Euler";
108
153
  })(Chalkboard || (Chalkboard = {}));
109
154
  if (typeof window === "undefined")
@@ -3057,38 +3102,34 @@ var Chalkboard;
3057
3102
  calc.fxdx = (func, inf, sup) => {
3058
3103
  if (func.field !== "real")
3059
3104
  throw new TypeError("Chalkboard.calc.fxdx: Property 'field' of 'func' must be 'real'.");
3105
+ const integrate = (f, a, b, eps = 1e-6) => {
3106
+ const asq = (a, b, fa, fm, fb, whole, eps, depth) => {
3107
+ const m = (a + b) / 2, h = (b - a) / 2;
3108
+ const lm = (a + m) / 2, rm = (m + b) / 2;
3109
+ const flm = f(lm), frm = f(rm);
3110
+ const left = (h / 6) * (fa + 4 * flm + fm);
3111
+ const right = (h / 6) * (fm + 4 * frm + fb);
3112
+ const delta = left + right - whole;
3113
+ if (depth >= 50 || Math.abs(delta) <= 15 * eps)
3114
+ return left + right + delta / 15;
3115
+ return asq(a, m, fa, flm, fm, left, eps / 2, depth + 1) + asq(m, b, fm, frm, fb, right, eps / 2, depth + 1);
3116
+ };
3117
+ const m = (a + b) / 2;
3118
+ const fa = f(a), fm = f(m), fb = f(b);
3119
+ const whole = ((b - a) / 6) * (fa + 4 * fm + fb);
3120
+ return asq(a, b, fa, fm, fb, whole, eps, 0);
3121
+ };
3060
3122
  if (func.type === "scalar2d") {
3061
3123
  const f = func.rule;
3062
- let fx = f(inf) + f(sup);
3063
- const dx = (sup - inf) / 1000000;
3064
- for (let i = 1; i < 1000000; i++) {
3065
- fx += i % 2 === 0 ? 2 * f(inf + i * dx) : 4 * f(inf + i * dx);
3066
- }
3067
- return (fx * dx) / 3;
3124
+ return integrate(f, inf, sup);
3068
3125
  }
3069
3126
  else if (func.type === "curve2d") {
3070
3127
  const f = func.rule;
3071
- let fx = f[0](inf) + f[0](sup);
3072
- let fy = f[1](inf) + f[1](sup);
3073
- const dt = (sup - inf) / 1000000;
3074
- for (let i = 1; i < 1000000; i++) {
3075
- fx += i % 2 === 0 ? 2 * f[0](inf + i * dt) : 4 * f[0](inf + i * dt);
3076
- fy += i % 2 === 0 ? 2 * f[1](inf + i * dt) : 4 * f[1](inf + i * dt);
3077
- }
3078
- return Chalkboard.vect.init((fx * dt) / 3, (fy * dt) / 3);
3128
+ return Chalkboard.vect.init(integrate(f[0], inf, sup), integrate(f[1], inf, sup));
3079
3129
  }
3080
3130
  else if (func.type === "curve3d") {
3081
3131
  const f = func.rule;
3082
- let fx = f[0](inf) + f[0](sup);
3083
- let fy = f[1](inf) + f[1](sup);
3084
- let fz = f[2](inf) + f[2](sup);
3085
- const dt = (sup - inf) / 1000000;
3086
- for (let i = 1; i < 1000000; i++) {
3087
- fx += i % 2 === 0 ? 2 * f[0](inf + i * dt) : 4 * f[0](inf + i * dt);
3088
- fy += i % 2 === 0 ? 2 * f[1](inf + i * dt) : 4 * f[1](inf + i * dt);
3089
- fz += i % 2 === 0 ? 2 * f[2](inf + i * dt) : 4 * f[2](inf + i * dt);
3090
- }
3091
- return Chalkboard.vect.init((fx * dt) / 3, (fy * dt) / 3, (fz * dt) / 3);
3132
+ return Chalkboard.vect.init(integrate(f[0], inf, sup), integrate(f[1], inf, sup), integrate(f[2], inf, sup));
3092
3133
  }
3093
3134
  throw new TypeError("Chalkboard.calc.fxdx: Property 'type' of 'func' must be 'scalar2d', 'curve2d', or 'curve3d'.");
3094
3135
  };
@@ -3097,15 +3138,25 @@ var Chalkboard;
3097
3138
  throw new TypeError("Chalkboard.calc.fxydxdy: Property 'field' of 'func' must be 'real'.");
3098
3139
  if (func.type === "scalar3d") {
3099
3140
  const f = func.rule;
3100
- let result = 0;
3101
- const dx = (xsup - xinf) / 10000;
3102
- const dy = (ysup - yinf) / 10000;
3103
- for (let x = xinf; x <= xsup; x += dx) {
3104
- for (let y = yinf; y <= ysup; y += dy) {
3105
- result += f(x, y);
3106
- }
3107
- }
3108
- return result * dx * dy;
3141
+ const integrate = (g, a, b, eps) => {
3142
+ const asq = (a, b, fa, fm, fb, whole, eps, depth) => {
3143
+ const m = (a + b) / 2, h = (b - a) / 2;
3144
+ const lm = (a + m) / 2, rm = (m + b) / 2;
3145
+ const flm = g(lm), frm = g(rm);
3146
+ const left = (h / 6) * (fa + 4 * flm + fm);
3147
+ const right = (h / 6) * (fm + 4 * frm + fb);
3148
+ const delta = left + right - whole;
3149
+ if (depth >= 50 || Math.abs(delta) <= 15 * eps)
3150
+ return left + right + delta / 15;
3151
+ return asq(a, m, fa, flm, fm, left, eps / 2, depth + 1) + asq(m, b, fm, frm, fb, right, eps / 2, depth + 1);
3152
+ };
3153
+ const m = (a + b) / 2;
3154
+ const fa = g(a), fm = g(m), fb = g(b);
3155
+ const whole = ((b - a) / 6) * (fa + 4 * fm + fb);
3156
+ return asq(a, b, fa, fm, fb, whole, eps, 0);
3157
+ };
3158
+ const g = (x) => integrate((y) => f(x, y), yinf, ysup, 1e-5);
3159
+ return integrate(g, xinf, xsup, 1e-5);
3109
3160
  }
3110
3161
  throw new TypeError("Chalkboard.calc.fxydxdy: Property 'type' of 'func' must be 'scalar3d'.");
3111
3162
  };
@@ -7517,16 +7568,24 @@ var Chalkboard;
7517
7568
  numb.combination = (n, r) => {
7518
7569
  if (!Number.isInteger(n) || !Number.isInteger(r) || n < 0 || r < 0 || r > n)
7519
7570
  throw new Error(`Chalkboard.numb.combination: Parameters "n" and "r" must be integers with 0 <= r <= n.`);
7520
- return Chalkboard.numb.factorial(n) / (Chalkboard.numb.factorial(n - r) * Chalkboard.numb.factorial(r));
7571
+ return Chalkboard.numb.binomial(n, r);
7521
7572
  };
7522
7573
  numb.compositeArr = (inf, sup) => {
7523
7574
  if (!Number.isInteger(inf) || !Number.isInteger(sup))
7524
7575
  throw new Error(`Chalkboard.numb.compositeArr: Parameters "inf" and "sup" must be integers.`);
7525
7576
  if (inf > sup)
7526
7577
  throw new Error(`Chalkboard.numb.compositeArr: Parameter "inf" must be less than or equal to "sup".`);
7578
+ if (sup < 4)
7579
+ return [];
7580
+ const sieve = new Uint8Array(sup + 1);
7581
+ for (let p = 2; p * p <= sup; p++)
7582
+ if (sieve[p] === 0)
7583
+ for (let i = p * p; i <= sup; i += p)
7584
+ sieve[i] = 1;
7527
7585
  const result = [];
7528
- for (let i = inf; i <= sup; i++)
7529
- if (i > 1 && !Chalkboard.numb.isPrime(i))
7586
+ const start = Math.max(4, inf);
7587
+ for (let i = start; i <= sup; i++)
7588
+ if (sieve[i] === 1)
7530
7589
  result.push(i);
7531
7590
  return result;
7532
7591
  };
@@ -7741,10 +7800,15 @@ var Chalkboard;
7741
7800
  if (!Number.isInteger(num) || num <= 0)
7742
7801
  throw new Error(`Chalkboard.numb.divisors: Parameter "num" must be a positive integer.`);
7743
7802
  const result = [];
7744
- for (let i = 1; i <= num; i++)
7745
- if (num % i === 0)
7803
+ const upper = Math.floor(Math.sqrt(num));
7804
+ for (let i = 1; i <= upper; i++) {
7805
+ if (num % i === 0) {
7746
7806
  result.push(i);
7747
- return result;
7807
+ if (i !== num / i)
7808
+ result.push(num / i);
7809
+ }
7810
+ }
7811
+ return result.sort((a, b) => a - b);
7748
7812
  };
7749
7813
  numb.Euler = (num) => {
7750
7814
  if (!Number.isInteger(num) || num <= 0)
@@ -7985,7 +8049,10 @@ var Chalkboard;
7985
8049
  numb.permutation = (n, r) => {
7986
8050
  if (!Number.isInteger(n) || !Number.isInteger(r) || n < 0 || r < 0 || r > n)
7987
8051
  throw new Error(`Chalkboard.numb.permutation: Parameters "n" and "r" must be integers with 0 <= r <= n.`);
7988
- return Chalkboard.numb.factorial(n) / Chalkboard.numb.factorial(n - r);
8052
+ let result = 1;
8053
+ for (let i = n; i > n - r; i--)
8054
+ result *= i;
8055
+ return Math.round(result);
7989
8056
  };
7990
8057
  numb.Poissonian = (l = 1) => {
7991
8058
  if (typeof l !== "number" || !Number.isFinite(l))
@@ -8019,9 +8086,19 @@ var Chalkboard;
8019
8086
  throw new Error(`Chalkboard.numb.primeArr: Parameters "inf" and "sup" must be integers.`);
8020
8087
  if (inf > sup)
8021
8088
  throw new Error(`Chalkboard.numb.primeArr: Parameter "inf" must be less than or equal to "sup".`);
8089
+ if (sup < 2)
8090
+ return [];
8091
+ const sieve = new Uint8Array(sup + 1);
8092
+ sieve[0] = 1;
8093
+ sieve[1] = 1;
8094
+ for (let p = 2; p * p <= sup; p++)
8095
+ if (sieve[p] === 0)
8096
+ for (let i = p * p; i <= sup; i += p)
8097
+ sieve[i] = 1;
8022
8098
  const result = [];
8023
- for (let i = inf; i <= sup; i++)
8024
- if (Chalkboard.numb.isPrime(i))
8099
+ const start = Math.max(2, inf);
8100
+ for (let i = start; i <= sup; i++)
8101
+ if (sieve[i] === 0)
8025
8102
  result.push(i);
8026
8103
  return result;
8027
8104
  };
@@ -8195,205 +8272,218 @@ var Chalkboard;
8195
8272
  throw new Error("Cannot initialize canvas context. Make sure an HTML <canvas> element exists in the webpage before using Chalkboard.plot functions.");
8196
8273
  }
8197
8274
  };
8275
+ const $ = (config, defaults) => {
8276
+ const ctx = (config?.context ?? defaults?.context ?? getContext());
8277
+ const configMap = (config ?? {});
8278
+ const defaultsMap = { x: ctx.canvas.width / 2, y: ctx.canvas.height / 2, size: 1, strokeStyle: "black", lineWidth: 2, context: ctx, ...(defaults ?? {}) };
8279
+ const merged = {};
8280
+ const keys = new Set([...Object.keys(defaultsMap), ...Object.keys(configMap)]);
8281
+ for (const key of keys)
8282
+ merged[key] = configMap[key] ?? defaultsMap[key];
8283
+ merged.context = ctx;
8284
+ merged.x = (merged.x ?? ctx.canvas.width / 2);
8285
+ merged.y = (merged.y ?? ctx.canvas.height / 2);
8286
+ merged.size = (merged.size ?? 1) / 100;
8287
+ return merged;
8288
+ };
8198
8289
  plot.autocorrelation = (func, config) => {
8199
- (config = {
8200
- x: (config = config || {}).x || getContext().canvas.width / 2,
8201
- y: config.y || getContext().canvas.height / 2,
8202
- size: config.size || 1,
8203
- strokeStyle: config.strokeStyle || "black",
8204
- lineWidth: config.lineWidth || 2,
8205
- domain: config.domain || [-10, 10],
8206
- res: config.res || 25,
8207
- context: config.context || getContext()
8208
- }).size /= 100;
8290
+ const _config = $(config, { domain: [-10, 10], res: 25 });
8209
8291
  const data = [];
8210
- config.context.save();
8211
- config.context.translate(config.x, config.y);
8212
- config.context.lineWidth = config.lineWidth;
8213
- config.context.strokeStyle = config.strokeStyle;
8214
- config.context.beginPath();
8215
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8216
- config.context.lineTo(i, -Chalkboard.calc.autocorrelation(func, i * config.size) / config.size);
8292
+ _config.context.save();
8293
+ _config.context.translate(_config.x, _config.y);
8294
+ _config.context.lineWidth = _config.lineWidth;
8295
+ _config.context.strokeStyle = _config.strokeStyle;
8296
+ _config.context.beginPath();
8297
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8298
+ let previousY = null;
8299
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8300
+ const currentY = -Chalkboard.calc.autocorrelation(func, i * _config.size) / _config.size;
8301
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8302
+ _config.context.moveTo(i, currentY);
8303
+ }
8304
+ else {
8305
+ _config.context.lineTo(i, currentY);
8306
+ }
8307
+ previousY = currentY;
8217
8308
  data.push([i, Chalkboard.calc.autocorrelation(func, i)]);
8218
8309
  }
8219
- config.context.stroke();
8220
- config.context.restore();
8310
+ _config.context.stroke();
8311
+ _config.context.restore();
8221
8312
  return data;
8222
8313
  };
8223
8314
  plot.barplot = (arr, bins, config) => {
8224
- (config = {
8225
- x: (config = config || {}).x || getContext().canvas.width / 2,
8226
- y: config.y || getContext().canvas.height / 2,
8227
- size: config.size || 1,
8228
- fillStyle: config.fillStyle || "white",
8229
- strokeStyle: config.strokeStyle || "black",
8230
- lineWidth: config.lineWidth || 2,
8231
- context: config.context || getContext()
8232
- }).size /= 100;
8233
- config.context.save();
8234
- config.context.translate(config.x, config.y);
8235
- config.context.lineWidth = config.lineWidth;
8236
- config.context.strokeStyle = config.strokeStyle;
8237
- config.context.fillStyle = config.fillStyle;
8315
+ const _config = $(config, { fillStyle: "white" });
8316
+ _config.context.save();
8317
+ _config.context.translate(_config.x, _config.y);
8318
+ _config.context.lineWidth = _config.lineWidth;
8319
+ _config.context.strokeStyle = _config.strokeStyle;
8320
+ _config.context.fillStyle = _config.fillStyle;
8238
8321
  const bars = [];
8239
- for (let i = 0; i < bins.length; i++) {
8240
- if (i === 0) {
8241
- bars.push(Chalkboard.stat.lt(arr, bins[0], true));
8242
- }
8243
- else if (i === bins.length) {
8244
- bars.push(Chalkboard.stat.gt(arr, bins[bins.length - 1], true));
8245
- }
8246
- else {
8247
- bars.push(Chalkboard.stat.ineq(arr, bins[i - 1], bins[i], false, true));
8248
- }
8322
+ for (let i = 1; i < bins.length; i++) {
8323
+ bars.push(Chalkboard.stat.ineq(arr, bins[i - 1], bins[i], i === 1, true));
8249
8324
  }
8250
8325
  const counts = [];
8251
8326
  for (let i = 0; i < bars.length; i++) {
8252
8327
  counts.push(bars[i].length);
8253
8328
  }
8254
- let x = 0;
8255
- const width = counts.length / (2 * config.size);
8256
8329
  for (let i = 0; i < counts.length; i++) {
8257
- config.context.fillRect(x - width, 0, 1 / config.size, -counts[i] / config.size);
8258
- config.context.strokeRect(x - width, 0, 1 / config.size, -counts[i] / config.size);
8259
- x += 1 / config.size;
8330
+ const xStart = bins[i] / _config.size;
8331
+ const xEnd = bins[i + 1] / _config.size;
8332
+ const dynamicWidth = xEnd - xStart;
8333
+ _config.context.fillRect(xStart, 0, dynamicWidth, -counts[i] / _config.size);
8334
+ _config.context.strokeRect(xStart, 0, dynamicWidth, -counts[i] / _config.size);
8260
8335
  }
8261
- config.context.restore();
8336
+ _config.context.restore();
8262
8337
  return bars;
8263
8338
  };
8264
8339
  plot.comp = (comp, config) => {
8265
- (config = {
8266
- x: (config = config || {}).x || getContext().canvas.width / 2,
8267
- y: config.y || getContext().canvas.height / 2,
8268
- size: config.size || 1,
8269
- fillStyle: config.fillStyle || "black",
8270
- lineWidth: config.lineWidth || 5,
8271
- context: config.context || getContext()
8272
- }).size /= 100;
8273
- config.context.fillStyle = config.fillStyle;
8274
- config.context.save();
8275
- config.context.translate(config.x, config.y);
8276
- config.context.beginPath();
8277
- config.context.ellipse(comp.a / config.size, -comp.b / config.size, config.lineWidth, config.lineWidth, 0, 0, Chalkboard.PI(2));
8278
- config.context.fill();
8279
- config.context.restore();
8340
+ const _config = $(config, { fillStyle: "black", lineWidth: 5 });
8341
+ _config.context.fillStyle = _config.fillStyle;
8342
+ _config.context.save();
8343
+ _config.context.translate(_config.x, _config.y);
8344
+ _config.context.beginPath();
8345
+ _config.context.ellipse(comp.a / _config.size, -comp.b / _config.size, _config.lineWidth, _config.lineWidth, 0, 0, Chalkboard.PI(2));
8346
+ _config.context.fill();
8347
+ _config.context.restore();
8280
8348
  return [[comp.a], [comp.b]];
8281
8349
  };
8282
8350
  plot.convolution = (func1, func2, config) => {
8283
- (config = {
8284
- x: (config = config || {}).x || getContext().canvas.width / 2,
8285
- y: config.y || getContext().canvas.height / 2,
8286
- size: config.size || 1,
8287
- strokeStyle: config.strokeStyle || "black",
8288
- lineWidth: config.lineWidth || 2,
8289
- domain: config.domain || [-10, 10],
8290
- res: config.res || 25,
8291
- context: config.context || getContext()
8292
- }).size /= 100;
8351
+ const _config = $(config, { domain: [-10, 10], res: 25 });
8293
8352
  const data = [];
8294
- config.context.save();
8295
- config.context.translate(config.x, config.y);
8296
- config.context.lineWidth = config.lineWidth;
8297
- config.context.strokeStyle = config.strokeStyle;
8298
- config.context.beginPath();
8299
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8300
- config.context.lineTo(i, -Chalkboard.calc.convolution(func1, func2, i * config.size) / config.size);
8353
+ _config.context.save();
8354
+ _config.context.translate(_config.x, _config.y);
8355
+ _config.context.lineWidth = _config.lineWidth;
8356
+ _config.context.strokeStyle = _config.strokeStyle;
8357
+ _config.context.beginPath();
8358
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8359
+ let previousY = null;
8360
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8361
+ const currentY = -Chalkboard.calc.convolution(func1, func2, i * _config.size) / _config.size;
8362
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8363
+ _config.context.moveTo(i, currentY);
8364
+ }
8365
+ else {
8366
+ _config.context.lineTo(i, currentY);
8367
+ }
8368
+ previousY = currentY;
8301
8369
  data.push([i, Chalkboard.calc.convolution(func1, func2, i)]);
8302
8370
  }
8303
- config.context.stroke();
8304
- config.context.restore();
8371
+ _config.context.stroke();
8372
+ _config.context.restore();
8305
8373
  return data;
8306
8374
  };
8307
8375
  plot.correlation = (func1, func2, config) => {
8308
- (config = {
8309
- x: (config = config || {}).x || getContext().canvas.width / 2,
8310
- y: config.y || getContext().canvas.height / 2,
8311
- size: config.size || 1,
8312
- strokeStyle: config.strokeStyle || "black",
8313
- lineWidth: config.lineWidth || 2,
8314
- domain: config.domain || [-10, 10],
8315
- res: config.res || 25,
8316
- context: config.context || getContext()
8317
- }).size /= 100;
8376
+ const _config = $(config, { domain: [-10, 10], res: 25 });
8318
8377
  const data = [];
8319
- config.context.save();
8320
- config.context.translate(config.x, config.y);
8321
- config.context.lineWidth = config.lineWidth;
8322
- config.context.strokeStyle = config.strokeStyle;
8323
- config.context.beginPath();
8324
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8325
- config.context.lineTo(i, -Chalkboard.calc.correlation(func1, func2, i * config.size) / config.size);
8378
+ _config.context.save();
8379
+ _config.context.translate(_config.x, _config.y);
8380
+ _config.context.lineWidth = _config.lineWidth;
8381
+ _config.context.strokeStyle = _config.strokeStyle;
8382
+ _config.context.beginPath();
8383
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8384
+ let previousY = null;
8385
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8386
+ const currentY = -Chalkboard.calc.correlation(func1, func2, i * _config.size) / _config.size;
8387
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8388
+ _config.context.moveTo(i, currentY);
8389
+ }
8390
+ else {
8391
+ _config.context.lineTo(i, currentY);
8392
+ }
8393
+ previousY = currentY;
8326
8394
  data.push([i, Chalkboard.calc.correlation(func1, func2, i)]);
8327
8395
  }
8328
- config.context.stroke();
8329
- config.context.restore();
8396
+ _config.context.stroke();
8397
+ _config.context.restore();
8330
8398
  return data;
8331
8399
  };
8332
8400
  plot.definition = (func, config) => {
8333
- (config = {
8334
- x: (config = config || {}).x || getContext().canvas.width / 2,
8335
- y: config.y || getContext().canvas.height / 2,
8336
- size: config.size || 1,
8337
- strokeStyle: config.strokeStyle || "black",
8338
- lineWidth: config.lineWidth || 2,
8339
- domain: config.domain || (func.field === "comp" ? [[-10, 10], [-10, 10]] : [-10, 10]),
8340
- res: config.res || (func.field === "comp" ? 5 : 1),
8341
- isInverse: config.isInverse || false,
8342
- isPolar: config.isPolar || false,
8343
- context: config.context || getContext()
8344
- }).size /= 100;
8345
- const xdomain = config.domain;
8346
- const xydomain = config.domain;
8401
+ const _config = $(config, { domain: (func.field === "comp" ? [[-10, 10], [-10, 10]] : [-10, 10]), res: (func.field === "comp" ? 5 : 1), isInverse: false, isPolar: false });
8402
+ const xdomain = _config.domain;
8403
+ const xydomain = _config.domain;
8347
8404
  const data = [];
8348
- config.context.save();
8349
- config.context.translate(config.x, config.y);
8350
- config.context.lineWidth = config.lineWidth;
8351
- config.context.strokeStyle = config.strokeStyle;
8352
- config.context.beginPath();
8353
- if (func.type === "scalar2d" && !config.isInverse && !config.isPolar) {
8405
+ _config.context.save();
8406
+ _config.context.translate(_config.x, _config.y);
8407
+ _config.context.lineWidth = _config.lineWidth;
8408
+ _config.context.strokeStyle = _config.strokeStyle;
8409
+ _config.context.beginPath();
8410
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8411
+ let previousY = null;
8412
+ let previousX = null;
8413
+ if (func.type === "scalar2d" && !_config.isInverse && !_config.isPolar) {
8354
8414
  const f = func.rule;
8355
- for (let i = xdomain[0] / config.size; i <= xdomain[1] / config.size; i += config.res) {
8356
- config.context.lineTo(i, -f(i * config.size) / config.size);
8415
+ for (let i = xdomain[0] / _config.size; i <= xdomain[1] / _config.size; i += _config.res) {
8416
+ const currentY = -f(i * _config.size) / _config.size;
8417
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8418
+ _config.context.moveTo(i, currentY);
8419
+ }
8420
+ else {
8421
+ _config.context.lineTo(i, currentY);
8422
+ }
8423
+ previousY = currentY;
8357
8424
  data.push([i, f(i)]);
8358
8425
  }
8359
8426
  }
8360
- else if (func.type === "scalar2d" && config.isInverse && !config.isPolar) {
8427
+ else if (func.type === "scalar2d" && _config.isInverse && !_config.isPolar) {
8361
8428
  const f = func.rule;
8362
- for (let i = xdomain[0] / config.size; i <= xdomain[1] / config.size; i += config.res) {
8363
- config.context.lineTo(f(i * config.size) / config.size, -i);
8429
+ for (let i = xdomain[0] / _config.size; i <= xdomain[1] / _config.size; i += _config.res) {
8430
+ const currentX = f(i * _config.size) / _config.size;
8431
+ const currentY = -i;
8432
+ if (previousX === null || Math.abs(currentX - previousX) > discontinuityThreshold) {
8433
+ _config.context.moveTo(currentX, currentY);
8434
+ }
8435
+ else {
8436
+ _config.context.lineTo(currentX, currentY);
8437
+ }
8438
+ previousX = currentX;
8364
8439
  data.push([f(i), i]);
8365
8440
  }
8366
8441
  }
8367
- else if (func.type === "scalar2d" && !config.isInverse && config.isPolar) {
8442
+ else if (func.type === "scalar2d" && !_config.isInverse && _config.isPolar) {
8368
8443
  const r = func.rule;
8369
- for (let i = xdomain[0] / config.size; i <= xdomain[1] / config.size; i += config.res) {
8370
- config.context.lineTo((r(i * config.size) / config.size) * Chalkboard.trig.cos(i * config.size), (-r(i * config.size) / config.size) * Chalkboard.trig.sin(i * config.size));
8444
+ for (let i = xdomain[0] / _config.size; i <= xdomain[1] / _config.size; i += _config.res) {
8445
+ const currentX = (r(i * _config.size) / _config.size) * Chalkboard.trig.cos(i * _config.size);
8446
+ const currentY = (-r(i * _config.size) / _config.size) * Chalkboard.trig.sin(i * _config.size);
8447
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8448
+ _config.context.moveTo(currentX, currentY);
8449
+ }
8450
+ else {
8451
+ _config.context.lineTo(currentX, currentY);
8452
+ }
8453
+ previousY = currentY;
8371
8454
  data.push([i, r(i)]);
8372
8455
  }
8373
8456
  }
8374
8457
  else if (func.type === "curve2d") {
8375
8458
  const f = func.rule;
8376
- for (let i = xdomain[0] / config.size; i <= xdomain[1] / config.size; i += config.res) {
8377
- config.context.lineTo(f[0](i * config.size) / config.size, -f[1](i * config.size) / config.size);
8459
+ for (let i = xdomain[0] / _config.size; i <= xdomain[1] / _config.size; i += _config.res) {
8460
+ const currentX = f[0](i * _config.size) / _config.size;
8461
+ const currentY = -f[1](i * _config.size) / _config.size;
8462
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8463
+ _config.context.moveTo(currentX, currentY);
8464
+ }
8465
+ else {
8466
+ _config.context.lineTo(currentX, currentY);
8467
+ }
8468
+ previousY = currentY;
8378
8469
  data.push([f[0](i), f[1](i)]);
8379
8470
  }
8380
8471
  }
8381
8472
  else if (func.field === "comp") {
8382
8473
  const f = func.rule;
8383
- for (let i = xydomain[0][0] / config.size; i <= xydomain[0][1] / config.size; i += config.res) {
8384
- for (let j = xydomain[1][0] / config.size; j <= xydomain[1][1] / config.size; j += config.res) {
8385
- const z = Chalkboard.comp.init(f[0](i * config.size, j * config.size) / config.size, f[1](i * config.size, j * config.size) / config.size);
8474
+ for (let i = xydomain[0][0] / _config.size; i <= xydomain[0][1] / _config.size; i += _config.res) {
8475
+ for (let j = xydomain[1][0] / _config.size; j <= xydomain[1][1] / _config.size; j += _config.res) {
8476
+ const z = Chalkboard.comp.init(f[0](i * _config.size, j * _config.size) / _config.size, f[1](i * _config.size, j * _config.size) / _config.size);
8386
8477
  if (z.a === 0 && z.b === 0) {
8387
- config.context.fillStyle = "rgb(0, 0, 0)";
8478
+ _config.context.fillStyle = "rgb(0, 0, 0)";
8388
8479
  }
8389
8480
  else if (z.a === Infinity && z.b === Infinity) {
8390
- config.context.fillStyle = "rgb(255, 255, 255)";
8481
+ _config.context.fillStyle = "rgb(255, 255, 255)";
8391
8482
  }
8392
8483
  else {
8393
- config.context.fillStyle =
8394
- "hsl(" + Chalkboard.trig.toDeg(Chalkboard.comp.arg(z)) + ", 100%, " + (Chalkboard.trig.tanh(Chalkboard.comp.mag(z) / Chalkboard.real.pow(10, 20)) + 0.5) * 100 + "%)";
8484
+ _config.context.fillStyle = "hsl(" + Chalkboard.trig.toDeg(Chalkboard.comp.arg(z)) + ", 100%, " + (Chalkboard.trig.tanh(Chalkboard.comp.mag(z) / Chalkboard.real.pow(10, 20)) + 0.5) * 100 + "%)";
8395
8485
  }
8396
- config.context.fillRect(i, j, 5, 5);
8486
+ _config.context.fillRect(i, j, 5, 5);
8397
8487
  data.push([f[0](i, j), f[1](i, j)]);
8398
8488
  }
8399
8489
  }
@@ -8401,475 +8491,441 @@ var Chalkboard;
8401
8491
  else {
8402
8492
  throw new TypeError('Parameter "func" must be of type "ChalkboardFunction" with a property "type" of "expl", "inve", "pola", "curv", or "comp".');
8403
8493
  }
8404
- config.context.stroke();
8405
- config.context.restore();
8494
+ _config.context.stroke();
8495
+ _config.context.restore();
8406
8496
  return data;
8407
8497
  };
8408
8498
  plot.dfdx = (func, config) => {
8409
- (config = {
8410
- x: (config = config || {}).x || getContext().canvas.width / 2,
8411
- y: config.y || getContext().canvas.height / 2,
8412
- size: config.size || 1,
8413
- strokeStyle: config.strokeStyle || "black",
8414
- lineWidth: config.lineWidth || 2,
8415
- domain: config.domain || [-10, 10],
8416
- res: config.res || 25,
8417
- isInverse: config.isInverse || false,
8418
- context: config.context || getContext()
8419
- }).size /= 100;
8499
+ const _config = $(config, { domain: [-10, 10], res: 25, isInverse: false });
8420
8500
  const data = [];
8421
- config.context.save();
8422
- config.context.translate(config.x, config.y);
8423
- config.context.lineWidth = config.lineWidth;
8424
- config.context.strokeStyle = config.strokeStyle;
8425
- config.context.beginPath();
8426
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8427
- if (func.type === "scalar2d" && !config.isInverse) {
8428
- config.context.lineTo(i, -Chalkboard.calc.dfdx(func, i * config.size) / config.size);
8501
+ _config.context.save();
8502
+ _config.context.translate(_config.x, _config.y);
8503
+ _config.context.lineWidth = _config.lineWidth;
8504
+ _config.context.strokeStyle = _config.strokeStyle;
8505
+ _config.context.beginPath();
8506
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8507
+ let previousY = null;
8508
+ let previousX = null;
8509
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8510
+ if (func.type === "scalar2d" && !_config.isInverse) {
8511
+ const currentY = -Chalkboard.calc.dfdx(func, i * _config.size) / _config.size;
8512
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8513
+ _config.context.moveTo(i, currentY);
8514
+ }
8515
+ else {
8516
+ _config.context.lineTo(i, currentY);
8517
+ }
8518
+ previousY = currentY;
8429
8519
  data.push([i, Chalkboard.calc.dfdx(func, i)]);
8430
8520
  }
8431
- else if (func.type === "scalar2d" && config.isInverse) {
8432
- config.context.lineTo(Chalkboard.calc.dfdx(func, i * config.size) / config.size, -i);
8521
+ else if (func.type === "scalar2d" && _config.isInverse) {
8522
+ const currentX = Chalkboard.calc.dfdx(func, i * _config.size) / _config.size;
8523
+ const currentY = -i;
8524
+ if (previousX === null || Math.abs(currentX - previousX) > discontinuityThreshold) {
8525
+ _config.context.moveTo(currentX, currentY);
8526
+ }
8527
+ else {
8528
+ _config.context.lineTo(currentX, currentY);
8529
+ }
8530
+ previousX = currentX;
8433
8531
  data.push([Chalkboard.calc.dfdx(func, i), i]);
8434
8532
  }
8435
8533
  }
8436
- config.context.stroke();
8437
- config.context.restore();
8534
+ _config.context.stroke();
8535
+ _config.context.restore();
8438
8536
  return data;
8439
8537
  };
8440
8538
  plot.d2fdx2 = (func, config) => {
8441
- (config = {
8442
- x: (config = config || {}).x || getContext().canvas.width / 2,
8443
- y: config.y || getContext().canvas.height / 2,
8444
- size: config.size || 1,
8445
- strokeStyle: config.strokeStyle || "black",
8446
- lineWidth: config.lineWidth || 2,
8447
- domain: config.domain || [-10, 10],
8448
- res: config.res || 25,
8449
- isInverse: config.isInverse || false,
8450
- context: config.context || getContext()
8451
- }).size /= 100;
8539
+ const _config = $(config, { domain: [-10, 10], res: 25, isInverse: false });
8452
8540
  const data = [];
8453
- config.context.save();
8454
- config.context.translate(config.x, config.y);
8455
- config.context.lineWidth = config.lineWidth;
8456
- config.context.strokeStyle = config.strokeStyle;
8457
- config.context.beginPath();
8458
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8459
- if (func.type === "scalar2d" && !config.isInverse) {
8460
- config.context.lineTo(i, -Chalkboard.calc.d2fdx2(func, i * config.size) / config.size);
8541
+ _config.context.save();
8542
+ _config.context.translate(_config.x, _config.y);
8543
+ _config.context.lineWidth = _config.lineWidth;
8544
+ _config.context.strokeStyle = _config.strokeStyle;
8545
+ _config.context.beginPath();
8546
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8547
+ let previousY = null;
8548
+ let previousX = null;
8549
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8550
+ if (func.type === "scalar2d" && !_config.isInverse) {
8551
+ const currentY = -Chalkboard.calc.d2fdx2(func, i * _config.size) / _config.size;
8552
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8553
+ _config.context.moveTo(i, currentY);
8554
+ }
8555
+ else {
8556
+ _config.context.lineTo(i, currentY);
8557
+ }
8558
+ previousY = currentY;
8461
8559
  data.push([i, Chalkboard.calc.d2fdx2(func, i)]);
8462
8560
  }
8463
- else if (func.type === "scalar2d" && config.isInverse) {
8464
- config.context.lineTo(Chalkboard.calc.d2fdx2(func, i * config.size) / config.size, -i);
8561
+ else if (func.type === "scalar2d" && _config.isInverse) {
8562
+ const currentX = Chalkboard.calc.d2fdx2(func, i * _config.size) / _config.size;
8563
+ const currentY = -i;
8564
+ if (previousX === null || Math.abs(currentX - previousX) > discontinuityThreshold) {
8565
+ _config.context.moveTo(currentX, currentY);
8566
+ }
8567
+ else {
8568
+ _config.context.lineTo(currentX, currentY);
8569
+ }
8570
+ previousX = currentX;
8465
8571
  data.push([Chalkboard.calc.d2fdx2(func, i), i]);
8466
8572
  }
8467
8573
  }
8468
- config.context.stroke();
8469
- config.context.restore();
8574
+ _config.context.stroke();
8575
+ _config.context.restore();
8470
8576
  return data;
8471
8577
  };
8472
8578
  plot.field = (vectfield, config) => {
8473
- (config = {
8474
- x: (config = config || {}).x || getContext().canvas.width / 2,
8475
- y: config.y || getContext().canvas.height / 2,
8476
- size: config.size || 1,
8477
- strokeStyle: config.strokeStyle || "black",
8478
- lineWidth: config.lineWidth || 2,
8479
- domain: config.domain || [[-10, 10], [-10, 10]],
8480
- res: config.res || 25,
8481
- context: config.context || getContext()
8482
- }).size /= 100;
8579
+ const _config = $(config, { domain: [[-10, 10], [-10, 10]], res: 25 });
8483
8580
  const data = [];
8484
- config.context.strokeStyle = config.strokeStyle;
8485
- config.context.lineWidth = config.lineWidth;
8486
- config.context.save();
8487
- config.context.translate(config.x, config.y);
8488
- for (let i = config.domain[0][0] / config.size; i <= config.domain[0][1] / config.size; i += config.res) {
8489
- for (let j = config.domain[1][0] / config.size; j <= config.domain[1][1] / config.size; j += config.res) {
8581
+ _config.context.strokeStyle = _config.strokeStyle;
8582
+ _config.context.lineWidth = _config.lineWidth;
8583
+ _config.context.save();
8584
+ _config.context.translate(_config.x, _config.y);
8585
+ for (let i = _config.domain[0][0] / _config.size; i <= _config.domain[0][1] / _config.size; i += _config.res) {
8586
+ for (let j = _config.domain[1][0] / _config.size; j <= _config.domain[1][1] / _config.size; j += _config.res) {
8490
8587
  const v = Chalkboard.vect.fromField(vectfield, Chalkboard.vect.init(i, j));
8491
- config.context.beginPath();
8492
- config.context.moveTo(i, j);
8493
- config.context.lineTo(i + v.x, j + v.y);
8494
- config.context.stroke();
8588
+ _config.context.beginPath();
8589
+ _config.context.moveTo(i, j);
8590
+ _config.context.lineTo(i + v.x, j + v.y);
8591
+ _config.context.stroke();
8495
8592
  data.push([i + v.x, j + v.y]);
8496
8593
  }
8497
8594
  }
8498
- config.context.restore();
8595
+ _config.context.restore();
8499
8596
  return data;
8500
8597
  };
8501
8598
  plot.Fourier = (func, config) => {
8502
- (config = {
8503
- x: (config = config || {}).x || getContext().canvas.width / 2,
8504
- y: config.y || getContext().canvas.height / 2,
8505
- size: config.size || 1,
8506
- strokeStyle: config.strokeStyle || "black",
8507
- lineWidth: config.lineWidth || 2,
8508
- domain: config.domain || [-10, 10],
8509
- res: config.res || 25,
8510
- context: config.context || getContext()
8511
- }).size /= 100;
8599
+ const _config = $(config, { domain: [-10, 10], res: 25 });
8512
8600
  const data = [];
8513
- config.context.save();
8514
- config.context.translate(config.x, config.y);
8515
- config.context.lineWidth = config.lineWidth;
8516
- config.context.strokeStyle = config.strokeStyle;
8517
- config.context.beginPath();
8518
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8519
- config.context.lineTo(i, -Chalkboard.calc.Fourier(func, i * config.size) / config.size);
8601
+ _config.context.save();
8602
+ _config.context.translate(_config.x, _config.y);
8603
+ _config.context.lineWidth = _config.lineWidth;
8604
+ _config.context.strokeStyle = _config.strokeStyle;
8605
+ _config.context.beginPath();
8606
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8607
+ let previousY = null;
8608
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8609
+ const currentY = -Chalkboard.calc.Fourier(func, i * _config.size) / _config.size;
8610
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8611
+ _config.context.moveTo(i, currentY);
8612
+ }
8613
+ else {
8614
+ _config.context.lineTo(i, currentY);
8615
+ }
8616
+ previousY = currentY;
8520
8617
  data.push([i, Chalkboard.calc.Fourier(func, i)]);
8521
8618
  }
8522
- config.context.stroke();
8523
- config.context.restore();
8619
+ _config.context.stroke();
8620
+ _config.context.restore();
8524
8621
  return data;
8525
8622
  };
8526
8623
  plot.fxdx = (func, config) => {
8527
- (config = {
8528
- x: (config = config || {}).x || getContext().canvas.width / 2,
8529
- y: config.y || getContext().canvas.height / 2,
8530
- size: config.size || 1,
8531
- strokeStyle: config.strokeStyle || "black",
8532
- lineWidth: config.lineWidth || 2,
8533
- domain: config.domain || [-10, 10],
8534
- res: config.res || 25,
8535
- isInverse: config.isInverse || false,
8536
- context: config.context || getContext()
8537
- }).size /= 100;
8624
+ const _config = $(config, { domain: [-10, 10], res: 25, isInverse: false });
8538
8625
  const data = [];
8539
- config.context.save();
8540
- config.context.translate(config.x, config.y);
8541
- config.context.lineWidth = config.lineWidth;
8542
- config.context.strokeStyle = config.strokeStyle;
8543
- config.context.beginPath();
8544
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8545
- if (func.type === "scalar2d" && !config.isInverse) {
8546
- config.context.lineTo(i, -Chalkboard.calc.fxdx(func, 0, i * config.size) / config.size);
8626
+ _config.context.save();
8627
+ _config.context.translate(_config.x, _config.y);
8628
+ _config.context.lineWidth = _config.lineWidth;
8629
+ _config.context.strokeStyle = _config.strokeStyle;
8630
+ _config.context.beginPath();
8631
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8632
+ let previousY = null;
8633
+ let previousX = null;
8634
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8635
+ if (func.type === "scalar2d" && !_config.isInverse) {
8636
+ const currentY = -Chalkboard.calc.fxdx(func, 0, i * _config.size) / _config.size;
8637
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8638
+ _config.context.moveTo(i, currentY);
8639
+ }
8640
+ else {
8641
+ _config.context.lineTo(i, currentY);
8642
+ }
8643
+ previousY = currentY;
8547
8644
  data.push([i, Chalkboard.calc.fxdx(func, 0, i)]);
8548
8645
  }
8549
- else if (func.type === "scalar2d" && config.isInverse) {
8550
- config.context.lineTo(Chalkboard.calc.fxdx(func, 0, i * config.size) / config.size, -i);
8646
+ else if (func.type === "scalar2d" && _config.isInverse) {
8647
+ const currentX = Chalkboard.calc.fxdx(func, 0, i * _config.size) / _config.size;
8648
+ const currentY = -i;
8649
+ if (previousX === null || Math.abs(currentX - previousX) > discontinuityThreshold) {
8650
+ _config.context.moveTo(currentX, currentY);
8651
+ }
8652
+ else {
8653
+ _config.context.lineTo(currentX, currentY);
8654
+ }
8655
+ previousX = currentX;
8551
8656
  data.push([Chalkboard.calc.fxdx(func, 0, i), i]);
8552
8657
  }
8553
8658
  }
8554
- config.context.stroke();
8555
- config.context.restore();
8659
+ _config.context.stroke();
8660
+ _config.context.restore();
8556
8661
  return data;
8557
8662
  };
8558
8663
  plot.Laplace = (func, config) => {
8559
- (config = {
8560
- x: (config = config || {}).x || getContext().canvas.width / 2,
8561
- y: config.y || getContext().canvas.height / 2,
8562
- size: config.size || 1,
8563
- strokeStyle: config.strokeStyle || "black",
8564
- lineWidth: config.lineWidth || 2,
8565
- domain: config.domain || [-10, 10],
8566
- res: config.res || 25,
8567
- context: config.context || getContext()
8568
- }).size /= 100;
8664
+ const _config = $(config, { domain: [-10, 10], res: 25 });
8569
8665
  const data = [];
8570
- config.context.save();
8571
- config.context.translate(config.x, config.y);
8572
- config.context.lineWidth = config.lineWidth;
8573
- config.context.strokeStyle = config.strokeStyle;
8574
- config.context.beginPath();
8575
- if (config.domain[0] >= 0) {
8576
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8577
- config.context.lineTo(i, -Chalkboard.calc.Laplace(func, i * config.size) / config.size);
8666
+ _config.context.save();
8667
+ _config.context.translate(_config.x, _config.y);
8668
+ _config.context.lineWidth = _config.lineWidth;
8669
+ _config.context.strokeStyle = _config.strokeStyle;
8670
+ _config.context.beginPath();
8671
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8672
+ let previousY = null;
8673
+ if (_config.domain[0] >= 0) {
8674
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8675
+ const currentY = -Chalkboard.calc.Laplace(func, i * _config.size) / _config.size;
8676
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8677
+ _config.context.moveTo(i, currentY);
8678
+ }
8679
+ else {
8680
+ _config.context.lineTo(i, currentY);
8681
+ }
8682
+ previousY = currentY;
8578
8683
  data.push([i, Chalkboard.calc.Laplace(func, i)]);
8579
8684
  }
8580
8685
  }
8581
8686
  else {
8582
- for (let i = 0; i <= config.domain[1] / config.size; i += config.res) {
8583
- config.context.lineTo(i, -Chalkboard.calc.Laplace(func, i * config.size) / config.size);
8687
+ for (let i = 0; i <= _config.domain[1] / _config.size; i += _config.res) {
8688
+ const currentY = -Chalkboard.calc.Laplace(func, i * _config.size) / _config.size;
8689
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8690
+ _config.context.moveTo(i, currentY);
8691
+ }
8692
+ else {
8693
+ _config.context.lineTo(i, currentY);
8694
+ }
8695
+ previousY = currentY;
8584
8696
  data.push([i, Chalkboard.calc.Laplace(func, i)]);
8585
8697
  }
8586
8698
  }
8587
- config.context.stroke();
8588
- config.context.restore();
8699
+ _config.context.stroke();
8700
+ _config.context.restore();
8589
8701
  return data;
8590
8702
  };
8591
8703
  plot.lineplot = (arr, bins, config) => {
8592
- (config = {
8593
- x: (config = config || {}).x || getContext().canvas.width / 2,
8594
- y: config.y || getContext().canvas.height / 2,
8595
- size: config.size || 1,
8596
- strokeStyle: config.strokeStyle || "black",
8597
- lineWidth: config.lineWidth || 2,
8598
- context: config.context || getContext()
8599
- }).size /= 100;
8600
- config.context.save();
8601
- config.context.translate(config.x, config.y);
8602
- config.context.lineWidth = config.lineWidth;
8603
- config.context.strokeStyle = config.strokeStyle;
8704
+ const _config = $(config);
8705
+ _config.context.save();
8706
+ _config.context.translate(_config.x, _config.y);
8707
+ _config.context.lineWidth = _config.lineWidth;
8708
+ _config.context.strokeStyle = _config.strokeStyle;
8604
8709
  const verts = [];
8605
- for (let i = 0; i < bins.length; i++) {
8606
- if (i === 0) {
8607
- verts.push(Chalkboard.stat.lt(arr, bins[0], true));
8608
- }
8609
- else if (i === bins.length) {
8610
- verts.push(Chalkboard.stat.gt(arr, bins[bins.length - 1], true));
8611
- }
8612
- else {
8613
- verts.push(Chalkboard.stat.ineq(arr, bins[i - 1], bins[i], false, true));
8614
- }
8710
+ for (let i = 1; i < bins.length; i++) {
8711
+ verts.push(Chalkboard.stat.ineq(arr, bins[i - 1], bins[i], i === 1, true));
8615
8712
  }
8616
8713
  const counts = [];
8617
8714
  for (let i = 0; i < verts.length; i++) {
8618
8715
  counts.push(verts[i].length);
8619
8716
  }
8620
- config.context.beginPath();
8717
+ _config.context.beginPath();
8718
+ _config.context.moveTo(bins[0] / _config.size, 0);
8621
8719
  for (let i = 0; i < counts.length; i++) {
8622
- config.context.lineTo(i / config.size, -counts[i] / config.size);
8720
+ const midX = (bins[i] + bins[i + 1]) / 2 / _config.size;
8721
+ _config.context.lineTo(midX, -counts[i] / _config.size);
8623
8722
  }
8624
- config.context.stroke();
8625
- config.context.restore();
8723
+ _config.context.lineTo(bins[bins.length - 1] / _config.size, 0);
8724
+ _config.context.stroke();
8725
+ _config.context.restore();
8626
8726
  return verts;
8627
8727
  };
8628
8728
  plot.matr = (matr, config) => {
8629
- (config = {
8630
- x: (config = config || {}).x || getContext().canvas.width / 2,
8631
- y: config.y || getContext().canvas.height / 2,
8632
- size: config.size || 1,
8633
- strokeStyle: config.strokeStyle || "black",
8634
- lineWidth: config.lineWidth || 2,
8635
- domain: config.domain || [-10, 10],
8636
- context: config.context || getContext()
8637
- }).size /= 100;
8638
- for (let i = config.domain[0]; i <= config.domain[1]; i++) {
8729
+ const _config = $(config, { domain: [-10, 10] });
8730
+ for (let i = _config.domain[0]; i <= _config.domain[1]; i++) {
8639
8731
  Chalkboard.plot.vect(Chalkboard.vect.init(matr[0][0], matr[1][0]), {
8640
- x: config.x,
8641
- y: config.y + (i / config.size) * matr[1][1],
8642
- size: config.size,
8643
- strokeStyle: config.strokeStyle,
8644
- lineWidth: config.lineWidth / 4,
8645
- context: config.context
8732
+ x: _config.x,
8733
+ y: _config.y + (i / _config.size) * matr[1][1],
8734
+ size: _config.size,
8735
+ strokeStyle: _config.strokeStyle,
8736
+ lineWidth: _config.lineWidth / 4,
8737
+ context: _config.context
8646
8738
  });
8647
8739
  Chalkboard.plot.vect(Chalkboard.vect.init(-matr[0][0], -matr[1][0]), {
8648
- x: config.x,
8649
- y: config.y + (i / config.size) * matr[1][1],
8650
- size: config.size,
8651
- strokeStyle: config.strokeStyle,
8652
- lineWidth: config.lineWidth / 4,
8653
- context: config.context
8740
+ x: _config.x,
8741
+ y: _config.y + (i / _config.size) * matr[1][1],
8742
+ size: _config.size,
8743
+ strokeStyle: _config.strokeStyle,
8744
+ lineWidth: _config.lineWidth / 4,
8745
+ context: _config.context
8654
8746
  });
8655
8747
  Chalkboard.plot.vect(Chalkboard.vect.init(matr[0][1], matr[1][1]), {
8656
- x: config.x + (i / config.size) * matr[0][0],
8657
- y: config.y,
8658
- size: config.size,
8659
- strokeStyle: config.strokeStyle,
8660
- lineWidth: config.lineWidth / 4,
8661
- context: config.context
8748
+ x: _config.x + (i / _config.size) * matr[0][0],
8749
+ y: _config.y,
8750
+ size: _config.size,
8751
+ strokeStyle: _config.strokeStyle,
8752
+ lineWidth: _config.lineWidth / 4,
8753
+ context: _config.context
8662
8754
  });
8663
8755
  Chalkboard.plot.vect(Chalkboard.vect.init(-matr[0][1], -matr[1][1]), {
8664
- x: config.x + (i / config.size) * matr[0][0],
8665
- y: config.y,
8666
- size: config.size,
8667
- strokeStyle: config.strokeStyle,
8668
- lineWidth: config.lineWidth / 4,
8669
- context: config.context
8756
+ x: _config.x + (i / _config.size) * matr[0][0],
8757
+ y: _config.y,
8758
+ size: _config.size,
8759
+ strokeStyle: _config.strokeStyle,
8760
+ lineWidth: _config.lineWidth / 4,
8761
+ context: _config.context
8670
8762
  });
8671
8763
  }
8672
- Chalkboard.plot.vect(Chalkboard.vect.init(matr[0][0], matr[1][0]), config);
8673
- Chalkboard.plot.vect(Chalkboard.vect.init(-matr[0][0], -matr[1][0]), config);
8674
- Chalkboard.plot.vect(Chalkboard.vect.init(matr[0][1], matr[1][1]), config);
8675
- Chalkboard.plot.vect(Chalkboard.vect.init(-matr[0][1], -matr[1][1]), config);
8764
+ Chalkboard.plot.vect(Chalkboard.vect.init(matr[0][0], matr[1][0]), _config);
8765
+ Chalkboard.plot.vect(Chalkboard.vect.init(-matr[0][0], -matr[1][0]), _config);
8766
+ Chalkboard.plot.vect(Chalkboard.vect.init(matr[0][1], matr[1][1]), _config);
8767
+ Chalkboard.plot.vect(Chalkboard.vect.init(-matr[0][1], -matr[1][1]), _config);
8676
8768
  return matr;
8677
8769
  };
8678
8770
  plot.ode = (sol, config = {}) => {
8771
+ const _config = $(config, { phase: false, i: 0, j: 1 });
8679
8772
  if (!sol || !Array.isArray(sol.t) || !Array.isArray(sol.y))
8680
8773
  throw new Error(`Chalkboard.plot.ode: Parameter "sol" must have properties "t" and "y" as arrays.`);
8681
8774
  if (sol.t.length !== sol.y.length || sol.t.length === 0)
8682
8775
  throw new Error(`Chalkboard.plot.ode: Invalid solution object (length mismatch or empty).`);
8683
- const ctx = config.context || getContext();
8684
- const x0 = (config.x ?? ctx.canvas.width / 2);
8685
- const y0 = (config.y ?? ctx.canvas.height / 2);
8686
- const strokeStyle = config.strokeStyle ?? "black";
8687
- const lineWidth = config.lineWidth ?? 2;
8688
- const size = ((config.size ?? 1) / 100);
8689
- const phase = config.phase ?? false;
8690
- const i = config.i ?? 0;
8691
- const j = config.j ?? 1;
8692
8776
  const dim = sol.y[0].length;
8693
- if (!Number.isInteger(i) || i < 0)
8777
+ if (!Number.isInteger(_config.i) || _config.i < 0)
8694
8778
  throw new Error(`Chalkboard.plot.ode: "i" must be an integer >= 0.`);
8695
- if (i >= dim)
8779
+ if (_config.i >= dim)
8696
8780
  throw new Error(`Chalkboard.plot.ode: "i" is out of range for solution dimension.`);
8697
- if (phase) {
8698
- if (!Number.isInteger(j) || j < 0)
8781
+ if (_config.phase) {
8782
+ if (!Number.isInteger(_config.j) || _config.j < 0)
8699
8783
  throw new Error(`Chalkboard.plot.ode: "j" must be an integer >= 0.`);
8700
- if (j >= dim)
8784
+ if (_config.j >= dim)
8701
8785
  throw new Error(`Chalkboard.plot.ode: "j" is out of range for solution dimension.`);
8702
- if (i === j)
8786
+ if (_config.i === _config.j)
8703
8787
  throw new Error(`Chalkboard.plot.ode: For phase plots, "i" and "j" must be different.`);
8704
8788
  }
8705
8789
  const data = [];
8706
- ctx.save();
8707
- ctx.translate(x0, y0);
8708
- ctx.lineWidth = lineWidth;
8709
- ctx.strokeStyle = strokeStyle;
8710
- ctx.beginPath();
8711
- if (!phase) {
8790
+ _config.context.save();
8791
+ _config.context.translate(_config.x, _config.y);
8792
+ _config.context.lineWidth = _config.lineWidth;
8793
+ _config.context.strokeStyle = _config.strokeStyle;
8794
+ _config.context.beginPath();
8795
+ if (!_config.phase) {
8712
8796
  for (let k = 0; k < sol.t.length; k++) {
8713
- const X = sol.t[k] / size;
8714
- const Y = -sol.y[k][i] / size;
8797
+ const X = sol.t[k] / _config.size;
8798
+ const Y = -sol.y[k][_config.i] / _config.size;
8715
8799
  if (k === 0)
8716
- ctx.moveTo(X, Y);
8800
+ _config.context.moveTo(X, Y);
8717
8801
  else
8718
- ctx.lineTo(X, Y);
8719
- data.push([sol.t[k], sol.y[k][i]]);
8802
+ _config.context.lineTo(X, Y);
8803
+ data.push([sol.t[k], sol.y[k][_config.i]]);
8720
8804
  }
8721
8805
  }
8722
8806
  else {
8723
8807
  for (let k = 0; k < sol.y.length; k++) {
8724
- const X = sol.y[k][i] / size;
8725
- const Y = -sol.y[k][j] / size;
8808
+ const X = sol.y[k][_config.i] / _config.size;
8809
+ const Y = -sol.y[k][_config.j] / _config.size;
8726
8810
  if (k === 0)
8727
- ctx.moveTo(X, Y);
8811
+ _config.context.moveTo(X, Y);
8728
8812
  else
8729
- ctx.lineTo(X, Y);
8730
- data.push([sol.y[k][i], sol.y[k][j]]);
8813
+ _config.context.lineTo(X, Y);
8814
+ data.push([sol.y[k][_config.i], sol.y[k][_config.j]]);
8731
8815
  }
8732
8816
  }
8733
- ctx.stroke();
8734
- ctx.restore();
8817
+ _config.context.stroke();
8818
+ _config.context.restore();
8735
8819
  return data;
8736
8820
  };
8737
8821
  plot.rOplane = (config) => {
8738
- (config = {
8739
- x: (config = config || {}).x || getContext().canvas.width / 2,
8740
- y: config.y || getContext().canvas.height / 2,
8741
- size: config.size || 1,
8742
- strokeStyle: config.strokeStyle || "black",
8743
- lineWidth: config.lineWidth || 2,
8744
- context: config.context || getContext()
8745
- }).size /= 100;
8822
+ const _config = $(config);
8746
8823
  const cw = getContext().canvas.width;
8747
- config.context.save();
8748
- config.context.translate(config.x, config.y);
8749
- config.context.strokeStyle = config.strokeStyle;
8750
- config.context.lineWidth = config.lineWidth / 4;
8751
- config.context.beginPath();
8752
- for (let i = 0; i <= (config.size * cw) / 2; i++) {
8753
- config.context.ellipse(0, 0, i / config.size, i / config.size, 0, 0, Chalkboard.PI(2));
8754
- }
8755
- config.context.stroke();
8756
- config.context.lineWidth = config.lineWidth;
8757
- config.context.beginPath();
8758
- config.context.moveTo(-config.x, 0);
8759
- config.context.lineTo(cw - config.x, 0);
8760
- config.context.stroke();
8761
- config.context.beginPath();
8762
- config.context.moveTo(0, -config.y);
8763
- config.context.lineTo(0, cw - config.y);
8764
- config.context.stroke();
8765
- config.context.restore();
8824
+ _config.context.save();
8825
+ _config.context.translate(_config.x, _config.y);
8826
+ _config.context.strokeStyle = _config.strokeStyle;
8827
+ _config.context.lineWidth = _config.lineWidth / 4;
8828
+ _config.context.beginPath();
8829
+ for (let i = 0; i <= (_config.size * cw) / 2; i++) {
8830
+ _config.context.ellipse(0, 0, i / _config.size, i / _config.size, 0, 0, Chalkboard.PI(2));
8831
+ }
8832
+ _config.context.stroke();
8833
+ _config.context.lineWidth = _config.lineWidth;
8834
+ _config.context.beginPath();
8835
+ _config.context.moveTo(-_config.x, 0);
8836
+ _config.context.lineTo(cw - _config.x, 0);
8837
+ _config.context.stroke();
8838
+ _config.context.beginPath();
8839
+ _config.context.moveTo(0, -_config.y);
8840
+ _config.context.lineTo(0, cw - _config.y);
8841
+ _config.context.stroke();
8842
+ _config.context.restore();
8766
8843
  };
8767
8844
  plot.scatterplot = (arr1, arr2, config) => {
8768
- (config = {
8769
- x: (config = config || {}).x || getContext().canvas.width / 2,
8770
- y: config.y || getContext().canvas.height / 2,
8771
- size: config.size || 1,
8772
- fillStyle: config.fillStyle || "black",
8773
- lineWidth: config.lineWidth || 5,
8774
- context: config.context || getContext()
8775
- }).size /= 100;
8845
+ const _config = $(config, { fillStyle: "black", lineWidth: 5 });
8776
8846
  const data = [];
8777
- config.context.save();
8778
- config.context.translate(config.x, config.y);
8779
- config.context.fillStyle = config.fillStyle;
8847
+ _config.context.save();
8848
+ _config.context.translate(_config.x, _config.y);
8849
+ _config.context.fillStyle = _config.fillStyle;
8780
8850
  if (arr1.length === arr2.length) {
8781
8851
  for (let i = 0; i < arr1.length; i++) {
8782
- config.context.beginPath();
8783
- config.context.ellipse(arr1[i] / config.size - arr1.length / (2 * config.size), -arr2[i] / config.size + arr1.length / (2 * config.size), config.lineWidth, config.lineWidth, 0, 0, Chalkboard.PI(2));
8784
- config.context.fill();
8852
+ _config.context.beginPath();
8853
+ _config.context.ellipse(arr1[i] / _config.size, -arr2[i] / _config.size, _config.lineWidth, _config.lineWidth, 0, 0, Chalkboard.PI(2));
8854
+ _config.context.fill();
8785
8855
  data.push([arr1[i], arr2[i]]);
8786
8856
  }
8787
8857
  }
8788
- config.context.restore();
8858
+ _config.context.restore();
8789
8859
  return data;
8790
8860
  };
8791
8861
  plot.Taylor = (func, n, a, config) => {
8792
- (config = {
8793
- x: (config = config || {}).x || getContext().canvas.width / 2,
8794
- y: config.y || getContext().canvas.height / 2,
8795
- size: config.size || 1,
8796
- strokeStyle: config.strokeStyle || "black",
8797
- lineWidth: config.lineWidth || 2,
8798
- domain: config.domain || [-10, 10],
8799
- res: config.res || 25,
8800
- context: config.context || getContext()
8801
- }).size /= 100;
8862
+ const _config = $(config, { domain: [-10, 10], res: 25 });
8802
8863
  const data = [];
8803
- config.context.save();
8804
- config.context.translate(config.x, config.y);
8805
- config.context.lineWidth = config.lineWidth;
8806
- config.context.strokeStyle = config.strokeStyle;
8807
- config.context.beginPath();
8808
- for (let i = config.domain[0] / config.size; i <= config.domain[1] / config.size; i += config.res) {
8809
- config.context.lineTo(i, -Chalkboard.calc.Taylor(func, i * config.size, n, a) / config.size);
8864
+ _config.context.save();
8865
+ _config.context.translate(_config.x, _config.y);
8866
+ _config.context.lineWidth = _config.lineWidth;
8867
+ _config.context.strokeStyle = _config.strokeStyle;
8868
+ _config.context.beginPath();
8869
+ const discontinuityThreshold = (_config.context.canvas.height / _config.size) * 1.5;
8870
+ let previousY = null;
8871
+ for (let i = _config.domain[0] / _config.size; i <= _config.domain[1] / _config.size; i += _config.res) {
8872
+ const currentY = -Chalkboard.calc.Taylor(func, i * _config.size, n, a) / _config.size;
8873
+ if (previousY === null || Math.abs(currentY - previousY) > discontinuityThreshold) {
8874
+ _config.context.moveTo(i, currentY);
8875
+ }
8876
+ else {
8877
+ _config.context.lineTo(i, currentY);
8878
+ }
8879
+ previousY = currentY;
8810
8880
  data.push([i, Chalkboard.calc.Taylor(func, i, n, a)]);
8811
8881
  }
8812
- config.context.stroke();
8813
- config.context.restore();
8882
+ _config.context.stroke();
8883
+ _config.context.restore();
8814
8884
  return data;
8815
8885
  };
8816
8886
  plot.vect = (vect, config) => {
8817
- (config = {
8818
- x: (config = config || {}).x || getContext().canvas.width / 2,
8819
- y: config.y || getContext().canvas.height / 2,
8820
- size: config.size || 1,
8821
- strokeStyle: config.strokeStyle || "black",
8822
- lineWidth: config.lineWidth || 5,
8823
- context: config.context || getContext()
8824
- }).size /= 100;
8887
+ const _config = $(config, { lineWidth: 5 });
8825
8888
  vect = vect;
8826
- config.context.strokeStyle = config.strokeStyle;
8827
- config.context.lineWidth = config.lineWidth;
8828
- config.context.save();
8829
- config.context.translate(config.x, config.y);
8830
- config.context.beginPath();
8831
- config.context.moveTo(0, 0);
8832
- config.context.lineTo(vect.x / config.size, -vect.y / config.size);
8833
- config.context.stroke();
8834
- config.context.restore();
8889
+ _config.context.strokeStyle = _config.strokeStyle;
8890
+ _config.context.lineWidth = _config.lineWidth;
8891
+ _config.context.save();
8892
+ _config.context.translate(_config.x, _config.y);
8893
+ _config.context.beginPath();
8894
+ _config.context.moveTo(0, 0);
8895
+ _config.context.lineTo(vect.x / _config.size, -vect.y / _config.size);
8896
+ _config.context.stroke();
8897
+ _config.context.restore();
8835
8898
  return [[vect.x], [vect.y]];
8836
8899
  };
8837
8900
  plot.xyplane = (config) => {
8838
- (config = {
8839
- x: (config = config || {}).x || getContext().canvas.width / 2,
8840
- y: config.y || getContext().canvas.height / 2,
8841
- size: config.size || 1,
8842
- strokeStyle: config.strokeStyle || "black",
8843
- lineWidth: config.lineWidth || 2,
8844
- context: config.context || getContext()
8845
- }).size /= 100;
8901
+ const _config = $(config);
8846
8902
  const cw = getContext().canvas.width;
8847
- config.context.save();
8848
- config.context.translate(config.x, config.y);
8849
- config.context.strokeStyle = config.strokeStyle;
8850
- config.context.lineWidth = config.lineWidth / 4;
8851
- config.context.beginPath();
8852
- for (let i = Math.floor(-config.x / config.size); i <= (cw - config.x) / config.size; i++) {
8853
- config.context.moveTo(i / config.size, -config.y);
8854
- config.context.lineTo(i / config.size, cw - config.y);
8855
- }
8856
- config.context.stroke();
8857
- config.context.beginPath();
8858
- for (let i = Math.floor(-config.y / config.size); i <= (cw - config.y) / config.size; i++) {
8859
- config.context.moveTo(-config.x, i / config.size);
8860
- config.context.lineTo(cw - config.x, i / config.size);
8861
- }
8862
- config.context.stroke();
8863
- config.context.lineWidth = config.lineWidth;
8864
- config.context.beginPath();
8865
- config.context.moveTo(-config.x, 0);
8866
- config.context.lineTo(cw - config.x, 0);
8867
- config.context.stroke();
8868
- config.context.beginPath();
8869
- config.context.moveTo(0, -config.y);
8870
- config.context.lineTo(0, cw - config.y);
8871
- config.context.stroke();
8872
- config.context.restore();
8903
+ _config.context.save();
8904
+ _config.context.translate(_config.x, _config.y);
8905
+ _config.context.strokeStyle = _config.strokeStyle;
8906
+ _config.context.lineWidth = _config.lineWidth / 4;
8907
+ _config.context.beginPath();
8908
+ for (let i = Math.floor(-_config.x / _config.size); i <= (cw - _config.x) / _config.size; i++) {
8909
+ _config.context.moveTo(i / _config.size, -_config.y);
8910
+ _config.context.lineTo(i / _config.size, cw - _config.y);
8911
+ }
8912
+ _config.context.stroke();
8913
+ _config.context.beginPath();
8914
+ for (let i = Math.floor(-_config.y / _config.size); i <= (cw - _config.y) / _config.size; i++) {
8915
+ _config.context.moveTo(-_config.x, i / _config.size);
8916
+ _config.context.lineTo(cw - _config.x, i / _config.size);
8917
+ }
8918
+ _config.context.stroke();
8919
+ _config.context.lineWidth = _config.lineWidth;
8920
+ _config.context.beginPath();
8921
+ _config.context.moveTo(-_config.x, 0);
8922
+ _config.context.lineTo(cw - _config.x, 0);
8923
+ _config.context.stroke();
8924
+ _config.context.beginPath();
8925
+ _config.context.moveTo(0, -_config.y);
8926
+ _config.context.lineTo(0, cw - _config.y);
8927
+ _config.context.stroke();
8928
+ _config.context.restore();
8873
8929
  };
8874
8930
  })(plot = Chalkboard.plot || (Chalkboard.plot = {}));
8875
8931
  })(Chalkboard || (Chalkboard = {}));
@@ -9240,15 +9296,15 @@ var Chalkboard;
9240
9296
  return 0;
9241
9297
  }
9242
9298
  };
9243
- real.discriminant = (a, b, c, form = "stan") => {
9244
- if (form === "stan") {
9299
+ real.discriminant = (a, b, c, form = "standard") => {
9300
+ if (form === "standard") {
9245
9301
  return b * b - 4 * a * c;
9246
9302
  }
9247
- else if (form === "vert") {
9303
+ else if (form === "vertex") {
9248
9304
  return 2 * a * b * (2 * a * b) - 4 * a * c;
9249
9305
  }
9250
9306
  else {
9251
- throw new TypeError("Chalkboard.real.discriminant: String 'form' must be 'stan' or 'vert'.");
9307
+ throw new TypeError("Chalkboard.real.discriminant: String 'form' must be 'standard' or 'vertex'.");
9252
9308
  }
9253
9309
  };
9254
9310
  real.div = (func1, func2) => {
@@ -9303,12 +9359,12 @@ var Chalkboard;
9303
9359
  const a4 = -1.453152027;
9304
9360
  const a5 = 1.061405429;
9305
9361
  const t = 1 / (1 + p * x);
9306
- const y = 1 - (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t) * Math.exp(-x * x);
9362
+ const y = 1 - (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t) * Chalkboard.E(-x * x);
9307
9363
  return sign * y;
9308
9364
  };
9309
9365
  real.Gamma = (num) => {
9310
9366
  if (typeof num !== "number" || !Number.isFinite(num))
9311
- throw new TypeError("Chalkboard.real.gamma: Parameter 'num' must be a finite number.");
9367
+ throw new TypeError("Chalkboard.real.Gamma: Parameter 'num' must be a finite number.");
9312
9368
  if (Number.isInteger(num) && num <= 0)
9313
9369
  return NaN;
9314
9370
  const p0 = 0.99999999999980993;
@@ -9321,7 +9377,7 @@ var Chalkboard;
9321
9377
  const p7 = 9.9843695780195716e-6;
9322
9378
  const p8 = 1.5056327351493116e-7;
9323
9379
  if (num < 0.5)
9324
- return Math.PI / (Math.sin(Math.PI * num) * Chalkboard.real.Gamma(1 - num));
9380
+ return Chalkboard.PI() / (Chalkboard.trig.sin(Chalkboard.PI(num)) * Chalkboard.real.Gamma(1 - num));
9325
9381
  const g = 7;
9326
9382
  let x = num - 1;
9327
9383
  let a = p0;
@@ -9334,7 +9390,7 @@ var Chalkboard;
9334
9390
  a += p7 / (x + 7);
9335
9391
  a += p8 / (x + 8);
9336
9392
  const t = x + g + 0.5;
9337
- return Math.sqrt(2 * Math.PI) * Math.pow(t, x + 0.5) * Math.exp(-t) * a;
9393
+ return Chalkboard.real.sqrt(Chalkboard.PI(2)) * Chalkboard.real.pow(t, x + 0.5) * Chalkboard.E(-t) * a;
9338
9394
  };
9339
9395
  real.Heaviside = (num, edge = 0, scl = 1) => {
9340
9396
  if (num >= edge) {
@@ -9362,7 +9418,25 @@ var Chalkboard;
9362
9418
  }
9363
9419
  };
9364
9420
  real.ln = (num) => {
9365
- return Chalkboard.calc.fxdx(Chalkboard.real.define((x) => 1 / x), 1, num);
9421
+ if (num <= 0)
9422
+ return NaN;
9423
+ if (num === 1)
9424
+ return 0;
9425
+ if (num === Infinity)
9426
+ return Infinity;
9427
+ const LN2 = 0.6931471805599453;
9428
+ let E = 0, m = num;
9429
+ while (m > 1.414213562373095) {
9430
+ m *= 0.5;
9431
+ E++;
9432
+ }
9433
+ while (m < 0.7071067811865475) {
9434
+ m *= 2.0;
9435
+ E--;
9436
+ }
9437
+ const y = (m - 1) / (m + 1), y2 = y * y, y3 = y2 * y, y5 = y3 * y2, y7 = y5 * y2, y9 = y7 * y2, y11 = y9 * y2, y13 = y11 * y2, y15 = y13 * y2, y17 = y15 * y2, y19 = y17 * y2;
9438
+ const series = y + y3 / 3 + y5 / 5 + y7 / 7 + y9 / 9 + y11 / 11 + y13 / 13 + y15 / 15 + y17 / 17 + y19 / 19;
9439
+ return 2 * series + E * LN2;
9366
9440
  };
9367
9441
  real.log = (base, num) => {
9368
9442
  return Chalkboard.real.ln(num) / Chalkboard.real.ln(base);
@@ -10293,11 +10367,30 @@ var Chalkboard;
10293
10367
  };
10294
10368
  real.pow = (base, num) => {
10295
10369
  if (typeof base === "number") {
10296
- if (base === 0 && num === 0) {
10370
+ if (base === 0 && num === 0)
10371
+ return 1;
10372
+ if (base === 0)
10373
+ return 0;
10374
+ if (num === 0)
10297
10375
  return 1;
10376
+ if (num === 1)
10377
+ return base;
10378
+ if (Number.isInteger(num)) {
10379
+ let res = 1;
10380
+ let b = base;
10381
+ let n = Math.abs(num);
10382
+ while (n > 0) {
10383
+ if (n % 2 === 1)
10384
+ res *= b;
10385
+ b *= b;
10386
+ n = Math.floor(n / 2);
10387
+ }
10388
+ return num < 0 ? 1 / res : res;
10298
10389
  }
10299
10390
  else {
10300
- return Math.exp(num * Math.log(base));
10391
+ if (base < 0)
10392
+ return NaN;
10393
+ return Chalkboard.E(num * Chalkboard.real.ln(base));
10301
10394
  }
10302
10395
  }
10303
10396
  else {
@@ -10346,26 +10439,26 @@ var Chalkboard;
10346
10439
  (p3[1] * p1[0] * p2[0]) / ((p3[0] - p1[0]) * (p3[0] - p2[0]));
10347
10440
  return a * t * t + b * t + c;
10348
10441
  };
10349
- real.quadratic = (a, b, c, form = "stan") => {
10350
- if (form === "stan") {
10442
+ real.quadratic = (a, b, c, form = "standard") => {
10443
+ if (form === "standard") {
10351
10444
  return Chalkboard.real.define((x) => a * x * x + b * x + c);
10352
10445
  }
10353
- else if (form === "vert") {
10446
+ else if (form === "vertex") {
10354
10447
  return Chalkboard.real.define((x) => a * (x - b) * (x - b) + c);
10355
10448
  }
10356
10449
  else {
10357
- throw new TypeError("Chalkboard.real.quadratic: String 'form' must be 'stan' or 'vert'.");
10450
+ throw new TypeError("Chalkboard.real.quadratic: String 'form' must be 'standard' or 'vertex'.");
10358
10451
  }
10359
10452
  };
10360
- real.quadraticFormula = (a, b, c, form = "stan") => {
10361
- if (form === "stan") {
10362
- return [(-b + Chalkboard.real.sqrt(Chalkboard.real.discriminant(a, b, c, "stan"))) / (2 * a), (-b - Math.sqrt(Chalkboard.real.discriminant(a, b, c, "stan"))) / (2 * a)];
10453
+ real.quadraticFormula = (a, b, c, form = "standard") => {
10454
+ if (form === "standard") {
10455
+ return [(-b + Chalkboard.real.sqrt(Chalkboard.real.discriminant(a, b, c, "standard"))) / (2 * a), (-b - Chalkboard.real.sqrt(Chalkboard.real.discriminant(a, b, c, "standard"))) / (2 * a)];
10363
10456
  }
10364
- else if (form === "vert") {
10457
+ else if (form === "vertex") {
10365
10458
  return [b + Chalkboard.real.sqrt(-c / a), b - Chalkboard.real.sqrt(-c / a)];
10366
10459
  }
10367
10460
  else {
10368
- throw new TypeError("Chalkboard.real.quadraticFormula: String 'form' must be 'stan' or 'vert'.");
10461
+ throw new TypeError("Chalkboard.real.quadraticFormula: String 'form' must be 'standard' or 'vertex'.");
10369
10462
  }
10370
10463
  };
10371
10464
  real.ramp = (num, edge = 0, scl = 1) => {
@@ -10422,7 +10515,14 @@ var Chalkboard;
10422
10515
  }
10423
10516
  };
10424
10517
  real.root = (num, index = 3) => {
10425
- return Math.exp(Math.log(num) / index);
10518
+ if (num === 0)
10519
+ return 0;
10520
+ if (num < 0) {
10521
+ if (Number.isInteger(index) && Math.abs(index) % 2 === 1)
10522
+ return -Chalkboard.E(Chalkboard.real.ln(-num) / index);
10523
+ return NaN;
10524
+ }
10525
+ return Chalkboard.E(Chalkboard.real.ln(num) / index);
10426
10526
  };
10427
10527
  real.scl = (func, num) => {
10428
10528
  if (func.field !== "real")
@@ -10462,12 +10562,26 @@ var Chalkboard;
10462
10562
  return (y2 - y1) / (x2 - x1);
10463
10563
  };
10464
10564
  real.sqrt = (num) => {
10465
- if (num >= 0) {
10466
- return Math.exp(Math.log(num) / 2);
10467
- }
10468
- else {
10565
+ if (num < 0)
10469
10566
  return NaN;
10470
- }
10567
+ if (num === 0 || num === 1 || num === Infinity)
10568
+ return num;
10569
+ let S = num, E = 0;
10570
+ while (S >= 1.0) {
10571
+ S *= 0.25;
10572
+ E++;
10573
+ }
10574
+ while (S < 0.25) {
10575
+ S *= 4.0;
10576
+ E--;
10577
+ }
10578
+ let x = (S + 1.0) * 0.5;
10579
+ x = 0.5 * (x + S / x);
10580
+ x = 0.5 * (x + S / x);
10581
+ x = 0.5 * (x + S / x);
10582
+ x = 0.5 * (x + S / x);
10583
+ x = 0.5 * (x + S / x);
10584
+ return x * (2 ** E);
10471
10585
  };
10472
10586
  real.sub = (func1, func2) => {
10473
10587
  if (func1.field !== "real" || func2.field !== "real")
@@ -10510,12 +10624,19 @@ var Chalkboard;
10510
10624
  throw new TypeError("Chalkboard.real.sub: Properties 'type' of 'func1' and 'func2' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
10511
10625
  };
10512
10626
  real.tetration = (base, num) => {
10513
- if (num === 0) {
10627
+ if (!Number.isInteger(num) || num < 0)
10628
+ return NaN;
10629
+ if (num === 0)
10514
10630
  return 1;
10631
+ if (num === 1)
10632
+ return base;
10633
+ let result = base;
10634
+ for (let i = 1; i < num; i++) {
10635
+ result = Chalkboard.real.pow(base, result);
10636
+ if (result === Infinity)
10637
+ return Infinity;
10515
10638
  }
10516
- else if (num > 0) {
10517
- return Math.pow(base, Chalkboard.real.tetration(base, num - 1));
10518
- }
10639
+ return result;
10519
10640
  };
10520
10641
  real.translate = (func, h = 0, v = 0) => {
10521
10642
  if (func.field !== "real")
@@ -10621,6 +10742,34 @@ var Chalkboard;
10621
10742
  (function (Chalkboard) {
10622
10743
  let stat;
10623
10744
  (function (stat) {
10745
+ const $quickselect = (arr, k) => {
10746
+ const select = (left, right, k) => {
10747
+ if (left === right)
10748
+ return arr[left];
10749
+ let pivotIndex = Math.floor(Math.random() * (right - left + 1)) + left;
10750
+ const pivotValue = arr[pivotIndex];
10751
+ arr[pivotIndex] = arr[right];
10752
+ arr[right] = pivotValue;
10753
+ let storeIndex = left;
10754
+ for (let i = left; i < right; i++) {
10755
+ if (arr[i] < pivotValue) {
10756
+ const temp = arr[storeIndex];
10757
+ arr[storeIndex] = arr[i];
10758
+ arr[i] = temp;
10759
+ storeIndex++;
10760
+ }
10761
+ }
10762
+ arr[right] = arr[storeIndex];
10763
+ arr[storeIndex] = pivotValue;
10764
+ if (k === storeIndex)
10765
+ return arr[k];
10766
+ else if (k < storeIndex)
10767
+ return select(left, storeIndex - 1, k);
10768
+ else
10769
+ return select(storeIndex + 1, right, k);
10770
+ };
10771
+ return select(0, arr.length - 1, k);
10772
+ };
10624
10773
  stat.absolute = (arr) => {
10625
10774
  const result = [];
10626
10775
  for (let i = 0; i < arr.length; i++) {
@@ -11081,14 +11230,15 @@ var Chalkboard;
11081
11230
  return sum / weightSum;
11082
11231
  };
11083
11232
  stat.median = (arr) => {
11084
- const temp = arr.slice().sort(function (a, b) {
11085
- return a - b;
11086
- });
11087
- if (temp.length % 2 === 1) {
11088
- return temp[Math.floor(temp.length / 2)];
11233
+ if (arr.length === 0)
11234
+ return NaN;
11235
+ const copy = arr.slice();
11236
+ const mid = Math.floor(copy.length / 2);
11237
+ if (copy.length % 2 === 1) {
11238
+ return $quickselect(copy, mid);
11089
11239
  }
11090
11240
  else {
11091
- return (temp[temp.length / 2] + temp[temp.length / 2 - 1]) / 2;
11241
+ return ($quickselect(copy, mid - 1) + $quickselect(copy, mid)) / 2.0;
11092
11242
  }
11093
11243
  };
11094
11244
  stat.min = (arr) => {
@@ -11101,30 +11251,21 @@ var Chalkboard;
11101
11251
  return min;
11102
11252
  };
11103
11253
  stat.mode = (arr) => {
11104
- const temp = arr.slice().sort(function (a, b) {
11105
- return a - b;
11106
- });
11107
- let bestStr = 1;
11108
- let currStr = 1;
11109
- let bestElm = temp[0];
11110
- let currElm = temp[0];
11111
- for (let i = 1; i < temp.length; i++) {
11112
- if (temp[i - 1] !== temp[i]) {
11113
- if (currStr > bestStr) {
11114
- bestStr = currStr;
11115
- bestElm = currElm;
11116
- }
11117
- currStr = 0;
11118
- currElm = temp[i];
11254
+ if (arr.length === 0)
11255
+ return NaN;
11256
+ const frequency = new Map();
11257
+ let maxFreq = 0;
11258
+ let result = arr[0];
11259
+ for (let i = 0; i < arr.length; i++) {
11260
+ const val = arr[i];
11261
+ const count = (frequency.get(val) || 0) + 1;
11262
+ frequency.set(val, count);
11263
+ if (count > maxFreq) {
11264
+ maxFreq = count;
11265
+ result = val;
11119
11266
  }
11120
- currStr++;
11121
- }
11122
- if (currStr > bestStr) {
11123
- return currElm;
11124
- }
11125
- else {
11126
- return bestElm;
11127
11267
  }
11268
+ return result;
11128
11269
  };
11129
11270
  stat.mul = (arr) => {
11130
11271
  let result = 1;
@@ -11231,19 +11372,26 @@ var Chalkboard;
11231
11372
  console.log(Chalkboard.stat.toString(arr));
11232
11373
  };
11233
11374
  stat.quartile = (arr, type) => {
11234
- const temp = arr.slice().sort(function (a, b) {
11235
- return a - b;
11236
- });
11237
- const lo = temp.slice(0, Math.floor(temp.length / 2));
11238
- const hi = temp.slice(Math.ceil(temp.length / 2));
11375
+ if (arr.length === 0)
11376
+ return NaN;
11377
+ const copy = arr.slice();
11378
+ if (type === "Q2")
11379
+ return Chalkboard.stat.median(copy);
11380
+ const mid = Math.floor(copy.length / 2);
11239
11381
  if (type === "Q1") {
11240
- return Chalkboard.stat.median(lo);
11241
- }
11242
- else if (type === "Q2") {
11243
- return Chalkboard.stat.median(arr);
11382
+ const q1Mid = Math.floor(mid / 2);
11383
+ if (mid % 2 === 1)
11384
+ return $quickselect(copy, q1Mid);
11385
+ else
11386
+ return ($quickselect(copy, q1Mid - 1) + $quickselect(copy, q1Mid)) / 2.0;
11244
11387
  }
11245
11388
  else if (type === "Q3") {
11246
- return Chalkboard.stat.median(hi);
11389
+ const offset = copy.length % 2 === 0 ? mid : mid + 1;
11390
+ const q3Mid = offset + Math.floor(mid / 2);
11391
+ if (mid % 2 === 1)
11392
+ return $quickselect(copy, q3Mid);
11393
+ else
11394
+ return ($quickselect(copy, q3Mid - 1) + $quickselect(copy, q3Mid)) / 2.0;
11247
11395
  }
11248
11396
  else {
11249
11397
  throw new TypeError('Parameter "type" must be "Q1", "Q2", or "Q3".');
@@ -11499,6 +11647,11 @@ var Chalkboard;
11499
11647
  }
11500
11648
  };
11501
11649
  stat.unique = (arr) => {
11650
+ if (arr.length === 0)
11651
+ return [];
11652
+ const firstType = typeof arr[0];
11653
+ if (firstType === "number" || firstType === "string" || firstType === "boolean")
11654
+ return Array.from(new Set(arr));
11502
11655
  const stableStringify = (obj) => {
11503
11656
  const replacer = (key, value) => {
11504
11657
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -11512,52 +11665,13 @@ var Chalkboard;
11512
11665
  };
11513
11666
  return JSON.stringify(obj, replacer);
11514
11667
  };
11515
- const getKey = (item) => {
11516
- let typePrefix;
11517
- let valuePart;
11518
- if (item === null) {
11519
- typePrefix = "null";
11520
- valuePart = stableStringify(item);
11521
- }
11522
- else if (Array.isArray(item)) {
11523
- typePrefix = "array";
11524
- valuePart = stableStringify(item);
11525
- }
11526
- else if (typeof item === "object") {
11527
- typePrefix = "object";
11528
- valuePart = stableStringify(item);
11529
- }
11530
- else if (typeof item === "number") {
11531
- typePrefix = "number";
11532
- if (Number.isNaN(item)) {
11533
- valuePart = "NaN";
11534
- }
11535
- else if (item === Infinity) {
11536
- valuePart = "Infinity";
11537
- }
11538
- else if (item === -Infinity) {
11539
- valuePart = "-Infinity";
11540
- }
11541
- else {
11542
- valuePart = JSON.stringify(item);
11543
- }
11544
- }
11545
- else if (typeof item === "undefined") {
11546
- typePrefix = "undefined";
11547
- valuePart = "";
11548
- }
11549
- else {
11550
- typePrefix = typeof item;
11551
- valuePart = stableStringify(item);
11552
- }
11553
- return `${typePrefix}:${valuePart}`;
11554
- };
11555
11668
  const seen = new Map();
11556
11669
  for (const item of arr) {
11557
- const key = getKey(item);
11558
- if (!seen.has(key)) {
11670
+ const typePrefix = item === null ? "null" : typeof item;
11671
+ const valuePart = (typePrefix === "undefined" || Number.isNaN(item)) ? "" : stableStringify(item);
11672
+ const key = `${typePrefix}:${valuePart}`;
11673
+ if (!seen.has(key))
11559
11674
  seen.set(key, item);
11560
- }
11561
11675
  }
11562
11676
  return Array.from(seen.values());
11563
11677
  };
@@ -12175,32 +12289,26 @@ var Chalkboard;
12175
12289
  };
12176
12290
  trig.arctan = (rad) => {
12177
12291
  const series = (x) => {
12178
- let sum = x;
12179
- let term = x;
12180
- const xx = x * x;
12181
- for (let n = 1; n < 20; n++) {
12182
- term *= -xx;
12183
- sum += term / (2 * n + 1);
12184
- }
12185
- return sum;
12292
+ const x2 = x * x, x3 = x2 * x, x5 = x3 * x2, x7 = x5 * x2, x9 = x7 * x2, x11 = x9 * x2, x13 = x11 * x2, x15 = x13 * x2, x17 = x15 * x2, x19 = x17 * x2, x21 = x19 * x2, x23 = x21 * x2, x25 = x23 * x2, x27 = x25 * x2, x29 = x27 * x2, x31 = x29 * x2, x33 = x31 * x2, x35 = x33 * x2, x37 = x35 * x2, x39 = x37 * x2;
12293
+ return x - x3 / 3 + x5 / 5 - x7 / 7 + x9 / 9 - x11 / 11 + x13 / 13 - x15 / 15 + x17 / 17 - x19 / 19 + x21 / 21 - x23 / 23 + x25 / 25 - x27 / 27 + x29 / 29 - x31 / 31 + x33 / 33 - x35 / 35 + x37 / 37 - x39 / 39;
12186
12294
  };
12187
- if (rad === 0) {
12295
+ if (rad === 0)
12188
12296
  return 0;
12189
- }
12190
- if (!Number.isFinite(rad)) {
12297
+ if (!Number.isFinite(rad))
12191
12298
  return rad > 0 ? Chalkboard.PI(0.5) : Chalkboard.PI(-0.5);
12192
- }
12193
12299
  const sign = rad < 0 ? -1 : 1;
12194
12300
  const x = Math.abs(rad);
12301
+ const SQRT2_MINUS_1 = Math.SQRT2 - 1, SQRT2_PLUS_1 = Math.SQRT2 + 1;
12302
+ const PI_FOURTH = Math.PI * 0.25, PI_HALF = Math.PI * 0.5;
12195
12303
  let result;
12196
- if (x <= Chalkboard.real.sqrt(2) - 1) {
12304
+ if (x <= SQRT2_MINUS_1) {
12197
12305
  result = series(x);
12198
12306
  }
12199
- else if (x <= Chalkboard.real.sqrt(2) + 1) {
12200
- result = Chalkboard.PI(0.25) + series((x - 1) / (x + 1));
12307
+ else if (x <= SQRT2_PLUS_1) {
12308
+ result = PI_FOURTH + series((x - 1) / (x + 1));
12201
12309
  }
12202
12310
  else {
12203
- result = Chalkboard.PI(0.5) - series(1 / x);
12311
+ result = PI_HALF - series(1 / x);
12204
12312
  }
12205
12313
  return sign * result;
12206
12314
  };