ui-svelte 0.2.10 → 0.2.12
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/charts/ArcChart.svelte +9 -13
- 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/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 +144 -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 +55 -29
- package/dist/control/Video.svelte.d.ts +1 -0
- package/dist/control/css/btn.css +200 -152
- package/dist/control/css/image.css +56 -0
- package/dist/control/css/media.css +95 -30
- package/dist/control/css/toggle-group.css +269 -84
- package/dist/control/css/video.css +1 -14
- package/dist/css/animations.css +427 -2
- package/dist/css/base.css +13 -347
- package/dist/css/decorations.css +402 -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/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 +54 -26
- package/dist/display/Avatar.svelte.d.ts +7 -1
- package/dist/display/AvatarGroup.svelte +26 -18
- package/dist/display/AvatarGroup.svelte.d.ts +9 -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 +6 -3
- 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/Empty.svelte +37 -3
- package/dist/display/Empty.svelte.d.ts +3 -0
- package/dist/display/Item.svelte +31 -18
- package/dist/display/Item.svelte.d.ts +2 -2
- package/dist/display/Map.svelte +488 -0
- package/dist/display/Map.svelte.d.ts +44 -0
- 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 +18 -25
- package/dist/display/css/avatar-group.css +38 -44
- package/dist/display/css/avatar.css +152 -123
- package/dist/display/css/badge.css +50 -27
- package/dist/display/css/card.css +51 -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/divider.css +8 -6
- package/dist/display/css/empty.css +7 -0
- package/dist/display/css/item.css +311 -89
- package/dist/display/css/map.css +164 -0
- package/dist/display/css/section.css +78 -33
- package/dist/display/css/skeleton.css +58 -0
- package/dist/display/css/table.css +320 -189
- package/dist/form/Checkbox.svelte +11 -5
- package/dist/form/Checkbox.svelte.d.ts +2 -1
- package/dist/form/ColorField.svelte +543 -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 +348 -0
- package/dist/form/DragDrop.svelte.d.ts +32 -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 +291 -61
- package/dist/form/ImageCropper.svelte.d.ts +15 -1
- package/dist/form/PasswordField.svelte +120 -75
- package/dist/form/PasswordField.svelte.d.ts +9 -10
- package/dist/form/PhoneField.svelte +34 -16
- 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 +4 -4
- 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 +4 -2
- package/dist/form/Slider.svelte.d.ts +1 -0
- package/dist/form/TextField.svelte +16 -7
- package/dist/form/TextField.svelte.d.ts +2 -2
- package/dist/form/Textarea.svelte +15 -6
- package/dist/form/Textarea.svelte.d.ts +2 -2
- package/dist/form/Toggle.svelte +11 -1
- package/dist/form/Toggle.svelte.d.ts +2 -0
- 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 +226 -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 +271 -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 +223 -22
- package/dist/form/css/password.css +1 -1
- package/dist/form/css/radio-group.css +1 -1
- package/dist/form/css/select.css +2 -2
- package/dist/form/css/slider.css +1 -0
- package/dist/form/css/textarea.css +178 -75
- package/dist/form/css/toggle.css +11 -2
- 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 +38 -2
- package/dist/icons/index.js +40 -4
- package/dist/index.css +16 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +10 -2
- 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 +27 -4
- package/dist/layout/Provider.svelte.d.ts +3 -1
- package/dist/layout/css/app-bar.css +63 -66
- package/dist/layout/css/footer.css +62 -65
- package/dist/navigation/BottomNav.svelte +41 -13
- 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 +279 -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 +332 -106
- package/dist/navigation/css/pagination.css +74 -0
- package/dist/navigation/css/side-nav.css +515 -75
- package/dist/navigation/css/tabs.css +246 -52
- package/dist/overlay/Command.svelte +340 -0
- package/dist/overlay/Command.svelte.d.ts +24 -25
- package/dist/overlay/Drawer.svelte +49 -21
- package/dist/overlay/Drawer.svelte.d.ts +2 -2
- package/dist/overlay/Dropdown.svelte +4 -5
- 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 +40 -26
- package/dist/overlay/Tooltip.svelte.d.ts +2 -2
- package/dist/overlay/css/command.css +80 -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 +27 -27
- package/dist/overlay/css/toast.css +17 -29
- package/dist/overlay/css/tooltip.css +83 -66
- package/dist/stores/theme.svelte.js +26 -1
- package/dist/stores/toast.svelte.d.ts +4 -4
- package/dist/stores/toast.svelte.js +2 -2
- package/package.json +1 -1
|
@@ -1,11 +1,17 @@
|
|
|
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 TooltipData = {
|
|
6
11
|
point: DataPoint;
|
|
7
12
|
seriesName: string;
|
|
8
13
|
color: Color;
|
|
14
|
+
seriesIndex: number;
|
|
9
15
|
};
|
|
10
16
|
|
|
11
17
|
type LinearScale = {
|
|
@@ -15,8 +21,6 @@
|
|
|
15
21
|
range: [number, number];
|
|
16
22
|
};
|
|
17
23
|
|
|
18
|
-
type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
|
|
19
|
-
|
|
20
24
|
type DataPoint = {
|
|
21
25
|
x: number;
|
|
22
26
|
y: number;
|
|
@@ -28,24 +32,46 @@
|
|
|
28
32
|
color?: Color;
|
|
29
33
|
};
|
|
30
34
|
|
|
35
|
+
type Margin = {
|
|
36
|
+
top: number;
|
|
37
|
+
right: number;
|
|
38
|
+
bottom: number;
|
|
39
|
+
left: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
31
42
|
type Props = {
|
|
32
43
|
data?: DataPoint[];
|
|
33
44
|
series?: Series[];
|
|
34
45
|
color?: Color;
|
|
35
46
|
colors?: Color[];
|
|
36
|
-
|
|
47
|
+
margin?: Margin;
|
|
48
|
+
hideLine?: boolean;
|
|
37
49
|
showPoints?: boolean;
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
hideGrid?: boolean;
|
|
51
|
+
hideLegend?: boolean;
|
|
40
52
|
curve?: 'linear' | 'smooth';
|
|
41
53
|
strokeWidth?: number;
|
|
42
54
|
fillOpacity?: number;
|
|
43
55
|
stacked?: boolean;
|
|
44
|
-
|
|
56
|
+
hideGradient?: boolean;
|
|
45
57
|
loading?: boolean;
|
|
46
58
|
empty?: boolean;
|
|
47
59
|
emptyText?: string;
|
|
48
60
|
rootClass?: string;
|
|
61
|
+
chartClass?: string;
|
|
62
|
+
size?: Size;
|
|
63
|
+
palette?: Palette;
|
|
64
|
+
legendPosition?: LegendPosition;
|
|
65
|
+
disableAnimation?: boolean;
|
|
66
|
+
animationDuration?: number;
|
|
67
|
+
valueFormatter?: (value: number) => string;
|
|
68
|
+
xFormatter?: (value: number) => string;
|
|
69
|
+
onClick?: (point: DataPoint, series: Series, index: number) => void;
|
|
70
|
+
onHover?: (point: DataPoint | null, series: Series | null, index: number) => void;
|
|
71
|
+
hideXAxis?: boolean;
|
|
72
|
+
hideYAxis?: boolean;
|
|
73
|
+
showGlow?: boolean;
|
|
74
|
+
tooltipContent?: Snippet<[{ point: DataPoint; series: Series; color: Color }]>;
|
|
49
75
|
};
|
|
50
76
|
|
|
51
77
|
let {
|
|
@@ -53,28 +79,53 @@
|
|
|
53
79
|
series = [],
|
|
54
80
|
color = 'primary' as Color,
|
|
55
81
|
colors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'] as Color[],
|
|
56
|
-
|
|
82
|
+
margin = { top: 20, right: 20, bottom: 40, left: 50 },
|
|
83
|
+
hideLine = false,
|
|
57
84
|
showPoints = false,
|
|
58
|
-
|
|
59
|
-
|
|
85
|
+
hideGrid = false,
|
|
86
|
+
hideLegend = false,
|
|
60
87
|
curve = 'linear' as 'linear' | 'smooth',
|
|
61
88
|
strokeWidth = 2,
|
|
62
89
|
fillOpacity = 0.3,
|
|
63
90
|
stacked = false,
|
|
64
|
-
|
|
91
|
+
hideGradient = false,
|
|
65
92
|
loading = false,
|
|
66
93
|
empty = false,
|
|
67
94
|
emptyText = 'No data available',
|
|
68
|
-
rootClass
|
|
95
|
+
rootClass,
|
|
96
|
+
chartClass,
|
|
97
|
+
size = 'md',
|
|
98
|
+
palette,
|
|
99
|
+
legendPosition = 'bottom',
|
|
100
|
+
disableAnimation = false,
|
|
101
|
+
animationDuration = 800,
|
|
102
|
+
valueFormatter,
|
|
103
|
+
xFormatter,
|
|
104
|
+
onClick,
|
|
105
|
+
onHover,
|
|
106
|
+
hideXAxis = false,
|
|
107
|
+
hideYAxis = false,
|
|
108
|
+
showGlow = false,
|
|
109
|
+
tooltipContent
|
|
69
110
|
}: Props = $props();
|
|
70
111
|
|
|
71
|
-
|
|
72
|
-
|
|
112
|
+
const sizePresets: Record<Size, { height: number; pointRadius: number; fontSize: number }> = {
|
|
113
|
+
sm: { height: 200, pointRadius: 2, fontSize: 10 },
|
|
114
|
+
md: { height: 300, pointRadius: 3, fontSize: 12 },
|
|
115
|
+
lg: { height: 400, pointRadius: 4, fontSize: 14 },
|
|
116
|
+
xl: { height: 500, pointRadius: 5, fontSize: 16 }
|
|
117
|
+
};
|
|
73
118
|
|
|
74
|
-
|
|
75
|
-
|
|
119
|
+
const colorPalettes: Record<Palette, Color[]> = {
|
|
120
|
+
default: ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'],
|
|
121
|
+
rainbow: ['danger', 'warning', 'success', 'info', 'primary', 'secondary', 'muted'],
|
|
122
|
+
ocean: ['info', 'primary', 'secondary', 'success', 'muted', 'warning', 'danger'],
|
|
123
|
+
sunset: ['warning', 'danger', 'secondary', 'primary', 'info', 'success', 'muted'],
|
|
124
|
+
forest: ['success', 'primary', 'info', 'secondary', 'muted', 'warning', 'danger'],
|
|
125
|
+
neon: ['secondary', 'primary', 'success', 'warning', 'danger', 'info', 'muted']
|
|
126
|
+
};
|
|
76
127
|
|
|
77
|
-
const colorClass = {
|
|
128
|
+
const colorClass: Record<Color, string> = {
|
|
78
129
|
primary: 'is-primary',
|
|
79
130
|
secondary: 'is-secondary',
|
|
80
131
|
success: 'is-success',
|
|
@@ -84,6 +135,27 @@
|
|
|
84
135
|
muted: 'is-muted'
|
|
85
136
|
};
|
|
86
137
|
|
|
138
|
+
function getSeriesColor(seriesItem: Series, index: number): Color {
|
|
139
|
+
if (seriesItem.color) return seriesItem.color;
|
|
140
|
+
if (palette) {
|
|
141
|
+
const paletteColors = colorPalettes[palette];
|
|
142
|
+
return paletteColors[index % paletteColors.length];
|
|
143
|
+
}
|
|
144
|
+
return colors[index % colors.length];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let containerEl: HTMLDivElement | undefined = $state();
|
|
148
|
+
let containerSize = $state({ width: 0, height: 0 });
|
|
149
|
+
let animationProgress = $state(0);
|
|
150
|
+
let animationFrameId: number | null = null;
|
|
151
|
+
|
|
152
|
+
let sizeConfig = $derived(sizePresets[size]);
|
|
153
|
+
let width = $derived(containerSize.width || 600);
|
|
154
|
+
let height = $derived(containerSize.height || sizeConfig.height);
|
|
155
|
+
|
|
156
|
+
let innerWidth = $derived(width - margin.left - margin.right);
|
|
157
|
+
let innerHeight = $derived(height - margin.top - margin.bottom);
|
|
158
|
+
|
|
87
159
|
function createLinearScale(domain: [number, number], range: [number, number]): LinearScale {
|
|
88
160
|
const [d0, d1] = domain;
|
|
89
161
|
const [r0, r1] = range;
|
|
@@ -103,6 +175,7 @@
|
|
|
103
175
|
}
|
|
104
176
|
|
|
105
177
|
function formatNumber(value: number): string {
|
|
178
|
+
if (valueFormatter) return valueFormatter(value);
|
|
106
179
|
if (value === 0) return '0';
|
|
107
180
|
const abs = Math.abs(value);
|
|
108
181
|
if (abs >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
|
|
@@ -112,12 +185,17 @@
|
|
|
112
185
|
return value.toFixed(0);
|
|
113
186
|
}
|
|
114
187
|
|
|
188
|
+
function formatX(value: number): string {
|
|
189
|
+
if (xFormatter) return xFormatter(value);
|
|
190
|
+
return formatNumber(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
115
193
|
let normalizedSeries = $derived.by((): Series[] => {
|
|
116
194
|
if (series.length > 0) {
|
|
117
195
|
return series.map((s, i) => ({
|
|
118
196
|
name: s.name,
|
|
119
197
|
data: s.data,
|
|
120
|
-
color: s
|
|
198
|
+
color: getSeriesColor(s, i)
|
|
121
199
|
}));
|
|
122
200
|
}
|
|
123
201
|
|
|
@@ -163,28 +241,33 @@
|
|
|
163
241
|
function createAreaPath(points: DataPoint[], baselineY: number, smooth: boolean = false): string {
|
|
164
242
|
if (points.length === 0) return '';
|
|
165
243
|
|
|
166
|
-
const xScale = createLinearScale(xDomain, [0,
|
|
167
|
-
const yScale = createLinearScale(yDomain, [
|
|
244
|
+
const xScale = createLinearScale(xDomain, [0, innerWidth]);
|
|
245
|
+
const yScale = createLinearScale(yDomain, [innerHeight, 0]);
|
|
168
246
|
|
|
169
|
-
|
|
170
|
-
|
|
247
|
+
const animatedPoints = points.map((p) => ({
|
|
248
|
+
x: p.x,
|
|
249
|
+
y: p.y * animationProgress
|
|
250
|
+
}));
|
|
171
251
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
252
|
+
let path = `M ${xScale(animatedPoints[0].x)} ${baselineY}`;
|
|
253
|
+
path += ` L ${xScale(animatedPoints[0].x)} ${yScale(animatedPoints[0].y)}`;
|
|
254
|
+
|
|
255
|
+
if (smooth && animatedPoints.length > 1) {
|
|
256
|
+
for (let i = 1; i < animatedPoints.length; i++) {
|
|
257
|
+
const x0 = xScale(animatedPoints[i - 1].x);
|
|
258
|
+
const y0 = yScale(animatedPoints[i - 1].y);
|
|
259
|
+
const x1 = xScale(animatedPoints[i].x);
|
|
260
|
+
const y1 = yScale(animatedPoints[i].y);
|
|
178
261
|
const cpx = (x0 + x1) / 2;
|
|
179
262
|
path += ` C ${cpx} ${y0}, ${cpx} ${y1}, ${x1} ${y1}`;
|
|
180
263
|
}
|
|
181
264
|
} else {
|
|
182
|
-
for (let i = 1; i <
|
|
183
|
-
path += ` L ${xScale(
|
|
265
|
+
for (let i = 1; i < animatedPoints.length; i++) {
|
|
266
|
+
path += ` L ${xScale(animatedPoints[i].x)} ${yScale(animatedPoints[i].y)}`;
|
|
184
267
|
}
|
|
185
268
|
}
|
|
186
269
|
|
|
187
|
-
path += ` L ${xScale(
|
|
270
|
+
path += ` L ${xScale(animatedPoints[animatedPoints.length - 1].x)} ${baselineY}`;
|
|
188
271
|
path += ' Z';
|
|
189
272
|
|
|
190
273
|
return path;
|
|
@@ -193,23 +276,28 @@
|
|
|
193
276
|
function createLinePath(points: DataPoint[], smooth: boolean = false): string {
|
|
194
277
|
if (points.length === 0) return '';
|
|
195
278
|
|
|
196
|
-
const xScale = createLinearScale(xDomain, [0,
|
|
197
|
-
const yScale = createLinearScale(yDomain, [
|
|
279
|
+
const xScale = createLinearScale(xDomain, [0, innerWidth]);
|
|
280
|
+
const yScale = createLinearScale(yDomain, [innerHeight, 0]);
|
|
281
|
+
|
|
282
|
+
const animatedPoints = points.map((p) => ({
|
|
283
|
+
x: p.x,
|
|
284
|
+
y: p.y * animationProgress
|
|
285
|
+
}));
|
|
198
286
|
|
|
199
|
-
let path = `M ${xScale(
|
|
287
|
+
let path = `M ${xScale(animatedPoints[0].x)} ${yScale(animatedPoints[0].y)}`;
|
|
200
288
|
|
|
201
|
-
if (smooth &&
|
|
202
|
-
for (let i = 1; i <
|
|
203
|
-
const x0 = xScale(
|
|
204
|
-
const y0 = yScale(
|
|
205
|
-
const x1 = xScale(
|
|
206
|
-
const y1 = yScale(
|
|
289
|
+
if (smooth && animatedPoints.length > 1) {
|
|
290
|
+
for (let i = 1; i < animatedPoints.length; i++) {
|
|
291
|
+
const x0 = xScale(animatedPoints[i - 1].x);
|
|
292
|
+
const y0 = yScale(animatedPoints[i - 1].y);
|
|
293
|
+
const x1 = xScale(animatedPoints[i].x);
|
|
294
|
+
const y1 = yScale(animatedPoints[i].y);
|
|
207
295
|
const cpx = (x0 + x1) / 2;
|
|
208
296
|
path += ` C ${cpx} ${y0}, ${cpx} ${y1}, ${x1} ${y1}`;
|
|
209
297
|
}
|
|
210
298
|
} else {
|
|
211
|
-
for (let i = 1; i <
|
|
212
|
-
path += ` L ${xScale(
|
|
299
|
+
for (let i = 1; i < animatedPoints.length; i++) {
|
|
300
|
+
path += ` L ${xScale(animatedPoints[i].x)} ${yScale(animatedPoints[i].y)}`;
|
|
213
301
|
}
|
|
214
302
|
}
|
|
215
303
|
|
|
@@ -217,19 +305,18 @@
|
|
|
217
305
|
}
|
|
218
306
|
|
|
219
307
|
function createGridLines(): Array<{ x: number; y: number; value: number }> {
|
|
220
|
-
const yScale = createLinearScale(yDomain, [
|
|
308
|
+
const yScale = createLinearScale(yDomain, [innerHeight, 0]);
|
|
221
309
|
const yTicks = 5;
|
|
222
310
|
|
|
223
311
|
return Array.from({ length: yTicks + 1 }, (_, i) => {
|
|
224
312
|
const value = yDomain[0] + (yDomain[1] - yDomain[0]) * (i / yTicks);
|
|
225
|
-
return { x:
|
|
313
|
+
return { x: innerWidth, y: yScale(value), value };
|
|
226
314
|
});
|
|
227
315
|
}
|
|
228
316
|
|
|
229
|
-
// NUEVA FUNCIÓN: Crear ticks para el eje X
|
|
230
317
|
function createXAxisTicks(): Array<{ x: number; value: number }> {
|
|
231
|
-
const xScale = createLinearScale(xDomain, [0,
|
|
232
|
-
const xTicks =
|
|
318
|
+
const xScale = createLinearScale(xDomain, [0, innerWidth]);
|
|
319
|
+
const xTicks = Math.min(6, Math.max(2, Math.floor(innerWidth / 80)));
|
|
233
320
|
|
|
234
321
|
return Array.from({ length: xTicks + 1 }, (_, i) => {
|
|
235
322
|
const value = xDomain[0] + (xDomain[1] - xDomain[0]) * (i / xTicks);
|
|
@@ -274,26 +361,43 @@
|
|
|
274
361
|
let tooltipData = $state<TooltipData | null>(null);
|
|
275
362
|
let tooltipPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
276
363
|
let isTooltipActive = $state(false);
|
|
364
|
+
let hoveredSeriesIndex = $state<number | null>(null);
|
|
277
365
|
|
|
278
366
|
function handlePointHover(
|
|
279
367
|
point: DataPoint,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
368
|
+
seriesItem: Series,
|
|
369
|
+
seriesIndex: number,
|
|
370
|
+
event: MouseEvent
|
|
283
371
|
): void {
|
|
284
372
|
const target = event.target as SVGCircleElement;
|
|
285
373
|
const rect = target.getBoundingClientRect();
|
|
286
374
|
|
|
287
|
-
tooltipData = {
|
|
375
|
+
tooltipData = {
|
|
376
|
+
point,
|
|
377
|
+
seriesName: seriesItem.name,
|
|
378
|
+
color: seriesItem.color as Color,
|
|
379
|
+
seriesIndex
|
|
380
|
+
};
|
|
288
381
|
tooltipPosition = {
|
|
289
|
-
x: rect.
|
|
290
|
-
y: rect.top
|
|
382
|
+
x: rect.left + rect.width / 2,
|
|
383
|
+
y: rect.top - 10
|
|
291
384
|
};
|
|
292
385
|
isTooltipActive = true;
|
|
386
|
+
hoveredSeriesIndex = seriesIndex;
|
|
387
|
+
|
|
388
|
+
if (onHover) {
|
|
389
|
+
onHover(point, seriesItem, seriesIndex);
|
|
390
|
+
}
|
|
293
391
|
}
|
|
294
392
|
|
|
295
393
|
function handlePointLeave(): void {
|
|
296
394
|
isTooltipActive = false;
|
|
395
|
+
hoveredSeriesIndex = null;
|
|
396
|
+
|
|
397
|
+
if (onHover) {
|
|
398
|
+
onHover(null, null, -1);
|
|
399
|
+
}
|
|
400
|
+
|
|
297
401
|
setTimeout(() => {
|
|
298
402
|
if (!isTooltipActive) {
|
|
299
403
|
tooltipData = null;
|
|
@@ -301,6 +405,36 @@
|
|
|
301
405
|
}, 100);
|
|
302
406
|
}
|
|
303
407
|
|
|
408
|
+
function handlePointClick(point: DataPoint, seriesItem: Series, index: number): void {
|
|
409
|
+
if (onClick) {
|
|
410
|
+
onClick(point, seriesItem, index);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let containerLayoutClass = $derived(() => {
|
|
415
|
+
switch (legendPosition) {
|
|
416
|
+
case 'top':
|
|
417
|
+
return 'flex-col-reverse';
|
|
418
|
+
case 'bottom':
|
|
419
|
+
return 'flex-col';
|
|
420
|
+
case 'left':
|
|
421
|
+
return 'flex-row-reverse';
|
|
422
|
+
case 'right':
|
|
423
|
+
default:
|
|
424
|
+
return 'flex-row';
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
let legendLayoutClass = $derived(() => {
|
|
429
|
+
switch (legendPosition) {
|
|
430
|
+
case 'top':
|
|
431
|
+
case 'bottom':
|
|
432
|
+
return 'flex-row flex-wrap';
|
|
433
|
+
default:
|
|
434
|
+
return 'flex-col';
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
304
438
|
onMount(() => {
|
|
305
439
|
const updateSize = () => {
|
|
306
440
|
if (containerEl) {
|
|
@@ -309,19 +443,65 @@
|
|
|
309
443
|
}
|
|
310
444
|
};
|
|
311
445
|
|
|
446
|
+
const handleScroll = () => {
|
|
447
|
+
if (isTooltipActive) {
|
|
448
|
+
isTooltipActive = false;
|
|
449
|
+
hoveredSeriesIndex = null;
|
|
450
|
+
tooltipData = null;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
312
454
|
updateSize();
|
|
313
455
|
const resizeObserver = new ResizeObserver(updateSize);
|
|
314
456
|
if (containerEl) {
|
|
315
457
|
resizeObserver.observe(containerEl);
|
|
316
458
|
}
|
|
317
459
|
|
|
460
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
461
|
+
|
|
318
462
|
return () => {
|
|
463
|
+
if (animationFrameId !== null) {
|
|
464
|
+
cancelAnimationFrame(animationFrameId);
|
|
465
|
+
}
|
|
319
466
|
resizeObserver.disconnect();
|
|
467
|
+
window.removeEventListener('scroll', handleScroll, true);
|
|
320
468
|
};
|
|
321
469
|
});
|
|
470
|
+
|
|
471
|
+
$effect(() => {
|
|
472
|
+
const _ = normalizedSeries;
|
|
473
|
+
|
|
474
|
+
if (animationFrameId !== null) {
|
|
475
|
+
cancelAnimationFrame(animationFrameId);
|
|
476
|
+
animationFrameId = null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (!disableAnimation) {
|
|
480
|
+
const startTime = Date.now();
|
|
481
|
+
const startProgress = untrack(() => animationProgress);
|
|
482
|
+
|
|
483
|
+
const animate = () => {
|
|
484
|
+
const now = Date.now();
|
|
485
|
+
const progress = Math.min((now - startTime) / animationDuration, 1);
|
|
486
|
+
const easeProgress = 1 - Math.pow(1 - progress, 3);
|
|
487
|
+
|
|
488
|
+
animationProgress = startProgress + (1 - startProgress) * easeProgress;
|
|
489
|
+
|
|
490
|
+
if (progress < 1) {
|
|
491
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
492
|
+
} else {
|
|
493
|
+
animationFrameId = null;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
498
|
+
} else {
|
|
499
|
+
animationProgress = 1;
|
|
500
|
+
}
|
|
501
|
+
});
|
|
322
502
|
</script>
|
|
323
503
|
|
|
324
|
-
<div class={cn('area-chart-container', rootClass)}>
|
|
504
|
+
<div class={cn('area-chart-container', `is-${size}`, containerLayoutClass(), rootClass)}>
|
|
325
505
|
{#if loading}
|
|
326
506
|
<div class="area-chart-loading">
|
|
327
507
|
<svg class="area-chart-loading-spinner" viewBox="0 0 24 24">
|
|
@@ -353,11 +533,11 @@
|
|
|
353
533
|
<span>{emptyText}</span>
|
|
354
534
|
</div>
|
|
355
535
|
{:else}
|
|
356
|
-
<div bind:this={containerEl} class=
|
|
357
|
-
<svg class="area-chart-svg">
|
|
536
|
+
<div bind:this={containerEl} class={cn('area-chart', `is-${size}`, chartClass)}>
|
|
537
|
+
<svg class="area-chart-svg" {width} {height}>
|
|
358
538
|
<defs>
|
|
359
539
|
{#each normalizedSeries as s, i}
|
|
360
|
-
{#if
|
|
540
|
+
{#if !hideGradient}
|
|
361
541
|
<linearGradient id="area-gradient-{i}" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
362
542
|
<stop
|
|
363
543
|
offset="0%"
|
|
@@ -372,16 +552,28 @@
|
|
|
372
552
|
</linearGradient>
|
|
373
553
|
{/if}
|
|
374
554
|
{/each}
|
|
555
|
+
|
|
556
|
+
{#if showGlow}
|
|
557
|
+
{#each normalizedSeries as s, i}
|
|
558
|
+
<filter id="area-glow-{i}" x="-50%" y="-50%" width="200%" height="200%">
|
|
559
|
+
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
|
|
560
|
+
<feMerge>
|
|
561
|
+
<feMergeNode in="coloredBlur" />
|
|
562
|
+
<feMergeNode in="SourceGraphic" />
|
|
563
|
+
</feMerge>
|
|
564
|
+
</filter>
|
|
565
|
+
{/each}
|
|
566
|
+
{/if}
|
|
375
567
|
</defs>
|
|
376
568
|
|
|
377
|
-
<g>
|
|
569
|
+
<g transform="translate({margin.left}, {margin.top})">
|
|
378
570
|
{#if normalizedSeries.length > 0}
|
|
379
571
|
{@const grid = createGridLines()}
|
|
380
572
|
{@const xTicks = createXAxisTicks()}
|
|
381
|
-
{@const xScale = createLinearScale(xDomain, [0,
|
|
382
|
-
{@const yScale = createLinearScale(yDomain, [
|
|
573
|
+
{@const xScale = createLinearScale(xDomain, [0, innerWidth])}
|
|
574
|
+
{@const yScale = createLinearScale(yDomain, [innerHeight, 0])}
|
|
383
575
|
|
|
384
|
-
{#if
|
|
576
|
+
{#if !hideGrid}
|
|
385
577
|
<g class="area-chart-grid">
|
|
386
578
|
{#each grid as line}
|
|
387
579
|
<line x1={0} y1={line.y} x2={line.x} y2={line.y} class="area-chart-grid-line" />
|
|
@@ -390,67 +582,90 @@
|
|
|
390
582
|
{/if}
|
|
391
583
|
|
|
392
584
|
<g class="area-chart-axis">
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
text
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
585
|
+
{#if !hideXAxis}
|
|
586
|
+
<line
|
|
587
|
+
x1={0}
|
|
588
|
+
y1={innerHeight}
|
|
589
|
+
x2={innerWidth}
|
|
590
|
+
y2={innerHeight}
|
|
591
|
+
class="area-chart-axis-line"
|
|
592
|
+
/>
|
|
593
|
+
{#each xTicks as tick}
|
|
594
|
+
<text
|
|
595
|
+
x={tick.x}
|
|
596
|
+
y={innerHeight + 20}
|
|
597
|
+
class="area-chart-axis-label"
|
|
598
|
+
text-anchor="middle"
|
|
599
|
+
style="font-size: {sizeConfig.fontSize}px;"
|
|
600
|
+
>
|
|
601
|
+
{formatX(tick.value)}
|
|
602
|
+
</text>
|
|
603
|
+
{/each}
|
|
604
|
+
{/if}
|
|
605
|
+
|
|
606
|
+
{#if !hideYAxis}
|
|
607
|
+
<line x1={0} y1={0} x2={0} y2={innerHeight} class="area-chart-axis-line" />
|
|
608
|
+
{#each grid as line}
|
|
609
|
+
<text
|
|
610
|
+
x={-10}
|
|
611
|
+
y={line.y}
|
|
612
|
+
class="area-chart-axis-label"
|
|
613
|
+
text-anchor="end"
|
|
614
|
+
dominant-baseline="middle"
|
|
615
|
+
style="font-size: {sizeConfig.fontSize}px;"
|
|
616
|
+
>
|
|
617
|
+
{formatNumber(line.value)}
|
|
618
|
+
</text>
|
|
619
|
+
{/each}
|
|
620
|
+
{/if}
|
|
421
621
|
</g>
|
|
422
622
|
|
|
423
623
|
{#each stacked ? stackedData : normalizedSeries as s, i}
|
|
424
624
|
{@const baselineY =
|
|
425
625
|
stacked && s.data[0]?.y !== undefined
|
|
426
626
|
? yScale((s.data[0] as StackedDataPoint).y0)
|
|
427
|
-
:
|
|
627
|
+
: innerHeight}
|
|
628
|
+
{@const isHovered = hoveredSeriesIndex === i}
|
|
428
629
|
|
|
429
630
|
<path
|
|
430
631
|
d={createAreaPath(s.data, baselineY, curve === 'smooth')}
|
|
431
|
-
fill={
|
|
432
|
-
class=
|
|
433
|
-
|
|
632
|
+
fill={!hideGradient ? `url(#area-gradient-${i})` : undefined}
|
|
633
|
+
class={cn(
|
|
634
|
+
'area-chart-area',
|
|
635
|
+
colorClass[s.color!],
|
|
636
|
+
isHovered && 'is-hovered',
|
|
637
|
+
showGlow && 'has-glow'
|
|
638
|
+
)}
|
|
639
|
+
style={hideGradient ? `opacity: ${fillOpacity}` : ''}
|
|
640
|
+
filter={showGlow ? `url(#area-glow-${i})` : undefined}
|
|
434
641
|
/>
|
|
435
642
|
|
|
436
|
-
{#if
|
|
643
|
+
{#if !hideLine}
|
|
437
644
|
<path
|
|
438
645
|
d={createLinePath(s.data, curve === 'smooth')}
|
|
439
|
-
class=
|
|
646
|
+
class={cn(
|
|
647
|
+
'area-chart-line',
|
|
648
|
+
colorClass[s.color!],
|
|
649
|
+
isHovered && 'is-hovered',
|
|
650
|
+
showGlow && 'has-glow'
|
|
651
|
+
)}
|
|
440
652
|
style="stroke-width: {strokeWidth};"
|
|
653
|
+
filter={showGlow ? `url(#area-glow-${i})` : undefined}
|
|
441
654
|
/>
|
|
442
655
|
{/if}
|
|
443
656
|
|
|
444
657
|
{#if showPoints}
|
|
445
|
-
{#each s.data as point}
|
|
658
|
+
{#each s.data as point, pointIndex}
|
|
446
659
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
660
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
447
661
|
<circle
|
|
448
662
|
cx={xScale(point.x)}
|
|
449
|
-
cy={yScale(point.y)}
|
|
450
|
-
r={
|
|
451
|
-
class=
|
|
452
|
-
onmouseenter={(e) => handlePointHover(point, s
|
|
663
|
+
cy={yScale(point.y * animationProgress)}
|
|
664
|
+
r={sizeConfig.pointRadius}
|
|
665
|
+
class={cn('area-chart-point', colorClass[s.color!], showGlow && 'has-glow')}
|
|
666
|
+
onmouseenter={(e) => handlePointHover(point, s, i, e)}
|
|
453
667
|
onmouseleave={handlePointLeave}
|
|
668
|
+
onclick={() => handlePointClick(point, s, pointIndex)}
|
|
454
669
|
/>
|
|
455
670
|
{/each}
|
|
456
671
|
{/if}
|
|
@@ -463,29 +678,43 @@
|
|
|
463
678
|
{#if tooltipData && isTooltipActive}
|
|
464
679
|
<div
|
|
465
680
|
class="area-chart-tooltip"
|
|
466
|
-
style="
|
|
681
|
+
style="left: {tooltipPosition.x}px; top: {tooltipPosition.y}px;"
|
|
467
682
|
>
|
|
468
|
-
|
|
469
|
-
{
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
683
|
+
{#if tooltipContent}
|
|
684
|
+
{@render tooltipContent({
|
|
685
|
+
point: tooltipData.point,
|
|
686
|
+
series: normalizedSeries[tooltipData.seriesIndex],
|
|
687
|
+
color: tooltipData.color
|
|
688
|
+
})}
|
|
689
|
+
{:else}
|
|
690
|
+
<div class="area-chart-tooltip-content">
|
|
691
|
+
{#if tooltipData.seriesName !== 'Data'}
|
|
692
|
+
<div class="area-chart-tooltip-title">{tooltipData.seriesName}</div>
|
|
693
|
+
{/if}
|
|
694
|
+
<div class="area-chart-tooltip-row">
|
|
695
|
+
<div class="area-chart-tooltip-color {colorClass[tooltipData.color]}"></div>
|
|
696
|
+
<span class="area-chart-tooltip-value">
|
|
697
|
+
{formatX(tooltipData.point.x)}: {formatNumber(tooltipData.point.y)}
|
|
698
|
+
</span>
|
|
699
|
+
</div>
|
|
479
700
|
</div>
|
|
480
|
-
|
|
701
|
+
{/if}
|
|
481
702
|
</div>
|
|
482
703
|
{/if}
|
|
483
704
|
|
|
484
|
-
{#if
|
|
485
|
-
<div class=
|
|
486
|
-
{#each normalizedSeries as s}
|
|
487
|
-
|
|
488
|
-
|
|
705
|
+
{#if !hideLegend && normalizedSeries.length > 1 && legendPosition !== 'none'}
|
|
706
|
+
<div class={cn('area-chart-legend', legendLayoutClass())}>
|
|
707
|
+
{#each normalizedSeries as s, i}
|
|
708
|
+
{@const isHovered = hoveredSeriesIndex === i}
|
|
709
|
+
<div
|
|
710
|
+
class={cn('area-chart-legend-item', isHovered && 'is-selected')}
|
|
711
|
+
onmouseenter={() => (hoveredSeriesIndex = i)}
|
|
712
|
+
onmouseleave={() => (hoveredSeriesIndex = null)}
|
|
713
|
+
role="button"
|
|
714
|
+
tabindex="0"
|
|
715
|
+
onkeydown={(e) => e.key === 'Enter' && (hoveredSeriesIndex = i)}
|
|
716
|
+
>
|
|
717
|
+
<div class={cn('area-chart-legend-color', colorClass[s.color!])}></div>
|
|
489
718
|
<span>{s.name}</span>
|
|
490
719
|
</div>
|
|
491
720
|
{/each}
|