svelteplot 0.4.5-pr-209.2 → 0.4.5-pr-208.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Mark.svelte +87 -73
- package/dist/Mark.svelte.d.ts +37 -34
- package/dist/helpers/scales.d.ts +1 -1
- package/dist/helpers/scales.js +21 -10
- package/dist/marks/Area.svelte.d.ts +37 -34
- package/dist/marks/AreaX.svelte.d.ts +36 -34
- package/dist/marks/AreaY.svelte.d.ts +36 -34
- package/dist/marks/Arrow.svelte.d.ts +37 -34
- package/dist/marks/AxisX.svelte.d.ts +37 -34
- package/dist/marks/AxisY.svelte.d.ts +37 -34
- package/dist/marks/BarX.svelte.d.ts +37 -34
- package/dist/marks/BarY.svelte.d.ts +37 -34
- package/dist/marks/Cell.svelte.d.ts +37 -34
- package/dist/marks/Dot.svelte +1 -1
- package/dist/marks/Dot.svelte.d.ts +37 -34
- package/dist/marks/DotX.svelte.d.ts +37 -34
- package/dist/marks/DotY.svelte.d.ts +37 -34
- package/dist/marks/Geo.svelte.d.ts +37 -34
- package/dist/marks/GridX.svelte.d.ts +37 -34
- package/dist/marks/GridY.svelte.d.ts +37 -34
- package/dist/marks/Line.svelte.d.ts +37 -34
- package/dist/marks/LineX.svelte.d.ts +36 -34
- package/dist/marks/LineY.svelte.d.ts +36 -34
- package/dist/marks/Link.svelte.d.ts +37 -34
- package/dist/marks/Rect.svelte.d.ts +37 -34
- package/dist/marks/RuleX.svelte.d.ts +37 -34
- package/dist/marks/RuleY.svelte.d.ts +37 -34
- package/dist/marks/Spike.svelte.d.ts +36 -34
- package/dist/marks/Text.svelte.d.ts +37 -34
- package/dist/marks/TickX.svelte.d.ts +37 -34
- package/dist/marks/TickY.svelte.d.ts +37 -34
- package/dist/marks/Vector.svelte.d.ts +37 -34
- package/dist/marks/helpers/Regression.svelte +1 -1
- package/dist/regression/exponential.d.ts +17 -0
- package/dist/regression/exponential.js +56 -0
- package/dist/regression/index.d.ts +10 -0
- package/dist/regression/index.js +10 -0
- package/dist/regression/linear.d.ts +17 -0
- package/dist/regression/linear.js +59 -0
- package/dist/regression/loess.d.ts +14 -0
- package/dist/regression/loess.js +122 -0
- package/dist/regression/logarithmic.d.ts +20 -0
- package/dist/regression/logarithmic.js +60 -0
- package/dist/regression/polynomial.d.ts +21 -0
- package/dist/regression/polynomial.js +160 -0
- package/dist/regression/power.d.ts +18 -0
- package/dist/regression/power.js +56 -0
- package/dist/regression/quadratic.d.ts +19 -0
- package/dist/regression/quadratic.js +70 -0
- package/dist/regression/types.d.ts +4 -0
- package/dist/regression/types.js +1 -0
- package/dist/regression/utils/determination.d.ts +6 -0
- package/dist/regression/utils/determination.js +16 -0
- package/dist/regression/utils/geometry.d.ts +9 -0
- package/dist/regression/utils/geometry.js +12 -0
- package/dist/regression/utils/interpose.d.ts +6 -0
- package/dist/regression/utils/interpose.js +37 -0
- package/dist/regression/utils/median.d.ts +4 -0
- package/dist/regression/utils/median.js +8 -0
- package/dist/regression/utils/ols.d.ts +6 -0
- package/dist/regression/utils/ols.js +9 -0
- package/dist/regression/utils/points.d.ts +11 -0
- package/dist/regression/utils/points.js +45 -0
- package/dist/transforms/dodge.d.ts +17 -0
- package/dist/transforms/dodge.js +128 -0
- package/dist/types/mark.d.ts +37 -34
- package/dist/types/scale.d.ts +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from https://github.com/HarryStevens/d3-regression
|
|
3
|
+
*/
|
|
4
|
+
// Adapted from regression-js by Tom Alexander
|
|
5
|
+
// Source: https://github.com/Tom-Alexander/regression-js/blob/master/src/regression.js#L246
|
|
6
|
+
// License: https://github.com/Tom-Alexander/regression-js/blob/master/LICENSE
|
|
7
|
+
// ...with ideas from vega-statistics by Jeffrey Heer
|
|
8
|
+
// Source: https://github.com/vega/vega/blob/f21cb8792b4e0cbe2b1a3fd44b0f5db370dbaadb/packages/vega-statistics/src/regression/poly.js
|
|
9
|
+
// License: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/LICENSE
|
|
10
|
+
import { determination } from './utils/determination';
|
|
11
|
+
import { interpose } from './utils/interpose';
|
|
12
|
+
import { points, visitPoints } from './utils/points';
|
|
13
|
+
import linear from './linear';
|
|
14
|
+
import quad from './quadratic';
|
|
15
|
+
export default function polynomial() {
|
|
16
|
+
let x = (d) => d[0], y = (d) => d[1], order = 3, domain;
|
|
17
|
+
const polynomialRegression = function polynomialRegression(data) {
|
|
18
|
+
// Shortcut for lower-order polynomials:
|
|
19
|
+
if (order === 1) {
|
|
20
|
+
const o = linear().x(x).y(y).domain(domain)(data);
|
|
21
|
+
const result = [o[0], o[1]];
|
|
22
|
+
result.coefficients = [o.b, o.a];
|
|
23
|
+
result.predict = o.predict;
|
|
24
|
+
result.rSquared = o.rSquared;
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
if (order === 2) {
|
|
28
|
+
const o = quad().x(x).y(y).domain(domain)(data);
|
|
29
|
+
const result = [o[0], o[1]];
|
|
30
|
+
result.coefficients = [o.c, o.b, o.a];
|
|
31
|
+
result.predict = o.predict;
|
|
32
|
+
result.rSquared = o.rSquared;
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
const [xv, yv, ux, uy] = points(data, x, y);
|
|
36
|
+
const n = xv.length;
|
|
37
|
+
const k = order + 1;
|
|
38
|
+
const lhs = [];
|
|
39
|
+
const rhs = [];
|
|
40
|
+
let Y = 0, n0 = 0, xmin = domain ? +domain[0] : Infinity, xmax = domain ? +domain[1] : -Infinity;
|
|
41
|
+
visitPoints(data, x, y, (dx, dy) => {
|
|
42
|
+
n0++;
|
|
43
|
+
Y += (dy - Y) / n0;
|
|
44
|
+
if (!domain) {
|
|
45
|
+
if (dx < xmin)
|
|
46
|
+
xmin = dx;
|
|
47
|
+
if (dx > xmax)
|
|
48
|
+
xmax = dx;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// Build normal equations
|
|
52
|
+
for (let i = 0; i < k; i++) {
|
|
53
|
+
// LHS
|
|
54
|
+
let v = 0;
|
|
55
|
+
for (let l = 0; l < n; l++) {
|
|
56
|
+
v += Math.pow(xv[l], i) * yv[l];
|
|
57
|
+
}
|
|
58
|
+
lhs.push(v);
|
|
59
|
+
// RHS
|
|
60
|
+
const c = new Float64Array(k);
|
|
61
|
+
for (let j = 0; j < k; j++) {
|
|
62
|
+
let v2 = 0;
|
|
63
|
+
for (let l = 0; l < n; l++) {
|
|
64
|
+
v2 += Math.pow(xv[l], i + j);
|
|
65
|
+
}
|
|
66
|
+
c[j] = v2;
|
|
67
|
+
}
|
|
68
|
+
rhs.push(c);
|
|
69
|
+
}
|
|
70
|
+
rhs.push(new Float64Array(lhs));
|
|
71
|
+
const coef = gaussianElimination(rhs);
|
|
72
|
+
const fn = (xx) => {
|
|
73
|
+
let shifted = xx - ux;
|
|
74
|
+
let val = uy + coef[0];
|
|
75
|
+
for (let i = 1; i < k; i++) {
|
|
76
|
+
val += coef[i] * Math.pow(shifted, i);
|
|
77
|
+
}
|
|
78
|
+
return val;
|
|
79
|
+
};
|
|
80
|
+
const out = interpose(xmin, xmax, fn);
|
|
81
|
+
out.coefficients = uncenter(k, coef, -ux, uy);
|
|
82
|
+
out.predict = fn;
|
|
83
|
+
out.rSquared = determination(data, x, y, Y, fn);
|
|
84
|
+
return out;
|
|
85
|
+
};
|
|
86
|
+
polynomialRegression.domain = function (arr) {
|
|
87
|
+
if (!arguments.length)
|
|
88
|
+
return domain;
|
|
89
|
+
domain = arr;
|
|
90
|
+
return polynomialRegression;
|
|
91
|
+
};
|
|
92
|
+
polynomialRegression.x = function (fn) {
|
|
93
|
+
if (!arguments.length)
|
|
94
|
+
return x;
|
|
95
|
+
x = fn;
|
|
96
|
+
return polynomialRegression;
|
|
97
|
+
};
|
|
98
|
+
polynomialRegression.y = function (fn) {
|
|
99
|
+
if (!arguments.length)
|
|
100
|
+
return y;
|
|
101
|
+
y = fn;
|
|
102
|
+
return polynomialRegression;
|
|
103
|
+
};
|
|
104
|
+
polynomialRegression.order = function (n) {
|
|
105
|
+
if (!arguments.length)
|
|
106
|
+
return order;
|
|
107
|
+
order = n;
|
|
108
|
+
return polynomialRegression;
|
|
109
|
+
};
|
|
110
|
+
return polynomialRegression;
|
|
111
|
+
}
|
|
112
|
+
function uncenter(k, a, x, y) {
|
|
113
|
+
const z = new Array(k).fill(0);
|
|
114
|
+
for (let i = k - 1; i >= 0; --i) {
|
|
115
|
+
let v = a[i];
|
|
116
|
+
z[i] += v;
|
|
117
|
+
let c = 1;
|
|
118
|
+
for (let j = 1; j <= i; ++j) {
|
|
119
|
+
c *= (i + 1 - j) / j; // binomial coefficient
|
|
120
|
+
z[i - j] += v * Math.pow(x, j) * c;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// bias term
|
|
124
|
+
z[0] += y;
|
|
125
|
+
return z;
|
|
126
|
+
}
|
|
127
|
+
// Solve A * x = b using Gaussian elimination
|
|
128
|
+
function gaussianElimination(matrix) {
|
|
129
|
+
const n = matrix.length - 1;
|
|
130
|
+
const coef = new Array(n);
|
|
131
|
+
for (let i = 0; i < n; i++) {
|
|
132
|
+
let r = i;
|
|
133
|
+
// find pivot row
|
|
134
|
+
for (let j = i + 1; j < n; j++) {
|
|
135
|
+
if (Math.abs(matrix[i][j]) > Math.abs(matrix[i][r])) {
|
|
136
|
+
r = j;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// swap columns
|
|
140
|
+
for (let k = i; k < n + 1; k++) {
|
|
141
|
+
const t = matrix[k][i];
|
|
142
|
+
matrix[k][i] = matrix[k][r];
|
|
143
|
+
matrix[k][r] = t;
|
|
144
|
+
}
|
|
145
|
+
// reduce
|
|
146
|
+
for (let j = i + 1; j < n; j++) {
|
|
147
|
+
for (let k = n; k >= i; k--) {
|
|
148
|
+
matrix[k][j] -= (matrix[k][i] * matrix[i][j]) / matrix[i][i];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (let j = n - 1; j >= 0; j--) {
|
|
153
|
+
let t = 0;
|
|
154
|
+
for (let k = j + 1; k < n; k++) {
|
|
155
|
+
t += matrix[k][j] * coef[k];
|
|
156
|
+
}
|
|
157
|
+
coef[j] = (matrix[n][j] - t) / matrix[j][j];
|
|
158
|
+
}
|
|
159
|
+
return coef;
|
|
160
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PredictFunction, DataPoint, Accessor, Domain } from './types.js';
|
|
2
|
+
export type PowerOutput = [DataPoint, DataPoint] & {
|
|
3
|
+
a: number;
|
|
4
|
+
b: number;
|
|
5
|
+
predict: PredictFunction;
|
|
6
|
+
rSquared: number;
|
|
7
|
+
};
|
|
8
|
+
interface PowerRegression<T> {
|
|
9
|
+
(data: T[]): PowerOutput;
|
|
10
|
+
domain(): Domain;
|
|
11
|
+
domain(domain?: Domain): this;
|
|
12
|
+
x(): Accessor<T>;
|
|
13
|
+
x(x: Accessor<T>): this;
|
|
14
|
+
y(): Accessor<T>;
|
|
15
|
+
y(y: Accessor<T>): this;
|
|
16
|
+
}
|
|
17
|
+
export default function power<T = DataPoint>(): PowerRegression<T>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from https://github.com/HarryStevens/d3-regression
|
|
3
|
+
*/
|
|
4
|
+
import { determination } from './utils/determination.js';
|
|
5
|
+
import { interpose } from './utils/interpose.js';
|
|
6
|
+
import { ols } from './utils/ols.js';
|
|
7
|
+
import { visitPoints } from './utils/points.js';
|
|
8
|
+
export default function power() {
|
|
9
|
+
let x = (d) => d[0], y = (d) => d[1], domain;
|
|
10
|
+
const powerRegression = function powerRegression(data) {
|
|
11
|
+
let n = 0, X = 0, Y = 0, XY = 0, X2 = 0, YS = 0, xmin = domain ? +domain[0] : Infinity, xmax = domain ? +domain[1] : -Infinity;
|
|
12
|
+
visitPoints(data, x, y, (dx, dy) => {
|
|
13
|
+
const lx = Math.log(dx), ly = Math.log(dy);
|
|
14
|
+
++n;
|
|
15
|
+
X += (lx - X) / n;
|
|
16
|
+
Y += (ly - Y) / n;
|
|
17
|
+
XY += (lx * ly - XY) / n;
|
|
18
|
+
X2 += (lx * lx - X2) / n;
|
|
19
|
+
YS += (dy - YS) / n;
|
|
20
|
+
if (!domain) {
|
|
21
|
+
if (dx < xmin)
|
|
22
|
+
xmin = dx;
|
|
23
|
+
if (dx > xmax)
|
|
24
|
+
xmax = dx;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
let [a, b] = ols(X, Y, XY, X2);
|
|
28
|
+
a = Math.exp(a);
|
|
29
|
+
const fn = (xx) => a * Math.pow(xx, b);
|
|
30
|
+
const out = interpose(xmin, xmax, fn);
|
|
31
|
+
out.a = a;
|
|
32
|
+
out.b = b;
|
|
33
|
+
out.predict = fn;
|
|
34
|
+
out.rSquared = determination(data, x, y, YS, fn);
|
|
35
|
+
return out;
|
|
36
|
+
};
|
|
37
|
+
powerRegression.domain = function (arr) {
|
|
38
|
+
if (!arguments.length)
|
|
39
|
+
return domain;
|
|
40
|
+
domain = arr;
|
|
41
|
+
return powerRegression;
|
|
42
|
+
};
|
|
43
|
+
powerRegression.x = function (fn) {
|
|
44
|
+
if (!arguments.length)
|
|
45
|
+
return x;
|
|
46
|
+
x = fn;
|
|
47
|
+
return powerRegression;
|
|
48
|
+
};
|
|
49
|
+
powerRegression.y = function (fn) {
|
|
50
|
+
if (!arguments.length)
|
|
51
|
+
return y;
|
|
52
|
+
y = fn;
|
|
53
|
+
return powerRegression;
|
|
54
|
+
};
|
|
55
|
+
return powerRegression;
|
|
56
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Accessor, DataPoint, PredictFunction, Domain } from './types.js';
|
|
2
|
+
export type QuadraticOutput = [DataPoint, DataPoint] & {
|
|
3
|
+
a: number;
|
|
4
|
+
b: number;
|
|
5
|
+
c: number;
|
|
6
|
+
predict: PredictFunction;
|
|
7
|
+
rSquared: number;
|
|
8
|
+
};
|
|
9
|
+
interface QuadraticRegression<T> {
|
|
10
|
+
(data: T[]): QuadraticOutput;
|
|
11
|
+
domain(): Domain;
|
|
12
|
+
domain(domain?: Domain): this;
|
|
13
|
+
x(): Accessor<T>;
|
|
14
|
+
x(x: Accessor<T>): this;
|
|
15
|
+
y(): Accessor<T>;
|
|
16
|
+
y(y: Accessor<T>): this;
|
|
17
|
+
}
|
|
18
|
+
export default function quadratic<T = DataPoint>(): QuadraticRegression<T>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from https://github.com/HarryStevens/d3-regression
|
|
3
|
+
*/
|
|
4
|
+
import { determination } from './utils/determination.js';
|
|
5
|
+
import { interpose } from './utils/interpose.js';
|
|
6
|
+
import { points, visitPoints } from './utils/points.js';
|
|
7
|
+
export default function quadratic() {
|
|
8
|
+
let x = (d) => d[0], y = (d) => d[1], domain;
|
|
9
|
+
const quadraticRegression = function quadraticRegression(data) {
|
|
10
|
+
const [xv, yv, ux, uy] = points(data, (dd) => x(dd), (dd) => y(dd));
|
|
11
|
+
const n = xv.length;
|
|
12
|
+
let X2 = 0, X3 = 0, X4 = 0, XY = 0, X2Y = 0, i, dx, dy, x2;
|
|
13
|
+
for (i = 0; i < n;) {
|
|
14
|
+
dx = xv[i];
|
|
15
|
+
dy = yv[i++];
|
|
16
|
+
x2 = dx * dx;
|
|
17
|
+
X2 += (x2 - X2) / i;
|
|
18
|
+
X3 += (x2 * dx - X3) / i;
|
|
19
|
+
X4 += (x2 * x2 - X4) / i;
|
|
20
|
+
XY += (dx * dy - XY) / i;
|
|
21
|
+
X2Y += (x2 * dy - X2Y) / i;
|
|
22
|
+
}
|
|
23
|
+
let Y = 0, n0 = 0, xmin = domain ? +domain[0] : Infinity, xmax = domain ? +domain[1] : -Infinity;
|
|
24
|
+
visitPoints(data, x, y, (dx2, dy2) => {
|
|
25
|
+
n0++;
|
|
26
|
+
Y += (dy2 - Y) / n0;
|
|
27
|
+
if (!domain) {
|
|
28
|
+
if (dx2 < xmin)
|
|
29
|
+
xmin = dx2;
|
|
30
|
+
if (dx2 > xmax)
|
|
31
|
+
xmax = dx2;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const X2X2 = X4 - X2 * X2;
|
|
35
|
+
const d = X2 * X2X2 - X3 * X3;
|
|
36
|
+
const a = (X2Y * X2 - XY * X3) / d;
|
|
37
|
+
const b = (XY * X2X2 - X2Y * X3) / d;
|
|
38
|
+
const c = -a * X2;
|
|
39
|
+
const fn = (xx) => {
|
|
40
|
+
const shifted = xx - ux;
|
|
41
|
+
return a * shifted * shifted + b * shifted + c + uy;
|
|
42
|
+
};
|
|
43
|
+
const out = interpose(xmin, xmax, fn);
|
|
44
|
+
out.a = a;
|
|
45
|
+
out.b = b - 2 * a * ux;
|
|
46
|
+
out.c = c - b * ux + a * ux * ux + uy;
|
|
47
|
+
out.predict = fn;
|
|
48
|
+
out.rSquared = determination(data, x, y, Y, fn);
|
|
49
|
+
return out;
|
|
50
|
+
};
|
|
51
|
+
quadraticRegression.domain = function (arr) {
|
|
52
|
+
if (!arguments.length)
|
|
53
|
+
return domain;
|
|
54
|
+
domain = arr;
|
|
55
|
+
return quadraticRegression;
|
|
56
|
+
};
|
|
57
|
+
quadraticRegression.x = function (fn) {
|
|
58
|
+
if (!arguments.length)
|
|
59
|
+
return x;
|
|
60
|
+
x = fn;
|
|
61
|
+
return quadraticRegression;
|
|
62
|
+
};
|
|
63
|
+
quadraticRegression.y = function (fn) {
|
|
64
|
+
if (!arguments.length)
|
|
65
|
+
return y;
|
|
66
|
+
y = fn;
|
|
67
|
+
return quadraticRegression;
|
|
68
|
+
};
|
|
69
|
+
return quadraticRegression;
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Accessor, PredictFunction } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Given a dataset, x- and y-accessors, the mean center of the y-values (uY),
|
|
4
|
+
* and a prediction function, return the coefficient of determination, R^2.
|
|
5
|
+
*/
|
|
6
|
+
export declare function determination<T>(data: T[], x: Accessor<T>, y: Accessor<T>, uY: number, predict: PredictFunction): number;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { visitPoints } from './points';
|
|
2
|
+
/**
|
|
3
|
+
* Given a dataset, x- and y-accessors, the mean center of the y-values (uY),
|
|
4
|
+
* and a prediction function, return the coefficient of determination, R^2.
|
|
5
|
+
*/
|
|
6
|
+
export function determination(data, x, y, uY, predict) {
|
|
7
|
+
let SSE = 0, // Sum of Squared Errors
|
|
8
|
+
SST = 0; // Total Sum of Squares
|
|
9
|
+
visitPoints(data, x, y, (dx, dy) => {
|
|
10
|
+
const sse = dy - predict(dx);
|
|
11
|
+
const sst = dy - uY;
|
|
12
|
+
SSE += sse * sse;
|
|
13
|
+
SST += sst * sst;
|
|
14
|
+
});
|
|
15
|
+
return 1 - SSE / SST;
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DataPoint } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the angle of a line in degrees.
|
|
4
|
+
*/
|
|
5
|
+
export declare function angle(line: [DataPoint, DataPoint]): number;
|
|
6
|
+
/**
|
|
7
|
+
* Returns the midpoint of a line.
|
|
8
|
+
*/
|
|
9
|
+
export declare function midpoint(line: [DataPoint, DataPoint]): DataPoint;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the angle of a line in degrees.
|
|
3
|
+
*/
|
|
4
|
+
export function angle(line) {
|
|
5
|
+
return Math.atan2(line[1][1] - line[0][1], line[1][0] - line[0][0]) * (180 / Math.PI);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Returns the midpoint of a line.
|
|
9
|
+
*/
|
|
10
|
+
export function midpoint(line) {
|
|
11
|
+
return [(line[0][0] + line[1][0]) / 2, (line[0][1] + line[1][1]) / 2];
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PredictFunction, DataPoint } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Given a start point (xmin), an end point (xmax),
|
|
4
|
+
* and a prediction function, returns a smooth line.
|
|
5
|
+
*/
|
|
6
|
+
export declare function interpose(xmin: number, xmax: number, predict: PredictFunction): [DataPoint, DataPoint];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { angle, midpoint } from './geometry';
|
|
2
|
+
/**
|
|
3
|
+
* Given a start point (xmin), an end point (xmax),
|
|
4
|
+
* and a prediction function, returns a smooth line.
|
|
5
|
+
*/
|
|
6
|
+
export function interpose(xmin, xmax, predict) {
|
|
7
|
+
const l = (Math.log(xmax - xmin) * Math.LOG10E + 1) | 0;
|
|
8
|
+
const precision = Math.pow(10, -l / 2 - 1);
|
|
9
|
+
const maxIter = 1e4;
|
|
10
|
+
let points = [px(xmin), px(xmax)];
|
|
11
|
+
let iter = 0;
|
|
12
|
+
while (find(points) && iter < maxIter)
|
|
13
|
+
;
|
|
14
|
+
return points;
|
|
15
|
+
function px(x) {
|
|
16
|
+
return [x, predict(x)];
|
|
17
|
+
}
|
|
18
|
+
function find(points) {
|
|
19
|
+
iter++;
|
|
20
|
+
const n = points.length;
|
|
21
|
+
let found = false;
|
|
22
|
+
for (let i = 0; i < n - 1; i++) {
|
|
23
|
+
const p0 = points[i];
|
|
24
|
+
const p1 = points[i + 1];
|
|
25
|
+
const m = midpoint([p0, p1]);
|
|
26
|
+
const mp = px(m[0]);
|
|
27
|
+
const a0 = angle([p0, m]);
|
|
28
|
+
const a1 = angle([p0, mp]);
|
|
29
|
+
const a = Math.abs(a0 - a1);
|
|
30
|
+
if (a > precision) {
|
|
31
|
+
points.splice(i + 1, 0, mp);
|
|
32
|
+
found = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return found;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordinary Least Squares from vega-statistics by Jeffrey Heer
|
|
3
|
+
* License: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/LICENSE
|
|
4
|
+
* Source: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/packages/vega-statistics/src/regression/ols.js
|
|
5
|
+
*/
|
|
6
|
+
export declare function ols(uX: number, uY: number, uXY: number, uX2: number): [number, number];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordinary Least Squares from vega-statistics by Jeffrey Heer
|
|
3
|
+
* License: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/LICENSE
|
|
4
|
+
* Source: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/packages/vega-statistics/src/regression/ols.js
|
|
5
|
+
*/
|
|
6
|
+
export function ols(uX, uY, uXY, uX2) {
|
|
7
|
+
const delta = uX2 - uX * uX, slope = Math.abs(delta) < 1e-24 ? 0 : (uXY - uX * uY) / delta, intercept = uY - slope * uX;
|
|
8
|
+
return [intercept, slope];
|
|
9
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Accessor } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Adapted from vega-statistics by Jeffrey Heer
|
|
4
|
+
* License: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/LICENSE
|
|
5
|
+
* Source: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/packages/vega-statistics/src/regression/points.js
|
|
6
|
+
*/
|
|
7
|
+
export declare function points<T>(data: T[], x: Accessor<T>, y: Accessor<T>, sort?: boolean): [Float64Array, Float64Array, number, number];
|
|
8
|
+
/**
|
|
9
|
+
* Iterates over valid data points, invoking a callback for each.
|
|
10
|
+
*/
|
|
11
|
+
export declare function visitPoints<T>(data: T[], x: Accessor<T>, y: Accessor<T>, cb: (dx: number, dy: number, index: number) => void): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from vega-statistics by Jeffrey Heer
|
|
3
|
+
* License: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/LICENSE
|
|
4
|
+
* Source: https://github.com/vega/vega/blob/f058b099decad9db78301405dd0d2e9d8ba3d51a/packages/vega-statistics/src/regression/points.js
|
|
5
|
+
*/
|
|
6
|
+
export function points(data, x, y, sort) {
|
|
7
|
+
data = data.filter((d, i) => {
|
|
8
|
+
let u = x(d, i), v = y(d, i);
|
|
9
|
+
return u != null && isFinite(u) && v != null && isFinite(v);
|
|
10
|
+
});
|
|
11
|
+
if (sort) {
|
|
12
|
+
data.sort((a, b) => x(a) - x(b));
|
|
13
|
+
}
|
|
14
|
+
const n = data.length, X = new Float64Array(n), Y = new Float64Array(n);
|
|
15
|
+
// extract values, calculate means
|
|
16
|
+
let ux = 0, uy = 0, xv, yv, d;
|
|
17
|
+
for (let i = 0; i < n;) {
|
|
18
|
+
d = data[i];
|
|
19
|
+
X[i] = xv = +x(d, i, data);
|
|
20
|
+
Y[i] = yv = +y(d, i, data);
|
|
21
|
+
++i;
|
|
22
|
+
ux += (xv - ux) / i;
|
|
23
|
+
uy += (yv - uy) / i;
|
|
24
|
+
}
|
|
25
|
+
// mean center the data
|
|
26
|
+
for (let i = 0; i < n; ++i) {
|
|
27
|
+
X[i] -= ux;
|
|
28
|
+
Y[i] -= uy;
|
|
29
|
+
}
|
|
30
|
+
return [X, Y, ux, uy];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Iterates over valid data points, invoking a callback for each.
|
|
34
|
+
*/
|
|
35
|
+
export function visitPoints(data, x, y, cb) {
|
|
36
|
+
let iterations = 0;
|
|
37
|
+
for (let i = 0; i < data.length; i++) {
|
|
38
|
+
const d = data[i];
|
|
39
|
+
const dx = +x(d, i, data);
|
|
40
|
+
const dy = +y(d, i, data);
|
|
41
|
+
if (dx != null && isFinite(dx) && dy != null && isFinite(dy)) {
|
|
42
|
+
cb(dx, dy, iterations++);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ScaledDataRecord, TransformArg, PlotState } from '../types';
|
|
2
|
+
type BaseDodgeOptions = {
|
|
3
|
+
anchor?: string;
|
|
4
|
+
padding?: number;
|
|
5
|
+
r?: number;
|
|
6
|
+
};
|
|
7
|
+
type AnchorX = 'left' | 'right' | 'middle';
|
|
8
|
+
type AnchorY = 'top' | 'bottom' | 'middle';
|
|
9
|
+
export type DodgeXOptions = AnchorX | (BaseDodgeOptions & {
|
|
10
|
+
anchor?: 'left' | 'right' | 'middle';
|
|
11
|
+
});
|
|
12
|
+
export type DodgeYOptions = AnchorY | (BaseDodgeOptions & {
|
|
13
|
+
anchor?: 'top' | 'bottom' | 'middle';
|
|
14
|
+
});
|
|
15
|
+
export declare function dodgeX(args: TransformArg<ScaledDataRecord>, plotState: PlotState): ScaledDataRecord[];
|
|
16
|
+
export declare function dodgeY(args: TransformArg<ScaledDataRecord>, plotState: PlotState): ScaledDataRecord[];
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import IntervalTree from 'interval-tree-1d';
|
|
2
|
+
import { groupFacetsAndZ } from '../helpers/group';
|
|
3
|
+
export function dodgeX(args, plotState) {
|
|
4
|
+
if (!args.dodgeX)
|
|
5
|
+
return args.data;
|
|
6
|
+
let { anchor = 'left', padding = 1, r = args.dodgeX.r } = maybeAnchor(args.dodgeX);
|
|
7
|
+
let anchorFunction;
|
|
8
|
+
switch (`${anchor}`.toLowerCase()) {
|
|
9
|
+
case 'left':
|
|
10
|
+
anchorFunction = anchorXLeft;
|
|
11
|
+
break;
|
|
12
|
+
case 'right':
|
|
13
|
+
anchorFunction = anchorXRight;
|
|
14
|
+
break;
|
|
15
|
+
case 'middle':
|
|
16
|
+
anchorFunction = anchorXMiddle;
|
|
17
|
+
break;
|
|
18
|
+
default:
|
|
19
|
+
throw new Error(`unknown dodge anchor: ${anchor}`);
|
|
20
|
+
}
|
|
21
|
+
return dodge('x', 'y', anchorFunction, Number(padding), r, args, plotState);
|
|
22
|
+
}
|
|
23
|
+
export function dodgeY(args, plotState) {
|
|
24
|
+
if (!args.dodgeY)
|
|
25
|
+
return args.data;
|
|
26
|
+
let { anchor = 'bottom', padding = 1, r = args.dodgeY.r } = maybeAnchor(args.dodgeY);
|
|
27
|
+
let anchorFunction;
|
|
28
|
+
switch (`${anchor}`.toLowerCase()) {
|
|
29
|
+
case 'top':
|
|
30
|
+
anchorFunction = anchorYTop;
|
|
31
|
+
break;
|
|
32
|
+
case 'bottom':
|
|
33
|
+
anchorFunction = anchorYBottom;
|
|
34
|
+
break;
|
|
35
|
+
case 'middle':
|
|
36
|
+
anchorFunction = anchorYMiddle;
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(`unknown dodge anchor: ${anchor}`);
|
|
40
|
+
}
|
|
41
|
+
return dodge('y', 'x', anchorFunction, Number(padding), r, args, plotState);
|
|
42
|
+
}
|
|
43
|
+
function dodge(y, x, anchor, padding, r, { data, ...channels }, plotState) {
|
|
44
|
+
if (r != null && typeof r !== 'number') {
|
|
45
|
+
// use the r channel
|
|
46
|
+
// let { channels, sort, reverse } = options;
|
|
47
|
+
// channels = maybeNamed(channels);
|
|
48
|
+
// if (channels?.r === undefined)
|
|
49
|
+
// options = { ...options, channels: { ...channels, r: { value: r, scale: 'r' } } };
|
|
50
|
+
// if (sort === undefined && reverse === undefined) options.sort = { channel: '-r' };
|
|
51
|
+
}
|
|
52
|
+
const { fx, fy } = channels;
|
|
53
|
+
let [ky, ty] = anchor(plotState);
|
|
54
|
+
const compare = ky ? compareAscending : compareSymmetric;
|
|
55
|
+
const cr = r !== undefined ? r : 3; // default radius if no r channel
|
|
56
|
+
// group data by facets
|
|
57
|
+
groupFacetsAndZ(data, { fx, fy }, (items) => {
|
|
58
|
+
// apply dodge within each facet
|
|
59
|
+
const tree = IntervalTree();
|
|
60
|
+
const data = items.filter((d) => (typeof d.r !== 'number' || d.r >= 0) && isFinite(d[x]) && isFinite(d[y]));
|
|
61
|
+
const intervals = new Float64Array(2 * data.length + 2);
|
|
62
|
+
data.forEach((d, i) => {
|
|
63
|
+
const ri = d.r ?? r ?? 3;
|
|
64
|
+
const y0 = ky ? ri + padding : 0; // offset baseline for varying radius
|
|
65
|
+
const l = d[x] - ri;
|
|
66
|
+
const h = d[x] + ri;
|
|
67
|
+
// The first two positions are 0 to test placing the dot on the baseline.
|
|
68
|
+
let k = 2;
|
|
69
|
+
// For any previously placed circles that may overlap this circle, compute
|
|
70
|
+
// the y-positions that place this circle tangent to these other circles.
|
|
71
|
+
// https://observablehq.com/@mbostock/circle-offset-along-line
|
|
72
|
+
tree.queryInterval(l - padding, h + padding, ([, , j]) => {
|
|
73
|
+
const yj = data[j][y] - y0;
|
|
74
|
+
const dx = d[x] - data[j][x];
|
|
75
|
+
const dr = padding + (channels.r ? d.r + data[j].r : 2 * cr);
|
|
76
|
+
const dy = Math.sqrt(dr * dr - dx * dx);
|
|
77
|
+
intervals[k++] = yj - dy;
|
|
78
|
+
intervals[k++] = yj + dy;
|
|
79
|
+
});
|
|
80
|
+
// Find the best y-value where this circle can fit.
|
|
81
|
+
let candidates = intervals.slice(0, k);
|
|
82
|
+
if (ky)
|
|
83
|
+
candidates = candidates.filter((y) => y >= 0);
|
|
84
|
+
out: for (const diff of candidates.sort(compare)) {
|
|
85
|
+
for (let j = 0; j < k; j += 2) {
|
|
86
|
+
if (intervals[j] + 1e-6 < diff && diff < intervals[j + 1] - 1e-6) {
|
|
87
|
+
continue out;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
d[y] = diff + y0;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
// Insert the placed circle into the interval tree.
|
|
94
|
+
tree.insert([l, h, i]);
|
|
95
|
+
});
|
|
96
|
+
if (!ky)
|
|
97
|
+
ky = 1;
|
|
98
|
+
data.forEach((d) => (d[y] = d[y] * ky + ty));
|
|
99
|
+
});
|
|
100
|
+
return data;
|
|
101
|
+
}
|
|
102
|
+
function maybeAnchor(anchor) {
|
|
103
|
+
return typeof anchor === 'string' ? { anchor } : anchor;
|
|
104
|
+
}
|
|
105
|
+
function anchorXLeft({ options: { marginLeft } }) {
|
|
106
|
+
return [1, marginLeft];
|
|
107
|
+
}
|
|
108
|
+
function anchorXRight({ facetWidth: width, options: { marginLeft } }) {
|
|
109
|
+
return [-1, marginLeft + width];
|
|
110
|
+
}
|
|
111
|
+
function anchorXMiddle({ facetWidth: width, options: { marginLeft } }) {
|
|
112
|
+
return [0, marginLeft + width / 2];
|
|
113
|
+
}
|
|
114
|
+
function anchorYTop({ options: { marginTop } }) {
|
|
115
|
+
return [1, marginTop];
|
|
116
|
+
}
|
|
117
|
+
function anchorYBottom({ facetHeight: height }) {
|
|
118
|
+
return [-1, height];
|
|
119
|
+
}
|
|
120
|
+
function anchorYMiddle({ facetHeight: height, options: { marginTop, marginBottom } }) {
|
|
121
|
+
return [0, (marginTop + height) / 2];
|
|
122
|
+
}
|
|
123
|
+
function compareSymmetric(a, b) {
|
|
124
|
+
return Math.abs(a) - Math.abs(b);
|
|
125
|
+
}
|
|
126
|
+
function compareAscending(a, b) {
|
|
127
|
+
return a - b;
|
|
128
|
+
}
|