@johnhenry/math-sample 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +244 -2
- package/dist/budget.d.ts +47 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +44 -0
- package/dist/budget.js.map +1 -0
- package/dist/complex.d.ts +79 -0
- package/dist/complex.d.ts.map +1 -0
- package/dist/complex.js +236 -0
- package/dist/complex.js.map +1 -0
- package/dist/expr.d.ts +38 -0
- package/dist/expr.d.ts.map +1 -0
- package/dist/expr.js +264 -0
- package/dist/expr.js.map +1 -0
- package/dist/field2d.d.ts +58 -0
- package/dist/field2d.d.ts.map +1 -0
- package/dist/field2d.js +199 -0
- package/dist/field2d.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/mesh.d.ts +90 -0
- package/dist/mesh.d.ts.map +1 -0
- package/dist/mesh.js +294 -0
- package/dist/mesh.js.map +1 -0
- package/dist/ode.d.ts +147 -0
- package/dist/ode.d.ts.map +1 -0
- package/dist/ode.js +466 -0
- package/dist/ode.js.map +1 -0
- package/dist/sample.d.ts +114 -0
- package/dist/sample.d.ts.map +1 -0
- package/dist/sample.js +308 -0
- package/dist/sample.js.map +1 -0
- package/dist/series.d.ts +53 -0
- package/dist/series.d.ts.map +1 -0
- package/dist/series.js +158 -0
- package/dist/series.js.map +1 -0
- package/dist/svg.d.ts +37 -0
- package/dist/svg.d.ts.map +1 -0
- package/dist/svg.js +82 -0
- package/dist/svg.js.map +1 -0
- package/dist/ticks.d.ts +17 -0
- package/dist/ticks.d.ts.map +1 -0
- package/dist/ticks.js +47 -0
- package/dist/ticks.js.map +1 -0
- package/dist/viewport.d.ts +59 -0
- package/dist/viewport.d.ts.map +1 -0
- package/dist/viewport.js +111 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +70 -4
- package/src/budget.ts +72 -0
- package/src/complex.ts +327 -0
- package/src/expr.ts +283 -0
- package/src/field2d.ts +259 -0
- package/src/index.ts +112 -0
- package/src/mesh.ts +376 -0
- package/src/ode.ts +633 -0
- package/src/sample.ts +391 -0
- package/src/series.ts +209 -0
- package/src/svg.ts +124 -0
- package/src/ticks.ts +46 -0
- package/src/viewport.ts +150 -0
package/dist/series.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { assertCount, assertRange } from "./budget.js";
|
|
2
|
+
/** Evaluate `sum a_k (x - x0)^k` for `k <= order` by Horner's rule. */
|
|
3
|
+
export function evaluateTaylor(coefficients, x0, x, order = coefficients.length - 1) {
|
|
4
|
+
const top = Math.min(order, coefficients.length - 1);
|
|
5
|
+
let acc = 0;
|
|
6
|
+
for (let k = top; k >= 0; k--)
|
|
7
|
+
acc = acc * (x - x0) + coefficients[k];
|
|
8
|
+
return acc;
|
|
9
|
+
}
|
|
10
|
+
function cleanSamples(x, y) {
|
|
11
|
+
// enforce: gaps not first/last/adjacent
|
|
12
|
+
const xs = [];
|
|
13
|
+
const ys = [];
|
|
14
|
+
for (let k = 0; k < x.length; k++) {
|
|
15
|
+
const yy = y[k];
|
|
16
|
+
if (Number.isFinite(yy)) {
|
|
17
|
+
xs.push(x[k]);
|
|
18
|
+
ys.push(yy);
|
|
19
|
+
}
|
|
20
|
+
else if (ys.length > 0 && !Number.isNaN(ys[ys.length - 1])) {
|
|
21
|
+
xs.push(x[k]);
|
|
22
|
+
ys.push(Number.NaN);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
while (ys.length > 0 && Number.isNaN(ys[ys.length - 1])) {
|
|
26
|
+
ys.pop();
|
|
27
|
+
xs.pop();
|
|
28
|
+
}
|
|
29
|
+
return { x: Float64Array.from(xs), y: Float64Array.from(ys) };
|
|
30
|
+
}
|
|
31
|
+
function grid(domain, n) {
|
|
32
|
+
assertRange("domain", domain);
|
|
33
|
+
assertCount("samples", n);
|
|
34
|
+
const x = new Float64Array(n);
|
|
35
|
+
for (let k = 0; k < n; k++)
|
|
36
|
+
x[k] = k === n - 1 ? domain.max : domain.min + ((domain.max - domain.min) * k) / (n - 1);
|
|
37
|
+
return x;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Taylor partial sums of the given coefficients (`a_k = f^(k)(x0) / k!`) about `x0`, one curve per requested
|
|
41
|
+
* order, over a uniform grid. With `@johnhenry/math`, `taylorCoefficients` in `@johnhenry/math-sample/expr`
|
|
42
|
+
* produces the coefficients from an expression.
|
|
43
|
+
*/
|
|
44
|
+
export function sampleTaylorPartialSums(coefficients, x0, domain, orders, samples = 400) {
|
|
45
|
+
const x = grid(domain, samples);
|
|
46
|
+
const curves = orders.map((order) => {
|
|
47
|
+
if (!Number.isInteger(order) || order < 0)
|
|
48
|
+
throw new RangeError(`order must be a non-negative integer, got ${order}`);
|
|
49
|
+
const y = new Float64Array(samples);
|
|
50
|
+
for (let k = 0; k < samples; k++)
|
|
51
|
+
y[k] = evaluateTaylor(coefficients, x0, x[k], order);
|
|
52
|
+
return cleanSamples(x, y);
|
|
53
|
+
});
|
|
54
|
+
return { orders: [...orders], curves };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Partial sums of a function series `sum_{k=from}^{n} term(k, x)`, one curve per order `n`, computed
|
|
58
|
+
* in a single pass over `k` so each term is evaluated once per sample.
|
|
59
|
+
*/
|
|
60
|
+
export function sampleSeriesPartialSums(term, from, domain, orders, samples = 400) {
|
|
61
|
+
if (!Number.isInteger(from))
|
|
62
|
+
throw new RangeError("from must be an integer");
|
|
63
|
+
const x = grid(domain, samples);
|
|
64
|
+
const sorted = [...new Set(orders)].sort((a, b) => a - b);
|
|
65
|
+
for (const o of sorted)
|
|
66
|
+
if (!Number.isInteger(o) || o < from)
|
|
67
|
+
throw new RangeError(`order ${o} must be an integer >= from (${from})`);
|
|
68
|
+
const sums = new Float64Array(samples);
|
|
69
|
+
const byOrder = new Map();
|
|
70
|
+
let k = from;
|
|
71
|
+
for (const o of sorted) {
|
|
72
|
+
for (; k <= o; k++)
|
|
73
|
+
for (let i = 0; i < samples; i++)
|
|
74
|
+
sums[i] = sums[i] + term(k, x[i]);
|
|
75
|
+
byOrder.set(o, cleanSamples(x, Float64Array.from(sums)));
|
|
76
|
+
}
|
|
77
|
+
return { orders: [...orders], curves: orders.map((o) => byOrder.get(o)) };
|
|
78
|
+
}
|
|
79
|
+
/** Partial sums of the numeric series `sum_{k=from}^{to} term(k)`, with an Aitken limit estimate. */
|
|
80
|
+
export function partialSums(term, from, to) {
|
|
81
|
+
if (!Number.isInteger(from) || !Number.isInteger(to) || to < from)
|
|
82
|
+
throw new RangeError("need integers from <= to");
|
|
83
|
+
const partial = new Float64Array(to - from + 1);
|
|
84
|
+
let s = 0;
|
|
85
|
+
for (let k = from; k <= to; k++) {
|
|
86
|
+
s += term(k);
|
|
87
|
+
partial[k - from] = s;
|
|
88
|
+
}
|
|
89
|
+
let limitEstimate = Number.NaN;
|
|
90
|
+
const n = partial.length;
|
|
91
|
+
if (n >= 3) {
|
|
92
|
+
const a = partial[n - 3];
|
|
93
|
+
const b = partial[n - 2];
|
|
94
|
+
const c = partial[n - 1];
|
|
95
|
+
const den = c - 2 * b + a;
|
|
96
|
+
limitEstimate = den === 0 ? c : c - ((c - b) * (c - b)) / den;
|
|
97
|
+
}
|
|
98
|
+
return { partial, from, limitEstimate };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fourier coefficients of `f` on `[a, b]` taken as one period, by the midpoint rule on `samples` points
|
|
102
|
+
* (exact for trig polynomials below the Nyquist order, being a full-period rule; for a function with jumps the coefficients carry
|
|
103
|
+
* ordinary O(1/samples) quadrature error). `terms` is the highest harmonic. Trig form:
|
|
104
|
+
* `a0/2 + sum a_k cos(k w x) + b_k sin(k w x)` about the interval centre.
|
|
105
|
+
*/
|
|
106
|
+
export function fourierCoefficients(f, period, terms, samples = 4096) {
|
|
107
|
+
assertRange("period", period);
|
|
108
|
+
assertCount("terms", terms, 1);
|
|
109
|
+
assertCount("samples", samples, 4);
|
|
110
|
+
const L = (period.max - period.min) / 2;
|
|
111
|
+
const center = (period.max + period.min) / 2;
|
|
112
|
+
const a = new Float64Array(terms);
|
|
113
|
+
const b = new Float64Array(terms);
|
|
114
|
+
const vals = new Float64Array(samples);
|
|
115
|
+
for (let i = 0; i < samples; i++)
|
|
116
|
+
vals[i] = f(period.min + ((period.max - period.min) * (i + 0.5)) / samples); // midpoint
|
|
117
|
+
let a0 = 0;
|
|
118
|
+
for (let i = 0; i < samples; i++)
|
|
119
|
+
a0 += vals[i];
|
|
120
|
+
a0 = (2 * a0) / samples;
|
|
121
|
+
for (let k = 1; k <= terms; k++) {
|
|
122
|
+
let sa = 0;
|
|
123
|
+
let sb = 0;
|
|
124
|
+
for (let i = 0; i < samples; i++) {
|
|
125
|
+
const t = Math.PI * k * (((i + 0.5) / samples) * 2 - 1);
|
|
126
|
+
sa += vals[i] * Math.cos(t);
|
|
127
|
+
sb += vals[i] * Math.sin(t);
|
|
128
|
+
}
|
|
129
|
+
a[k - 1] = (2 * sa) / samples;
|
|
130
|
+
b[k - 1] = (2 * sb) / samples;
|
|
131
|
+
}
|
|
132
|
+
return { halfPeriod: L, center, a0, a, b };
|
|
133
|
+
}
|
|
134
|
+
/** Value of the Fourier partial sum with harmonics up to `n` at `x`. */
|
|
135
|
+
export function evaluateFourier(c, x, n = c.a.length) {
|
|
136
|
+
const top = Math.min(n, c.a.length);
|
|
137
|
+
const w = Math.PI / c.halfPeriod;
|
|
138
|
+
let s = c.a0 / 2;
|
|
139
|
+
for (let k = 1; k <= top; k++) {
|
|
140
|
+
const t = k * w * (x - c.center);
|
|
141
|
+
s += c.a[k - 1] * Math.cos(t) + c.b[k - 1] * Math.sin(t);
|
|
142
|
+
}
|
|
143
|
+
return s;
|
|
144
|
+
}
|
|
145
|
+
/** One curve per harmonic count in `orders` (0 = just the constant term). */
|
|
146
|
+
export function sampleFourierPartialSums(c, domain, orders, samples = 400) {
|
|
147
|
+
const x = grid(domain, samples);
|
|
148
|
+
const curves = orders.map((n) => {
|
|
149
|
+
if (!Number.isInteger(n) || n < 0)
|
|
150
|
+
throw new RangeError(`order must be a non-negative integer, got ${n}`);
|
|
151
|
+
const y = new Float64Array(samples);
|
|
152
|
+
for (let i = 0; i < samples; i++)
|
|
153
|
+
y[i] = evaluateFourier(c, x[i], n);
|
|
154
|
+
return cleanSamples(x, y);
|
|
155
|
+
});
|
|
156
|
+
return { orders: [...orders], curves };
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=series.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"series.js","sourceRoot":"","sources":["../src/series.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGvD,uEAAuE;AACvE,MAAM,UAAU,cAAc,CAC5B,YAA+B,EAC/B,EAAU,EACV,CAAS,EACT,KAAK,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC;IAE/B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrD,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;QAAE,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAI,YAAY,CAAC,CAAC,CAAY,CAAC;IAClF,OAAO,GAAG,CAAC;AACb,CAAC;AASD,SAAS,YAAY,CAAC,CAAe,EAAE,CAAe;IACpD,wCAAwC;IACxC,MAAM,EAAE,GAAa,EAAE,CAAC;IACxB,MAAM,EAAE,GAAa,EAAE,CAAC;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAW,CAAC;QAC1B,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YACxB,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;YACxB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACd,CAAC;aAAM,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC,EAAE,CAAC;YACvE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;YACxB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC,EAAE,CAAC;QAClE,EAAE,CAAC,GAAG,EAAE,CAAC;QACT,EAAE,CAAC,GAAG,EAAE,CAAC;IACX,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AAChE,CAAC;AAED,SAAS,IAAI,CAAC,MAAc,EAAE,CAAS;IACrC,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9B,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACrH,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,YAA+B,EAC/B,EAAU,EACV,MAAc,EACd,MAAgB,EAChB,OAAO,GAAG,GAAG;IAEb,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;YACvC,MAAM,IAAI,UAAU,CAAC,6CAA6C,KAAK,EAAE,CAAC,CAAC;QAC7E,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE;YAAE,CAAC,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAW,EAAE,KAAK,CAAC,CAAC;QACjG,OAAO,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,IAAsC,EACtC,IAAY,EACZ,MAAc,EACd,MAAgB,EAChB,OAAO,GAAG,GAAG;IAEb,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,yBAAyB,CAAC,CAAC;IAC7E,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,KAAK,MAAM,CAAC,IAAI,MAAM;QACpB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI;YAAE,MAAM,IAAI,UAAU,CAAC,SAAS,CAAC,gCAAgC,IAAI,GAAG,CAAC,CAAC;IAChH,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC3C,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;YAAE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE;gBAAE,IAAI,CAAC,CAAC,CAAC,GAAI,IAAI,CAAC,CAAC,CAAY,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;QAC9G,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAY,CAAC,EAAE,CAAC;AACvF,CAAC;AAUD,qGAAqG;AACrG,MAAM,UAAU,WAAW,CAAC,IAA2B,EAAE,IAAY,EAAE,EAAU;IAC/E,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,IAAI;QAAE,MAAM,IAAI,UAAU,CAAC,0BAA0B,CAAC,CAAC;IACpH,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;QACb,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC;IAC/B,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IACzB,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;QACnC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;QACnC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;QACnC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1B,aAAa,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;AAC1C,CAAC;AAeD;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,CAAwB,EACxB,MAAc,EACd,KAAa,EACb,OAAO,GAAG,IAAI;IAEd,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9B,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/B,WAAW,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WAAW;IAC1H,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE;QAAE,EAAE,IAAI,IAAI,CAAC,CAAC,CAAW,CAAC;IAC1D,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YACxD,EAAE,IAAK,IAAI,CAAC,CAAC,CAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACxC,EAAE,IAAK,IAAI,CAAC,CAAC,CAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC;QACD,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;QAC9B,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,CAAsB,EAAE,CAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM;IAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC;IACjC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,IAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,wBAAwB,CACtC,CAAsB,EACtB,MAAc,EACd,MAAgB,EAChB,OAAO,GAAG,GAAG;IAEb,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,6CAA6C,CAAC,EAAE,CAAC,CAAC;QAC1G,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE;YAAE,CAAC,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAW,EAAE,CAAC,CAAC,CAAC;QAC/E,OAAO,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;AACzC,CAAC"}
|
package/dist/svg.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Viewport } from "./viewport.ts";
|
|
2
|
+
/** Anything with parallel `x` / `y` arrays, e.g. {@link Samples}. `NaN` in `y` breaks the path. */
|
|
3
|
+
export interface XY {
|
|
4
|
+
x: ArrayLike<number>;
|
|
5
|
+
y: ArrayLike<number>;
|
|
6
|
+
}
|
|
7
|
+
export interface PathOptions {
|
|
8
|
+
/** Decimal places for screen coordinates. Default 2. */
|
|
9
|
+
digits?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* SVG path data (`d` attribute) for a sampled curve, mapped through the
|
|
13
|
+
* viewport to a `width` x `height` screen box. Each run of finite rows is one
|
|
14
|
+
* `M ... L ...` subpath, so gaps stay gaps. Empty input gives `""`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function samplesToSvgPath(samples: XY, viewport: Viewport, width: number, height: number, options?: PathOptions): string;
|
|
17
|
+
export interface AxesOptions {
|
|
18
|
+
/** Approximate tick count per axis. Default 6. */
|
|
19
|
+
targetTickCount?: number;
|
|
20
|
+
/** Axis and tick colour. Default `#888`. */
|
|
21
|
+
stroke?: string;
|
|
22
|
+
/** Label colour. Default `#222`. */
|
|
23
|
+
labelFill?: string;
|
|
24
|
+
/** Label size in px. Default 11. */
|
|
25
|
+
fontSize?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* SVG elements (`<line>` and `<text>` with inline attributes, no stylesheet)
|
|
29
|
+
* for x/y axes with ticks and labels. An axis sits at data zero when that is
|
|
30
|
+
* on screen and otherwise hugs the nearest edge, labels flipping inward so
|
|
31
|
+
* they never clip. The origin is labelled once. Returns `""` for a
|
|
32
|
+
* degenerate viewport.
|
|
33
|
+
*/
|
|
34
|
+
export declare function axesToSvg(viewport: Viewport, width: number, height: number, options?: AxesOptions): string;
|
|
35
|
+
/** Wrap SVG `content` in a standalone `<svg>` document of the given size. */
|
|
36
|
+
export declare function svgDocument(width: number, height: number, content: string): string;
|
|
37
|
+
//# sourceMappingURL=svg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../src/svg.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEpE,mGAAmG;AACnG,MAAM,WAAW,EAAE;IACjB,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,EAAE,EACX,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,WAAgB,GACxB,MAAM,CAkBR;AAOD,MAAM,WAAW,WAAW;IAC1B,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAkD9G;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF"}
|
package/dist/svg.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { formatTick, niceTicks, tickStep } from "./ticks.js";
|
|
2
|
+
import { toScreenX, toScreenY } from "./viewport.js";
|
|
3
|
+
/**
|
|
4
|
+
* SVG path data (`d` attribute) for a sampled curve, mapped through the
|
|
5
|
+
* viewport to a `width` x `height` screen box. Each run of finite rows is one
|
|
6
|
+
* `M ... L ...` subpath, so gaps stay gaps. Empty input gives `""`.
|
|
7
|
+
*/
|
|
8
|
+
export function samplesToSvgPath(samples, viewport, width, height, options = {}) {
|
|
9
|
+
const digits = options.digits ?? 2;
|
|
10
|
+
const parts = [];
|
|
11
|
+
let pen = false;
|
|
12
|
+
const n = Math.min(samples.x.length, samples.y.length);
|
|
13
|
+
for (let i = 0; i < n; i++) {
|
|
14
|
+
const y = samples.y[i];
|
|
15
|
+
const x = samples.x[i];
|
|
16
|
+
if (!Number.isFinite(y) || !Number.isFinite(x)) {
|
|
17
|
+
pen = false;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const sx = toScreenX(x, viewport, width).toFixed(digits);
|
|
21
|
+
const sy = toScreenY(y, viewport, height).toFixed(digits);
|
|
22
|
+
parts.push(`${pen ? "L" : "M"}${sx} ${sy}`);
|
|
23
|
+
pen = true;
|
|
24
|
+
}
|
|
25
|
+
return parts.join(" ");
|
|
26
|
+
}
|
|
27
|
+
/** Escape a value for use inside a double-quoted SVG attribute, so a caller-supplied colour cannot break out of it. */
|
|
28
|
+
function attr(value) {
|
|
29
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* SVG elements (`<line>` and `<text>` with inline attributes, no stylesheet)
|
|
33
|
+
* for x/y axes with ticks and labels. An axis sits at data zero when that is
|
|
34
|
+
* on screen and otherwise hugs the nearest edge, labels flipping inward so
|
|
35
|
+
* they never clip. The origin is labelled once. Returns `""` for a
|
|
36
|
+
* degenerate viewport.
|
|
37
|
+
*/
|
|
38
|
+
export function axesToSvg(viewport, width, height, options = {}) {
|
|
39
|
+
const { xMin, xMax, yMin, yMax } = viewport;
|
|
40
|
+
if (!(xMax > xMin) || !(yMax > yMin))
|
|
41
|
+
return "";
|
|
42
|
+
const stroke = attr(options.stroke ?? "#888");
|
|
43
|
+
const fill = attr(options.labelFill ?? "#222");
|
|
44
|
+
const fontSize = options.fontSize !== undefined && Number.isFinite(options.fontSize) ? options.fontSize : 11;
|
|
45
|
+
const count = options.targetTickCount ?? 6;
|
|
46
|
+
const half = 4;
|
|
47
|
+
const font = `font-size="${fontSize}" font-family="system-ui, sans-serif"`;
|
|
48
|
+
const xAxisAtBottom = yMin > 0;
|
|
49
|
+
const xAxisAtTop = yMax < 0;
|
|
50
|
+
const axisY = xAxisAtBottom ? height : xAxisAtTop ? 0 : toScreenY(0, viewport, height);
|
|
51
|
+
const yAxisAtRight = xMax < 0;
|
|
52
|
+
const yAxisAtLeft = xMin > 0;
|
|
53
|
+
const axisX = yAxisAtRight ? width : yAxisAtLeft ? 0 : toScreenX(0, viewport, width);
|
|
54
|
+
const out = [
|
|
55
|
+
`<line x1="0" y1="${axisY}" x2="${width}" y2="${axisY}" stroke="${stroke}" stroke-width="1" />`,
|
|
56
|
+
`<line x1="${axisX}" y1="0" x2="${axisX}" y2="${height}" stroke="${stroke}" stroke-width="1" />`,
|
|
57
|
+
];
|
|
58
|
+
const xStep = tickStep(xMin, xMax, count);
|
|
59
|
+
const labelBelow = !xAxisAtBottom;
|
|
60
|
+
for (const v of niceTicks(xMin, xMax, count)) {
|
|
61
|
+
const sx = toScreenX(v, viewport, width);
|
|
62
|
+
out.push(`<line x1="${sx.toFixed(2)}" y1="${(axisY - half).toFixed(2)}" x2="${sx.toFixed(2)}" y2="${(axisY + half).toFixed(2)}" stroke="${stroke}" stroke-width="1" />`);
|
|
63
|
+
if (v !== 0) {
|
|
64
|
+
const ty = axisY + (labelBelow ? half + 2 : -(half + 2));
|
|
65
|
+
out.push(`<text x="${sx.toFixed(2)}" y="${ty.toFixed(2)}" fill="${fill}" ${font} text-anchor="middle" dominant-baseline="${labelBelow ? "hanging" : "auto"}">${formatTick(v, xStep)}</text>`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const yStep = tickStep(yMin, yMax, count);
|
|
69
|
+
const labelLeft = !yAxisAtLeft;
|
|
70
|
+
for (const v of niceTicks(yMin, yMax, count)) {
|
|
71
|
+
const sy = toScreenY(v, viewport, height);
|
|
72
|
+
out.push(`<line x1="${(axisX - half).toFixed(2)}" y1="${sy.toFixed(2)}" x2="${(axisX + half).toFixed(2)}" y2="${sy.toFixed(2)}" stroke="${stroke}" stroke-width="1" />`);
|
|
73
|
+
const tx = axisX + (labelLeft ? -(half + 4) : half + 4);
|
|
74
|
+
out.push(`<text x="${tx.toFixed(2)}" y="${sy.toFixed(2)}" fill="${fill}" ${font} text-anchor="${labelLeft ? "end" : "start"}" dominant-baseline="middle">${formatTick(v, yStep)}</text>`);
|
|
75
|
+
}
|
|
76
|
+
return out.join("\n");
|
|
77
|
+
}
|
|
78
|
+
/** Wrap SVG `content` in a standalone `<svg>` document of the given size. */
|
|
79
|
+
export function svgDocument(width, height, content) {
|
|
80
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">\n${content}\n</svg>`;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=svg.js.map
|
package/dist/svg.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg.js","sourceRoot":"","sources":["../src/svg.ts"],"names":[],"mappings":"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"}
|
package/dist/ticks.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step "nice numbers" tick generation uses for `[min, max]`: the value of
|
|
3
|
+
* 1, 2, 5 or 10 times a power of ten at or just above `(max - min) / targetCount`
|
|
4
|
+
* (rounded up, so there are never more than about `targetCount` ticks).
|
|
5
|
+
* Returns `NaN` for a degenerate range.
|
|
6
|
+
*/
|
|
7
|
+
export declare function tickStep(min: number, max: number, targetCount?: number): number;
|
|
8
|
+
/**
|
|
9
|
+
* Round tick values inside `[min, max]`, about `targetCount` of them (default
|
|
10
|
+
* 6). Ticks are integer multiples of the step, rounded to the step's own
|
|
11
|
+
* decimal places, so `0.1 * 3` reads `0.3` and not `0.30000000000000004`.
|
|
12
|
+
* Returns `[]` for a degenerate or non-finite range.
|
|
13
|
+
*/
|
|
14
|
+
export declare function niceTicks(min: number, max: number, targetCount?: number): number[];
|
|
15
|
+
/** A tick label with as many decimals as `step` needs, so a run of labels reads uniformly (`3.0, 3.5, 4.0`). */
|
|
16
|
+
export declare function formatTick(value: number, step: number): string;
|
|
17
|
+
//# sourceMappingURL=ticks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticks.d.ts","sourceRoot":"","sources":["../src/ticks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,SAAI,GAAG,MAAM,CAO1E;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,SAAI,GAAG,MAAM,EAAE,CAS7E;AAED,gHAAgH;AAChH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG9D"}
|
package/dist/ticks.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step "nice numbers" tick generation uses for `[min, max]`: the value of
|
|
3
|
+
* 1, 2, 5 or 10 times a power of ten at or just above `(max - min) / targetCount`
|
|
4
|
+
* (rounded up, so there are never more than about `targetCount` ticks).
|
|
5
|
+
* Returns `NaN` for a degenerate range.
|
|
6
|
+
*/
|
|
7
|
+
export function tickStep(min, max, targetCount = 6) {
|
|
8
|
+
if (!(max > min) || !Number.isFinite(max - min) || !(targetCount >= 1))
|
|
9
|
+
return Number.NaN;
|
|
10
|
+
const roughStep = (max - min) / targetCount;
|
|
11
|
+
const magnitude = 10 ** Math.floor(Math.log10(roughStep));
|
|
12
|
+
const residual = roughStep / magnitude;
|
|
13
|
+
const nice = residual > 5 ? 10 : residual > 2 ? 5 : residual > 1 ? 2 : 1;
|
|
14
|
+
return nice * magnitude;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Round tick values inside `[min, max]`, about `targetCount` of them (default
|
|
18
|
+
* 6). Ticks are integer multiples of the step, rounded to the step's own
|
|
19
|
+
* decimal places, so `0.1 * 3` reads `0.3` and not `0.30000000000000004`.
|
|
20
|
+
* Returns `[]` for a degenerate or non-finite range.
|
|
21
|
+
*/
|
|
22
|
+
export function niceTicks(min, max, targetCount = 6) {
|
|
23
|
+
const step = tickStep(min, max, targetCount);
|
|
24
|
+
if (!Number.isFinite(step) || step < 1e-20)
|
|
25
|
+
return []; // below this, fixed-point rounding would collapse every tick to 0
|
|
26
|
+
const decimals = decimalsOf(step);
|
|
27
|
+
const first = Math.ceil(min / step - 1e-9);
|
|
28
|
+
const last = Math.floor(max / step + 1e-9);
|
|
29
|
+
const ticks = [];
|
|
30
|
+
for (let i = first; i <= last; i++)
|
|
31
|
+
ticks.push(roundTo(i * step, decimals));
|
|
32
|
+
return ticks;
|
|
33
|
+
}
|
|
34
|
+
/** A tick label with as many decimals as `step` needs, so a run of labels reads uniformly (`3.0, 3.5, 4.0`). */
|
|
35
|
+
export function formatTick(value, step) {
|
|
36
|
+
if (value === 0)
|
|
37
|
+
return "0";
|
|
38
|
+
return value.toFixed(decimalsOf(step));
|
|
39
|
+
}
|
|
40
|
+
function decimalsOf(step) {
|
|
41
|
+
return Math.min(20, Math.max(0, -Math.floor(Math.log10(step) + 1e-9)));
|
|
42
|
+
}
|
|
43
|
+
function roundTo(value, decimals) {
|
|
44
|
+
const r = Number(value.toFixed(decimals));
|
|
45
|
+
return r === 0 ? 0 : r; // fold -0 into 0
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=ticks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticks.js","sourceRoot":"","sources":["../src/ticks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,GAAW,EAAE,WAAW,GAAG,CAAC;IAChE,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IAC1F,MAAM,SAAS,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,WAAW,CAAC;IAC5C,MAAM,SAAS,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IACvC,MAAM,IAAI,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,IAAI,GAAG,SAAS,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,GAAW,EAAE,WAAW,GAAG,CAAC;IACjE,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,KAAK;QAAE,OAAO,EAAE,CAAC,CAAC,kEAAkE;IACzH,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC5E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,IAAY;IACpD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,OAAO,CAAC,KAAa,EAAE,QAAgB;IAC9C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;AAC3C,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** A rectangle of data space: the part of the plane a plot currently shows. */
|
|
2
|
+
export interface Viewport {
|
|
3
|
+
xMin: number;
|
|
4
|
+
xMax: number;
|
|
5
|
+
yMin: number;
|
|
6
|
+
yMax: number;
|
|
7
|
+
}
|
|
8
|
+
/** Data x to a screen coordinate in `[0, width]` (left to right). */
|
|
9
|
+
export declare function toScreenX(x: number, viewport: Viewport, width: number): number;
|
|
10
|
+
/** Data y to a screen coordinate in `[0, height]` (screen y grows downward, so this flips). */
|
|
11
|
+
export declare function toScreenY(y: number, viewport: Viewport, height: number): number;
|
|
12
|
+
/** Inverse of {@link toScreenX}. */
|
|
13
|
+
export declare function toDataX(sx: number, viewport: Viewport, width: number): number;
|
|
14
|
+
/** Inverse of {@link toScreenY}. */
|
|
15
|
+
export declare function toDataY(sy: number, viewport: Viewport, height: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* The viewport of the given data-space span in which data point
|
|
18
|
+
* (`anchorX`, `anchorY`) sits under screen point (`sx`, `sy`). Panning,
|
|
19
|
+
* wheel-zoom and pinch-zoom all reduce to this: panning keeps the span and
|
|
20
|
+
* moves the anchor's screen position; zooming keeps the anchor's screen
|
|
21
|
+
* position and changes the span.
|
|
22
|
+
*/
|
|
23
|
+
export declare function viewportFromAnchor(anchorX: number, anchorY: number, sx: number, sy: number, spanX: number, spanY: number, width: number, height: number): Viewport;
|
|
24
|
+
/**
|
|
25
|
+
* Pan by a pointer drag of (`dxPx`, `dyPx`) screen pixels: the content follows
|
|
26
|
+
* the pointer, so dragging right moves the window left and dragging down moves
|
|
27
|
+
* it up. Pixels are whatever unit `width`/`height` use (CSS px or device px,
|
|
28
|
+
* as long as they agree).
|
|
29
|
+
*/
|
|
30
|
+
export declare function panViewport(viewport: Viewport, dxPx: number, dyPx: number, width: number, height: number): Viewport;
|
|
31
|
+
/**
|
|
32
|
+
* Scale both spans by `factor` (below 1 zooms in, above 1 zooms out) while
|
|
33
|
+
* keeping the data point under screen point (`sx`, `sy`) fixed there.
|
|
34
|
+
*/
|
|
35
|
+
export declare function zoomViewport(viewport: Viewport, factor: number, sx: number, sy: number, width: number, height: number): Viewport;
|
|
36
|
+
/** Wheel step: scrolling down (`deltaY > 0`) zooms out (factor above 1), up zooms in. Feed to {@link zoomViewport}. */
|
|
37
|
+
export declare function wheelZoomFactor(deltaY: number, step?: number): number;
|
|
38
|
+
/** Pinch span factor: fingers moving apart zoom in (factor below 1). Feed to {@link zoomViewport}. */
|
|
39
|
+
export declare function pinchZoomFactor(startDistancePx: number, currentDistancePx: number): number;
|
|
40
|
+
/**
|
|
41
|
+
* The viewport that bounds the finite `(x, y)` points, grown by `padding` (a
|
|
42
|
+
* fraction of each span, default 0.05). A degenerate extent (one point, or a
|
|
43
|
+
* constant) is widened to a span of 2 so the result is always drawable.
|
|
44
|
+
* Non-finite values (gaps) are ignored. With no finite points, returns `[-1, 1]^2`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function fitViewport(x: ArrayLike<number>, y: ArrayLike<number>, padding?: number): Viewport;
|
|
47
|
+
/**
|
|
48
|
+
* Canvas backing-store size for a CSS-pixel box at a device pixel ratio:
|
|
49
|
+
* set `canvas.width/height` to `width`/`height` and `ctx.scale(scale, scale)`,
|
|
50
|
+
* then draw in CSS pixels. A non-finite or non-positive `dpr` is treated as 1.
|
|
51
|
+
* Sample against the CSS size (or the backing size) consistently; the
|
|
52
|
+
* viewport maths is unit-agnostic.
|
|
53
|
+
*/
|
|
54
|
+
export declare function backingStoreSize(cssWidth: number, cssHeight: number, dpr: number): {
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
scale: number;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=viewport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport.d.ts","sourceRoot":"","sources":["../src/viewport.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED,oCAAoC;AACpC,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,oCAAoC;AACpC,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,QAAQ,CAIV;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CASnH;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,QAAQ,CAaV;AAED,uHAAuH;AACvH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,MAAM,CAElE;AAED,sGAAsG;AACtG,wBAAgB,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAE1F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,SAAO,GAAG,QAAQ,CAwBhG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAGlD"}
|
package/dist/viewport.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/** Data x to a screen coordinate in `[0, width]` (left to right). */
|
|
2
|
+
export function toScreenX(x, viewport, width) {
|
|
3
|
+
return ((x - viewport.xMin) / (viewport.xMax - viewport.xMin)) * width;
|
|
4
|
+
}
|
|
5
|
+
/** Data y to a screen coordinate in `[0, height]` (screen y grows downward, so this flips). */
|
|
6
|
+
export function toScreenY(y, viewport, height) {
|
|
7
|
+
return height - ((y - viewport.yMin) / (viewport.yMax - viewport.yMin)) * height;
|
|
8
|
+
}
|
|
9
|
+
/** Inverse of {@link toScreenX}. */
|
|
10
|
+
export function toDataX(sx, viewport, width) {
|
|
11
|
+
return viewport.xMin + (sx / width) * (viewport.xMax - viewport.xMin);
|
|
12
|
+
}
|
|
13
|
+
/** Inverse of {@link toScreenY}. */
|
|
14
|
+
export function toDataY(sy, viewport, height) {
|
|
15
|
+
return viewport.yMin + ((height - sy) / height) * (viewport.yMax - viewport.yMin);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The viewport of the given data-space span in which data point
|
|
19
|
+
* (`anchorX`, `anchorY`) sits under screen point (`sx`, `sy`). Panning,
|
|
20
|
+
* wheel-zoom and pinch-zoom all reduce to this: panning keeps the span and
|
|
21
|
+
* moves the anchor's screen position; zooming keeps the anchor's screen
|
|
22
|
+
* position and changes the span.
|
|
23
|
+
*/
|
|
24
|
+
export function viewportFromAnchor(anchorX, anchorY, sx, sy, spanX, spanY, width, height) {
|
|
25
|
+
const xMin = anchorX - (sx / width) * spanX;
|
|
26
|
+
const yMin = anchorY - ((height - sy) / height) * spanY;
|
|
27
|
+
return { xMin, xMax: xMin + spanX, yMin, yMax: yMin + spanY };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Pan by a pointer drag of (`dxPx`, `dyPx`) screen pixels: the content follows
|
|
31
|
+
* the pointer, so dragging right moves the window left and dragging down moves
|
|
32
|
+
* it up. Pixels are whatever unit `width`/`height` use (CSS px or device px,
|
|
33
|
+
* as long as they agree).
|
|
34
|
+
*/
|
|
35
|
+
export function panViewport(viewport, dxPx, dyPx, width, height) {
|
|
36
|
+
const dx = (dxPx / width) * (viewport.xMax - viewport.xMin);
|
|
37
|
+
const dy = (dyPx / height) * (viewport.yMax - viewport.yMin);
|
|
38
|
+
return {
|
|
39
|
+
xMin: viewport.xMin - dx,
|
|
40
|
+
xMax: viewport.xMax - dx,
|
|
41
|
+
yMin: viewport.yMin + dy,
|
|
42
|
+
yMax: viewport.yMax + dy,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Scale both spans by `factor` (below 1 zooms in, above 1 zooms out) while
|
|
47
|
+
* keeping the data point under screen point (`sx`, `sy`) fixed there.
|
|
48
|
+
*/
|
|
49
|
+
export function zoomViewport(viewport, factor, sx, sy, width, height) {
|
|
50
|
+
const ax = toDataX(sx, viewport, width);
|
|
51
|
+
const ay = toDataY(sy, viewport, height);
|
|
52
|
+
return viewportFromAnchor(ax, ay, sx, sy, (viewport.xMax - viewport.xMin) * factor, (viewport.yMax - viewport.yMin) * factor, width, height);
|
|
53
|
+
}
|
|
54
|
+
/** Wheel step: scrolling down (`deltaY > 0`) zooms out (factor above 1), up zooms in. Feed to {@link zoomViewport}. */
|
|
55
|
+
export function wheelZoomFactor(deltaY, step = 1.1) {
|
|
56
|
+
return deltaY > 0 ? step : 1 / step;
|
|
57
|
+
}
|
|
58
|
+
/** Pinch span factor: fingers moving apart zoom in (factor below 1). Feed to {@link zoomViewport}. */
|
|
59
|
+
export function pinchZoomFactor(startDistancePx, currentDistancePx) {
|
|
60
|
+
return startDistancePx / currentDistancePx;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The viewport that bounds the finite `(x, y)` points, grown by `padding` (a
|
|
64
|
+
* fraction of each span, default 0.05). A degenerate extent (one point, or a
|
|
65
|
+
* constant) is widened to a span of 2 so the result is always drawable.
|
|
66
|
+
* Non-finite values (gaps) are ignored. With no finite points, returns `[-1, 1]^2`.
|
|
67
|
+
*/
|
|
68
|
+
export function fitViewport(x, y, padding = 0.05) {
|
|
69
|
+
let xMin = Number.POSITIVE_INFINITY;
|
|
70
|
+
let xMax = Number.NEGATIVE_INFINITY;
|
|
71
|
+
let yMin = Number.POSITIVE_INFINITY;
|
|
72
|
+
let yMax = Number.NEGATIVE_INFINITY;
|
|
73
|
+
const n = Math.min(x.length, y.length);
|
|
74
|
+
for (let i = 0; i < n; i++) {
|
|
75
|
+
const px = x[i];
|
|
76
|
+
const py = y[i];
|
|
77
|
+
if (!Number.isFinite(px) || !Number.isFinite(py))
|
|
78
|
+
continue;
|
|
79
|
+
if (px < xMin)
|
|
80
|
+
xMin = px;
|
|
81
|
+
if (px > xMax)
|
|
82
|
+
xMax = px;
|
|
83
|
+
if (py < yMin)
|
|
84
|
+
yMin = py;
|
|
85
|
+
if (py > yMax)
|
|
86
|
+
yMax = py;
|
|
87
|
+
}
|
|
88
|
+
if (xMin > xMax)
|
|
89
|
+
return { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };
|
|
90
|
+
const pad = (lo, hi) => {
|
|
91
|
+
if (hi === lo)
|
|
92
|
+
return [lo - 1, hi + 1];
|
|
93
|
+
const p = (hi - lo) * padding;
|
|
94
|
+
return [lo - p, hi + p];
|
|
95
|
+
};
|
|
96
|
+
const [x0, x1] = pad(xMin, xMax);
|
|
97
|
+
const [y0, y1] = pad(yMin, yMax);
|
|
98
|
+
return { xMin: x0, xMax: x1, yMin: y0, yMax: y1 };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Canvas backing-store size for a CSS-pixel box at a device pixel ratio:
|
|
102
|
+
* set `canvas.width/height` to `width`/`height` and `ctx.scale(scale, scale)`,
|
|
103
|
+
* then draw in CSS pixels. A non-finite or non-positive `dpr` is treated as 1.
|
|
104
|
+
* Sample against the CSS size (or the backing size) consistently; the
|
|
105
|
+
* viewport maths is unit-agnostic.
|
|
106
|
+
*/
|
|
107
|
+
export function backingStoreSize(cssWidth, cssHeight, dpr) {
|
|
108
|
+
const scale = Number.isFinite(dpr) && dpr > 0 ? dpr : 1;
|
|
109
|
+
return { width: Math.round(cssWidth * scale), height: Math.round(cssHeight * scale), scale };
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=viewport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport.js","sourceRoot":"","sources":["../src/viewport.ts"],"names":[],"mappings":"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"}
|