layerchart 2.0.0-next.50 → 2.0.0-next.51

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.
Files changed (49) hide show
  1. package/dist/components/Axis.svelte +25 -0
  2. package/dist/components/Axis.svelte.d.ts +10 -0
  3. package/dist/components/Circle.svelte +82 -59
  4. package/dist/components/Ellipse.svelte +83 -64
  5. package/dist/components/GeoRaster.svelte +311 -0
  6. package/dist/components/GeoRaster.svelte.d.ts +61 -0
  7. package/dist/components/Grid.svelte +15 -0
  8. package/dist/components/Grid.svelte.d.ts +5 -0
  9. package/dist/components/Image.svelte +2 -2
  10. package/dist/components/Line.svelte +82 -62
  11. package/dist/components/Points.svelte +2 -2
  12. package/dist/components/Polygon.svelte +92 -56
  13. package/dist/components/Rect.svelte +113 -64
  14. package/dist/components/Rule.svelte +2 -0
  15. package/dist/components/Sankey.svelte +0 -2
  16. package/dist/components/Text.svelte +83 -52
  17. package/dist/components/charts/PieChart.svelte +2 -2
  18. package/dist/components/index.d.ts +2 -0
  19. package/dist/components/index.js +2 -0
  20. package/dist/components/layers/Canvas.svelte +65 -48
  21. package/dist/components/layers/Canvas.svelte.d.ts +10 -0
  22. package/dist/contexts/canvas.d.ts +3 -0
  23. package/dist/server/ContextCapture.svelte +30 -0
  24. package/dist/server/ContextCapture.svelte.d.ts +8 -0
  25. package/dist/server/ServerChart.svelte +26 -0
  26. package/dist/server/ServerChart.svelte.d.ts +11 -0
  27. package/dist/server/TestBarChart.svelte +35 -0
  28. package/dist/server/TestBarChart.svelte.d.ts +14 -0
  29. package/dist/server/TestLineChart.svelte +35 -0
  30. package/dist/server/TestLineChart.svelte.d.ts +14 -0
  31. package/dist/server/captureStore.d.ts +8 -0
  32. package/dist/server/captureStore.js +18 -0
  33. package/dist/server/index.d.ts +137 -0
  34. package/dist/server/index.js +141 -0
  35. package/dist/server/renderChart.ssr.test.d.ts +1 -0
  36. package/dist/server/renderChart.ssr.test.js +205 -0
  37. package/dist/server/renderTree.d.ts +8 -0
  38. package/dist/server/renderTree.js +29 -0
  39. 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
  40. 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
  41. package/dist/states/chart.svelte.d.ts +5 -1
  42. package/dist/states/chart.svelte.js +18 -3
  43. package/dist/states/chart.svelte.test.js +110 -0
  44. package/dist/states/geo.svelte.d.ts +5 -1
  45. package/dist/states/geo.svelte.js +80 -68
  46. package/dist/utils/canvas.js +29 -10
  47. package/dist/utils/canvas.svelte.test.js +2 -2
  48. package/dist/utils/motion.svelte.js +14 -0
  49. 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}
@@ -125,7 +125,13 @@
125
125
  import { getLayerContext } from '../contexts/layer.js';
126
126
  import { getChartContext } from '../contexts/chart.js';
127
127
  import { createDataMotionMap } from '../utils/motion.svelte.js';
128
- import { hasAnyDataProp, resolveDataProp, resolveColorProp, resolveGeoDataPair, resolveStyleProp } from '../utils/dataProp.js';
128
+ import {
129
+ hasAnyDataProp,
130
+ resolveDataProp,
131
+ resolveColorProp,
132
+ resolveGeoDataPair,
133
+ resolveStyleProp,
134
+ } from '../utils/dataProp.js';
129
135
  import { getGeoContext } from '../contexts/geo.js';
130
136
  import { chartDataArray } from '../utils/common.js';
131
137
 
@@ -167,9 +173,7 @@
167
173
  const geo = getGeoContext();
168
174
 
169
175
  // Data to iterate over in data mode
170
- const resolvedData: any[] = $derived(
171
- dataMode ? (dataProp ?? chartDataArray(chartCtx.data)) : []
172
- );
176
+ const resolvedData: any[] = $derived(dataMode ? (dataProp ?? chartDataArray(chartCtx.data)) : []);
173
177
 
