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.
Files changed (70) hide show
  1. package/dist/components/Arc.svelte +12 -4
  2. package/dist/components/Arc.svelte.d.ts +4 -0
  3. package/dist/components/ArcLabel.svelte +259 -0
  4. package/dist/components/ArcLabel.svelte.d.ts +73 -0
  5. package/dist/components/ArcLabel.svelte.test.d.ts +1 -0
  6. package/dist/components/ArcLabel.svelte.test.js +235 -0
  7. package/dist/components/Axis.svelte +25 -0
  8. package/dist/components/Axis.svelte.d.ts +10 -0
  9. package/dist/components/Circle.svelte +82 -59
  10. package/dist/components/CircleLegend.svelte +389 -0
  11. package/dist/components/CircleLegend.svelte.d.ts +114 -0
  12. package/dist/components/Ellipse.svelte +83 -64
  13. package/dist/components/GeoLegend.svelte +404 -0
  14. package/dist/components/GeoLegend.svelte.d.ts +106 -0
  15. package/dist/components/GeoRaster.svelte +311 -0
  16. package/dist/components/GeoRaster.svelte.d.ts +61 -0
  17. package/dist/components/Grid.svelte +15 -0
  18. package/dist/components/Grid.svelte.d.ts +5 -0
  19. package/dist/components/Image.svelte +2 -2
  20. package/dist/components/Labels.svelte +46 -11
  21. package/dist/components/Labels.svelte.d.ts +7 -3
  22. package/dist/components/Legend.svelte +58 -3
  23. package/dist/components/Legend.svelte.d.ts +7 -0
  24. package/dist/components/Line.svelte +82 -62
  25. package/dist/components/Points.svelte +2 -2
  26. package/dist/components/Polygon.svelte +92 -56
  27. package/dist/components/Rect.svelte +113 -64
  28. package/dist/components/Rule.svelte +2 -0
  29. package/dist/components/Sankey.svelte +0 -2
  30. package/dist/components/Text.svelte +83 -52
  31. package/dist/components/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-defaults-placement-to-centroid--x-y-at-the-centroid--middle-anchors--1.png +0 -0
  32. package/dist/components/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-defaults-placement-to-centroid--x-y-at-the-centroid--middle-anchors--2.png +0 -0
  33. package/dist/components/charts/ArcChart.svelte +39 -2
  34. package/dist/components/charts/ArcChart.svelte.d.ts +12 -1
  35. package/dist/components/charts/PieChart.svelte +40 -2
  36. package/dist/components/charts/PieChart.svelte.d.ts +10 -0
  37. package/dist/components/index.d.ts +8 -0
  38. package/dist/components/index.js +8 -0
  39. package/dist/components/layers/Canvas.svelte +65 -48
  40. package/dist/components/layers/Canvas.svelte.d.ts +10 -0
  41. package/dist/contexts/canvas.d.ts +3 -0
  42. package/dist/server/ContextCapture.svelte +30 -0
  43. package/dist/server/ContextCapture.svelte.d.ts +8 -0
  44. package/dist/server/ServerChart.svelte +26 -0
  45. package/dist/server/ServerChart.svelte.d.ts +11 -0
  46. package/dist/server/TestBarChart.svelte +35 -0
  47. package/dist/server/TestBarChart.svelte.d.ts +14 -0
  48. package/dist/server/TestLineChart.svelte +35 -0
  49. package/dist/server/TestLineChart.svelte.d.ts +14 -0
  50. package/dist/server/captureStore.d.ts +8 -0
  51. package/dist/server/captureStore.js +18 -0
  52. package/dist/server/index.d.ts +137 -0
  53. package/dist/server/index.js +141 -0
  54. package/dist/server/renderChart.ssr.test.d.ts +1 -0
  55. package/dist/server/renderChart.ssr.test.js +205 -0
  56. package/dist/server/renderTree.d.ts +8 -0
  57. package/dist/server/renderTree.js +29 -0
  58. 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
  59. 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
  60. package/dist/states/chart.svelte.d.ts +5 -1
  61. package/dist/states/chart.svelte.js +18 -3
  62. package/dist/states/chart.svelte.test.js +110 -0
  63. package/dist/states/geo.svelte.d.ts +5 -1
  64. package/dist/states/geo.svelte.js +80 -68
  65. package/dist/utils/arcText.svelte.d.ts +7 -1
  66. package/dist/utils/arcText.svelte.js +8 -4
  67. package/dist/utils/canvas.js +29 -10
  68. package/dist/utils/canvas.svelte.test.js +2 -2
  69. package/dist/utils/motion.svelte.js +14 -0
  70. package/package.json +7 -1
