layerchart 2.0.0-next.50 → 2.0.0-next.52
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/components/Arc.svelte +12 -4
- package/dist/components/Arc.svelte.d.ts +4 -0
- package/dist/components/ArcLabel.svelte +259 -0
- package/dist/components/ArcLabel.svelte.d.ts +73 -0
- package/dist/components/ArcLabel.svelte.test.d.ts +1 -0
- package/dist/components/ArcLabel.svelte.test.js +235 -0
- package/dist/components/Axis.svelte +25 -0
- package/dist/components/Axis.svelte.d.ts +10 -0
- package/dist/components/Circle.svelte +82 -59
- package/dist/components/CircleLegend.svelte +389 -0
- package/dist/components/CircleLegend.svelte.d.ts +114 -0
- package/dist/components/Ellipse.svelte +83 -64
- package/dist/components/GeoLegend.svelte +404 -0
- package/dist/components/GeoLegend.svelte.d.ts +106 -0
- package/dist/components/GeoRaster.svelte +311 -0
- package/dist/components/GeoRaster.svelte.d.ts +61 -0
- package/dist/components/Grid.svelte +15 -0
- package/dist/components/Grid.svelte.d.ts +5 -0
- package/dist/components/Image.svelte +2 -2
- package/dist/components/Labels.svelte +46 -11
- package/dist/components/Labels.svelte.d.ts +7 -3
- package/dist/components/Legend.svelte +58 -3
- package/dist/components/Legend.svelte.d.ts +7 -0
- package/dist/components/Line.svelte +82 -62
- package/dist/components/Points.svelte +2 -2
- package/dist/components/Polygon.svelte +92 -56
- package/dist/components/Rect.svelte +113 -64
- package/dist/components/Rule.svelte +2 -0
- package/dist/components/Sankey.svelte +0 -2
- package/dist/components/Text.svelte +83 -52
- package/dist/components/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-defaults-placement-to-centroid--x-y-at-the-centroid--middle-anchors--1.png +0 -0
- package/dist/components/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-defaults-placement-to-centroid--x-y-at-the-centroid--middle-anchors--2.png +0 -0
- package/dist/components/charts/ArcChart.svelte +39 -2
- package/dist/components/charts/ArcChart.svelte.d.ts +12 -1
- package/dist/components/charts/PieChart.svelte +40 -2
- package/dist/components/charts/PieChart.svelte.d.ts +10 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.js +8 -0
- package/dist/components/layers/Canvas.svelte +65 -48
- package/dist/components/layers/Canvas.svelte.d.ts +10 -0
- package/dist/contexts/canvas.d.ts +3 -0
- package/dist/server/ContextCapture.svelte +30 -0
- package/dist/server/ContextCapture.svelte.d.ts +8 -0
- package/dist/server/ServerChart.svelte +26 -0
- package/dist/server/ServerChart.svelte.d.ts +11 -0
- package/dist/server/TestBarChart.svelte +35 -0
- package/dist/server/TestBarChart.svelte.d.ts +14 -0
- package/dist/server/TestLineChart.svelte +35 -0
- package/dist/server/TestLineChart.svelte.d.ts +14 -0
- package/dist/server/captureStore.d.ts +8 -0
- package/dist/server/captureStore.js +18 -0
- package/dist/server/index.d.ts +137 -0
- package/dist/server/index.js +141 -0
- package/dist/server/renderChart.ssr.test.d.ts +1 -0
- package/dist/server/renderChart.ssr.test.js +205 -0
- package/dist/server/renderTree.d.ts +8 -0
- package/dist/server/renderTree.js +29 -0
- package/dist/states/__screenshots__/chart.svelte.test.ts/ChartState-geo-projection-skips-markInfo-should-not-derive-x-y-accessors-from-marks-when-geo-projection-is-active-1.png +0 -0
- package/dist/states/__screenshots__/chart.svelte.test.ts/ChartState-geo-projection-skips-markInfo-should-not-derive-x-y-accessors-from-marks-when-geo-projection-is-active-2.png +0 -0
- package/dist/states/chart.svelte.d.ts +5 -1
- package/dist/states/chart.svelte.js +18 -3
- package/dist/states/chart.svelte.test.js +110 -0
- package/dist/states/geo.svelte.d.ts +5 -1
- package/dist/states/geo.svelte.js +80 -68
- package/dist/utils/arcText.svelte.d.ts +7 -1
- package/dist/utils/arcText.svelte.js +8 -4
- package/dist/utils/canvas.js +29 -10
- package/dist/utils/canvas.svelte.test.js +2 -2
- package/dist/utils/motion.svelte.js +14 -0
- package/package.json +7 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { GeoProjection } from 'd3-geo';
|
|
3
|
+
|
|
4
|
+
export type GeoRasterPropsWithoutHTML = {
|
|
5
|
+
/**
|
|
6
|
+
* The image to reproject. Either:
|
|
7
|
+
*
|
|
8
|
+
* - a URL string (loaded via `Image` with `crossOrigin="anonymous"`),
|
|
9
|
+
* - a preloaded `HTMLImageElement`,
|
|
10
|
+
* - an `HTMLCanvasElement` (e.g. a stitched tile mosaic),
|
|
11
|
+
* - or an `ImageBitmap`.
|
|
12
|
+
*
|
|
13
|
+
* The image pixel data must be readable (not tainted by CORS). When passing a
|
|
14
|
+
* URL, the component sets `crossOrigin` on the underlying `Image` — the host
|
|
15
|
+
* must respond with the appropriate `Access-Control-Allow-Origin` header.
|
|
16
|
+
*/
|
|
17
|
+
image: string | HTMLImageElement | HTMLCanvasElement | ImageBitmap;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Projection used by the source image. Pass as an uncalled function, e.g.
|
|
21
|
+
* `sourceProjection={geoEquirectangular}`.
|
|
22
|
+
*
|
|
23
|
+
* When omitted, the source is assumed to be an equirectangular (plate carrée)
|
|
24
|
+
* image covering the full globe — i.e. `x = ((lon + 180) / 360) * width`,
|
|
25
|
+
* `y = ((90 - lat) / 180) * height`. This is the common layout for imagery
|
|
26
|
+
* such as NASA Blue Marble / Black Marble.
|
|
27
|
+
*/
|
|
28
|
+
sourceProjection?: () => GeoProjection;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Pixel sampling strategy.
|
|
32
|
+
*
|
|
33
|
+
* - `nearest` (default): fastest, slightly blocky at low source resolution.
|
|
34
|
+
* - `bilinear`: smoother, ~4x slower per pixel.
|
|
35
|
+
*
|
|
36
|
+
* @default 'nearest'
|
|
37
|
+
*/
|
|
38
|
+
interpolate?: 'nearest' | 'bilinear';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `crossOrigin` attribute applied to the underlying `Image` element when
|
|
42
|
+
* loading from a URL. Set to `null` to disable.
|
|
43
|
+
*
|
|
44
|
+
* @default 'anonymous'
|
|
45
|
+
*/
|
|
46
|
+
crossOrigin?: string | null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolution multiplier for the reprojected output, relative to chart CSS
|
|
50
|
+
* pixels. A value greater than 1 produces a sharper image on HiDPI
|
|
51
|
+
* displays at the cost of more work per frame.
|
|
52
|
+
*
|
|
53
|
+
* @default 1
|
|
54
|
+
*/
|
|
55
|
+
resolution?: number;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Disable image smoothing when compositing the reprojected buffer onto the
|
|
59
|
+
* destination canvas. Useful for a pixel-art look.
|
|
60
|
+
*
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
pixelated?: boolean;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type GeoRasterProps = GeoRasterPropsWithoutHTML;
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<script lang="ts">
|
|
70
|
+
import { untrack } from 'svelte';
|
|
71
|
+
import { geoPath } from 'd3-geo';
|
|
72
|
+
|
|
73
|
+
import { getChartContext } from '../contexts/chart.js';
|
|
74
|
+
import { getGeoContext } from '../contexts/geo.js';
|
|
75
|
+
import { getLayerContext } from '../contexts/layer.js';
|
|
76
|
+
|
|
77
|
+
let {
|
|
78
|
+
image,
|
|
79
|
+
sourceProjection,
|
|
80
|
+
interpolate = 'nearest',
|
|
81
|
+
crossOrigin = 'anonymous',
|
|
82
|
+
resolution = 1,
|
|
83
|
+
pixelated = false,
|
|
84
|
+
}: GeoRasterProps = $props();
|
|
85
|
+
|
|
86
|
+
const ctx = getChartContext();
|
|
87
|
+
const geo = getGeoContext();
|
|
88
|
+
const layerCtx = getLayerContext();
|
|
89
|
+
|
|
90
|
+
type SourcePixels = {
|
|
91
|
+
data: Uint8ClampedArray;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
let source = $state<SourcePixels | null>(null);
|
|
97
|
+
|
|
98
|
+
$effect(() => {
|
|
99
|
+
if (typeof window === 'undefined') return;
|
|
100
|
+
// Re-run when `image` changes
|
|
101
|
+
const src = image;
|
|
102
|
+
|
|
103
|
+
let cancelled = false;
|
|
104
|
+
|
|
105
|
+
async function load() {
|
|
106
|
+
let drawable: CanvasImageSource;
|
|
107
|
+
let sw: number;
|
|
108
|
+
let sh: number;
|
|
109
|
+
|
|
110
|
+
if (typeof src === 'string') {
|
|
111
|
+
const img = new Image();
|
|
112
|
+
if (crossOrigin !== null) img.crossOrigin = crossOrigin;
|
|
113
|
+
img.src = src;
|
|
114
|
+
try {
|
|
115
|
+
await img.decode();
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.warn('[GeoRaster] Failed to decode image', err);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
drawable = img;
|
|
121
|
+
sw = img.naturalWidth;
|
|
122
|
+
sh = img.naturalHeight;
|
|
123
|
+
} else if (src instanceof HTMLImageElement) {
|
|
124
|
+
try {
|
|
125
|
+
await src.decode();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.warn('[GeoRaster] Failed to decode image', err);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
drawable = src;
|
|
131
|
+
sw = src.naturalWidth;
|
|
132
|
+
sh = src.naturalHeight;
|
|
133
|
+
} else {
|
|
134
|
+
// HTMLCanvasElement or ImageBitmap — already decoded
|
|
135
|
+
drawable = src;
|
|
136
|
+
sw = src.width;
|
|
137
|
+
sh = src.height;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (cancelled) return;
|
|
141
|
+
if (!sw || !sh) return;
|
|
142
|
+
|
|
143
|
+
const offscreen = document.createElement('canvas');
|
|
144
|
+
offscreen.width = sw;
|
|
145
|
+
offscreen.height = sh;
|
|
146
|
+
const octx = offscreen.getContext('2d', { willReadFrequently: true });
|
|
147
|
+
if (!octx) return;
|
|
148
|
+
octx.drawImage(drawable, 0, 0);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const imageData = octx.getImageData(0, 0, sw, sh);
|
|
152
|
+
untrack(() => {
|
|
153
|
+
source = { data: imageData.data, width: sw, height: sh };
|
|
154
|
+
});
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.warn(
|
|
157
|
+
'[GeoRaster] Unable to read image pixels — the image is likely tainted by CORS',
|
|
158
|
+
err
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
load();
|
|
164
|
+
|
|
165
|
+
return () => {
|
|
166
|
+
cancelled = true;
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
function render(dstCtx: CanvasRenderingContext2D) {
|
|
171
|
+
const src = source;
|
|
172
|
+
if (!src) return;
|
|
173
|
+
|
|
174
|
+
const projection = geo.projection;
|
|
175
|
+
if (!projection?.invert) return;
|
|
176
|
+
|
|
177
|
+
const width = Math.max(1, Math.floor(ctx.width * resolution));
|
|
178
|
+
const height = Math.max(1, Math.floor(ctx.height * resolution));
|
|
179
|
+
if (width <= 0 || height <= 0) return;
|
|
180
|
+
|
|
181
|
+
const buffer = document.createElement('canvas');
|
|
182
|
+
buffer.width = width;
|
|
183
|
+
buffer.height = height;
|
|
184
|
+
const bctx = buffer.getContext('2d');
|
|
185
|
+
if (!bctx) return;
|
|
186
|
+
|
|
187
|
+
const dstImage = bctx.createImageData(width, height);
|
|
188
|
+
const dstData = dstImage.data;
|
|
189
|
+
|
|
190
|
+
const srcData = src.data;
|
|
191
|
+
const sw = src.width;
|
|
192
|
+
const sh = src.height;
|
|
193
|
+
|
|
194
|
+
const srcProj = sourceProjection?.();
|
|
195
|
+
const invert = projection.invert.bind(projection);
|
|
196
|
+
const bilinear = interpolate === 'bilinear';
|
|
197
|
+
|
|
198
|
+
for (let y = 0; y < height; y++) {
|
|
199
|
+
for (let x = 0; x < width; x++) {
|
|
200
|
+
// Inverse-project destination pixel (in chart-space coordinates) to lon/lat.
|
|
201
|
+
// When `resolution !== 1`, scale the sample point back to chart coordinates.
|
|
202
|
+
const lonlat = invert([x / resolution, y / resolution]);
|
|
203
|
+
if (!lonlat) continue;
|
|
204
|
+
|
|
205
|
+
const lon = lonlat[0];
|
|
206
|
+
const lat = lonlat[1];
|
|
207
|
+
if (!Number.isFinite(lon) || !Number.isFinite(lat)) continue;
|
|
208
|
+
|
|
209
|
+
let sx: number;
|
|
210
|
+
let sy: number;
|
|
211
|
+
|
|
212
|
+
if (srcProj) {
|
|
213
|
+
const p = srcProj([lon, lat]);
|
|
214
|
+
if (!p) continue;
|
|
215
|
+
sx = p[0];
|
|
216
|
+
sy = p[1];
|
|
217
|
+
} else {
|
|
218
|
+
// Equirectangular source (plate carrée) — the default for most
|
|
219
|
+
// full-globe imagery like NASA Blue Marble.
|
|
220
|
+
sx = ((lon + 180) / 360) * sw;
|
|
221
|
+
sy = ((90 - lat) / 180) * sh;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!(sx >= 0 && sx < sw && sy >= 0 && sy < sh)) continue;
|
|
225
|
+
|
|
226
|
+
const di = (y * width + x) * 4;
|
|
227
|
+
|
|
228
|
+
if (bilinear) {
|
|
229
|
+
const x0 = Math.floor(sx);
|
|
230
|
+
const y0 = Math.floor(sy);
|
|
231
|
+
const x1 = Math.min(x0 + 1, sw - 1);
|
|
232
|
+
const y1 = Math.min(y0 + 1, sh - 1);
|
|
233
|
+
const fx = sx - x0;
|
|
234
|
+
const fy = sy - y0;
|
|
235
|
+
const w00 = (1 - fx) * (1 - fy);
|
|
236
|
+
const w10 = fx * (1 - fy);
|
|
237
|
+
const w01 = (1 - fx) * fy;
|
|
238
|
+
const w11 = fx * fy;
|
|
239
|
+
const i00 = (y0 * sw + x0) * 4;
|
|
240
|
+
const i10 = (y0 * sw + x1) * 4;
|
|
241
|
+
const i01 = (y1 * sw + x0) * 4;
|
|
242
|
+
const i11 = (y1 * sw + x1) * 4;
|
|
243
|
+
dstData[di] =
|
|
244
|
+
srcData[i00] * w00 + srcData[i10] * w10 + srcData[i01] * w01 + srcData[i11] * w11;
|
|
245
|
+
dstData[di + 1] =
|
|
246
|
+
srcData[i00 + 1] * w00 +
|
|
247
|
+
srcData[i10 + 1] * w10 +
|
|
248
|
+
srcData[i01 + 1] * w01 +
|
|
249
|
+
srcData[i11 + 1] * w11;
|
|
250
|
+
dstData[di + 2] =
|
|
251
|
+
srcData[i00 + 2] * w00 +
|
|
252
|
+
srcData[i10 + 2] * w10 +
|
|
253
|
+
srcData[i01 + 2] * w01 +
|
|
254
|
+
srcData[i11 + 2] * w11;
|
|
255
|
+
dstData[di + 3] =
|
|
256
|
+
srcData[i00 + 3] * w00 +
|
|
257
|
+
srcData[i10 + 3] * w10 +
|
|
258
|
+
srcData[i01 + 3] * w01 +
|
|
259
|
+
srcData[i11 + 3] * w11;
|
|
260
|
+
} else {
|
|
261
|
+
const sxi = sx | 0;
|
|
262
|
+
const syi = sy | 0;
|
|
263
|
+
const si = (syi * sw + sxi) * 4;
|
|
264
|
+
dstData[di] = srcData[si];
|
|
265
|
+
dstData[di + 1] = srcData[si + 1];
|
|
266
|
+
dstData[di + 2] = srcData[si + 2];
|
|
267
|
+
dstData[di + 3] = srcData[si + 3];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
bctx.putImageData(dstImage, 0, 0);
|
|
273
|
+
|
|
274
|
+
// Clip to the projection's sphere so pixels outside the visible region
|
|
275
|
+
// (e.g. the far hemisphere of an orthographic globe) aren't drawn. For
|
|
276
|
+
// non-clipped projections (mercator, etc.) the sphere still renders as a
|
|
277
|
+
// finite region covering the full map.
|
|
278
|
+
dstCtx.save();
|
|
279
|
+
const pathGen = geoPath(projection, dstCtx);
|
|
280
|
+
dstCtx.beginPath();
|
|
281
|
+
pathGen({ type: 'Sphere' });
|
|
282
|
+
dstCtx.clip();
|
|
283
|
+
|
|
284
|
+
const prevSmoothing = dstCtx.imageSmoothingEnabled;
|
|
285
|
+
if (pixelated) dstCtx.imageSmoothingEnabled = false;
|
|
286
|
+
dstCtx.drawImage(buffer, 0, 0, ctx.width, ctx.height);
|
|
287
|
+
if (pixelated) dstCtx.imageSmoothingEnabled = prevSmoothing;
|
|
288
|
+
|
|
289
|
+
dstCtx.restore();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (layerCtx === 'canvas') {
|
|
293
|
+
ctx.registerComponent({
|
|
294
|
+
name: 'GeoRaster',
|
|
295
|
+
kind: 'mark',
|
|
296
|
+
canvasRender: {
|
|
297
|
+
render,
|
|
298
|
+
deps: () => [
|
|
299
|
+
source,
|
|
300
|
+
geo.projection,
|
|
301
|
+
ctx.width,
|
|
302
|
+
ctx.height,
|
|
303
|
+
interpolate,
|
|
304
|
+
resolution,
|
|
305
|
+
pixelated,
|
|
306
|
+
sourceProjection,
|
|
307
|
+
],
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { GeoProjection } from 'd3-geo';
|
|
2
|
+
export type GeoRasterPropsWithoutHTML = {
|
|
3
|
+
/**
|
|
4
|
+
* The image to reproject. Either:
|
|
5
|
+
*
|
|
6
|
+
* - a URL string (loaded via `Image` with `crossOrigin="anonymous"`),
|
|
7
|
+
* - a preloaded `HTMLImageElement`,
|
|
8
|
+
* - an `HTMLCanvasElement` (e.g. a stitched tile mosaic),
|
|
9
|
+
* - or an `ImageBitmap`.
|
|
10
|
+
*
|
|
11
|
+
* The image pixel data must be readable (not tainted by CORS). When passing a
|
|
12
|
+
* URL, the component sets `crossOrigin` on the underlying `Image` — the host
|
|
13
|
+
* must respond with the appropriate `Access-Control-Allow-Origin` header.
|
|
14
|
+
*/
|
|
15
|
+
image: string | HTMLImageElement | HTMLCanvasElement | ImageBitmap;
|
|
16
|
+
/**
|
|
17
|
+
* Projection used by the source image. Pass as an uncalled function, e.g.
|
|
18
|
+
* `sourceProjection={geoEquirectangular}`.
|
|
19
|
+
*
|
|
20
|
+
* When omitted, the source is assumed to be an equirectangular (plate carrée)
|
|
21
|
+
* image covering the full globe — i.e. `x = ((lon + 180) / 360) * width`,
|
|
22
|
+
* `y = ((90 - lat) / 180) * height`. This is the common layout for imagery
|
|
23
|
+
* such as NASA Blue Marble / Black Marble.
|
|
24
|
+
*/
|
|
25
|
+
sourceProjection?: () => GeoProjection;
|
|
26
|
+
/**
|
|
27
|
+
* Pixel sampling strategy.
|
|
28
|
+
*
|
|
29
|
+
* - `nearest` (default): fastest, slightly blocky at low source resolution.
|
|
30
|
+
* - `bilinear`: smoother, ~4x slower per pixel.
|
|
31
|
+
*
|
|
32
|
+
* @default 'nearest'
|
|
33
|
+
*/
|
|
34
|
+
interpolate?: 'nearest' | 'bilinear';
|
|
35
|
+
/**
|
|
36
|
+
* `crossOrigin` attribute applied to the underlying `Image` element when
|
|
37
|
+
* loading from a URL. Set to `null` to disable.
|
|
38
|
+
*
|
|
39
|
+
* @default 'anonymous'
|
|
40
|
+
*/
|
|
41
|
+
crossOrigin?: string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Resolution multiplier for the reprojected output, relative to chart CSS
|
|
44
|
+
* pixels. A value greater than 1 produces a sharper image on HiDPI
|
|
45
|
+
* displays at the cost of more work per frame.
|
|
46
|
+
*
|
|
47
|
+
* @default 1
|
|
48
|
+
*/
|
|
49
|
+
resolution?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Disable image smoothing when compositing the reprojected buffer onto the
|
|
52
|
+
* destination canvas. Useful for a pixel-art look.
|
|
53
|
+
*
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
pixelated?: boolean;
|
|
57
|
+
};
|
|
58
|
+
export type GeoRasterProps = GeoRasterPropsWithoutHTML;
|
|
59
|
+
declare const GeoRaster: import("svelte").Component<GeoRasterPropsWithoutHTML, {}, "">;
|
|
60
|
+
type GeoRaster = ReturnType<typeof GeoRaster>;
|
|
61
|
+
export default GeoRaster;
|
|
@@ -44,6 +44,12 @@
|
|
|
44
44
|
*/
|
|
45
45
|
radialY?: 'circle' | 'linear';
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Stroke color for grid lines.
|
|
49
|
+
* Useful for server-side rendering where CSS variables are not available.
|
|
50
|
+
*/
|
|
51
|
+
stroke?: string;
|
|
52
|
+
|
|
47
53
|
/**
|
|
48
54
|
* Classes to apply to the rendered elements.
|
|
49
55
|
*
|
|
@@ -113,6 +119,7 @@
|
|
|
113
119
|
yTicks: yTicksProp,
|
|
114
120
|
bandAlign = 'center',
|
|
115
121
|
radialY = 'circle',
|
|
122
|
+
stroke,
|
|
116
123
|
motion,
|
|
117
124
|
transitionIn: transitionInProp,
|
|
118
125
|
transitionInParams = { easing: cubicIn },
|
|
@@ -168,6 +175,7 @@
|
|
|
168
175
|
{y1}
|
|
169
176
|
{x2}
|
|
170
177
|
{y2}
|
|
178
|
+
{stroke}
|
|
171
179
|
motion={tweenConfig}
|
|
172
180
|
{...splineProps}
|
|
173
181
|
class={cls('lc-grid-x-radial-line', classes.line, splineProps?.class)}
|
|
@@ -176,6 +184,7 @@
|
|
|
176
184
|
<Rule
|
|
177
185
|
{x}
|
|
178
186
|
xOffset={xBandOffset}
|
|
187
|
+
{stroke}
|
|
179
188
|
{motion}
|
|
180
189
|
{...splineProps}
|
|
181
190
|
class={cls('lc-grid-x-rule', classes.line, splineProps?.class)}
|
|
@@ -189,6 +198,7 @@
|
|
|
189
198
|
<Rule
|
|
190
199
|
x={xTickVals[xTickVals.length - 1]}
|
|
191
200
|
xOffset={ctx.xScale.step() + xBandOffset}
|
|
201
|
+
{stroke}
|
|
192
202
|
{motion}
|
|
193
203
|
{...splineProps}
|
|
194
204
|
class={cls('lc-grid-x-end-rule', classes.line, splineProps?.class)}
|
|
@@ -205,6 +215,7 @@
|
|
|
205
215
|
{#if radialY === 'circle'}
|
|
206
216
|
<Circle
|
|
207
217
|
r={ctx.yScale(y) + yBandOffset}
|
|
218
|
+
{stroke}
|
|
208
219
|
{motion}
|
|
209
220
|
{...splineProps}
|
|
210
221
|
class={cls('lc-grid-y-radial-circle', classes.line, splineProps?.class)}
|
|
@@ -214,6 +225,7 @@
|
|
|
214
225
|
data={xTickVals.map((x) => ({ x, y }))}
|
|
215
226
|
x="x"
|
|
216
227
|
y="y"
|
|
228
|
+
{stroke}
|
|
217
229
|
motion={tweenConfig}
|
|
218
230
|
curve={curveLinearClosed}
|
|
219
231
|
{...splineProps}
|
|
@@ -226,6 +238,7 @@
|
|
|
226
238
|
y1={ctx.yScale(y) + yBandOffset}
|
|
227
239
|
x2={ctx.xRange[1]}
|
|
228
240
|
y2={ctx.yScale(y) + yBandOffset}
|
|
241
|
+
{stroke}
|
|
229
242
|
{motion}
|
|
230
243
|
{...splineProps}
|
|
231
244
|
class={cls('lc-grid-y-rule', classes.line, splineProps?.class)}
|
|
@@ -238,6 +251,7 @@
|
|
|
238
251
|
{#if ctx.radial}
|
|
239
252
|
<Circle
|
|
240
253
|
r={ctx.yScale(yTickVals[yTickVals.length - 1])! + ctx.yScale.step() + yBandOffset}
|
|
254
|
+
{stroke}
|
|
241
255
|
{motion}
|
|
242
256
|
{...splineProps}
|
|
243
257
|
class={cls('lc-grid-y-radial-circle', classes.line, splineProps?.class)}
|
|
@@ -250,6 +264,7 @@
|
|
|
250
264
|
y1={y}
|
|
251
265
|
x2={ctx.xRange[1]}
|
|
252
266
|
y2={y}
|
|
267
|
+
{stroke}
|
|
253
268
|
{motion}
|
|
254
269
|
{...splineProps}
|
|
255
270
|
class={cls('lc-grid-y-end-rule', classes.line, splineProps?.class)}
|
|
@@ -36,6 +36,11 @@ export type GridPropsWithoutHTML<In extends Transition = Transition> = {
|
|
|
36
36
|
* @default 'circle'
|
|
37
37
|
*/
|
|
38
38
|
radialY?: 'circle' | 'linear';
|
|
39
|
+
/**
|
|
40
|
+
* Stroke color for grid lines.
|
|
41
|
+
* Useful for server-side rendering where CSS variables are not available.
|
|
42
|
+
*/
|
|
43
|
+
stroke?: string;
|
|
39
44
|
/**
|
|
40
45
|
* Classes to apply to the rendered elements.
|
|
41
46
|
*
|
|
@@ -514,7 +514,7 @@
|
|
|
514
514
|
style:object-fit="cover"
|
|
515
515
|
crossorigin={crossOrigin}
|
|
516
516
|
class={cls('lc-image', className)}
|
|
517
|
-
{...restProps}
|
|
517
|
+
{...restProps as any}
|
|
518
518
|
/>
|
|
519
519
|
{/each}
|
|
520
520
|
{:else}
|
|
@@ -532,7 +532,7 @@
|
|
|
532
532
|
style:object-fit="cover"
|
|
533
533
|
crossorigin={crossOrigin}
|
|
534
534
|
class={cls('lc-image', className)}
|
|
535
|
-
{...restProps}
|
|
535
|
+
{...restProps as any}
|
|
536
536
|
/>
|
|
537
537
|
{/if}
|
|
538
538
|
{/if}
|
|
@@ -42,15 +42,19 @@
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* The placement of the label relative to the point.
|
|
45
|
-
* `
|
|
45
|
+
* - `outside`: outside the bar/point.
|
|
46
|
+
* - `inside`: inside the bar/point near the value edge.
|
|
47
|
+
* - `middle`: aligned to the value edge with a middle anchor.
|
|
48
|
+
* - `center`: centered within the bar body (between the value edge and baseline).
|
|
49
|
+
* - `smart`: dynamically positions labels based on neighboring point values (peak, trough, rising, falling).
|
|
46
50
|
* @default 'outside'
|
|
47
51
|
*/
|
|
48
|
-
placement?: 'inside' | 'outside' | 'center' | 'smart';
|
|
52
|
+
placement?: 'inside' | 'outside' | 'middle' | 'center' | 'smart';
|
|
49
53
|
|
|
50
54
|
/**
|
|
51
55
|
* The offset of the label from the point
|
|
52
56
|
*
|
|
53
|
-
* @default placement === 'center' ? 0 : 4
|
|
57
|
+
* @default placement === 'center' || placement === 'middle' ? 0 : 4
|
|
54
58
|
*/
|
|
55
59
|
offset?: number;
|
|
56
60
|
|
|
@@ -81,6 +85,7 @@
|
|
|
81
85
|
import { getChartContext } from '../contexts/chart.js';
|
|
82
86
|
import Group from './Group.svelte';
|
|
83
87
|
import { extractLayerProps } from '../utils/attributes.js';
|
|
88
|
+
import { createDimensionGetter } from '../utils/rect.svelte.js';
|
|
84
89
|
|
|
85
90
|
const ctx = getChartContext();
|
|
86
91
|
|
|
@@ -94,7 +99,7 @@
|
|
|
94
99
|
y,
|
|
95
100
|
seriesKey,
|
|
96
101
|
placement = 'outside',
|
|
97
|
-
offset = placement === 'center' ? 0 : 4,
|
|
102
|
+
offset = placement === 'center' || placement === 'middle' ? 0 : 4,
|
|
98
103
|
format,
|
|
99
104
|
key = (_: any, i: number) => i,
|
|
100
105
|
children: childrenProp,
|
|
@@ -104,6 +109,9 @@
|
|
|
104
109
|
...restProps
|
|
105
110
|
}: LabelsProps<TData> = $props();
|
|
106
111
|
|
|
112
|
+
// Used to compute the bar's bounding rect for `center` placement
|
|
113
|
+
const getDimensions = $derived(createDimensionGetter(ctx, () => ({ x, y })));
|
|
114
|
+
|
|
107
115
|
// TODO: Should we let `Points` handle opacity for children snippet as well?
|
|
108
116
|
let series = $derived(ctx.series.series.find((s) => s.key === seriesKey));
|
|
109
117
|
let derivedOpacity = $derived(
|
|
@@ -146,14 +154,27 @@
|
|
|
146
154
|
|
|
147
155
|
if (isScaleBand(ctx.yScale)) {
|
|
148
156
|
// Position label left/right on horizontal bars
|
|
149
|
-
if (
|
|
157
|
+
if (placement === 'center') {
|
|
158
|
+
// Center within the bar body
|
|
159
|
+
const dims = getDimensions(point.data) ?? { x: point.x, y: point.y, width: 0, height: 0 };
|
|
160
|
+
result = {
|
|
161
|
+
value: formattedValue,
|
|
162
|
+
fill: fillValue,
|
|
163
|
+
x: dims.x + dims.width / 2,
|
|
164
|
+
y: dims.y + dims.height / 2,
|
|
165
|
+
textAnchor: 'middle',
|
|
166
|
+
verticalAnchor: 'middle',
|
|
167
|
+
capHeight: '.6rem',
|
|
168
|
+
};
|
|
169
|
+
} else if (isLowEdge) {
|
|
150
170
|
// left
|
|
151
171
|
result = {
|
|
152
172
|
value: formattedValue,
|
|
153
173
|
fill: fillValue,
|
|
154
174
|
x: point.x + (placement === 'outside' ? -offset : offset),
|
|
155
175
|
y: point.y,
|
|
156
|
-
textAnchor:
|
|
176
|
+
textAnchor:
|
|
177
|
+
placement === 'middle' ? 'middle' : placement === 'outside' ? 'end' : 'start',
|
|
157
178
|
verticalAnchor: 'middle',
|
|
158
179
|
capHeight: '.6rem',
|
|
159
180
|
};
|
|
@@ -164,14 +185,27 @@
|
|
|
164
185
|
fill: fillValue,
|
|
165
186
|
x: point.x + (placement === 'outside' ? offset : -offset),
|
|
166
187
|
y: point.y,
|
|
167
|
-
textAnchor:
|
|
188
|
+
textAnchor:
|
|
189
|
+
placement === 'middle' ? 'middle' : placement === 'outside' ? 'start' : 'end',
|
|
168
190
|
verticalAnchor: 'middle',
|
|
169
191
|
capHeight: '.6rem',
|
|
170
192
|
};
|
|
171
193
|
}
|
|
172
194
|
} else {
|
|
173
195
|
// Position label top/bottom on vertical bars
|
|
174
|
-
if (
|
|
196
|
+
if (placement === 'center') {
|
|
197
|
+
// Center within the bar body
|
|
198
|
+
const dims = getDimensions(point.data) ?? { x: point.x, y: point.y, width: 0, height: 0 };
|
|
199
|
+
result = {
|
|
200
|
+
value: formattedValue,
|
|
201
|
+
fill: fillValue,
|
|
202
|
+
x: dims.x + dims.width / 2,
|
|
203
|
+
y: dims.y + dims.height / 2,
|
|
204
|
+
capHeight: '.6rem',
|
|
205
|
+
textAnchor: 'middle',
|
|
206
|
+
verticalAnchor: 'middle',
|
|
207
|
+
};
|
|
208
|
+
} else if (isLowEdge) {
|
|
175
209
|
// bottom
|
|
176
210
|
result = {
|
|
177
211
|
value: formattedValue,
|
|
@@ -181,7 +215,7 @@
|
|
|
181
215
|
capHeight: '.6rem',
|
|
182
216
|
textAnchor: 'middle',
|
|
183
217
|
verticalAnchor:
|
|
184
|
-
placement === '
|
|
218
|
+
placement === 'middle' ? 'middle' : placement === 'outside' ? 'start' : 'end',
|
|
185
219
|
};
|
|
186
220
|
} else {
|
|
187
221
|
// top
|
|
@@ -193,7 +227,7 @@
|
|
|
193
227
|
capHeight: '.6rem',
|
|
194
228
|
textAnchor: 'middle',
|
|
195
229
|
verticalAnchor:
|
|
196
|
-
placement === '
|
|
230
|
+
placement === 'middle' ? 'middle' : placement === 'outside' ? 'end' : 'start',
|
|
197
231
|
};
|
|
198
232
|
}
|
|
199
233
|
}
|
|
@@ -271,7 +305,8 @@
|
|
|
271
305
|
--fill-color: var(--color-surface-content, currentColor);
|
|
272
306
|
--stroke-color: var(--color-surface-100, light-dark(white, black));
|
|
273
307
|
|
|
274
|
-
&[data-placement='inside']
|
|
308
|
+
&[data-placement='inside'],
|
|
309
|
+
&[data-placement='center'] {
|
|
275
310
|
--fill-color: var(--color-surface-100, light-dark(white, black));
|
|
276
311
|
--stroke-color: var(--color-surface-content, currentColor);
|
|
277
312
|
}
|
|
@@ -34,14 +34,18 @@ export type LabelsPropsWithoutHTML<T = any> = {
|
|
|
34
34
|
seriesKey?: string;
|
|
35
35
|
/**
|
|
36
36
|
* The placement of the label relative to the point.
|
|
37
|
-
* `
|
|
37
|
+
* - `outside`: outside the bar/point.
|
|
38
|
+
* - `inside`: inside the bar/point near the value edge.
|
|
39
|
+
* - `middle`: aligned to the value edge with a middle anchor.
|
|
40
|
+
* - `center`: centered within the bar body (between the value edge and baseline).
|
|
41
|
+
* - `smart`: dynamically positions labels based on neighboring point values (peak, trough, rising, falling).
|
|
38
42
|
* @default 'outside'
|
|
39
43
|
*/
|
|
40
|
-
placement?: 'inside' | 'outside' | 'center' | 'smart';
|
|
44
|
+
placement?: 'inside' | 'outside' | 'middle' | 'center' | 'smart';
|
|
41
45
|
/**
|
|
42
46
|
* The offset of the label from the point
|
|
43
47
|
*
|
|
44
|
-
* @default placement === 'center' ? 0 : 4
|
|
48
|
+
* @default placement === 'center' || placement === 'middle' ? 0 : 4
|
|
45
49
|
*/
|
|
46
50
|
offset?: number;
|
|
47
51
|
/**
|