174
178
  // Resolve a single data item to pixel coordinates
175
179
  function resolveLine(d: any) {
@@ -233,29 +237,21 @@
233
237
  const _initialX2 = initialX2 ?? (typeof x2 === 'number' ? x2 : 0);
234
238
  const _initialY2 = initialY2 ?? (typeof y2 === 'number' ? y2 : 0);
235
239
 
236
- const motionX1 = createMotion(
237
- _initialX1,
238
- () => (typeof x1 === 'number' ? x1 : 0),
239
- motion
240
- );
241
- const motionY1 = createMotion(
242
- _initialY1,
243
- () => (typeof y1 === 'number' ? y1 : 0),
244
- motion
245
- );
246
- const motionX2 = createMotion(
247
- _initialX2,
248
- () => (typeof x2 === 'number' ? x2 : 0),
249
- motion
250
- );
251
- const motionY2 = createMotion(
252
- _initialY2,
253
- () => (typeof y2 === 'number' ? y2 : 0),
254
- motion
255
- );
240
+ const motionX1 = createMotion(_initialX1, () => (typeof x1 === 'number' ? x1 : 0), motion);
241
+ const motionY1 = createMotion(_initialY1, () => (typeof y1 === 'number' ? y1 : 0), motion);
242
+ const motionX2 = createMotion(_initialX2, () => (typeof x2 === 'number' ? x2 : 0), motion);
243
+ const motionY2 = createMotion(_initialY2, () => (typeof y2 === 'number' ? y2 : 0), motion);
256
244
 
257
245
  const layerCtx = getLayerContext();
258
246
 
247
+ const staticFill = $derived(typeof fill === 'string' ? fill : undefined);
248
+ const staticStroke = $derived(typeof stroke === 'string' ? stroke : undefined);
249
+ const staticFillOpacity = $derived(typeof fillOpacity === 'number' ? fillOpacity : undefined);
250
+ const staticStrokeWidth = $derived(typeof strokeWidth === 'number' ? strokeWidth : undefined);
251
+ const staticOpacity = $derived(typeof opacity === 'number' ? opacity : undefined);
252
+ const staticClassName = $derived(typeof className === 'string' ? className : undefined);
253
+ const staticHeight = $derived(typeof strokeWidth === 'number' ? `${strokeWidth}px` : '1px');
254
+
259
255
  function getStyleOptions(
260
256
  styleOverrides: ComputedStylesOptions | undefined,
261
257
  itemFill?: string | undefined,
@@ -266,16 +262,29 @@
266
262
  itemClass?: string | undefined
267
263
  ) {
268
264
  return styleOverrides
269
- ? merge({ styles: { strokeWidth: itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined) } }, styleOverrides)
265
+ ? merge(
266
+ {
267
+ styles: {
268
+ strokeWidth:
269
+ itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined),
270
+ },
271
+ },
272
+ styleOverrides
273
+ )
270
274
  : {
271
275
  styles: {
272
276
  fill: itemFill ?? fill,
273
- fillOpacity: itemFillOpacity ?? (typeof fillOpacity === 'number' ? fillOpacity : undefined),
277
+ fillOpacity:
278
+ itemFillOpacity ?? (typeof fillOpacity === 'number' ? fillOpacity : undefined),
274
279
  stroke: itemStroke ?? stroke,
275
- strokeWidth: itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined),
280
+ strokeWidth:
281
+ itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined),
276
282
  opacity: itemOpacity ?? (typeof opacity === 'number' ? opacity : undefined),
277
283
  },
278
- classes: cls('lc-line', itemClass ?? (typeof className === 'string' ? className : undefined)),
284
+ classes: cls(
285
+ 'lc-line',
286
+ itemClass ?? (typeof className === 'string' ? className : undefined)
287
+ ),
279
288
  style: restProps.style as string | undefined,
280
289
  };
281
290
  }
@@ -292,7 +301,15 @@
292
301
  const resolvedStrokeWidth = resolveStyleProp(strokeWidth, item.d);
293
302
  const resolvedOpacity = resolveStyleProp(opacity, item.d);
294
303
  const resolvedClass = resolveStyleProp(className, item.d);
