@spatika/react 1.3.1 → 1.5.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.
Files changed (127) hide show
  1. package/AGENTS.md +6 -3
  2. package/README.md +1 -1
  3. package/dist/composites/EventCalendar.d.ts +13 -2
  4. package/dist/composites/EventCalendar.d.ts.map +1 -1
  5. package/dist/composites/EventCalendar.js +13 -27
  6. package/dist/composites/GlassCard.d.ts +1 -1
  7. package/dist/composites/RichTextEditor.d.ts +1 -4
  8. package/dist/composites/RichTextEditor.d.ts.map +1 -1
  9. package/dist/composites/RichTextEditor.js +12 -19
  10. package/dist/composites/SurfaceCard.d.ts +1 -1
  11. package/dist/composites/Tag.d.ts +2 -2
  12. package/dist/composites/cartesian-charts.d.ts +12 -2
  13. package/dist/composites/cartesian-charts.d.ts.map +1 -1
  14. package/dist/composites/cartesian-charts.js +27 -13
  15. package/dist/composites/chart-composition.d.ts +19 -1
  16. package/dist/composites/chart-composition.d.ts.map +1 -1
  17. package/dist/composites/chart-composition.js +124 -62
  18. package/dist/composites/chart-interaction.d.ts +81 -0
  19. package/dist/composites/chart-interaction.d.ts.map +1 -0
  20. package/dist/composites/chart-interaction.js +56 -0
  21. package/dist/composites/chart-ui.d.ts +8 -13
  22. package/dist/composites/chart-ui.d.ts.map +1 -1
  23. package/dist/composites/chart-ui.js +9 -6
  24. package/dist/composites/flow-charts.d.ts +6 -1
  25. package/dist/composites/flow-charts.d.ts.map +1 -1
  26. package/dist/composites/flow-charts.js +28 -14
  27. package/dist/composites/radial-charts.d.ts +11 -1
  28. package/dist/composites/radial-charts.d.ts.map +1 -1
  29. package/dist/composites/radial-charts.js +33 -14
  30. package/dist/composites/scheduler-ui.d.ts +14 -1
  31. package/dist/composites/scheduler-ui.d.ts.map +1 -1
  32. package/dist/composites/scheduler-ui.js +23 -2
  33. package/dist/index.d.ts +14 -26
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +8 -14
  36. package/dist/lib/breakpoints.d.ts +17 -0
  37. package/dist/lib/breakpoints.d.ts.map +1 -0
  38. package/dist/lib/breakpoints.js +23 -0
  39. package/dist/lib/chips.d.ts +3 -0
  40. package/dist/lib/chips.d.ts.map +1 -1
  41. package/dist/lib/chips.js +10 -0
  42. package/dist/lib/create-theme.d.ts +162 -0
  43. package/dist/lib/create-theme.d.ts.map +1 -0
  44. package/dist/lib/create-theme.js +138 -0
  45. package/dist/lib/scheduler.d.ts +12 -0
  46. package/dist/lib/scheduler.d.ts.map +1 -1
  47. package/dist/lib/scheduler.js +24 -2
  48. package/dist/lib/themes.d.ts +17 -7
  49. package/dist/lib/themes.d.ts.map +1 -1
  50. package/dist/lib/themes.js +22 -8
  51. package/dist/lib/use-media-query.d.ts +9 -0
  52. package/dist/lib/use-media-query.d.ts.map +1 -1
  53. package/dist/lib/use-media-query.js +28 -1
  54. package/dist/primitives/Alert.d.ts +1 -1
  55. package/dist/primitives/Badge.d.ts +2 -2
  56. package/dist/primitives/Button.d.ts +2 -2
  57. package/dist/primitives/Container.d.ts +2 -2
  58. package/dist/primitives/Fab.d.ts +4 -4
  59. package/dist/primitives/Grid.d.ts +2 -1
  60. package/dist/primitives/Grid.d.ts.map +1 -1
  61. package/dist/primitives/Grid.js +10 -2
  62. package/dist/primitives/IconButton.d.ts +2 -2
  63. package/dist/primitives/NativeSelect.d.ts +10 -1
  64. package/dist/primitives/NativeSelect.d.ts.map +1 -1
  65. package/dist/primitives/NativeSelect.js +2 -2
  66. package/dist/primitives/ToggleButton.d.ts +1 -1
  67. package/dist/theme/SpatikaThemeProvider.d.ts +4 -5
  68. package/dist/theme/SpatikaThemeProvider.d.ts.map +1 -1
  69. package/dist/theme/SpatikaThemeProvider.js +28 -13
  70. package/package.json +4 -2
  71. package/skills/spatika-ui/SKILL.md +28 -5
  72. package/src/composites/EventCalendar.test.tsx +293 -0
  73. package/src/composites/EventCalendar.tsx +38 -27
  74. package/src/composites/RichTextEditor.test.tsx +51 -0
  75. package/src/composites/RichTextEditor.tsx +20 -84
  76. package/src/composites/chart-ui.test.tsx +21 -0
  77. package/src/composites/scheduler-ui.tsx +128 -26
  78. package/src/composites/scheduler.test.tsx +0 -219
  79. package/src/index.test.ts +10 -0
  80. package/src/index.ts +148 -65
  81. package/src/lib/breakpoints.test.ts +29 -0
  82. package/src/lib/breakpoints.ts +28 -0
  83. package/src/lib/chips.ts +16 -0
  84. package/src/lib/create-theme.test.ts +79 -0
  85. package/src/lib/create-theme.ts +249 -0
  86. package/src/lib/scheduler.test.ts +26 -0
  87. package/src/lib/scheduler.ts +36 -2
  88. package/src/lib/themes.test.ts +17 -0
  89. package/src/lib/themes.ts +43 -12
  90. package/src/lib/use-media-query.test.tsx +80 -0
  91. package/src/lib/use-media-query.ts +35 -1
  92. package/src/primitives/Grid.test.tsx +55 -0
  93. package/src/primitives/Grid.tsx +13 -3
  94. package/src/primitives/NativeSelect.test.tsx +28 -0
  95. package/src/primitives/NativeSelect.tsx +14 -4
  96. package/src/primitives/mui-more.test.tsx +17 -0
  97. package/src/theme/SpatikaThemeProvider.test.tsx +38 -0
  98. package/src/theme/SpatikaThemeProvider.tsx +43 -15
  99. package/src/composites/ChartDataGrid.test.tsx +0 -57
  100. package/src/composites/ChartDataGrid.tsx +0 -196
  101. package/src/composites/MapChart.test.tsx +0 -39
  102. package/src/composites/MapChart.tsx +0 -130
  103. package/src/composites/ScatterWebGL.test.tsx +0 -25
  104. package/src/composites/ScatterWebGL.tsx +0 -79
  105. package/src/composites/cartesian-charts.test.tsx +0 -52
  106. package/src/composites/cartesian-charts.tsx +0 -1287
  107. package/src/composites/chart-composition.test.tsx +0 -53
  108. package/src/composites/chart-composition.tsx +0 -898
  109. package/src/composites/chart-ui.tsx +0 -328
  110. package/src/composites/charts-3d.test.tsx +0 -25
  111. package/src/composites/charts-3d.tsx +0 -318
  112. package/src/composites/charts.test.tsx +0 -254
  113. package/src/composites/flow-charts.test.tsx +0 -79
  114. package/src/composites/flow-charts.tsx +0 -607
  115. package/src/composites/radial-charts.test.tsx +0 -74
  116. package/src/composites/radial-charts.tsx +0 -617
  117. package/src/lib/chart-export.test.ts +0 -20
  118. package/src/lib/chart-export.ts +0 -99
  119. package/src/lib/charts-platform.test.ts +0 -93
  120. package/src/lib/charts.test.ts +0 -132
  121. package/src/lib/charts.ts +0 -1021
  122. package/src/lib/geo.test.ts +0 -42
  123. package/src/lib/geo.ts +0 -138
  124. package/src/lib/iso.test.ts +0 -22
  125. package/src/lib/iso.ts +0 -102
  126. package/src/lib/webgl-scatter.test.ts +0 -36
  127. package/src/lib/webgl-scatter.ts +0 -230
