svelteplot 0.11.1 → 0.12.0-pr-523.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/dist/core/Plot.svelte +31 -1
- package/dist/helpers/autoScales.js +3 -1
- package/dist/helpers/facets.d.ts +2 -2
- package/dist/helpers/rasterInterpolate.d.ts +26 -0
- package/dist/helpers/rasterInterpolate.js +220 -0
- package/dist/marks/Contour.svelte +516 -0
- package/dist/marks/Contour.svelte.d.ts +138 -0
- package/dist/marks/Geo.svelte +9 -5
- package/dist/marks/Line.svelte +52 -15
- package/dist/marks/Raster.svelte +421 -0
- package/dist/marks/Raster.svelte.d.ts +95 -0
- package/dist/marks/Text.svelte +8 -6
- package/dist/marks/helpers/GroupMultiple.svelte +6 -1
- package/dist/marks/helpers/GroupMultiple.svelte.d.ts +1 -0
- package/dist/marks/index.d.ts +2 -0
- package/dist/marks/index.js +2 -0
- package/dist/types/mark.d.ts +1 -1
- package/dist/types/plot.d.ts +10 -1
- package/package.json +5 -1
package/dist/core/Plot.svelte
CHANGED
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
colorScheme: 'turbo',
|
|
75
75
|
unknown: '#cccccc99',
|
|
76
76
|
sortOrdinalDomains: true,
|
|
77
|
+
divergingColorScheme: 'RdBu',
|
|
77
78
|
categoricalColorScheme: 'observable10',
|
|
78
79
|
pointScaleHeight: 20,
|
|
79
80
|
bandScaleHeight: 30,
|
|
@@ -209,6 +210,24 @@
|
|
|
209
210
|
(fixedWidth || width) - plotOptions.marginLeft - plotOptions.marginRight
|
|
210
211
|
);
|
|
211
212
|
|
|
213
|
+
// Width used for geo-projection aspect-ratio height computation only.
|
|
214
|
+
// Excludes reactive auto-margins to prevent a feedback loop in projection mode.
|
|
215
|
+
const plotWidthForAspectRatio = $derived(
|
|
216
|
+
(fixedWidth || width) -
|
|
217
|
+
maybeMargin(
|
|
218
|
+
initialOptions.margin as number | 'auto' | PlotMargin | undefined,
|
|
219
|
+
'left',
|
|
220
|
+
DEFAULTS.margin,
|
|
221
|
+
{ left: 0, right: 0, top: 0, bottom: 0 }
|
|
222
|
+
) -
|
|
223
|
+
maybeMargin(
|
|
224
|
+
initialOptions.margin as number | 'auto' | PlotMargin | undefined,
|
|
225
|
+
'right',
|
|
226
|
+
DEFAULTS.margin,
|
|
227
|
+
{ left: 0, right: 0, top: 0, bottom: 0 }
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
|
|
212
231
|
// the facet and y domain counts are used for computing the automatic height
|
|
213
232
|
const xFacetCount = $derived(Math.max(1, preScales.fx.domain.length));
|
|
214
233
|
const yFacetCount = $derived(Math.max(1, preScales.fy.domain.length));
|
|
@@ -235,7 +254,8 @@
|
|
|
235
254
|
: maybeNumber(plotOptions.height) === null || plotOptions.height === 'auto'
|
|
236
255
|
? Math.round(
|
|
237
256
|
preScales.projection && (preScales.projection as any).aspectRatio
|
|
238
|
-
? ((
|
|
257
|
+
? ((plotWidthForAspectRatio * (preScales.projection as any).aspectRatio) /
|
|
258
|
+
xFacetCount) *
|
|
239
259
|
yFacetCount +
|
|
240
260
|
plotOptions.marginTop +
|
|
241
261
|
plotOptions.marginBottom
|
|
@@ -396,6 +416,16 @@
|
|
|
396
416
|
y.type === 'band' || y.type === 'point'
|
|
397
417
|
? y.domain.length
|
|
398
418
|
: Math.abs((y.domain[1] as number) - (y.domain[0] as number));
|
|
419
|
+
// Guard against degenerate/empty domains (e.g. before marks have mounted).
|
|
420
|
+
// Returning NaN here would propagate through scale ranges → NaN pixel coords.
|
|
421
|
+
if (
|
|
422
|
+
!xDomainExtent ||
|
|
423
|
+
!yDomainExtent ||
|
|
424
|
+
!isFinite(xDomainExtent) ||
|
|
425
|
+
!isFinite(yDomainExtent)
|
|
426
|
+
) {
|
|
427
|
+
return DEFAULTS.height + marginTop + marginBottom;
|
|
428
|
+
}
|
|
399
429
|
return (
|
|
400
430
|
((plotWidth / xDomainExtent) * yDomainExtent) / aspectRatio + marginTop + marginBottom
|
|
401
431
|
);
|
|
@@ -171,10 +171,12 @@ export function autoScaleColor({ type, domain, scaleOptions, plotOptions: _plotO
|
|
|
171
171
|
}
|
|
172
172
|
else if (SequentialScales[type] ||
|
|
173
173
|
DivergingScales[type]) {
|
|
174
|
+
const isDivergingType = DivergingScales.hasOwnProperty(type);
|
|
174
175
|
// continuous color scale
|
|
175
176
|
const scale = (SequentialScales[type] ||
|
|
176
177
|
DivergingScales[type]);
|
|
177
|
-
const scheme_ = scheme ||
|
|
178
|
+
const scheme_ = scheme ||
|
|
179
|
+
(isDivergingType ? plotDefaults.divergingColorScheme : plotDefaults.colorScheme);
|
|
178
180
|
if (interpolate) {
|
|
179
181
|
// user-defined interpolation function [0, 1] -> color
|
|
180
182
|
fn = scale(domain, interpolate);
|
package/dist/helpers/facets.d.ts
CHANGED
|
@@ -46,8 +46,8 @@ export declare function findFacetFromDOM(target: Element | null): {
|
|
|
46
46
|
* translation of the facet from the plot body origin.
|
|
47
47
|
*/
|
|
48
48
|
export declare function detectFacet(evt: MouseEvent, plot: PlotState): {
|
|
49
|
-
fxValue: RawValue
|
|
50
|
-
fyValue: RawValue
|
|
49
|
+
fxValue: RawValue;
|
|
50
|
+
fyValue: RawValue;
|
|
51
51
|
offsetX: number;
|
|
52
52
|
offsetY: number;
|
|
53
53
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type InterpolateFunction = (index: number[], width: number, height: number, X: Float64Array, Y: Float64Array, V: ArrayLike<any>) => ArrayLike<any>;
|
|
2
|
+
/**
|
|
3
|
+
* Simple forward mapping: each sample is binned to its nearest pixel.
|
|
4
|
+
* If multiple samples map to the same pixel, the last one wins.
|
|
5
|
+
*/
|
|
6
|
+
export declare function interpolateNone(index: number[], width: number, height: number, X: Float64Array, Y: Float64Array, V: ArrayLike<any>): any[];
|
|
7
|
+
/**
|
|
8
|
+
* Nearest-neighbor interpolation using Delaunay triangulation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function interpolateNearest(index: number[], width: number, height: number, X: Float64Array, Y: Float64Array, V: ArrayLike<any>): any;
|
|
11
|
+
/**
|
|
12
|
+
* Barycentric interpolation: fills the interior of each Delaunay triangle with
|
|
13
|
+
* barycentric-weighted values, then extrapolates exterior pixels to the hull.
|
|
14
|
+
*/
|
|
15
|
+
export declare function interpolatorBarycentric({ random }?: {
|
|
16
|
+
random?: (() => number) | undefined;
|
|
17
|
+
}): InterpolateFunction;
|
|
18
|
+
/**
|
|
19
|
+
* Walk-on-spheres algorithm for smooth interpolation.
|
|
20
|
+
* https://observablehq.com/@observablehq/walk-on-spheres-precision
|
|
21
|
+
*/
|
|
22
|
+
export declare function interpolatorRandomWalk({ random, minDistance, maxSteps }?: {
|
|
23
|
+
random?: (() => number) | undefined;
|
|
24
|
+
minDistance?: number | undefined;
|
|
25
|
+
maxSteps?: number | undefined;
|
|
26
|
+
}): InterpolateFunction;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Delaunay } from 'd3-delaunay';
|
|
2
|
+
import { randomLcg } from 'd3-random';
|
|
3
|
+
/**
|
|
4
|
+
* Simple forward mapping: each sample is binned to its nearest pixel.
|
|
5
|
+
* If multiple samples map to the same pixel, the last one wins.
|
|
6
|
+
*/
|
|
7
|
+
export function interpolateNone(index, width, height, X, Y, V) {
|
|
8
|
+
const W = new Array(width * height);
|
|
9
|
+
for (const i of index) {
|
|
10
|
+
if (X[i] < 0 || X[i] >= width || Y[i] < 0 || Y[i] >= height)
|
|
11
|
+
continue;
|
|
12
|
+
W[Math.floor(Y[i]) * width + Math.floor(X[i])] = V[i];
|
|
13
|
+
}
|
|
14
|
+
return W;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Nearest-neighbor interpolation using Delaunay triangulation.
|
|
18
|
+
*/
|
|
19
|
+
export function interpolateNearest(index, width, height, X, Y, V) {
|
|
20
|
+
const n = width * height;
|
|
21
|
+
// Use typed array if V is typed, otherwise plain array
|
|
22
|
+
const W = isTypedArray(V) ? new V.constructor(n) : new Array(n);
|
|
23
|
+
const delaunay = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
24
|
+
// Memoize delaunay.find for the line start (iy) and current pixel (ix)
|
|
25
|
+
let iy, ix;
|
|
26
|
+
for (let y = 0.5, k = 0; y < height; ++y) {
|
|
27
|
+
ix = iy;
|
|
28
|
+
for (let x = 0.5; x < width; ++x, ++k) {
|
|
29
|
+
ix = delaunay.find(x, y, ix);
|
|
30
|
+
if (x === 0.5)
|
|
31
|
+
iy = ix;
|
|
32
|
+
W[k] = V[index[ix]];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return W;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Barycentric interpolation: fills the interior of each Delaunay triangle with
|
|
39
|
+
* barycentric-weighted values, then extrapolates exterior pixels to the hull.
|
|
40
|
+
*/
|
|
41
|
+
export function interpolatorBarycentric({ random = randomLcg(42) } = {}) {
|
|
42
|
+
return (index, width, height, X, Y, V) => {
|
|
43
|
+
const { points, triangles, hull } = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
44
|
+
const n = width * height;
|
|
45
|
+
const W = isTypedArray(V)
|
|
46
|
+
? new V.constructor(n).fill(NaN)
|
|
47
|
+
: new Array(n).fill(NaN);
|
|
48
|
+
const S = new Uint8Array(n); // 1 if pixel has been written
|
|
49
|
+
const mix = mixer(V, random);
|
|
50
|
+
for (let i = 0; i < triangles.length; i += 3) {
|
|
51
|
+
const ta = triangles[i];
|
|
52
|
+
const tb = triangles[i + 1];
|
|
53
|
+
const tc = triangles[i + 2];
|
|
54
|
+
const Ax = points[2 * ta];
|
|
55
|
+
const Bx = points[2 * tb];
|
|
56
|
+
const Cx = points[2 * tc];
|
|
57
|
+
const Ay = points[2 * ta + 1];
|
|
58
|
+
const By = points[2 * tb + 1];
|
|
59
|
+
const Cy = points[2 * tc + 1];
|
|
60
|
+
const x1 = Math.min(Ax, Bx, Cx);
|
|
61
|
+
const x2 = Math.max(Ax, Bx, Cx);
|
|
62
|
+
const y1 = Math.min(Ay, By, Cy);
|
|
63
|
+
const y2 = Math.max(Ay, By, Cy);
|
|
64
|
+
const z = (By - Cy) * (Ax - Cx) + (Ay - Cy) * (Cx - Bx);
|
|
65
|
+
if (!z)
|
|
66
|
+
continue;
|
|
67
|
+
const va = V[index[ta]];
|
|
68
|
+
const vb = V[index[tb]];
|
|
69
|
+
const vc = V[index[tc]];
|
|
70
|
+
for (let x = Math.floor(x1); x < x2; ++x) {
|
|
71
|
+
for (let y = Math.floor(y1); y < y2; ++y) {
|
|
72
|
+
if (x < 0 || x >= width || y < 0 || y >= height)
|
|
73
|
+
continue;
|
|
74
|
+
const xp = x + 0.5;
|
|
75
|
+
const yp = y + 0.5;
|
|
76
|
+
const s = Math.sign(z);
|
|
77
|
+
const ga = (By - Cy) * (xp - Cx) + (yp - Cy) * (Cx - Bx);
|
|
78
|
+
if (ga * s < 0)
|
|
79
|
+
continue;
|
|
80
|
+
const gb = (Cy - Ay) * (xp - Cx) + (yp - Cy) * (Ax - Cx);
|
|
81
|
+
if (gb * s < 0)
|
|
82
|
+
continue;
|
|
83
|
+
const gc = z - (ga + gb);
|
|
84
|
+
if (gc * s < 0)
|
|
85
|
+
continue;
|
|
86
|
+
const idx = x + width * y;
|
|
87
|
+
W[idx] = mix(va, ga / z, vb, gb / z, vc, gc / z, x, y);
|
|
88
|
+
S[idx] = 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix);
|
|
93
|
+
return W;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix) {
|
|
97
|
+
const hX = Float64Array.from(hull, (i) => X[index[i]]);
|
|
98
|
+
const hY = Float64Array.from(hull, (i) => Y[index[i]]);
|
|
99
|
+
const hV = Array.from(hull, (i) => V[index[i]]);
|
|
100
|
+
const n = hX.length;
|
|
101
|
+
const rays = Array.from({ length: n }, (_, j) => ray(j, hX, hY));
|
|
102
|
+
let k = 0;
|
|
103
|
+
for (let y = 0; y < height; ++y) {
|
|
104
|
+
const yp = y + 0.5;
|
|
105
|
+
for (let x = 0; x < width; ++x) {
|
|
106
|
+
const i = x + width * y;
|
|
107
|
+
if (!S[i]) {
|
|
108
|
+
const xp = x + 0.5;
|
|
109
|
+
for (let l = 0; l < n; ++l) {
|
|
110
|
+
const j = (n + k + (l % 2 ? (l + 1) / 2 : -l / 2)) % n;
|
|
111
|
+
if (rays[j](xp, yp)) {
|
|
112
|
+
const t = segmentProject(at(hX, j - 1), at(hY, j - 1), hX[j], hY[j], xp, yp);
|
|
113
|
+
W[i] = mix(at(hV, j - 1), t, hV[j], 1 - t, hV[j], 0, x, y);
|
|
114
|
+
k = j;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Walk-on-spheres algorithm for smooth interpolation.
|
|
124
|
+
* https://observablehq.com/@observablehq/walk-on-spheres-precision
|
|
125
|
+
*/
|
|
126
|
+
export function interpolatorRandomWalk({ random = randomLcg(42), minDistance = 0.5, maxSteps = 2 } = {}) {
|
|
127
|
+
return (index, width, height, X, Y, V) => {
|
|
128
|
+
const n = width * height;
|
|
129
|
+
const W = isTypedArray(V) ? new V.constructor(n) : new Array(n);
|
|
130
|
+
const delaunay = Delaunay.from(index, (i) => X[i], (i) => Y[i]);
|
|
131
|
+
let iy, ix, iw;
|
|
132
|
+
for (let y = 0.5, k = 0; y < height; ++y) {
|
|
133
|
+
ix = iy;
|
|
134
|
+
for (let x = 0.5; x < width; ++x, ++k) {
|
|
135
|
+
let cx = x;
|
|
136
|
+
let cy = y;
|
|
137
|
+
iw = ix = delaunay.find(cx, cy, ix);
|
|
138
|
+
if (x === 0.5)
|
|
139
|
+
iy = ix;
|
|
140
|
+
let distance;
|
|
141
|
+
let step = 0;
|
|
142
|
+
while ((distance = Math.hypot(X[index[iw]] - cx, Y[index[iw]] - cy)) > minDistance &&
|
|
143
|
+
step < maxSteps) {
|
|
144
|
+
const angle = random(x, y, step) * 2 * Math.PI;
|
|
145
|
+
cx += Math.cos(angle) * distance;
|
|
146
|
+
cy += Math.sin(angle) * distance;
|
|
147
|
+
iw = delaunay.find(cx, cy, iw);
|
|
148
|
+
++step;
|
|
149
|
+
}
|
|
150
|
+
W[k] = V[index[iw]];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return W;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// --- Internal helpers ---
|
|
157
|
+
function blend(a, ca, b, cb, c, cc) {
|
|
158
|
+
return ca * a + cb * b + cc * c;
|
|
159
|
+
}
|
|
160
|
+
function pick(random) {
|
|
161
|
+
return (a, ca, b, cb, c, _cc, x, y) => {
|
|
162
|
+
const u = random(x, y);
|
|
163
|
+
return u < ca ? a : u < ca + cb ? b : c;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function mixer(V, random) {
|
|
167
|
+
const first = findFirst(V);
|
|
168
|
+
return typeof first === 'number' || first instanceof Date ? blend : pick(random);
|
|
169
|
+
}
|
|
170
|
+
function findFirst(V) {
|
|
171
|
+
for (let i = 0; i < V.length; ++i)
|
|
172
|
+
if (V[i] != null)
|
|
173
|
+
return V[i];
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
function isTypedArray(V) {
|
|
177
|
+
return ArrayBuffer.isView(V) && !(V instanceof DataView);
|
|
178
|
+
}
|
|
179
|
+
function at(arr, i) {
|
|
180
|
+
return arr[(i + arr.length) % arr.length];
|
|
181
|
+
}
|
|
182
|
+
function segmentProject(x1, y1, x2, y2, x, y) {
|
|
183
|
+
const dx = x2 - x1;
|
|
184
|
+
const dy = y2 - y1;
|
|
185
|
+
const a = dx * (x2 - x) + dy * (y2 - y);
|
|
186
|
+
const b = dx * (x - x1) + dy * (y - y1);
|
|
187
|
+
return a > 0 && b > 0 ? a / (a + b) : +(a > b);
|
|
188
|
+
}
|
|
189
|
+
function cross(xa, ya, xb, yb) {
|
|
190
|
+
return xa * yb - xb * ya;
|
|
191
|
+
}
|
|
192
|
+
function ray(j, X, Y) {
|
|
193
|
+
const n = X.length;
|
|
194
|
+
const xc = at(X, j - 2);
|
|
195
|
+
const yc = at(Y, j - 2);
|
|
196
|
+
const xa = at(X, j - 1);
|
|
197
|
+
const ya = at(Y, j - 1);
|
|
198
|
+
const xb = X[j];
|
|
199
|
+
const yb = Y[j];
|
|
200
|
+
const xd = at(X, j + 1 - n);
|
|
201
|
+
const yd = at(Y, j + 1 - n);
|
|
202
|
+
const dxab = xa - xb;
|
|
203
|
+
const dyab = ya - yb;
|
|
204
|
+
const dxca = xc - xa;
|
|
205
|
+
const dyca = yc - ya;
|
|
206
|
+
const dxbd = xb - xd;
|
|
207
|
+
const dybd = yb - yd;
|
|
208
|
+
const hab = Math.hypot(dxab, dyab);
|
|
209
|
+
const hca = Math.hypot(dxca, dyca);
|
|
210
|
+
const hbd = Math.hypot(dxbd, dybd);
|
|
211
|
+
return (x, y) => {
|
|
212
|
+
const dxa = x - xa;
|
|
213
|
+
const dya = y - ya;
|
|
214
|
+
const dxb = x - xb;
|
|
215
|
+
const dyb = y - yb;
|
|
216
|
+
return (cross(dxa, dya, dxb, dyb) > -1e-6 &&
|
|
217
|
+
cross(dxa, dya, dxab, dyab) * hca - cross(dxa, dya, dxca, dyca) * hab > -1e-6 &&
|
|
218
|
+
cross(dxb, dyb, dxbd, dybd) * hab - cross(dxb, dyb, dxab, dyab) * hbd <= 0);
|
|
219
|
+
};
|
|
220
|
+
}
|