295
- const styleOpts = getStyleOptions(styleOverrides, resolvedFill, resolvedStroke, resolvedFillOpacity, resolvedStrokeWidth, resolvedOpacity, resolvedClass);
304
+ const styleOpts = getStyleOptions(
305
+ styleOverrides,
306
+ resolvedFill,
307
+ resolvedStroke,
308
+ resolvedFillOpacity,
309
+ resolvedStrokeWidth,
310
+ resolvedOpacity,
311
+ resolvedClass
312
+ );
296
313
  const pathData = `M ${item.x1},${item.y1} L ${item.x2},${item.y2}`;
297
314
  renderPathData(ctx, pathData, styleOpts);
298
315
  }
@@ -318,29 +335,32 @@
318
335
  color: typeof stroke === 'string' ? stroke : typeof fill === 'string' ? fill : undefined,
319
336
  };
320
337
  },
321
- canvasRender: layerCtx === 'canvas' ? {
322
- render,
323
- events: {
324
- click: restProps.onclick,
325
- pointerenter: restProps.onpointerenter,
326
- pointermove: restProps.onpointermove,
327
- pointerleave: restProps.onpointerleave,
328
- },
329
- deps: () => [
330
- dataMode,
331
- dataMode ? resolvedItems : null,
332
- motionX1.current,
333
- motionY1.current,
334
- motionX2.current,
335
- motionY2.current,
336
- fillKey!.current,
337
- strokeKey!.current,
338
- strokeWidth,
339
- opacity,
340
- className,
341
- restProps.style,
342
- ],
343
- } : undefined,
338
+ canvasRender:
339
+ layerCtx === 'canvas'
340
+ ? {
341
+ render,
342
+ events: {
343
+ click: restProps.onclick,
344
+ pointerenter: restProps.onpointerenter,
345
+ pointermove: restProps.onpointermove,
346
+ pointerleave: restProps.onpointerleave,
347
+ },
348
+ deps: () => [
349
+ dataMode,
350
+ dataMode ? resolvedItems : null,
351
+ motionX1.current,
352
+ motionY1.current,
353
+ motionX2.current,
354
+ motionY2.current,
355
+ fillKey!.current,
356
+ strokeKey!.current,
357
+ strokeWidth,
358
+ opacity,
359
+ className,
360
+ restProps.style,
361
+ ],
362
+ }
363
+ : undefined,
344
364
  });
345
365
  </script>
346
366
 
@@ -380,15 +400,15 @@
380
400
  y1={motionY1.current}
381
401
  x2={motionX2.current}
382
402
  y2={motionY2.current}
383
- fill={fill as string}
384
- stroke={stroke as string}
385
- fill-opacity={fillOpacity as number}
386
- stroke-width={strokeWidth as number}
387
- opacity={opacity as number}
403
+ fill={staticFill}
404
+ stroke={staticStroke}
405
+ fill-opacity={staticFillOpacity}
406
+ stroke-width={staticStrokeWidth}
407
+ opacity={staticOpacity}
388
408
  marker-start={markerStartId ? `url(#${markerStartId})` : undefined}
389
409
  marker-mid={markerMidId ? `url(#${markerMidId})` : undefined}
390
410
  marker-end={markerEndId ? `url(#${markerEndId})` : undefined}
391
- class={cls('lc-line', className as string)}
411
+ class={cls('lc-line', staticClassName)}
392
412
  {...restProps}
393
413
  />
394
414
  <MarkerWrapper id={markerStartId} marker={markerStart ?? marker} />
@@ -431,12 +451,12 @@
431
451
  style:left="{motionX1.current}px"
432
452
  style:top="{motionY1.current}px"
433
453
  style:width="{length}px"
434
- style:height="{(strokeWidth as number) ?? 1}px"
454
+ style:height={staticHeight}
435
455
  style:transform="translateY(-50%) rotate({angle}deg)"
436
456
  style:transform-origin="0 50%"
437
- style:opacity={opacity as number}
438
- style:background-color={stroke as string}
439
- class={cls('lc-line', className as string)}
457
+ style:opacity={staticOpacity}
458
+ style:background-color={staticStroke}
459
+ class={cls('lc-line', staticClassName)}
440
460
  style={restProps.style}
441
461
  ></div>
442
462
  {/if}