@@ -0,0 +1,404 @@
1
+ <script lang="ts" module>
2
+ import type { Placement } from './types.js';
3
+ import { asAny, type Without } from '../utils/types.js';
4
+
5
+ export type GeoLegendUnits = 'km' | 'mi';
6
+
7
+ export type GeoLegendVariant = 'bracket' | 'alternating';
8
+
9
+ export type GeoLegendPropsWithoutHTML = {
10
+ /**
11
+ * Units to display.
12
+ *
13
+ * @default 'mi'
14
+ */
15
+ units?: GeoLegendUnits;
16
+
17
+ /**
18
+ * Visual style of the bar.
19
+ * - `'bracket'`: top rule with downward brackets at each tick (default)
20
+ * - `'alternating'`: alternating filled/unfilled segments between ticks
21
+ *
22
+ * @default 'bracket'
23
+ */
24
+ variant?: GeoLegendVariant;
25
+
26
+ /**
27
+ * Explicit distance to represent (in `units`). When omitted, a "nice" round
28
+ * value is chosen so the bar covers roughly 25% of the chart width.
29
+ */
30
+ distance?: number;
31
+
32
+ /**
33
+ * Number of tick subdivisions of the bar.
34
+ *
35
+ * @default 4
36
+ */
37
+ ticks?: number;
38
+
39
+ /**
40
+ * Where to place the tick labels relative to the bar. Useful for stacking
41
+ * two legends (e.g. kilometers + miles) tightly.
42
+ *
43
+ * @default 'bottom'
44
+ */
45
+ labelPlacement?: 'top' | 'bottom';
46
+
47
+ /**
48
+ * Format for the tick labels. Receives the distance in `units`.
49
+ */
50
+ tickFormat?: FormatType | FormatConfig | ((value: number) => string);
51
+
52
+ /**
53
+ * The font size of the tick labels.
54
+ *
55
+ * @default 10
56
+ */
57
+ tickFontSize?: number;
58
+
59
+ /**
60
+ * The font size of the title.
61
+ *
62
+ * @default 10
63
+ */
64
+ titleFontSize?: number;
65
+
66
+ /**
67
+ * The thickness of the bar.
68
+ *
69
+ * @default 4
70
+ */
71
+ height?: number;
72
+
73
+ /**
74
+ * The title of the legend.
75
+ *
76
+ * @default ''
77
+ */
78
+ title?: string;
79
+
80
+ /**
81
+ * Reference point in chart pixel coordinates used to compute the
82
+ * pixels-per-distance ratio. Defaults to the center of the chart's plot
83
+ * area, which is generally a reasonable approximation away from the poles.
84
+ */
85
+ referencePoint?: [number, number];
86
+
87
+ /**
88
+ * The placement of the legend.
89
+ */
90
+ placement?: Placement;
91
+
92
+ /**
93
+ * The fill/stroke color of the bar.
94
+ *
95
+ * @default 'currentColor'
96
+ */
97
+ color?: string;
98
+
99
+ /**
100
+ * Classes to apply to the elements.
101
+ *
102
+ * @default {}
103
+ */
104
+ classes?: {
105
+ root?: string;
106
+ title?: string;
107
+ bar?: string;
108
+ tick?: string;
109
+ label?: string;
110
+ };
111
+
112
+ /**
113
+ * A bindable reference to the wrapping `<div>` element.
114
+ *
115
+ * @bindable
116
+ */
117
+ ref?: HTMLElement;
118
+ };
119
+
120
+ export type GeoLegendProps = GeoLegendPropsWithoutHTML &
121
+ Without<HTMLAttributes<HTMLElement>, GeoLegendPropsWithoutHTML>;
122
+ </script>
123
+
124
+ <script lang="ts">
125
+ import type { HTMLAttributes } from 'svelte/elements';
126
+ import { geoDistance } from 'd3-geo';
127
+ import { format, type FormatType, type FormatConfig } from '@layerstack/utils';
128
+
129
+ import { cls } from '@layerstack/tailwind';
130
+ import { getChartContext } from '../contexts/chart.js';
131
+
132
+ let {
133
+ units = 'mi',
134
+ variant = 'bracket',
135
+ distance: distanceProp,
136
+ ticks = 4,
137
+ labelPlacement = 'bottom',
138
+ tickFormat: tickFormatProp,
139
+ tickFontSize = 10,
140
+ titleFontSize = 10,
141
+ height = 4,
142
+ title = '',
143
+ referencePoint,
144
+ placement,
145
+ color = 'currentColor',
146
+ classes = {},
147
+ ref: refProp = $bindable(),
148
+ class: className,
149
+ ...restProps
150
+ }: GeoLegendProps = $props();
151
+
152
+ let ref = $state<HTMLElement>();
153
+ $effect.pre(() => {
154
+ refProp = ref;
155
+ });
156
+
157
+ const ctx = getChartContext();
158
+
159
+ // Earth radius in the selected units
160
+ const earthRadius = $derived(units === 'mi' ? 3958.8 : 6371);
161
+
162
+ // Pixels per unit at the reference point on the current projection.
163
+ // `null` if no projection or invert is unavailable (or numerically degenerate).
164
+ const pixelsPerUnit = $derived.by(() => {
165
+ const projection = ctx.geo?.projection;
166
+ if (!projection || typeof projection.invert !== 'function') return null;
167
+
168
+ const refPx: [number, number] = referencePoint ?? [ctx.width / 2, ctx.height / 2];
169
+ const a = projection.invert(refPx);
170
+ const b = projection.invert([refPx[0] + 1, refPx[1]]);
171
+ if (!a || !b) return null;
172
+ if (!Number.isFinite(a[0]) || !Number.isFinite(b[0])) return null;
173
+
174
+ const radiansPerPx = geoDistance(a, b);
175
+ if (!Number.isFinite(radiansPerPx) || radiansPerPx === 0) return null;
176
+
177
+ const unitsPerPx = radiansPerPx * earthRadius;
178
+ let pxPerUnit = 1 / unitsPerPx;
179
+
180
+ // In `canvas` transform mode the projection itself is not re-scaled — the
181
+ // rendered output is visually scaled by `ctx.transform.scale`, so we need
182
+ // to multiply to keep the bar consistent with what the user sees.
183
+ if (ctx.transform?.mode === 'canvas') {
184
+ pxPerUnit *= ctx.transform.scale ?? 1;
185
+ }
186
+
187
+ return pxPerUnit;
188
+ });
189
+
190
+ function niceDistance(d: number) {
191
+ if (!(d > 0)) return 0;
192
+ const exp = Math.floor(Math.log10(d));
193
+ const base = Math.pow(10, exp);
194
+ const mantissa = d / base;
195
+ let nice;
196
+ if (mantissa < 1.5) nice = 1;
197
+ else if (mantissa < 3) nice = 2;
198
+ else if (mantissa < 7) nice = 5;
199
+ else nice = 10;
200
+ return nice * base;
201
+ }
202
+
203
+ const distance = $derived.by(() => {
204
+ if (distanceProp != null) return distanceProp;
205
+ if (pixelsPerUnit == null) return 0;
206
+ const viewportUnits = ctx.width / pixelsPerUnit;
207
+ return niceDistance(viewportUnits * 0.25);
208
+ });
209
+
210
+ const barWidth = $derived(pixelsPerUnit && distance > 0 ? distance * pixelsPerUnit : 0);
211
+
212
+ const tickValues = $derived.by(() => {
213
+ if (distance <= 0) return [] as number[];
214
+ return Array.from({ length: ticks + 1 }, (_, i) => (distance * i) / ticks);
215
+ });
216
+
217
+ function formatTick(value: number) {
218
+ if (typeof tickFormatProp === 'function') return tickFormatProp(value);
219
+ if (tickFormatProp) return format(value, asAny(tickFormatProp));
220
+ // Default: append unit on the last tick only
221
+ return value === distance ? `${value} ${units}` : String(value);
222
+ }
223
+
224
+ const padding = 2;
225
+ const titleHeight = $derived(title ? titleFontSize + 6 : 0);
226
+ const tickLabelHeight = $derived(tickFontSize + 3);
227
+ const width = $derived(Math.ceil(barWidth) + padding * 2);
228
+ const svgHeight = $derived(titleHeight + height + tickLabelHeight + padding * 2 + 3);
229
+ const barY = $derived(
230
+ labelPlacement === 'top'
231
+ ? titleHeight + padding + tickLabelHeight
232
+ : titleHeight + padding
233
+ );
234
+ const tickLabelY = $derived(
235
+ labelPlacement === 'top'
236
+ ? titleHeight + padding + tickFontSize
237
+ : barY + height + 3 + tickFontSize
238
+ );
239
+
240
+ // Single path for the `bracket` variant: outer bracket as one continuous
241
+ // polyline (so corners join cleanly) plus interior ticks. The top rule sits
242
+ // on the opposite side of the labels so the bracket "opens" toward them.
243
+ const bracketPath = $derived.by(() => {
244
+ if (barWidth <= 0) return '';
245
+ const x0 = padding;
246
+ const x1 = padding + barWidth;
247
+ const yRule = labelPlacement === 'top' ? barY + height : barY;
248
+ const yTicks = labelPlacement === 'top' ? barY : barY + height;
249
+ let d = `M${x0},${yTicks}L${x0},${yRule}L${x1},${yRule}L${x1},${yTicks}`;
250
+ for (let i = 1; i < ticks; i++) {
251
+ const tx = padding + (barWidth * i) / ticks;
252
+ d += `M${tx},${yRule}L${tx},${yTicks}`;
253
+ }
254
+ return d;
255
+ });
256
+ </script>
257
+
258
+ <div
259
+ bind:this={ref}
260
+ {...restProps}
261
+ data-placement={placement}
262
+ class={cls('lc-geo-legend-container', className, classes.root)}
263
+ >
264
+ {#if barWidth > 0}
265
+ <svg {width} height={svgHeight} viewBox="0 0 {width} {svgHeight}" class="lc-geo-legend-svg">
266
+ {#if title}
267
+ <text
268
+ x={padding}
269
+ y={titleFontSize}
270
+ style:font-size={titleFontSize}
271
+ class={cls('lc-geo-legend-title', classes.title)}
272
+ >
273
+ {title}
274
+ </text>
275
+ {/if}
276
+ {#if variant === 'bracket'}
277
+ <path
278
+ d={bracketPath}
279
+ style:fill="none"
280
+ stroke={color}
281
+ stroke-linecap="round"
282
+ stroke-linejoin="round"
283
+ class={cls('lc-geo-legend-bar', classes.bar)}
284
+ />
285
+ {:else if variant === 'alternating'}
286
+ <!-- Outline + alternating filled segments between consecutive ticks -->
287
+
288
+ {#each Array.from({ length: ticks }) as _, i}
289
+ {#if i % 2 === 0}
290
+ {@const x1 = padding + (barWidth * i) / ticks}
291
+ {@const x2 = padding + (barWidth * (i + 1)) / ticks}
292
+ <rect
293
+ x={x1}
294
+ y={barY}
295
+ width={x2 - x1}
296
+ {height}
297
+ fill={color}
298
+ class={cls('lc-geo-legend-bar', classes.bar)}
299
+ />
300
+ {/if}
301
+ {/each}
302
+ <rect
303
+ x={padding}
304
+ y={barY}
305
+ width={barWidth}
306
+ {height}
307
+ style:fill="none"
308
+ stroke={color}
309
+ class={cls('lc-geo-legend-bar', classes.bar)}
310
+ />
311
+ {/if}
312
+ <g class="lc-geo-legend-ticks">
313
+ {#each tickValues as value, i}
314
+ {@const x = padding + (barWidth * i) / ticks}
315
+ <text
316
+ {x}
317
+ y={tickLabelY}
318
+ text-anchor="middle"
319
+ style:font-size={tickFontSize}
320
+ class={cls('lc-geo-legend-label', classes.label)}
321
+ >
322
+ {formatTick(value)}
323
+ </text>
324
+ {/each}
325
+ </g>
326
+ </svg>
327
+ {/if}
328
+ </div>
329
+
330
+ <style>
331
+ @layer components {
332
+ :where(.lc-geo-legend-container) {
333
+ display: inline-block;
334
+ z-index: 1;
335
+
336
+ &[data-placement] {
337
+ position: absolute;
338
+ }
339
+
340
+ &[data-placement='top-left'] {
341
+ top: 0;
342
+ left: 0;
343
+ }
344
+ &[data-placement='top'] {
345
+ top: 0;
346
+ left: 50%;
347
+ transform: translateX(-50%);
348
+ }
349
+ &[data-placement='top-right'] {
350
+ top: 0;
351
+ right: 0;
352
+ }
353
+ &[data-placement='left'] {
354
+ top: 50%;
355
+ left: 0;
356
+ transform: translateY(-50%);
357
+ }
358
+ &[data-placement='center'] {
359
+ top: 50%;
360
+ left: 50%;
361
+ transform: translate(-50%, -50%);
362
+ }
363
+ &[data-placement='right'] {
364
+ top: 50%;
365
+ right: 0;
366
+ transform: translateY(-50%);
367
+ }
368
+ &[data-placement='bottom-left'] {
369
+ bottom: 0;
370
+ left: 0;
371
+ }
372
+ &[data-placement='bottom'] {
373
+ bottom: 0;
374
+ left: 50%;
375
+ transform: translateX(-50%);
376
+ }
377
+ &[data-placement='bottom-right'] {
378
+ bottom: 0;
379
+ right: 0;
380
+ }
381
+ }
382
+
383
+ :where(.lc-geo-legend-svg) {
384
+ overflow: visible;
385
+ }
386
+
387
+ :where(.lc-geo-legend-title) {
388
+ font-weight: 600;
389
+ fill: var(--color-surface-content, currentColor);
390
+ }
391
+
392
+ :where(.lc-geo-legend-bar) {
393
+ fill: var(--color-surface-content, currentColor);
394
+ }
395
+
396
+ :where(.lc-geo-legend-tick) {
397
+ stroke: var(--color-surface-content, currentColor);
398
+ }
399
+
400
+ :where(.lc-geo-legend-label) {
401
+ fill: var(--color-surface-content, currentColor);
402
+ }
403
+ }
404
+ </style>
@@ -0,0 +1,106 @@
1
+ import type { Placement } from './types.js';
2
+ import { type Without } from '../utils/types.js';
3
+ export type GeoLegendUnits = 'km' | 'mi';
4
+ export type GeoLegendVariant = 'bracket' | 'alternating';
5
+ export type GeoLegendPropsWithoutHTML = {
6
+ /**
7
+ * Units to display.
8
+ *
9
+ * @default 'mi'
10
+ */
11
+ units?: GeoLegendUnits;
12
+ /**
13
+ * Visual style of the bar.
14
+ * - `'bracket'`: top rule with downward brackets at each tick (default)
15
+ * - `'alternating'`: alternating filled/unfilled segments between ticks
16
+ *
17
+ * @default 'bracket'
18
+ */
19
+ variant?: GeoLegendVariant;
20
+ /**
21
+ * Explicit distance to represent (in `units`). When omitted, a "nice" round
22
+ * value is chosen so the bar covers roughly 25% of the chart width.
23
+ */
24
+ distance?: number;
25
+ /**
26
+ * Number of tick subdivisions of the bar.
27
+ *
28
+ * @default 4
29
+ */
30
+ ticks?: number;
31
+ /**
32
+ * Where to place the tick labels relative to the bar. Useful for stacking
33
+ * two legends (e.g. kilometers + miles) tightly.
34
+ *
35
+ * @default 'bottom'
36
+ */
37
+ labelPlacement?: 'top' | 'bottom';
38
+ /**
39
+ * Format for the tick labels. Receives the distance in `units`.
40
+ */
41
+ tickFormat?: FormatType | FormatConfig | ((value: number) => string);
42
+ /**
43
+ * The font size of the tick labels.
44
+ *
45
+ * @default 10
46
+ */
47
+ tickFontSize?: number;
48
+ /**
49
+ * The font size of the title.
50
+ *
51
+ * @default 10
52
+ */
53
+ titleFontSize?: number;
54
+ /**
55
+ * The thickness of the bar.
56
+ *
57
+ * @default 4
58
+ */
59
+ height?: number;
60
+ /**
61
+ * The title of the legend.
62
+ *
63
+ * @default ''
64
+ */
65
+ title?: string;
66
+ /**
67
+ * Reference point in chart pixel coordinates used to compute the
68
+ * pixels-per-distance ratio. Defaults to the center of the chart's plot
69
+ * area, which is generally a reasonable approximation away from the poles.
70
+ */
71
+ referencePoint?: [number, number];
72
+ /**
73
+ * The placement of the legend.
74
+ */
75
+ placement?: Placement;
76
+ /**
77
+ * The fill/stroke color of the bar.
78
+ *
79
+ * @default 'currentColor'
80
+ */
81
+ color?: string;
82
+ /**
83
+ * Classes to apply to the elements.
84
+ *
85
+ * @default {}
86
+ */
87
+ classes?: {
88
+ root?: string;
89
+ title?: string;
90
+ bar?: string;
91
+ tick?: string;
92
+ label?: string;
93
+ };
94
+ /**
95
+ * A bindable reference to the wrapping `<div>` element.
96
+ *
97
+ * @bindable
98
+ */
99
+ ref?: HTMLElement;
100
+ };
101
+ export type GeoLegendProps = GeoLegendPropsWithoutHTML & Without<HTMLAttributes<HTMLElement>, GeoLegendPropsWithoutHTML>;
102
+ import type { HTMLAttributes } from 'svelte/elements';
103
+ import { type FormatType, type FormatConfig } from '@layerstack/utils';
104
+ declare const GeoLegend: import("svelte").Component<GeoLegendProps, {}, "ref">;
105
+ type GeoLegend = ReturnType<typeof GeoLegend>;
106
+ export default GeoLegend;