ui-svelte 0.2.3 → 0.2.5
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/assets/country-flags.d.ts +1 -0
- package/dist/assets/country-flags.js +1612 -0
- package/dist/charts/ArcChart.svelte +291 -48
- package/dist/charts/ArcChart.svelte.d.ts +32 -1
- package/dist/charts/Candlestick.svelte +663 -115
- package/dist/charts/Candlestick.svelte.d.ts +40 -0
- package/dist/charts/css/arc-chart.css +76 -6
- package/dist/charts/css/candlestick.css +234 -11
- package/dist/control/Audio.svelte +8 -12
- package/dist/control/Button.svelte +3 -1
- package/dist/control/Button.svelte.d.ts +1 -0
- package/dist/control/IconButton.svelte +3 -1
- package/dist/control/IconButton.svelte.d.ts +1 -0
- package/dist/control/ToggleGroup.svelte +82 -0
- package/dist/control/ToggleGroup.svelte.d.ts +20 -0
- package/dist/control/css/btn.css +1 -1
- package/dist/control/css/toggle-group.css +85 -0
- package/dist/css/base.css +23 -15
- package/dist/css/utilities.css +45 -0
- package/dist/display/AvatarGroup.svelte +59 -0
- package/dist/display/AvatarGroup.svelte.d.ts +17 -0
- package/dist/display/Code.svelte +9 -2
- package/dist/display/Code.svelte.d.ts +1 -0
- package/dist/display/Section.svelte +1 -1
- package/dist/display/css/avatar-group.css +46 -0
- package/dist/display/css/avatar.css +1 -10
- package/dist/display/css/card.css +0 -10
- package/dist/form/ComboBox.svelte.d.ts +1 -1
- package/dist/form/PhoneField.svelte +8 -4
- package/dist/form/Select.svelte.d.ts +1 -1
- package/dist/index.css +43 -21
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/layout/AppBar.svelte +28 -1
- package/dist/layout/AppBar.svelte.d.ts +2 -0
- package/dist/layout/Footer.svelte +25 -1
- package/dist/layout/Footer.svelte.d.ts +1 -0
- package/dist/layout/Sidebar.svelte +33 -3
- package/dist/layout/Sidebar.svelte.d.ts +1 -0
- package/dist/layout/css/app-bar.css +63 -0
- package/dist/layout/css/bottom-bar.css +63 -0
- package/dist/layout/css/footer.css +63 -0
- package/dist/layout/css/sidebar.css +63 -0
- package/dist/navigation/NavMenu.svelte +3 -9
- package/dist/navigation/SideNav.svelte +0 -9
- package/dist/navigation/SideNav.svelte.d.ts +0 -1
- package/dist/navigation/css/footer-group.css +3 -4
- package/dist/navigation/css/nav-menu.css +90 -30
- package/dist/navigation/css/side-nav.css +127 -66
- package/dist/overlay/css/modal.css +2 -2
- package/package.json +2 -2
- /package/dist/{form/js → assets}/countries.d.ts +0 -0
- /package/dist/{form/js → assets}/countries.js +0 -0
- /package/dist/{form/js → assets}/phone-examples.d.ts +0 -0
- /package/dist/{form/js → assets}/phone-examples.js +0 -0
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../utils/class-names.js';
|
|
3
|
-
import { onMount, untrack } from 'svelte';
|
|
3
|
+
import { onMount, untrack, type Snippet } from 'svelte';
|
|
4
4
|
|
|
5
5
|
type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
|
|
6
|
+
type Size = 'sm' | 'md' | 'lg' | 'xl';
|
|
7
|
+
type LegendPosition = 'top' | 'right' | 'bottom' | 'left' | 'none';
|
|
8
|
+
type Linecap = 'round' | 'butt' | 'square';
|
|
9
|
+
type Palette = 'default' | 'rainbow' | 'ocean' | 'sunset' | 'forest' | 'neon';
|
|
6
10
|
|
|
7
11
|
type ArcData = {
|
|
8
12
|
value: number;
|
|
9
13
|
max?: number;
|
|
10
|
-
color
|
|
14
|
+
color?: Color;
|
|
15
|
+
gradient?: { from: string; to: string };
|
|
11
16
|
label?: string;
|
|
17
|
+
unit?: string;
|
|
18
|
+
disabled?: boolean;
|
|
12
19
|
};
|
|
13
20
|
|
|
14
21
|
type Props = {
|
|
@@ -26,6 +33,23 @@
|
|
|
26
33
|
showValues?: boolean;
|
|
27
34
|
rootClass?: string;
|
|
28
35
|
chartClass?: string;
|
|
36
|
+
size?: Size;
|
|
37
|
+
innerRadius?: number;
|
|
38
|
+
outerRadius?: number;
|
|
39
|
+
startAngle?: number;
|
|
40
|
+
endAngle?: number;
|
|
41
|
+
showGradient?: boolean;
|
|
42
|
+
showGlow?: boolean;
|
|
43
|
+
linecap?: Linecap;
|
|
44
|
+
palette?: Palette;
|
|
45
|
+
legendPosition?: LegendPosition;
|
|
46
|
+
valueFormatter?: (value: number) => string;
|
|
47
|
+
onClick?: (arc: ArcData, index: number) => void;
|
|
48
|
+
onHover?: (arc: ArcData | null, index: number) => void;
|
|
49
|
+
selected?: number[];
|
|
50
|
+
showInlineLabels?: boolean;
|
|
51
|
+
centerContent?: Snippet;
|
|
52
|
+
tooltipContent?: Snippet<[{ arc: ArcData; percentage: number }]>;
|
|
29
53
|
};
|
|
30
54
|
|
|
31
55
|
let {
|
|
@@ -42,10 +66,46 @@
|
|
|
42
66
|
showLegend = true,
|
|
43
67
|
showValues = true,
|
|
44
68
|
rootClass,
|
|
45
|
-
chartClass
|
|
69
|
+
chartClass,
|
|
70
|
+
size = 'md',
|
|
71
|
+
innerRadius,
|
|
72
|
+
outerRadius,
|
|
73
|
+
startAngle = -90,
|
|
74
|
+
endAngle = 270,
|
|
75
|
+
showGradient = false,
|
|
76
|
+
showGlow = false,
|
|
77
|
+
linecap = 'round',
|
|
78
|
+
palette,
|
|
79
|
+
legendPosition = 'right',
|
|
80
|
+
valueFormatter,
|
|
81
|
+
onClick,
|
|
82
|
+
onHover,
|
|
83
|
+
selected = [],
|
|
84
|
+
showInlineLabels = false,
|
|
85
|
+
centerContent,
|
|
86
|
+
tooltipContent
|
|
46
87
|
}: Props = $props();
|
|
47
88
|
|
|
48
|
-
const
|
|
89
|
+
const sizePresets: Record<
|
|
90
|
+
Size,
|
|
91
|
+
{ height: number; thickness: number; labelSize: number; valueSize: number }
|
|
92
|
+
> = {
|
|
93
|
+
sm: { height: 150, thickness: 12, labelSize: 10, valueSize: 20 },
|
|
94
|
+
md: { height: 224, thickness: 16, labelSize: 14, valueSize: 32 },
|
|
95
|
+
lg: { height: 300, thickness: 20, labelSize: 16, valueSize: 40 },
|
|
96
|
+
xl: { height: 400, thickness: 24, labelSize: 18, valueSize: 48 }
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const colorPalettes: Record<Palette, Color[]> = {
|
|
100
|
+
default: ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'],
|
|
101
|
+
rainbow: ['danger', 'warning', 'success', 'info', 'primary', 'secondary', 'muted'],
|
|
102
|
+
ocean: ['info', 'primary', 'secondary', 'success', 'muted', 'warning', 'danger'],
|
|
103
|
+
sunset: ['warning', 'danger', 'secondary', 'primary', 'info', 'success', 'muted'],
|
|
104
|
+
forest: ['success', 'primary', 'info', 'secondary', 'muted', 'warning', 'danger'],
|
|
105
|
+
neon: ['secondary', 'primary', 'success', 'warning', 'danger', 'info', 'muted']
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const colorClass: Record<Color, string> = {
|
|
49
109
|
primary: 'is-primary',
|
|
50
110
|
secondary: 'is-secondary',
|
|
51
111
|
success: 'is-success',
|
|
@@ -55,50 +115,76 @@
|
|
|
55
115
|
muted: 'is-muted'
|
|
56
116
|
};
|
|
57
117
|
|
|
118
|
+
// Get arc color based on palette or individual color
|
|
119
|
+
function getArcColor(arc: ArcData, index: number): Color {
|
|
120
|
+
if (arc.color) return arc.color;
|
|
121
|
+
if (palette) {
|
|
122
|
+
const paletteColors = colorPalettes[palette];
|
|
123
|
+
return paletteColors[index % paletteColors.length];
|
|
124
|
+
}
|
|
125
|
+
return colorPalettes.default[index % colorPalettes.default.length];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function formatValue(value: number, unit?: string): string {
|
|
129
|
+
if (valueFormatter) return valueFormatter(value);
|
|
130
|
+
if (unit) return `${value}${unit}`;
|
|
131
|
+
return String(value);
|
|
132
|
+
}
|
|
133
|
+
|
|
58
134
|
let containerEl: HTMLDivElement | undefined = $state();
|
|
135
|
+
// svelte-ignore state_referenced_locally
|
|
59
136
|
let displayPercentages = $state<number[]>(data.map(() => 0));
|
|
60
137
|
let animationFrameId: number | null = null;
|
|
61
138
|
|
|
62
|
-
let tooltipData = $state<(ArcData & { percentage: number }) | null>(null);
|
|
139
|
+
let tooltipData = $state<(ArcData & { percentage: number; index: number }) | null>(null);
|
|
63
140
|
let tooltipPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
64
141
|
let isTooltipActive = $state(false);
|
|
142
|
+
let hoveredIndex = $state<number | null>(null);
|
|
65
143
|
|
|
66
144
|
let containerSize = $state({ width: 0, height: 0 });
|
|
145
|
+
let sizeConfig = $derived(sizePresets[size]);
|
|
146
|
+
let effectiveThickness = $derived(thickness || sizeConfig.thickness);
|
|
147
|
+
let effectiveHeight = $derived(sizeConfig.height);
|
|
148
|
+
|
|
67
149
|
let width = $derived(containerSize.width);
|
|
68
150
|
let height = $derived(containerSize.height);
|
|
69
|
-
let
|
|
70
|
-
|
|
151
|
+
let viewBoxSize = $derived(
|
|
152
|
+
Math.min(containerSize.width, containerSize.height) || effectiveHeight
|
|
153
|
+
);
|
|
71
154
|
let center = $derived(viewBoxSize / 2);
|
|
72
155
|
|
|
73
156
|
let responsiveThickness = $derived(() => {
|
|
74
157
|
const baseSize = 300;
|
|
75
|
-
const scale =
|
|
76
|
-
return Math.max(8, Math.min(
|
|
158
|
+
const scale = viewBoxSize / baseSize;
|
|
159
|
+
return Math.max(8, Math.min(effectiveThickness * scale, effectiveThickness));
|
|
77
160
|
});
|
|
78
161
|
|
|
79
162
|
let responsiveGap = $derived(() => {
|
|
80
163
|
const baseSize = 300;
|
|
81
|
-
const scale =
|
|
164
|
+
const scale = viewBoxSize / baseSize;
|
|
82
165
|
return Math.max(4, Math.min(gap * scale, gap));
|
|
83
166
|
});
|
|
84
167
|
|
|
85
|
-
let maxRadius = $derived(center - 20);
|
|
168
|
+
let maxRadius = $derived(outerRadius || center - 20);
|
|
86
169
|
|
|
87
170
|
let responsiveLabelFontSize = $derived(() => {
|
|
88
171
|
const baseSize = 300;
|
|
89
|
-
const scale =
|
|
90
|
-
return Math.max(10, Math.min(
|
|
172
|
+
const scale = viewBoxSize / baseSize;
|
|
173
|
+
return Math.max(10, Math.min(sizeConfig.labelSize * scale, sizeConfig.labelSize));
|
|
91
174
|
});
|
|
92
175
|
|
|
93
176
|
let responsiveValueFontSize = $derived(() => {
|
|
94
177
|
const baseSize = 300;
|
|
95
|
-
const scale =
|
|
96
|
-
return Math.max(16, Math.min(
|
|
178
|
+
const scale = viewBoxSize / baseSize;
|
|
179
|
+
return Math.max(16, Math.min(sizeConfig.valueSize * scale, sizeConfig.valueSize));
|
|
97
180
|
});
|
|
98
181
|
|
|
99
|
-
let shouldShowText = $derived(
|
|
182
|
+
let shouldShowText = $derived(viewBoxSize >= 120);
|
|
100
183
|
|
|
101
|
-
let computedCenterValue = $derived(
|
|
184
|
+
let computedCenterValue = $derived(() => {
|
|
185
|
+
const val = centerValue || data.reduce((sum, arc) => sum + arc.value, 0);
|
|
186
|
+
return valueFormatter && typeof val === 'number' ? valueFormatter(val) : val;
|
|
187
|
+
});
|
|
102
188
|
|
|
103
189
|
let totalMax = $derived(() => {
|
|
104
190
|
if (data.length === 0) return 0;
|
|
@@ -108,6 +194,14 @@
|
|
|
108
194
|
return data.reduce((sum, arc) => sum + (arc.max || arc.value), 0);
|
|
109
195
|
});
|
|
110
196
|
|
|
197
|
+
// Arc angle calculations
|
|
198
|
+
let angleRange = $derived(endAngle - startAngle);
|
|
199
|
+
|
|
200
|
+
// Generate gradient IDs
|
|
201
|
+
let gradientIds = $derived(
|
|
202
|
+
data.map((_, i) => `arc-gradient-${i}-${Math.random().toString(36).slice(2, 9)}`)
|
|
203
|
+
);
|
|
204
|
+
|
|
111
205
|
onMount(() => {
|
|
112
206
|
const updateSize = () => {
|
|
113
207
|
if (containerEl) {
|
|
@@ -116,17 +210,28 @@
|
|
|
116
210
|
}
|
|
117
211
|
};
|
|
118
212
|
|
|
213
|
+
const handleScroll = () => {
|
|
214
|
+
if (isTooltipActive) {
|
|
215
|
+
isTooltipActive = false;
|
|
216
|
+
hoveredIndex = null;
|
|
217
|
+
tooltipData = null;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
119
221
|
updateSize();
|
|
120
222
|
const resizeObserver = new ResizeObserver(updateSize);
|
|
121
223
|
if (containerEl) {
|
|
122
224
|
resizeObserver.observe(containerEl);
|
|
123
225
|
}
|
|
124
226
|
|
|
227
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
228
|
+
|
|
125
229
|
return () => {
|
|
126
230
|
if (animationFrameId !== null) {
|
|
127
231
|
cancelAnimationFrame(animationFrameId);
|
|
128
232
|
}
|
|
129
233
|
resizeObserver.disconnect();
|
|
234
|
+
window.removeEventListener('scroll', handleScroll, true);
|
|
130
235
|
};
|
|
131
236
|
});
|
|
132
237
|
|
|
@@ -152,7 +257,7 @@
|
|
|
152
257
|
const easeProgress = 1 - Math.pow(1 - progress, 3);
|
|
153
258
|
|
|
154
259
|
const newPercentages = startPercentages.map(
|
|
155
|
-
(start, i) => start + (targetPercentages[i] - start) * easeProgress
|
|
260
|
+
(start, i) => start + ((targetPercentages[i] || 0) - start) * easeProgress
|
|
156
261
|
);
|
|
157
262
|
|
|
158
263
|
displayPercentages = newPercentages;
|
|
@@ -170,31 +275,86 @@
|
|
|
170
275
|
}
|
|
171
276
|
});
|
|
172
277
|
|
|
173
|
-
function handleArcHover(
|
|
278
|
+
function handleArcHover(
|
|
279
|
+
arc: ArcData,
|
|
280
|
+
percentage: number,
|
|
281
|
+
index: number,
|
|
282
|
+
event: MouseEvent
|
|
283
|
+
): void {
|
|
174
284
|
const target = event.target as SVGCircleElement;
|
|
175
285
|
const rect = target.getBoundingClientRect();
|
|
176
286
|
|
|
177
287
|
const max = data.length === 1 && arc.max !== undefined ? arc.max : totalMax();
|
|
178
288
|
|
|
179
|
-
tooltipData = { ...arc, max, percentage: percentage * 100 };
|
|
289
|
+
tooltipData = { ...arc, max, percentage: percentage * 100, index };
|
|
180
290
|
tooltipPosition = {
|
|
181
291
|
x: rect.left + rect.width / 2,
|
|
182
292
|
y: rect.top - 10
|
|
183
293
|
};
|
|
184
294
|
isTooltipActive = true;
|
|
295
|
+
hoveredIndex = index;
|
|
296
|
+
|
|
297
|
+
if (onHover) {
|
|
298
|
+
onHover(arc, index);
|
|
299
|
+
}
|
|
185
300
|
}
|
|
186
301
|
|
|
187
302
|
function handleArcLeave(): void {
|
|
188
303
|
isTooltipActive = false;
|
|
304
|
+
hoveredIndex = null;
|
|
305
|
+
|
|
306
|
+
if (onHover) {
|
|
307
|
+
onHover(null, -1);
|
|
308
|
+
}
|
|
309
|
+
|
|
189
310
|
setTimeout(() => {
|
|
190
311
|
if (!isTooltipActive) {
|
|
191
312
|
tooltipData = null;
|
|
192
313
|
}
|
|
193
314
|
}, 100);
|
|
194
315
|
}
|
|
316
|
+
|
|
317
|
+
function handleArcClick(arc: ArcData, index: number): void {
|
|
318
|
+
if (onClick && !arc.disabled) {
|
|
319
|
+
onClick(arc, index);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getArcMidpoint(radius: number, percentage: number): { x: number; y: number } {
|
|
324
|
+
const angle = startAngle + (angleRange * percentage) / 2;
|
|
325
|
+
const radians = (angle * Math.PI) / 180;
|
|
326
|
+
return {
|
|
327
|
+
x: center + radius * Math.cos(radians),
|
|
328
|
+
y: center + radius * Math.sin(radians)
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let containerLayoutClass = $derived(() => {
|
|
333
|
+
switch (legendPosition) {
|
|
334
|
+
case 'top':
|
|
335
|
+
return 'flex-col-reverse';
|
|
336
|
+
case 'bottom':
|
|
337
|
+
return 'flex-col';
|
|
338
|
+
case 'left':
|
|
339
|
+
return 'flex-row-reverse';
|
|
340
|
+
case 'right':
|
|
341
|
+
default:
|
|
342
|
+
return 'flex-row';
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
let legendLayoutClass = $derived(() => {
|
|
347
|
+
switch (legendPosition) {
|
|
348
|
+
case 'top':
|
|
349
|
+
case 'bottom':
|
|
350
|
+
return 'flex-row flex-wrap';
|
|
351
|
+
default:
|
|
352
|
+
return 'flex-col';
|
|
353
|
+
}
|
|
354
|
+
});
|
|
195
355
|
</script>
|
|
196
356
|
|
|
197
|
-
<div class={cn('arc-chart-container', rootClass)}>
|
|
357
|
+
<div class={cn('arc-chart-container', `is-${size}`, containerLayoutClass(), rootClass)}>
|
|
198
358
|
{#if loading}
|
|
199
359
|
<div class="arc-chart-loading">
|
|
200
360
|
<svg class="arc-chart-loading-spinner" viewBox="0 0 24 24">
|
|
@@ -218,43 +378,109 @@
|
|
|
218
378
|
<span>{emptyText}</span>
|
|
219
379
|
</div>
|
|
220
380
|
{:else}
|
|
221
|
-
<div bind:this={containerEl} class={cn('arc-chart', chartClass)}>
|
|
381
|
+
<div bind:this={containerEl} class={cn('arc-chart', `is-${size}`, chartClass)}>
|
|
222
382
|
<svg class="arc-chart-svg" viewBox="0 0 {viewBoxSize} {viewBoxSize}">
|
|
383
|
+
{#if showGradient}
|
|
384
|
+
<defs>
|
|
385
|
+
{#each data as arc, i}
|
|
386
|
+
{@const color = getArcColor(arc, i)}
|
|
387
|
+
{#if arc.gradient}
|
|
388
|
+
<linearGradient id={gradientIds[i]} x1="0%" y1="0%" x2="100%" y2="0%">
|
|
389
|
+
<stop offset="0%" style="stop-color:{arc.gradient.from}" />
|
|
390
|
+
<stop offset="100%" style="stop-color:{arc.gradient.to}" />
|
|
391
|
+
</linearGradient>
|
|
392
|
+
{:else}
|
|
393
|
+
<linearGradient id={gradientIds[i]} x1="0%" y1="0%" x2="100%" y2="0%">
|
|
394
|
+
<stop offset="0%" style="stop-color:var(--color-{color})" />
|
|
395
|
+
<stop
|
|
396
|
+
offset="100%"
|
|
397
|
+
style="stop-color:var(--color-{color}-dark, var(--color-{color}))"
|
|
398
|
+
/>
|
|
399
|
+
</linearGradient>
|
|
400
|
+
{/if}
|
|
401
|
+
{/each}
|
|
402
|
+
</defs>
|
|
403
|
+
{/if}
|
|
404
|
+
|
|
223
405
|
{#each data as arc, i}
|
|
224
406
|
{@const currentThickness = responsiveThickness()}
|
|
225
407
|
{@const currentGap = responsiveGap()}
|
|
226
408
|
{@const radius = maxRadius - i * (currentThickness + currentGap)}
|
|
227
|
-
{@const percentage = displayPercentages[i]}
|
|
409
|
+
{@const percentage = displayPercentages[i] || 0}
|
|
228
410
|
{@const circumference = 2 * Math.PI * radius}
|
|
229
|
-
{@const
|
|
230
|
-
|
|
411
|
+
{@const arcLength = (circumference * angleRange) / 360}
|
|
412
|
+
{@const strokeDashoffset = arcLength * (1 - percentage)}
|
|
413
|
+
{@const color = getArcColor(arc, i)}
|
|
414
|
+
{@const isSelected = selected.includes(i)}
|
|
415
|
+
{@const isHovered = hoveredIndex === i}
|
|
416
|
+
{@const isDisabled = arc.disabled}
|
|
417
|
+
|
|
418
|
+
<!-- Background arc -->
|
|
231
419
|
<circle
|
|
232
420
|
cx={center}
|
|
233
421
|
cy={center}
|
|
234
422
|
r={radius}
|
|
235
423
|
class="arc-chart-background"
|
|
236
424
|
stroke-width={currentThickness}
|
|
425
|
+
stroke-linecap={linecap}
|
|
426
|
+
style="
|
|
427
|
+
transform: rotate({startAngle}deg);
|
|
428
|
+
transform-origin: center;
|
|
429
|
+
stroke-dasharray: {arcLength} {circumference};
|
|
430
|
+
"
|
|
237
431
|
/>
|
|
238
432
|
|
|
433
|
+
<!-- Active arc -->
|
|
239
434
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
435
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
240
436
|
<circle
|
|
241
437
|
cx={center}
|
|
242
438
|
cy={center}
|
|
243
439
|
r={radius}
|
|
244
|
-
class=
|
|
440
|
+
class={cn(
|
|
441
|
+
'arc-chart-arc',
|
|
442
|
+
colorClass[color],
|
|
443
|
+
isSelected && 'is-selected',
|
|
444
|
+
showGlow && 'has-glow',
|
|
445
|
+
isHovered && 'is-hovered',
|
|
446
|
+
isDisabled && 'is-disabled'
|
|
447
|
+
)}
|
|
245
448
|
stroke-width={currentThickness}
|
|
449
|
+
stroke-linecap={linecap}
|
|
246
450
|
style="
|
|
247
|
-
stroke-dasharray: {
|
|
451
|
+
stroke-dasharray: {arcLength};
|
|
248
452
|
stroke-dashoffset: {strokeDashoffset};
|
|
249
|
-
transform: rotate(
|
|
453
|
+
transform: rotate({startAngle}deg);
|
|
250
454
|
transform-origin: center;
|
|
455
|
+
{showGradient ? `stroke: url(#${gradientIds[i]});` : ''}
|
|
251
456
|
"
|
|
252
|
-
onmouseenter={(e) => handleArcHover(arc, percentage, e)}
|
|
457
|
+
onmouseenter={(e) => handleArcHover(arc, percentage, i, e)}
|
|
253
458
|
onmouseleave={handleArcLeave}
|
|
459
|
+
onclick={() => handleArcClick(arc, i)}
|
|
254
460
|
/>
|
|
461
|
+
|
|
462
|
+
{#if showInlineLabels && arc.label && percentage > 0.1}
|
|
463
|
+
{@const midpoint = getArcMidpoint(radius, percentage)}
|
|
464
|
+
<text
|
|
465
|
+
x={midpoint.x}
|
|
466
|
+
y={midpoint.y}
|
|
467
|
+
class="arc-chart-inline-label"
|
|
468
|
+
text-anchor="middle"
|
|
469
|
+
dominant-baseline="middle"
|
|
470
|
+
style="font-size: {responsiveLabelFontSize() * 0.8}px;"
|
|
471
|
+
>
|
|
472
|
+
{arc.label}
|
|
473
|
+
</text>
|
|
474
|
+
{/if}
|
|
255
475
|
{/each}
|
|
256
476
|
|
|
257
|
-
{#if
|
|
477
|
+
{#if centerContent}
|
|
478
|
+
<foreignObject x={center - 50} y={center - 30} width="100" height="60">
|
|
479
|
+
<div class="arc-chart-center-custom">
|
|
480
|
+
{@render centerContent()}
|
|
481
|
+
</div>
|
|
482
|
+
</foreignObject>
|
|
483
|
+
{:else if (centerText || computedCenterValue()) && shouldShowText}
|
|
258
484
|
<text
|
|
259
485
|
x={center}
|
|
260
486
|
y={center - 10}
|
|
@@ -273,7 +499,7 @@
|
|
|
273
499
|
dominant-baseline="middle"
|
|
274
500
|
style="font-size: {responsiveValueFontSize()}px;"
|
|
275
501
|
>
|
|
276
|
-
{computedCenterValue}
|
|
502
|
+
{computedCenterValue()}
|
|
277
503
|
</text>
|
|
278
504
|
{/if}
|
|
279
505
|
</svg>
|
|
@@ -284,32 +510,49 @@
|
|
|
284
510
|
class="arc-chart-tooltip"
|
|
285
511
|
style="left: {tooltipPosition.x}px; top: {tooltipPosition.y}px;"
|
|
286
512
|
>
|
|
287
|
-
|
|
288
|
-
{
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
513
|
+
{#if tooltipContent}
|
|
514
|
+
{@render tooltipContent({ arc: tooltipData, percentage: tooltipData.percentage })}
|
|
515
|
+
{:else}
|
|
516
|
+
<div class="arc-chart-tooltip-content">
|
|
517
|
+
{#if tooltipData.label}
|
|
518
|
+
<div class="arc-chart-tooltip-title">{tooltipData.label}</div>
|
|
519
|
+
{/if}
|
|
520
|
+
<div class="arc-chart-tooltip-row">
|
|
521
|
+
<div
|
|
522
|
+
class="arc-chart-tooltip-color is-{getArcColor(tooltipData, tooltipData.index)}"
|
|
523
|
+
></div>
|
|
524
|
+
<span class="arc-chart-tooltip-value">
|
|
525
|
+
{formatValue(tooltipData.value, tooltipData.unit)} / {formatValue(
|
|
526
|
+
tooltipData.max || 0,
|
|
527
|
+
tooltipData.unit
|
|
528
|
+
)} ({tooltipData.percentage.toFixed(1)}%)
|
|
529
|
+
</span>
|
|
530
|
+
</div>
|
|
296
531
|
</div>
|
|
297
|
-
|
|
532
|
+
{/if}
|
|
298
533
|
</div>
|
|
299
534
|
{/if}
|
|
300
535
|
|
|
301
|
-
{#if showLegend}
|
|
302
|
-
<div class=
|
|
536
|
+
{#if showLegend && legendPosition !== 'none'}
|
|
537
|
+
<div class={cn('arc-chart-legend', legendLayoutClass())}>
|
|
303
538
|
{#each data as arc, i}
|
|
304
|
-
{@const percentage = displayPercentages[i]}
|
|
539
|
+
{@const percentage = displayPercentages[i] || 0}
|
|
540
|
+
{@const color = getArcColor(arc, i)}
|
|
305
541
|
{@const max = data.length === 1 && arc.max !== undefined ? arc.max : totalMax()}
|
|
306
|
-
|
|
307
|
-
|
|
542
|
+
{@const isSelected = selected.includes(i)}
|
|
543
|
+
<div
|
|
544
|
+
class={cn('arc-chart-legend-item', isSelected && 'is-selected')}
|
|
545
|
+
onclick={() => handleArcClick(arc, i)}
|
|
546
|
+
onkeydown={(e) => e.key === 'Enter' && handleArcClick(arc, i)}
|
|
547
|
+
role="button"
|
|
548
|
+
tabindex="0"
|
|
549
|
+
>
|
|
550
|
+
<div class={cn('arc-chart-legend-color', colorClass[color])}></div>
|
|
308
551
|
<span>{arc.label || `Arc ${i + 1}`}</span>
|
|
309
552
|
{#if showValues}
|
|
310
553
|
<span class="arc-chart-legend-value">
|
|
311
|
-
({arc.value}{#if max}
|
|
312
|
-
/ {max}{/if})
|
|
554
|
+
({formatValue(arc.value, arc.unit)}{#if max}
|
|
555
|
+
/ {formatValue(max, arc.unit)}{/if})
|
|
313
556
|
</span>
|
|
314
557
|
{/if}
|
|
315
558
|
</div>
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
1
2
|
type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
|
|
3
|
+
type Size = 'sm' | 'md' | 'lg' | 'xl';
|
|
4
|
+
type LegendPosition = 'top' | 'right' | 'bottom' | 'left' | 'none';
|
|
5
|
+
type Linecap = 'round' | 'butt' | 'square';
|
|
6
|
+
type Palette = 'default' | 'rainbow' | 'ocean' | 'sunset' | 'forest' | 'neon';
|
|
2
7
|
type ArcData = {
|
|
3
8
|
value: number;
|
|
4
9
|
max?: number;
|
|
5
|
-
color
|
|
10
|
+
color?: Color;
|
|
11
|
+
gradient?: {
|
|
12
|
+
from: string;
|
|
13
|
+
to: string;
|
|
14
|
+
};
|
|
6
15
|
label?: string;
|
|
16
|
+
unit?: string;
|
|
17
|
+
disabled?: boolean;
|
|
7
18
|
};
|
|
8
19
|
type Props = {
|
|
9
20
|
data?: ArcData[];
|
|
@@ -20,6 +31,26 @@ type Props = {
|
|
|
20
31
|
showValues?: boolean;
|
|
21
32
|
rootClass?: string;
|
|
22
33
|
chartClass?: string;
|
|
34
|
+
size?: Size;
|
|
35
|
+
innerRadius?: number;
|
|
36
|
+
outerRadius?: number;
|
|
37
|
+
startAngle?: number;
|
|
38
|
+
endAngle?: number;
|
|
39
|
+
showGradient?: boolean;
|
|
40
|
+
showGlow?: boolean;
|
|
41
|
+
linecap?: Linecap;
|
|
42
|
+
palette?: Palette;
|
|
43
|
+
legendPosition?: LegendPosition;
|
|
44
|
+
valueFormatter?: (value: number) => string;
|
|
45
|
+
onClick?: (arc: ArcData, index: number) => void;
|
|
46
|
+
onHover?: (arc: ArcData | null, index: number) => void;
|
|
47
|
+
selected?: number[];
|
|
48
|
+
showInlineLabels?: boolean;
|
|
49
|
+
centerContent?: Snippet;
|
|
50
|
+
tooltipContent?: Snippet<[{
|
|
51
|
+
arc: ArcData;
|
|
52
|
+
percentage: number;
|
|
53
|
+
}]>;
|
|
23
54
|
};
|
|
24
55
|
declare const ArcChart: import("svelte").Component<Props, {}, "">;
|
|
25
56
|
type ArcChart = ReturnType<typeof ArcChart>;
|