@@ -1,617 +0,0 @@
1
- import {
2
- arcPath,
3
- formatChartNumber,
4
- linePath,
5
- pieSlices,
6
- polarPoint,
7
- type ChartMargin,
8
- } from "../lib/charts";
9
- import {
10
- ChartFrame,
11
- formatSeriesValue,
12
- seriesMeta,
13
- useChartHover,
14
- useHiddenSeries,
15
- } from "./chart-ui";
16
-
17
- export type PieDatum = {
18
- id?: string | number;
19
- value: number;
20
- label?: string;
21
- color?: string;
22
- };
23
-
24
- export type PieSeries = {
25
- data: PieDatum[];
26
- innerRadius?: number;
27
- outerRadius?: number;
28
- paddingAngle?: number;
29
- startAngle?: number;
30
- endAngle?: number;
31
- cx?: number;
32
- cy?: number;
33
- faded?: boolean;
34
- };
35
-
36
- export type PieChartProps = {
37
- series: PieSeries[];
38
- height?: number;
39
- width?: number;
40
- colors?: string[];
41
- hideLegend?: boolean;
42
- margin?: ChartMargin;
43
- className?: string;
44
- slot?: string;
45
- };
46
-
47
- export function PieChart({
48
- series,
49
- height = 280,
50
- width,
51
- colors,
52
- hideLegend,
53
- margin,
54
- className,
55
- slot = "pie-chart",
56
- }: PieChartProps) {
57
- const first = series[0] ?? { data: [] };
58
- const meta = seriesMeta(
59
- first.data.map((item, i) => ({
60
- id: String(item.id ?? item.label ?? i),
61
- label: item.label ?? `Slice ${i + 1}`,
62
- color: item.color,
63
- })),
64
- colors,
65
- );
66
- const ids = meta.map((item) => item.id);
67
- const { hidden, toggle } = useHiddenSeries(ids);
68
- const { hover, setHover, clear } = useChartHover();
69
-
70
- return (
71
- <ChartFrame
72
- slot={slot}
73
- className={className}
74
- width={width}
75
- height={height}
76
- margin={margin ?? { top: 12, right: 12, bottom: 12, left: 12 }}
77
- legend={hideLegend ? undefined : meta}
78
- hiddenIds={hidden}
79
- onToggleSeries={toggle}
80
- hover={hover}
81
- aria-label="Pie chart"
82
- >
83
- {({ width: w, height: h, m }) => {
84
- const cx = first.cx ?? w / 2;
85
- const cy = first.cy ?? (h - 8) / 2;
86
- const maxR = Math.min(w - m.left - m.right, h - m.top - m.bottom) / 2;
87
- const outer = first.outerRadius ?? maxR;
88
- const inner = first.innerRadius ?? 0;
89
- const visible = first.data.map((item, i) => (hidden.has(meta[i]!.id) ? 0 : item.value));
90
- const slices = pieSlices(visible, {
91
- cx,
92
- cy,
93
- innerRadius: inner,
94
- outerRadius: outer,
95
- startAngle: first.startAngle,
96
- endAngle: first.endAngle,
97
- paddingAngle: first.paddingAngle,
98
- });
99
- return (
100
- <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
101
- {slices.map((slice, i) => {
102
- const info = meta[i]!;
103
- if (hidden.has(info.id) || !slice.path) return null;
104
- const item = first.data[i]!;
105
- return (
106
- <g key={info.id}>
107
- <path
108
- d={slice.path}
109
- fill={info.color}
110
- className="spk-chart-mark"
111
- onMouseEnter={(event) =>
112
- setHover({
113
- x: event.nativeEvent.offsetX,
114
- y: event.nativeEvent.offsetY,
115
- title: info.label,
116
- items: [
117
- {
118
- color: info.color,
119
- label: info.label,
120
- value: `${formatChartNumber(item.value)} (${Math.round(slice.percent * 100)}%)`,
121
- },
122
- ],
123
- })
124
- }
125
- onMouseLeave={clear}
126
- />
127
- {slice.percent > 0.08 ? (
128
- <text
129
- className="spk-chart-label"
130
- fill="var(--primary-foreground)"
131
- x={slice.labelX}
132
- y={slice.labelY}
133
- textAnchor="middle"
134
- dominantBaseline="middle"
135
- >
136
- {Math.round(slice.percent * 100)}%
137
- </text>
138
- ) : null}
139
- </g>
140
- );
141
- })}
142
- </svg>
143
- );
144
- }}
145
- </ChartFrame>
146
- );
147
- }
148
-
149
- export type GaugeSection = {
150
- value: number;
151
- color?: string;
152
- label?: string;
153
- };
154
-
155
- export type GaugeProps = {
156
- value?: number;
157
- valueMin?: number;
158
- valueMax?: number;
159
- startAngle?: number;
160
- endAngle?: number;
161
- innerRadius?: number;
162
- outerRadius?: number;
163
- height?: number;
164
- width?: number;
165
- color?: string;
166
- colors?: string[];
167
- text?: string | null | ((value: number) => string);
168
- sections?: GaugeSection[];
169
- hideLegend?: boolean;
170
- paddingAngle?: number;
171
- className?: string;
172
- };
173
-
174
- export function Gauge({
175
- value = 0,
176
- valueMin = 0,
177
- valueMax = 100,
178
- startAngle = -110,
179
- endAngle = 110,
180
- innerRadius,
181
- outerRadius,
182
- height = 200,
183
- width,
184
- color = "var(--chart-1)",
185
- colors,
186
- text,
187
- sections,
188
- hideLegend,
189
- paddingAngle = 2,
190
- className,
191
- }: GaugeProps) {
192
- const pct = (value - valueMin) / (valueMax - valueMin || 1);
193
- const segmented = Boolean(sections?.length);
194
- const resolvedText =
195
- text === null
196
- ? null
197
- : typeof text === "function"
198
- ? text(value)
199
- : (text ?? (segmented ? null : `${Math.round(pct * 100)}%`));
200
- const meta = seriesMeta(
201
- (sections ?? []).map((item, i) => ({
202
- id: item.label ?? `section-${i}`,
203
- label: item.label ?? `Section ${i + 1}`,
204
- color: item.color,
205
- })),
206
- colors,
207
- );
208
-
209
- return (
210
- <ChartFrame
211
- slot="gauge"
212
- className={className}
213
- width={width}
214
- height={height}
215
- margin={{ top: 8, right: 8, bottom: 8, left: 8 }}
216
- legend={segmented && !hideLegend ? meta : undefined}
217
- aria-label={segmented ? "Gauge" : `Gauge ${value}`}
218
- >
219
- {({ width: w, height: h }) => {
220
- const cx = w / 2;
221
- const cy = segmented ? h * 0.72 : h * 0.62;
222
- const maxR = Math.min(w, h) * (segmented ? 0.5 : 0.42);
223
- const outer = outerRadius ?? maxR;
224
- const inner = innerRadius ?? outer * 0.72;
225
- const span = endAngle - startAngle;
226
- const valueAngle = startAngle + span * Math.min(1, Math.max(0, pct));
227
- const slices = segmented
228
- ? pieSlices(
229
- (sections ?? []).map((item) => item.value),
230
- {
231
- cx,
232
- cy,
233
- innerRadius: inner,
234
- outerRadius: outer,
235
- startAngle,
236
- endAngle,
237
- paddingAngle,
238
- },
239
- )
240
- : [];
241
- return (
242
- <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
243
- {segmented ? (
244
- slices.map((slice, i) => {
245
- const info = meta[i]!;
246
- if (!slice.path) return null;
247
- return (
248
- <path
249
- key={info.id}
250
- d={slice.path}
251
- fill={info.color}
252
- className="spk-chart-mark"
253
- />
254
- );
255
- })
256
- ) : (
257
- <>
258
- <path
259
- d={arcPath(cx, cy, inner, outer, startAngle, endAngle)}
260
- fill="color-mix(in srgb, var(--muted) 80%, transparent)"
261
- />
262
- <path d={arcPath(cx, cy, inner, outer, startAngle, valueAngle)} fill={color} />
263
- </>
264
- )}
265
- {resolvedText != null ? (
266
- <text
267
- className="spk-chart-label"
268
- x={cx}
269
- y={cy}
270
- textAnchor="middle"
271
- dominantBaseline="middle"
272
- fontSize="1.35rem"
273
- >
274
- {resolvedText}
275
- </text>
276
- ) : null}
277
- </svg>
278
- );
279
- }}
280
- </ChartFrame>
281
- );
282
- }
283
-
284
- export type LinearGaugeProps = {
285
- value: number;
286
- valueMin?: number;
287
- valueMax?: number;
288
- height?: number;
289
- width?: number;
290
- color?: string;
291
- className?: string;
292
- };
293
-
294
- export function LinearGauge({
295
- value,
296
- valueMin = 0,
297
- valueMax = 100,
298
- height = 48,
299
- width,
300
- color = "var(--chart-1)",
301
- className,
302
- }: LinearGaugeProps) {
303
- const pct = Math.min(1, Math.max(0, (value - valueMin) / (valueMax - valueMin || 1)));
304
- return (
305
- <ChartFrame
306
- slot="linear-gauge"
307
- className={className}
308
- width={width}
309
- height={height}
310
- margin={{ top: 14, right: 12, bottom: 14, left: 12 }}
311
- aria-label={`Linear gauge ${value}`}
312
- >
313
- {({ width: w, height: h, m }) => {
314
- const y = h / 2 - 7;
315
- const trackW = w - m.left - m.right;
316
- return (
317
- <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
318
- <rect
319
- x={m.left}
320
- y={y}
321
- width={trackW}
322
- height={14}
323
- rx={7}
324
- fill="color-mix(in srgb, var(--muted) 80%, transparent)"
325
- />
326
- <rect x={m.left} y={y} width={Math.max(14, trackW * pct)} height={14} rx={7} fill={color} />
327
- <text
328
- className="spk-chart-label"
329
- x={m.left + trackW * pct}
330
- y={y - 4}
331
- textAnchor={pct > 0.85 ? "end" : "middle"}
332
- >
333
- {formatChartNumber(value)}
334
- </text>
335
- </svg>
336
- );
337
- }}
338
- </ChartFrame>
339
- );
340
- }
341
-
342
- export type RadarSeries = {
343
- id?: string;
344
- label?: string;
345
- color?: string;
346
- data: number[];
347
- };
348
-
349
- export type RadarChartProps = {
350
- series: RadarSeries[];
351
- radar: { metrics: string[]; max?: number };
352
- height?: number;
353
- width?: number;
354
- colors?: string[];
355
- hideLegend?: boolean;
356
- className?: string;
357
- fill?: boolean;
358
- slot?: string;
359
- };
360
-
361
- export function RadarChart({
362
- series,
363
- radar,
364
- height = 300,
365
- width,
366
- colors,
367
- hideLegend,
368
- className,
369
- fill = true,
370
- slot = "radar-chart",
371
- }: RadarChartProps) {
372
- const meta = seriesMeta(series, colors);
373
- const ids = meta.map((item) => item.id);
374
- const { hidden, toggle } = useHiddenSeries(ids);
375
- const { hover, setHover, clear } = useChartHover();
376
- const max = radar.max ?? Math.max(...series.flatMap((item) => item.data), 1);
377
-
378
- return (
379
- <ChartFrame
380
- slot={slot}
381
- className={className}
382
- width={width}
383
- height={height}
384
- margin={{ top: 28, right: 28, bottom: 28, left: 28 }}
385
- legend={hideLegend ? undefined : meta}
386
- hiddenIds={hidden}
387
- onToggleSeries={toggle}
388
- hover={hover}
389
- aria-label="Radar chart"
390
- >
391
- {({ width: w, height: h }) => {
392
- const cx = w / 2;
393
- const cy = h / 2;
394
- const radius = Math.min(w, h) * 0.36;
395
- const n = radar.metrics.length || 1;
396
- const angle = 360 / n;
397
- const rings = [0.25, 0.5, 0.75, 1];
398
- return (
399
- <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
400
- {rings.map((ring) => (
401
- <polygon
402
- key={ring}
403
- fill="none"
404
- className="spk-chart-grid"
405
- points={radar.metrics
406
- .map((_, i) => {
407
- const p = polarPoint(cx, cy, radius * ring, i * angle);
408
- return `${p.x},${p.y}`;
409
- })
410
- .join(" ")}
411
- />
412
- ))}
413
- {radar.metrics.map((metric, i) => {
414
- const p = polarPoint(cx, cy, radius, i * angle);
415
- const label = polarPoint(cx, cy, radius + 18, i * angle);
416
- return (
417
- <g key={metric}>
418
- <line className="spk-chart-grid" x1={cx} y1={cy} x2={p.x} y2={p.y} />
419
- <text
420
- className="spk-chart-tick"
421
- x={label.x}
422
- y={label.y}
423
- textAnchor="middle"
424
- dominantBaseline="middle"
425
- >
426
- {metric}
427
- </text>
428
- </g>
429
- );
430
- })}
431
- {series.map((item, s) => {
432
- const info = meta[s]!;
433
- if (hidden.has(info.id)) return null;
434
- const pts = item.data.map((value, i) =>
435
- polarPoint(cx, cy, radius * (value / max), i * angle),
436
- );
437
- const closed = [...pts, pts[0]!];
438
- return (
439
- <g key={info.id}>
440
- {fill ? (
441
- <polygon
442
- points={pts.map((p) => `${p.x},${p.y}`).join(" ")}
443
- fill={info.color}
444
- fillOpacity={0.18}
445
- stroke={info.color}
446
- strokeWidth={2}
447
- className="spk-chart-mark"
448
- />
449
- ) : (
450
- <path
451
- d={linePath(closed, "linear")}
452
- fill="none"
453
- stroke={info.color}
454
- strokeWidth={2}
455
- className="spk-chart-mark"
456
- />
457
- )}
458
- {pts.map((p, i) => (
459
- <circle
460
- key={i}
461
- cx={p.x}
462
- cy={p.y}
463
- r={3.2}
464
- fill={info.color}
465
- onMouseEnter={() =>
466
- setHover({
467
- x: p.x,
468
- y: p.y,
469
- title: radar.metrics[i],
470
- items: [
471
- {
472
- color: info.color,
473
- label: info.label,
474
- value: formatSeriesValue(item.data[i]),
475
- },
476
- ],
477
- })
478
- }
479
- onMouseLeave={clear}
480
- />
481
- ))}
482
- </g>
483
- );
484
- })}
485
- </svg>
486
- );
487
- }}
488
- </ChartFrame>
489
- );
490
- }
491
-
492
- export function PolarLineChart(props: RadarChartProps) {
493
- return <RadarChart {...props} fill={false} slot="polar-line-chart" />;
494
- }
495
-
496
- export type RadialBarChartProps = {
497
- series: Array<{ data: PieDatum[] }>;
498
- height?: number;
499
- width?: number;
500
- colors?: string[];
501
- hideLegend?: boolean;
502
- className?: string;
503
- startAngle?: number;
504
- endAngle?: number;
505
- };
506
-
507
- export function RadialBarChart({
508
- series,
509
- height = 280,
510
- width,
511
- colors,
512
- hideLegend,
513
- className,
514
- startAngle = -90,
515
- endAngle = 180,
516
- }: RadialBarChartProps) {
517
- const data = series[0]?.data ?? [];
518
- const meta = seriesMeta(
519
- data.map((item, i) => ({ id: String(item.id ?? item.label ?? i), label: item.label ?? `Item ${i + 1}`, color: item.color })),
520
- colors,
521
- );
522
- const ids = meta.map((item) => item.id);
523
- const { hidden, toggle } = useHiddenSeries(ids);
524
- const { hover, setHover, clear } = useChartHover();
525
- const max = Math.max(...data.map((item) => item.value), 1);
526
-
527
- return (
528
- <ChartFrame
529
- slot="radial-bar-chart"
530
- className={className}
531
- width={width}
532
- height={height}
533
- margin={{ top: 12, right: 12, bottom: 12, left: 12 }}
534
- legend={hideLegend ? undefined : meta}
535
- hiddenIds={hidden}
536
- onToggleSeries={toggle}
537
- hover={hover}
538
- aria-label="Radial bar chart"
539
- >
540
- {({ width: w, height: h }) => {
541
- const cx = w / 2;
542
- const cy = h / 2;
543
- const outer = Math.min(w, h) * 0.42;
544
- const track = Math.max(8, (outer * 0.62) / Math.max(data.length, 1));
545
- return (
546
- <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
547
- {data.map((item, i) => {
548
- const info = meta[i]!;
549
- if (hidden.has(info.id)) return null;
550
- const r1 = outer - i * track;
551
- const r0 = r1 - track * 0.72;
552
- const span = endAngle - startAngle;
553
- const a1 = startAngle + span * (item.value / max);
554
- return (
555
- <g key={info.id}>
556
- <path
557
- d={arcPath(cx, cy, r0, r1, startAngle, endAngle)}
558
- fill="color-mix(in srgb, var(--muted) 70%, transparent)"
559
- />
560
- <path
561
- d={arcPath(cx, cy, r0, r1, startAngle, a1)}
562
- fill={info.color}
563
- className="spk-chart-mark"
564
- onMouseEnter={(event) =>
565
- setHover({
566
- x: event.nativeEvent.offsetX,
567
- y: event.nativeEvent.offsetY,
568
- title: info.label,
569
- items: [{ color: info.color, label: info.label, value: formatChartNumber(item.value) }],
570
- })
571
- }
572
- onMouseLeave={clear}
573
- />
574
- </g>
575
- );
576
- })}
577
- </svg>
578
- );
579
- }}
580
- </ChartFrame>
581
- );
582
- }
583
-
584
- export type RadialLineChartProps = {
585
- series: RadarSeries[];
586
- metrics?: string[];
587
- height?: number;
588
- width?: number;
589
- colors?: string[];
590
- hideLegend?: boolean;
591
- className?: string;
592
- };
593
-
594
- export function RadialLineChart({
595
- series,
596
- metrics,
597
- height = 280,
598
- width,
599
- colors,
600
- hideLegend,
601
- className,
602
- }: RadialLineChartProps) {
603
- const labels = metrics ?? series[0]?.data.map((_, i) => String(i + 1)) ?? [];
604
- return (
605
- <RadarChart
606
- series={series}
607
- radar={{ metrics: labels }}
608
- height={height}
609
- width={width}
610
- colors={colors}
611
- hideLegend={hideLegend}
612
- className={className}
613
- fill={false}
614
- slot="radial-line-chart"
615
- />
616
- );
617
- }
@@ -1,20 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { serializeChartSvg } from "./chart-export";
3
-
4
- describe("chart export", () => {
5
- it("serializes an SVG with an XML header", () => {
6
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
7
- svg.setAttribute("width", "120");
8
- svg.setAttribute("height", "80");
9
- const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
10
- rect.setAttribute("width", "120");
11
- rect.setAttribute("height", "80");
12
- rect.setAttribute("fill", "var(--chart-1)");
13
- svg.appendChild(rect);
14
- document.body.appendChild(svg);
15
- const xml = serializeChartSvg(svg);
16
- expect(xml).toContain("<?xml");
17
- expect(xml).toContain("<svg");
18
- svg.remove();
19
- });
20
- });