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