ui-svelte 0.2.11 → 0.2.13
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/README.md +2 -2
- package/dist/charts/ArcChart.svelte +9 -14
- package/dist/charts/ArcChart.svelte.d.ts +3 -3
- package/dist/charts/AreaChart.svelte +347 -118
- package/dist/charts/AreaChart.svelte.d.ts +33 -4
- package/dist/charts/BarChart.svelte +288 -66
- package/dist/charts/BarChart.svelte.d.ts +26 -1
- package/dist/charts/Candlestick.svelte +53 -50
- package/dist/charts/Candlestick.svelte.d.ts +8 -8
- package/dist/charts/LineChart.svelte +391 -91
- package/dist/charts/LineChart.svelte.d.ts +26 -3
- package/dist/charts/PieChart.svelte +333 -92
- package/dist/charts/PieChart.svelte.d.ts +33 -5
- package/dist/charts/css/arc-chart.css +3 -3
- package/dist/charts/css/area-chart.css +127 -29
- package/dist/charts/css/bar-chart.css +114 -8
- package/dist/charts/css/candlestick.css +2 -0
- package/dist/charts/css/line-chart.css +111 -13
- package/dist/charts/css/pie-chart.css +92 -20
- package/dist/control/Audio.svelte +86 -44
- package/dist/control/Audio.svelte.d.ts +4 -1
- package/dist/control/Button.svelte +18 -27
- package/dist/control/Button.svelte.d.ts +3 -2
- package/dist/control/Fab.svelte +103 -0
- package/dist/control/Fab.svelte.d.ts +25 -0
- package/dist/control/IconButton.svelte +17 -27
- package/dist/control/IconButton.svelte.d.ts +3 -3
- package/dist/control/Image.svelte +123 -0
- package/dist/control/Image.svelte.d.ts +13 -0
- package/dist/control/Record.svelte +141 -98
- package/dist/control/Record.svelte.d.ts +2 -1
- package/dist/control/ToggleGroup.svelte +22 -8
- package/dist/control/ToggleGroup.svelte.d.ts +2 -1
- package/dist/control/ToggleTheme.svelte +13 -11
- package/dist/control/ToggleTheme.svelte.d.ts +3 -2
- package/dist/control/Video.svelte +57 -29
- package/dist/control/Video.svelte.d.ts +1 -0
- package/dist/control/css/btn.css +200 -152
- package/dist/control/css/fab.css +84 -0
- package/dist/control/css/image.css +56 -0
- package/dist/control/css/media.css +95 -30
- package/dist/control/css/toggle-group.css +253 -84
- package/dist/control/css/video.css +1 -14
- package/dist/css/animations.css +5 -9
- package/dist/css/base.css +13 -347
- package/dist/css/decorations.css +561 -0
- package/dist/css/rich-text.css +485 -0
- package/dist/css/transitions.css +158 -0
- package/dist/css/typography.css +291 -0
- package/dist/css/utilities.css +0 -4
- package/dist/display/Accordion.svelte +28 -4
- package/dist/display/Accordion.svelte.d.ts +2 -1
- package/dist/display/Alert.svelte +32 -12
- package/dist/display/Alert.svelte.d.ts +2 -3
- package/dist/display/Avatar.svelte +23 -18
- package/dist/display/Avatar.svelte.d.ts +4 -1
- package/dist/display/AvatarGroup.svelte +20 -18
- package/dist/display/AvatarGroup.svelte.d.ts +6 -3
- package/dist/display/Badge.svelte +11 -4
- package/dist/display/Badge.svelte.d.ts +2 -1
- package/dist/display/Card.svelte +15 -14
- package/dist/display/Card.svelte.d.ts +2 -3
- package/dist/display/Carousel.svelte +130 -99
- package/dist/display/Carousel.svelte.d.ts +6 -4
- package/dist/display/ChatBox.svelte +245 -106
- package/dist/display/ChatBox.svelte.d.ts +32 -5
- package/dist/display/Chip.svelte +31 -17
- package/dist/display/Chip.svelte.d.ts +3 -2
- package/dist/display/Code.svelte +7 -4
- package/dist/display/Code.svelte.d.ts +1 -0
- package/dist/display/Collapsible.svelte +30 -4
- package/dist/display/Collapsible.svelte.d.ts +2 -1
- package/dist/display/Countdown.svelte +169 -0
- package/dist/display/Countdown.svelte.d.ts +21 -0
- package/dist/display/Empty.svelte +37 -3
- package/dist/display/Empty.svelte.d.ts +3 -0
- package/dist/display/Item.svelte +42 -11
- package/dist/display/Item.svelte.d.ts +4 -2
- package/dist/display/Map.svelte +488 -0
- package/dist/display/Map.svelte.d.ts +44 -0
- package/dist/display/Marquee.svelte +0 -2
- package/dist/display/Section.svelte +14 -12
- package/dist/display/Section.svelte.d.ts +2 -3
- package/dist/display/Skeleton.svelte +32 -0
- package/dist/display/Skeleton.svelte.d.ts +10 -0
- package/dist/display/Table.svelte +94 -132
- package/dist/display/Table.svelte.d.ts +10 -1
- package/dist/display/css/accordion.css +349 -52
- package/dist/display/css/alert.css +38 -18
- package/dist/display/css/avatar-group.css +38 -75
- package/dist/display/css/avatar.css +139 -121
- package/dist/display/css/badge.css +50 -27
- package/dist/display/css/card.css +123 -71
- package/dist/display/css/carousel.css +25 -5
- package/dist/display/css/chat-box.css +158 -26
- package/dist/display/css/chip.css +142 -68
- package/dist/display/css/code.css +2 -6
- package/dist/display/css/collapsible.css +349 -45
- package/dist/display/css/countdown.css +206 -0
- package/dist/display/css/divider.css +8 -6
- package/dist/display/css/empty.css +7 -0
- package/dist/display/css/item.css +330 -84
- package/dist/display/css/map.css +164 -0
- package/dist/display/css/marquee.css +0 -3
- package/dist/display/css/section.css +89 -65
- package/dist/display/css/skeleton.css +58 -0
- package/dist/display/css/table.css +309 -193
- package/dist/form/Checkbox.svelte +11 -5
- package/dist/form/Checkbox.svelte.d.ts +2 -1
- package/dist/form/ColorField.svelte +601 -0
- package/dist/form/ColorField.svelte.d.ts +29 -0
- package/dist/form/ComboBox.svelte +24 -9
- package/dist/form/ComboBox.svelte.d.ts +2 -2
- package/dist/form/CsvField.svelte +62 -136
- package/dist/form/CsvField.svelte.d.ts +2 -2
- package/dist/form/DateField.svelte +33 -15
- package/dist/form/DateField.svelte.d.ts +2 -1
- package/dist/form/DateRange.svelte +436 -0
- package/dist/form/DateRange.svelte.d.ts +24 -0
- package/dist/form/DragDrop.svelte +578 -0
- package/dist/form/DragDrop.svelte.d.ts +33 -0
- package/dist/form/Dropzone.svelte +28 -8
- package/dist/form/Dropzone.svelte.d.ts +2 -2
- package/dist/form/Editor.svelte +626 -0
- package/dist/form/Editor.svelte.d.ts +50 -0
- package/dist/form/ImageCropper.svelte +422 -61
- package/dist/form/ImageCropper.svelte.d.ts +15 -1
- package/dist/form/{PasswordStrength.svelte → PasswordField.svelte} +58 -24
- package/dist/form/{PasswordStrength.svelte.d.ts → PasswordField.svelte.d.ts} +6 -5
- package/dist/form/PhoneField.svelte +26 -14
- package/dist/form/PhoneField.svelte.d.ts +4 -3
- package/dist/form/PinField.svelte +39 -31
- package/dist/form/PinField.svelte.d.ts +3 -3
- package/dist/form/RadioGroup.svelte +9 -5
- package/dist/form/RadioGroup.svelte.d.ts +1 -1
- package/dist/form/Select.svelte +20 -19
- package/dist/form/Select.svelte.d.ts +2 -2
- package/dist/form/Slider.svelte +10 -4
- package/dist/form/Slider.svelte.d.ts +2 -1
- package/dist/form/TextField.svelte +29 -11
- package/dist/form/TextField.svelte.d.ts +5 -4
- package/dist/form/Textarea.svelte +15 -6
- package/dist/form/Textarea.svelte.d.ts +2 -2
- package/dist/form/Toggle.svelte +7 -3
- package/dist/form/Toggle.svelte.d.ts +1 -1
- package/dist/form/css/checkbox.css +18 -2
- package/dist/form/css/color-field.css +141 -0
- package/dist/form/css/control.css +193 -82
- package/dist/form/css/csv-field.css +221 -0
- package/dist/form/css/date-range.css +122 -0
- package/dist/form/css/date.css +24 -2
- package/dist/form/css/drag-drop.css +234 -0
- package/dist/form/css/dropzone.css +153 -34
- package/dist/form/css/editor.css +367 -0
- package/dist/form/css/field.css +4 -0
- package/dist/form/css/image-cropper.css +242 -20
- package/dist/form/css/radio-group.css +26 -1
- package/dist/form/css/select.css +2 -2
- package/dist/form/css/slider.css +37 -0
- package/dist/form/css/textarea.css +178 -75
- package/dist/form/css/toggle.css +15 -3
- package/dist/hooks/use-chat.svelte.js +1 -1
- package/dist/hooks/use-form.svelte.js +3 -3
- package/dist/hooks/use-search.svelte.js +0 -3
- package/dist/hooks/use-table.svelte.d.ts +1 -0
- package/dist/hooks/use-table.svelte.js +6 -0
- package/dist/icons/index.d.ts +34 -2
- package/dist/icons/index.js +36 -4
- package/dist/index.css +44 -49
- package/dist/index.d.ts +14 -4
- package/dist/index.js +13 -3
- package/dist/layout/AppBar.svelte +22 -14
- package/dist/layout/AppBar.svelte.d.ts +2 -1
- package/dist/layout/Footer.svelte +19 -11
- package/dist/layout/Footer.svelte.d.ts +2 -1
- package/dist/layout/Provider.svelte +32 -9
- package/dist/layout/Provider.svelte.d.ts +3 -1
- package/dist/layout/Sidebar.svelte +17 -8
- package/dist/layout/Sidebar.svelte.d.ts +2 -1
- package/dist/layout/css/app-bar.css +63 -66
- package/dist/layout/css/footer.css +62 -65
- package/dist/layout/css/sidebar.css +120 -59
- package/dist/navigation/BottomNav.svelte +51 -14
- package/dist/navigation/FooterGroup.svelte +1 -1
- package/dist/navigation/NavMenu.svelte +47 -23
- package/dist/navigation/NavMenu.svelte.d.ts +29 -0
- package/dist/navigation/Pagination.svelte +158 -0
- package/dist/navigation/Pagination.svelte.d.ts +18 -0
- package/dist/navigation/SideNav.svelte +30 -25
- package/dist/navigation/SideNav.svelte.d.ts +2 -3
- package/dist/navigation/Tabs.svelte +17 -7
- package/dist/navigation/Tabs.svelte.d.ts +2 -2
- package/dist/navigation/css/bottom-nav.css +319 -257
- package/dist/navigation/css/footer-group.css +1 -1
- package/dist/navigation/css/footer-nav.css +1 -1
- package/dist/navigation/css/nav-menu.css +331 -106
- package/dist/navigation/css/pagination.css +74 -0
- package/dist/navigation/css/side-nav.css +514 -75
- package/dist/navigation/css/tabs.css +246 -52
- package/dist/overlay/AlertDialog.svelte +58 -0
- package/dist/overlay/AlertDialog.svelte.d.ts +14 -25
- package/dist/overlay/Command.svelte +347 -0
- package/dist/overlay/Command.svelte.d.ts +33 -25
- package/dist/overlay/Drawer.svelte +49 -21
- package/dist/overlay/Drawer.svelte.d.ts +2 -2
- package/dist/overlay/Dropdown.svelte +3 -3
- package/dist/overlay/Modal.svelte +51 -16
- package/dist/overlay/Modal.svelte.d.ts +3 -3
- package/dist/overlay/Toast.svelte +41 -17
- package/dist/overlay/Toast.svelte.d.ts +1 -1
- package/dist/overlay/Tooltip.svelte +36 -27
- package/dist/overlay/Tooltip.svelte.d.ts +2 -2
- package/dist/overlay/css/command.css +68 -0
- package/dist/overlay/css/drawer.css +63 -24
- package/dist/overlay/css/dropdown.css +1 -1
- package/dist/overlay/css/hovercard.css +1 -1
- package/dist/overlay/css/modal.css +79 -27
- package/dist/overlay/css/toast.css +40 -24
- package/dist/overlay/css/tooltip.css +110 -67
- package/dist/stores/theme.svelte.js +44 -12
- package/dist/stores/toast.svelte.d.ts +4 -4
- package/dist/stores/toast.svelte.js +2 -2
- package/package.json +1 -1
- package/dist/utils/charts.d.ts +0 -27
- package/dist/utils/charts.js +0 -140
|
@@ -1,4 +1,8 @@
|
|
|
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 Palette = 'default' | 'rainbow' | 'ocean' | 'sunset' | 'forest' | 'neon';
|
|
2
6
|
type DataPoint = {
|
|
3
7
|
x: number;
|
|
4
8
|
y: number;
|
|
@@ -20,9 +24,11 @@ type Props = {
|
|
|
20
24
|
margin?: Margin;
|
|
21
25
|
color?: Color;
|
|
22
26
|
colors?: Color[];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
hidePoints?: boolean;
|
|
28
|
+
hideGrid?: boolean;
|
|
29
|
+
hideLegend?: boolean;
|
|
30
|
+
hideXAxis?: boolean;
|
|
31
|
+
hideYAxis?: boolean;
|
|
26
32
|
curve?: 'linear' | 'smooth';
|
|
27
33
|
strokeWidth?: number;
|
|
28
34
|
pointRadius?: number;
|
|
@@ -30,6 +36,23 @@ type Props = {
|
|
|
30
36
|
empty?: boolean;
|
|
31
37
|
emptyText?: string;
|
|
32
38
|
rootClass?: string;
|
|
39
|
+
chartClass?: string;
|
|
40
|
+
size?: Size;
|
|
41
|
+
palette?: Palette;
|
|
42
|
+
legendPosition?: LegendPosition;
|
|
43
|
+
disableAnimation?: boolean;
|
|
44
|
+
animationDuration?: number;
|
|
45
|
+
valueFormatter?: (value: number) => string;
|
|
46
|
+
xFormatter?: (value: number) => string;
|
|
47
|
+
onClick?: (point: DataPoint, seriesName: string, index: number) => void;
|
|
48
|
+
onHover?: (point: DataPoint | null, seriesName: string, index: number) => void;
|
|
49
|
+
tooltipContent?: Snippet<[{
|
|
50
|
+
point: DataPoint;
|
|
51
|
+
seriesName: string;
|
|
52
|
+
color: Color;
|
|
53
|
+
}]>;
|
|
54
|
+
showGradientFill?: boolean;
|
|
55
|
+
showGlow?: boolean;
|
|
33
56
|
};
|
|
34
57
|
declare const LineChart: import("svelte").Component<Props, {}, "">;
|
|
35
58
|
type LineChart = ReturnType<typeof LineChart>;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../utils/class-names.js';
|
|
3
|
-
import { onMount } from 'svelte';
|
|
3
|
+
import { onMount, untrack, type Snippet } from 'svelte';
|
|
4
|
+
|
|
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 Palette = 'default' | 'rainbow' | 'ocean' | 'sunset' | 'forest' | 'neon';
|
|
4
9
|
|
|
5
10
|
type Slice = DataPoint & {
|
|
6
11
|
startAngle: number;
|
|
@@ -8,57 +13,97 @@
|
|
|
8
13
|
midAngle: number;
|
|
9
14
|
percentage: number;
|
|
10
15
|
color: Color;
|
|
16
|
+
index: number;
|
|
11
17
|
};
|
|
12
18
|
|
|
13
|
-
type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
|
|
14
|
-
|
|
15
19
|
type DataPoint = {
|
|
16
20
|
label: string;
|
|
17
21
|
value: number;
|
|
18
22
|
color?: Color;
|
|
23
|
+
disabled?: boolean;
|
|
19
24
|
};
|
|
20
25
|
|
|
21
26
|
type Props = {
|
|
22
27
|
data?: DataPoint[];
|
|
23
|
-
colors?: Color[];
|
|
24
28
|
donut?: boolean;
|
|
25
29
|
donutWidth?: number;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
showPercentages?: boolean;
|
|
30
|
+
size?: Size;
|
|
31
|
+
palette?: Palette;
|
|
32
|
+
legendPosition?: LegendPosition;
|
|
30
33
|
centerLabel?: string;
|
|
31
34
|
centerValue?: string | number;
|
|
32
35
|
startAngle?: number;
|
|
33
36
|
padAngle?: number;
|
|
37
|
+
disableAnimation?: boolean;
|
|
38
|
+
animationDuration?: number;
|
|
39
|
+
showGlow?: boolean;
|
|
40
|
+
showGradient?: boolean;
|
|
41
|
+
hideLabels?: boolean;
|
|
42
|
+
hideValues?: boolean;
|
|
43
|
+
hideLegend?: boolean;
|
|
44
|
+
hidePercentages?: boolean;
|
|
34
45
|
loading?: boolean;
|
|
35
46
|
empty?: boolean;
|
|
36
47
|
emptyText?: string;
|
|
37
48
|
rootClass?: string;
|
|
38
49
|
chartClass?: string;
|
|
50
|
+
valueFormatter?: (value: number) => string;
|
|
51
|
+
onClick?: (slice: DataPoint, index: number) => void;
|
|
52
|
+
onHover?: (slice: DataPoint | null, index: number) => void;
|
|
53
|
+
selected?: number[];
|
|
54
|
+
centerContent?: Snippet;
|
|
55
|
+
tooltipContent?: Snippet<[{ slice: Slice; percentage: number }]>;
|
|
39
56
|
};
|
|
40
57
|
|
|
41
58
|
let {
|
|
42
59
|
data = [],
|
|
43
|
-
colors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'] as Color[],
|
|
44
60
|
donut = false,
|
|
45
61
|
donutWidth = 60,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
showPercentages = true,
|
|
62
|
+
size = 'md',
|
|
63
|
+
palette,
|
|
64
|
+
legendPosition = 'right',
|
|
50
65
|
centerLabel = 'Total',
|
|
51
66
|
centerValue = undefined,
|
|
52
67
|
startAngle = -90,
|
|
53
68
|
padAngle = 0,
|
|
69
|
+
disableAnimation = false,
|
|
70
|
+
animationDuration = 800,
|
|
71
|
+
showGlow = false,
|
|
72
|
+
showGradient = false,
|
|
73
|
+
hideLabels = false,
|
|
74
|
+
hideValues = false,
|
|
75
|
+
hideLegend = false,
|
|
76
|
+
hidePercentages = false,
|
|
54
77
|
loading = false,
|
|
55
78
|
empty = false,
|
|
56
79
|
emptyText = 'No data available',
|
|
57
80
|
rootClass,
|
|
58
|
-
chartClass
|
|
81
|
+
chartClass,
|
|
82
|
+
valueFormatter,
|
|
83
|
+
onClick,
|
|
84
|
+
onHover,
|
|
85
|
+
selected = [],
|
|
86
|
+
centerContent,
|
|
87
|
+
tooltipContent
|
|
59
88
|
}: Props = $props();
|
|
60
89
|
|
|
61
|
-
const
|
|
90
|
+
const sizePresets: Record<Size, { height: number; labelSize: number; valueSize: number }> = {
|
|
91
|
+
sm: { height: 150, labelSize: 10, valueSize: 18 },
|
|
92
|
+
md: { height: 224, labelSize: 12, valueSize: 24 },
|
|
93
|
+
lg: { height: 300, labelSize: 14, valueSize: 32 },
|
|
94
|
+
xl: { height: 400, labelSize: 16, valueSize: 40 }
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const colorPalettes: Record<Palette, Color[]> = {
|
|
98
|
+
default: ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'],
|
|
99
|
+
rainbow: ['danger', 'warning', 'success', 'info', 'primary', 'secondary', 'muted'],
|
|
100
|
+
ocean: ['info', 'primary', 'secondary', 'success', 'muted', 'warning', 'danger'],
|
|
101
|
+
sunset: ['warning', 'danger', 'secondary', 'primary', 'info', 'success', 'muted'],
|
|
102
|
+
forest: ['success', 'primary', 'info', 'secondary', 'muted', 'warning', 'danger'],
|
|
103
|
+
neon: ['secondary', 'primary', 'success', 'warning', 'danger', 'info', 'muted']
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const colorClass: Record<Color, string> = {
|
|
62
107
|
primary: 'is-primary',
|
|
63
108
|
secondary: 'is-secondary',
|
|
64
109
|
success: 'is-success',
|
|
@@ -68,39 +113,71 @@
|
|
|
68
113
|
muted: 'is-muted'
|
|
69
114
|
};
|
|
70
115
|
|
|
116
|
+
function getSliceColor(item: DataPoint, index: number): Color {
|
|
117
|
+
if (item.color) return item.color;
|
|
118
|
+
if (palette) {
|
|
119
|
+
const paletteColors = colorPalettes[palette];
|
|
120
|
+
return paletteColors[index % paletteColors.length];
|
|
121
|
+
}
|
|
122
|
+
return colorPalettes.default[index % colorPalettes.default.length];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatValue(value: number): string {
|
|
126
|
+
if (valueFormatter) return valueFormatter(value);
|
|
127
|
+
return String(value);
|
|
128
|
+
}
|
|
129
|
+
|
|
71
130
|
let containerEl: HTMLDivElement | undefined = $state();
|
|
72
131
|
let containerSize = $state({ width: 0, height: 0 });
|
|
132
|
+
let animationFrameId: number | null = null;
|
|
73
133
|
|
|
74
|
-
let
|
|
75
|
-
let
|
|
134
|
+
let tooltipData = $state<Slice | null>(null);
|
|
135
|
+
let tooltipPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
136
|
+
let isTooltipActive = $state(false);
|
|
137
|
+
let hoveredIndex = $state<number | null>(null);
|
|
138
|
+
|
|
139
|
+
let sizeConfig = $derived(sizePresets[size]);
|
|
140
|
+
let effectiveHeight = $derived(sizeConfig.height);
|
|
141
|
+
|
|
142
|
+
let width = $derived(containerSize.width || effectiveHeight);
|
|
143
|
+
let height = $derived(containerSize.height || effectiveHeight);
|
|
144
|
+
let viewBoxSize = $derived(Math.min(width, height) || effectiveHeight);
|
|
76
145
|
|
|
77
146
|
let total = $derived(data.reduce((sum, d) => sum + d.value, 0));
|
|
78
147
|
|
|
148
|
+
// svelte-ignore state_referenced_locally
|
|
149
|
+
let displayPercentages = $state<number[]>(data.map(() => 0));
|
|
150
|
+
|
|
79
151
|
let slices = $derived.by((): Slice[] => {
|
|
80
152
|
const startRad = (startAngle * Math.PI) / 180;
|
|
81
153
|
let currentAngle = startRad;
|
|
82
154
|
const padRad = (padAngle * Math.PI) / 180;
|
|
83
155
|
|
|
84
156
|
return data.map((d, i) => {
|
|
85
|
-
const percentage = (d.value / total) * 100;
|
|
86
|
-
const angle = (d.value / total) * 2 * Math.PI - padRad;
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const midAngle =
|
|
157
|
+
const percentage = total > 0 ? (d.value / total) * 100 : 0;
|
|
158
|
+
const angle = total > 0 ? (d.value / total) * 2 * Math.PI - padRad : 0;
|
|
159
|
+
const sliceStartAngle = currentAngle;
|
|
160
|
+
const sliceEndAngle = currentAngle + angle;
|
|
161
|
+
const midAngle = sliceStartAngle + angle / 2;
|
|
90
162
|
|
|
91
|
-
currentAngle =
|
|
163
|
+
currentAngle = sliceEndAngle + padRad;
|
|
92
164
|
|
|
93
165
|
return {
|
|
94
166
|
...d,
|
|
95
|
-
startAngle,
|
|
96
|
-
endAngle,
|
|
167
|
+
startAngle: sliceStartAngle,
|
|
168
|
+
endAngle: sliceEndAngle,
|
|
97
169
|
midAngle,
|
|
98
170
|
percentage,
|
|
99
|
-
color: d
|
|
171
|
+
color: getSliceColor(d, i),
|
|
172
|
+
index: i
|
|
100
173
|
};
|
|
101
174
|
});
|
|
102
175
|
});
|
|
103
176
|
|
|
177
|
+
let gradientIds = $derived(
|
|
178
|
+
data.map((_, i) => `pie-gradient-${i}-${Math.random().toString(36).slice(2, 9)}`)
|
|
179
|
+
);
|
|
180
|
+
|
|
104
181
|
function createArc(
|
|
105
182
|
startAngle: number,
|
|
106
183
|
endAngle: number,
|
|
@@ -130,24 +207,102 @@
|
|
|
130
207
|
return path;
|
|
131
208
|
}
|
|
132
209
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
210
|
+
onMount(() => {
|
|
211
|
+
const updateSize = () => {
|
|
212
|
+
if (containerEl) {
|
|
213
|
+
const rect = containerEl.getBoundingClientRect();
|
|
214
|
+
containerSize = { width: rect.width, height: rect.height };
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const handleScroll = () => {
|
|
219
|
+
if (isTooltipActive) {
|
|
220
|
+
isTooltipActive = false;
|
|
221
|
+
hoveredIndex = null;
|
|
222
|
+
tooltipData = null;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
updateSize();
|
|
227
|
+
const resizeObserver = new ResizeObserver(updateSize);
|
|
228
|
+
if (containerEl) {
|
|
229
|
+
resizeObserver.observe(containerEl);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
233
|
+
|
|
234
|
+
return () => {
|
|
235
|
+
if (animationFrameId !== null) {
|
|
236
|
+
cancelAnimationFrame(animationFrameId);
|
|
237
|
+
}
|
|
238
|
+
resizeObserver.disconnect();
|
|
239
|
+
window.removeEventListener('scroll', handleScroll, true);
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
$effect(() => {
|
|
244
|
+
const targetPercentages = slices.map((s) => s.percentage / 100);
|
|
245
|
+
|
|
246
|
+
if (animationFrameId !== null) {
|
|
247
|
+
cancelAnimationFrame(animationFrameId);
|
|
248
|
+
animationFrameId = null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!disableAnimation) {
|
|
252
|
+
const startTime = Date.now();
|
|
253
|
+
const startPercentages = untrack(() => [...displayPercentages]);
|
|
254
|
+
|
|
255
|
+
const animate = () => {
|
|
256
|
+
const now = Date.now();
|
|
257
|
+
const progress = Math.min((now - startTime) / animationDuration, 1);
|
|
258
|
+
const easeProgress = 1 - Math.pow(1 - progress, 3);
|
|
259
|
+
|
|
260
|
+
const newPercentages = startPercentages.map(
|
|
261
|
+
(start, i) => start + ((targetPercentages[i] || 0) - start) * easeProgress
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
displayPercentages = newPercentages;
|
|
265
|
+
|
|
266
|
+
if (progress < 1) {
|
|
267
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
268
|
+
} else {
|
|
269
|
+
animationFrameId = null;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
274
|
+
} else {
|
|
275
|
+
displayPercentages = targetPercentages;
|
|
276
|
+
}
|
|
277
|
+
});
|
|
136
278
|
|
|
137
279
|
function handleSliceHover(slice: Slice, event: MouseEvent): void {
|
|
280
|
+
if (slice.disabled) return;
|
|
281
|
+
|
|
138
282
|
const target = event.target as SVGPathElement;
|
|
139
283
|
const rect = target.getBoundingClientRect();
|
|
140
284
|
|
|
141
285
|
tooltipData = slice;
|
|
142
286
|
tooltipPosition = {
|
|
143
|
-
x: rect.
|
|
144
|
-
y: rect.top
|
|
287
|
+
x: rect.left + rect.width / 2,
|
|
288
|
+
y: rect.top - 10
|
|
145
289
|
};
|
|
146
290
|
isTooltipActive = true;
|
|
291
|
+
hoveredIndex = slice.index;
|
|
292
|
+
|
|
293
|
+
if (onHover) {
|
|
294
|
+
onHover(slice, slice.index);
|
|
295
|
+
}
|
|
147
296
|
}
|
|
148
297
|
|
|
149
298
|
function handleSliceLeave(): void {
|
|
150
299
|
isTooltipActive = false;
|
|
300
|
+
hoveredIndex = null;
|
|
301
|
+
|
|
302
|
+
if (onHover) {
|
|
303
|
+
onHover(null, -1);
|
|
304
|
+
}
|
|
305
|
+
|
|
151
306
|
setTimeout(() => {
|
|
152
307
|
if (!isTooltipActive) {
|
|
153
308
|
tooltipData = null;
|
|
@@ -155,27 +310,57 @@
|
|
|
155
310
|
}, 100);
|
|
156
311
|
}
|
|
157
312
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
};
|
|
313
|
+
function handleSliceClick(slice: Slice): void {
|
|
314
|
+
if (onClick && !slice.disabled) {
|
|
315
|
+
onClick(slice, slice.index);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
165
318
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
319
|
+
let containerLayoutClass = $derived(() => {
|
|
320
|
+
switch (legendPosition) {
|
|
321
|
+
case 'top':
|
|
322
|
+
return 'flex-col-reverse';
|
|
323
|
+
case 'bottom':
|
|
324
|
+
return 'flex-col';
|
|
325
|
+
case 'left':
|
|
326
|
+
return 'flex-row-reverse';
|
|
327
|
+
case 'right':
|
|
328
|
+
default:
|
|
329
|
+
return 'flex-row';
|
|
170
330
|
}
|
|
331
|
+
});
|
|
171
332
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
333
|
+
let legendLayoutClass = $derived(() => {
|
|
334
|
+
switch (legendPosition) {
|
|
335
|
+
case 'top':
|
|
336
|
+
case 'bottom':
|
|
337
|
+
return 'flex-row flex-wrap';
|
|
338
|
+
default:
|
|
339
|
+
return 'flex-col';
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
let responsiveLabelFontSize = $derived(() => {
|
|
344
|
+
const baseSize = 300;
|
|
345
|
+
const scale = viewBoxSize / baseSize;
|
|
346
|
+
return Math.max(8, Math.min(sizeConfig.labelSize * scale, sizeConfig.labelSize));
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
let responsiveValueFontSize = $derived(() => {
|
|
350
|
+
const baseSize = 300;
|
|
351
|
+
const scale = viewBoxSize / baseSize;
|
|
352
|
+
return Math.max(14, Math.min(sizeConfig.valueSize * scale, sizeConfig.valueSize));
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
let shouldShowCenterText = $derived(viewBoxSize >= 100);
|
|
356
|
+
|
|
357
|
+
let computedCenterValue = $derived(() => {
|
|
358
|
+
const val = centerValue ?? total;
|
|
359
|
+
return valueFormatter && typeof val === 'number' ? valueFormatter(val) : val;
|
|
175
360
|
});
|
|
176
361
|
</script>
|
|
177
362
|
|
|
178
|
-
<div class={cn('pie-chart-container', rootClass)}>
|
|
363
|
+
<div class={cn('pie-chart-container', `is-${size}`, containerLayoutClass(), rootClass)}>
|
|
179
364
|
{#if loading}
|
|
180
365
|
<div class="pie-chart-loading">
|
|
181
366
|
<svg class="pie-chart-loading-spinner" viewBox="0 0 24 24">
|
|
@@ -213,64 +398,107 @@
|
|
|
213
398
|
<span>{emptyText}</span>
|
|
214
399
|
</div>
|
|
215
400
|
{:else}
|
|
216
|
-
<div bind:this={containerEl} class={cn('pie-chart', chartClass)}>
|
|
217
|
-
<svg class="pie-chart-svg">
|
|
401
|
+
<div bind:this={containerEl} class={cn('pie-chart', `is-${size}`, chartClass)}>
|
|
402
|
+
<svg class="pie-chart-svg" viewBox="0 0 {viewBoxSize} {viewBoxSize}">
|
|
403
|
+
{#if showGradient}
|
|
404
|
+
<defs>
|
|
405
|
+
{#each slices as slice, i}
|
|
406
|
+
<linearGradient id={gradientIds[i]} x1="0%" y1="0%" x2="100%" y2="0%">
|
|
407
|
+
<stop offset="0%" style="stop-color:var(--color-{slice.color})" />
|
|
408
|
+
<stop
|
|
409
|
+
offset="100%"
|
|
410
|
+
style="stop-color:var(--color-{slice.color}-dark, var(--color-{slice.color}))"
|
|
411
|
+
/>
|
|
412
|
+
</linearGradient>
|
|
413
|
+
{/each}
|
|
414
|
+
</defs>
|
|
415
|
+
{/if}
|
|
416
|
+
|
|
218
417
|
{#if slices.length > 0}
|
|
219
|
-
{@const centerX =
|
|
220
|
-
{@const centerY =
|
|
221
|
-
{@const radius =
|
|
418
|
+
{@const centerX = viewBoxSize / 2}
|
|
419
|
+
{@const centerY = viewBoxSize / 2}
|
|
420
|
+
{@const radius = viewBoxSize / 2 - 10}
|
|
222
421
|
{@const innerRadius = donut ? radius - donutWidth : 0}
|
|
223
422
|
|
|
224
423
|
<g transform="translate({centerX}, {centerY})">
|
|
225
|
-
{#each slices as slice}
|
|
226
|
-
{@const
|
|
424
|
+
{#each slices as slice, i}
|
|
425
|
+
{@const isSelected = selected.includes(i)}
|
|
426
|
+
{@const isHovered = hoveredIndex === i}
|
|
427
|
+
{@const isDisabled = slice.disabled}
|
|
428
|
+
{@const labelRadius = donut ? radius - donutWidth / 2 : radius * 0.65}
|
|
227
429
|
{@const labelX = Math.cos(slice.midAngle) * labelRadius}
|
|
228
430
|
{@const labelY = Math.sin(slice.midAngle) * labelRadius}
|
|
229
431
|
|
|
230
432
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
433
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
231
434
|
<path
|
|
232
435
|
d={createArc(slice.startAngle, slice.endAngle, innerRadius, radius)}
|
|
233
|
-
class=
|
|
436
|
+
class={cn(
|
|
437
|
+
'pie-chart-slice',
|
|
438
|
+
colorClass[slice.color],
|
|
439
|
+
isSelected && 'is-selected',
|
|
440
|
+
showGlow && 'has-glow',
|
|
441
|
+
isHovered && 'is-hovered',
|
|
442
|
+
isDisabled && 'is-disabled'
|
|
443
|
+
)}
|
|
444
|
+
style={showGradient ? `fill: url(#${gradientIds[i]});` : ''}
|
|
234
445
|
onmouseenter={(e) => handleSliceHover(slice, e)}
|
|
235
446
|
onmouseleave={handleSliceLeave}
|
|
447
|
+
onclick={() => handleSliceClick(slice)}
|
|
236
448
|
/>
|
|
237
449
|
|
|
238
|
-
{#if
|
|
450
|
+
{#if !hideLabels && slice.percentage > 5}
|
|
239
451
|
<text
|
|
240
452
|
x={labelX}
|
|
241
453
|
y={labelY}
|
|
242
454
|
class="pie-chart-slice-label"
|
|
243
455
|
text-anchor="middle"
|
|
244
456
|
dominant-baseline="middle"
|
|
457
|
+
style="font-size: {responsiveLabelFontSize()}px;"
|
|
245
458
|
>
|
|
246
|
-
{#if
|
|
459
|
+
{#if !hidePercentages}
|
|
247
460
|
{slice.percentage.toFixed(1)}%
|
|
248
|
-
{:else if
|
|
249
|
-
{slice.value}
|
|
461
|
+
{:else if !hideValues}
|
|
462
|
+
{formatValue(slice.value)}
|
|
250
463
|
{/if}
|
|
251
464
|
</text>
|
|
252
465
|
{/if}
|
|
253
466
|
{/each}
|
|
254
467
|
|
|
255
|
-
{#if donut}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
468
|
+
{#if donut && shouldShowCenterText}
|
|
469
|
+
{#if centerContent}
|
|
470
|
+
<foreignObject
|
|
471
|
+
x={-innerRadius * 0.7}
|
|
472
|
+
y={-innerRadius * 0.4}
|
|
473
|
+
width={innerRadius * 1.4}
|
|
474
|
+
height={innerRadius * 0.8}
|
|
475
|
+
>
|
|
476
|
+
<div class="pie-chart-center-custom">
|
|
477
|
+
{@render centerContent()}
|
|
478
|
+
</div>
|
|
479
|
+
</foreignObject>
|
|
480
|
+
{:else}
|
|
481
|
+
<text
|
|
482
|
+
x="0"
|
|
483
|
+
y="-8"
|
|
484
|
+
class="pie-chart-center-value"
|
|
485
|
+
text-anchor="middle"
|
|
486
|
+
dominant-baseline="middle"
|
|
487
|
+
style="font-size: {responsiveValueFontSize()}px;"
|
|
488
|
+
>
|
|
489
|
+
{computedCenterValue()}
|
|
490
|
+
</text>
|
|
491
|
+
<text
|
|
492
|
+
x="0"
|
|
493
|
+
y="12"
|
|
494
|
+
class="pie-chart-center-label"
|
|
495
|
+
text-anchor="middle"
|
|
496
|
+
dominant-baseline="middle"
|
|
497
|
+
style="font-size: {responsiveLabelFontSize()}px;"
|
|
498
|
+
>
|
|
499
|
+
{centerLabel}
|
|
500
|
+
</text>
|
|
501
|
+
{/if}
|
|
274
502
|
{/if}
|
|
275
503
|
</g>
|
|
276
504
|
{/if}
|
|
@@ -280,28 +508,41 @@
|
|
|
280
508
|
{#if tooltipData && isTooltipActive}
|
|
281
509
|
<div
|
|
282
510
|
class="pie-chart-tooltip"
|
|
283
|
-
style="
|
|
511
|
+
style="left: {tooltipPosition.x}px; top: {tooltipPosition.y}px;"
|
|
284
512
|
>
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
|
|
513
|
+
{#if tooltipContent}
|
|
514
|
+
{@render tooltipContent({ slice: tooltipData, percentage: tooltipData.percentage })}
|
|
515
|
+
{:else}
|
|
516
|
+
<div class="pie-chart-tooltip-content">
|
|
517
|
+
<div class="pie-chart-tooltip-title">{tooltipData.label}</div>
|
|
518
|
+
<div class="pie-chart-tooltip-row">
|
|
519
|
+
<div class="pie-chart-tooltip-color is-{tooltipData.color}"></div>
|
|
520
|
+
<span class="pie-chart-tooltip-value">
|
|
521
|
+
{formatValue(tooltipData.value)} ({tooltipData.percentage.toFixed(1)}%)
|
|
522
|
+
</span>
|
|
523
|
+
</div>
|
|
292
524
|
</div>
|
|
293
|
-
|
|
525
|
+
{/if}
|
|
294
526
|
</div>
|
|
295
527
|
{/if}
|
|
296
528
|
|
|
297
|
-
{#if
|
|
298
|
-
<div class=
|
|
299
|
-
{#each slices as slice}
|
|
300
|
-
|
|
529
|
+
{#if !hideLegend && legendPosition !== 'none'}
|
|
530
|
+
<div class={cn('pie-chart-legend', legendLayoutClass())}>
|
|
531
|
+
{#each slices as slice, i}
|
|
532
|
+
{@const isSelected = selected.includes(i)}
|
|
533
|
+
<div
|
|
534
|
+
class={cn('pie-chart-legend-item', isSelected && 'is-selected')}
|
|
535
|
+
onclick={() => handleSliceClick(slice)}
|
|
536
|
+
onkeydown={(e) => e.key === 'Enter' && handleSliceClick(slice)}
|
|
537
|
+
role="button"
|
|
538
|
+
tabindex="0"
|
|
539
|
+
>
|
|
301
540
|
<div class={cn('pie-chart-legend-color', colorClass[slice.color])}></div>
|
|
302
541
|
<span>{slice.label}</span>
|
|
303
|
-
{#if
|
|
304
|
-
<span class="pie-chart-legend-value">
|
|
542
|
+
{#if !hideValues}
|
|
543
|
+
<span class="pie-chart-legend-value">
|
|
544
|
+
({formatValue(slice.value)})
|
|
545
|
+
</span>
|
|
305
546
|
{/if}
|
|
306
547
|
</div>
|
|
307
548
|
{/each}
|
|
@@ -1,27 +1,55 @@
|
|
|
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 Palette = 'default' | 'rainbow' | 'ocean' | 'sunset' | 'forest' | 'neon';
|
|
2
6
|
type DataPoint = {
|
|
3
7
|
label: string;
|
|
4
8
|
value: number;
|
|
5
9
|
color?: Color;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
};
|
|
12
|
+
type Slice = DataPoint & {
|
|
13
|
+
startAngle: number;
|
|
14
|
+
endAngle: number;
|
|
15
|
+
midAngle: number;
|
|
16
|
+
percentage: number;
|
|
17
|
+
color: Color;
|
|
18
|
+
index: number;
|
|
6
19
|
};
|
|
7
20
|
type Props = {
|
|
8
21
|
data?: DataPoint[];
|
|
9
|
-
colors?: Color[];
|
|
10
22
|
donut?: boolean;
|
|
11
23
|
donutWidth?: number;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
showPercentages?: boolean;
|
|
24
|
+
size?: Size;
|
|
25
|
+
palette?: Palette;
|
|
26
|
+
legendPosition?: LegendPosition;
|
|
16
27
|
centerLabel?: string;
|
|
17
28
|
centerValue?: string | number;
|
|
18
29
|
startAngle?: number;
|
|
19
30
|
padAngle?: number;
|
|
31
|
+
disableAnimation?: boolean;
|
|
32
|
+
animationDuration?: number;
|
|
33
|
+
showGlow?: boolean;
|
|
34
|
+
showGradient?: boolean;
|
|
35
|
+
hideLabels?: boolean;
|
|
36
|
+
hideValues?: boolean;
|
|
37
|
+
hideLegend?: boolean;
|
|
38
|
+
hidePercentages?: boolean;
|
|
20
39
|
loading?: boolean;
|
|
21
40
|
empty?: boolean;
|
|
22
41
|
emptyText?: string;
|
|
23
42
|
rootClass?: string;
|
|
24
43
|
chartClass?: string;
|
|
44
|
+
valueFormatter?: (value: number) => string;
|
|
45
|
+
onClick?: (slice: DataPoint, index: number) => void;
|
|
46
|
+
onHover?: (slice: DataPoint | null, index: number) => void;
|
|
47
|
+
selected?: number[];
|
|
48
|
+
centerContent?: Snippet;
|
|
49
|
+
tooltipContent?: Snippet<[{
|
|
50
|
+
slice: Slice;
|
|
51
|
+
percentage: number;
|
|
52
|
+
}]>;
|
|
25
53
|
};
|
|
26
54
|
declare const PieChart: import("svelte").Component<Props, {}, "">;
|
|
27
55
|
type PieChart = ReturnType<typeof PieChart>;
|