@bagelink/vue 1.15.63 → 1.15.65
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/components/AccordionItem.vue.d.ts.map +1 -1
- package/dist/components/Avatar.vue.d.ts +6 -1
- package/dist/components/Avatar.vue.d.ts.map +1 -1
- package/dist/components/Badge.vue.d.ts.map +1 -1
- package/dist/components/Card.vue.d.ts +7 -0
- package/dist/components/Card.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/EmptyState.vue.d.ts +43 -0
- package/dist/components/EmptyState.vue.d.ts.map +1 -0
- package/dist/components/Icon/Icon.vue.d.ts +13 -0
- package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
- package/dist/components/Image.vue.d.ts +26 -1
- package/dist/components/Image.vue.d.ts.map +1 -1
- package/dist/components/ListItem.vue.d.ts +9 -9
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/Menu.vue.d.ts.map +1 -1
- package/dist/components/Swiper.vue.d.ts +3 -3
- package/dist/components/calendar/CalendarPopover.vue.d.ts +10 -0
- package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
- package/dist/components/charts/BarChart.vue.d.ts +34 -0
- package/dist/components/charts/BarChart.vue.d.ts.map +1 -0
- package/dist/components/charts/ChartTooltip.vue.d.ts +33 -0
- package/dist/components/charts/ChartTooltip.vue.d.ts.map +1 -0
- package/dist/components/charts/Donut.vue.d.ts +53 -0
- package/dist/components/charts/Donut.vue.d.ts.map +1 -0
- package/dist/components/charts/Funnel.vue.d.ts +53 -0
- package/dist/components/charts/Funnel.vue.d.ts.map +1 -0
- package/dist/components/charts/Gauge.vue.d.ts +28 -0
- package/dist/components/charts/Gauge.vue.d.ts.map +1 -0
- package/dist/components/charts/LineChart.vue.d.ts +37 -0
- package/dist/components/charts/LineChart.vue.d.ts.map +1 -0
- package/dist/components/charts/RadialBars.vue.d.ts +34 -0
- package/dist/components/charts/RadialBars.vue.d.ts.map +1 -0
- package/dist/components/charts/RankBars.vue.d.ts +27 -0
- package/dist/components/charts/RankBars.vue.d.ts.map +1 -0
- package/dist/components/charts/Sparkline.vue.d.ts +25 -0
- package/dist/components/charts/Sparkline.vue.d.ts.map +1 -0
- package/dist/components/charts/StatCard.vue.d.ts +28 -0
- package/dist/components/charts/StatCard.vue.d.ts.map +1 -0
- package/dist/components/charts/core/data.d.ts +46 -0
- package/dist/components/charts/core/data.d.ts.map +1 -0
- package/dist/components/charts/core/format.d.ts +13 -0
- package/dist/components/charts/core/format.d.ts.map +1 -0
- package/dist/components/charts/core/palette.d.ts +19 -0
- package/dist/components/charts/core/palette.d.ts.map +1 -0
- package/dist/components/charts/core/uid.d.ts +2 -0
- package/dist/components/charts/core/uid.d.ts.map +1 -0
- package/dist/components/charts/core/useChartAnim.d.ts +11 -0
- package/dist/components/charts/core/useChartAnim.d.ts.map +1 -0
- package/dist/components/charts/core/useChartFrame.d.ts +21 -0
- package/dist/components/charts/core/useChartFrame.d.ts.map +1 -0
- package/dist/components/charts/core/useScale.d.ts +16 -0
- package/dist/components/charts/core/useScale.d.ts.map +1 -0
- package/dist/components/charts/index.d.ts +12 -0
- package/dist/components/charts/index.d.ts.map +1 -0
- package/dist/components/form/inputs/RadioGroup.vue.d.ts +1 -0
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RangeInput.vue.d.ts +13 -4
- package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layout/Layout.vue.d.ts +1 -1
- package/dist/components/layout/Layout.vue.d.ts.map +1 -1
- package/dist/components/layout/Panel.vue.d.ts +1 -1
- package/dist/components/layout/Panel.vue.d.ts.map +1 -1
- package/dist/components/layout/Timeline.types.d.ts +9 -0
- package/dist/components/layout/Timeline.types.d.ts.map +1 -0
- package/dist/components/layout/Timeline.vue.d.ts +42 -0
- package/dist/components/layout/Timeline.vue.d.ts.map +1 -0
- package/dist/components/layout/TimelineItem.vue.d.ts +37 -0
- package/dist/components/layout/TimelineItem.vue.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/dialog/Dialog.vue.d.ts +4 -0
- package/dist/dialog/Dialog.vue.d.ts.map +1 -1
- package/dist/index.cjs +110 -116
- package/dist/index.mjs +38059 -37009
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/components/AccordionItem.vue +24 -22
- package/src/components/Avatar.vue +49 -11
- package/src/components/Badge.vue +4 -7
- package/src/components/Card.vue +32 -2
- package/src/components/Dropdown.vue +14 -3
- package/src/components/EmptyState.vue +91 -0
- package/src/components/Icon/Icon.vue +118 -25
- package/src/components/Image.vue +70 -3
- package/src/components/ListItem.vue +43 -22
- package/src/components/Menu.vue +10 -2
- package/src/components/charts/BarChart.vue +197 -0
- package/src/components/charts/ChartTooltip.vue +74 -0
- package/src/components/charts/Donut.vue +219 -0
- package/src/components/charts/Funnel.vue +377 -0
- package/src/components/charts/Gauge.vue +90 -0
- package/src/components/charts/LineChart.vue +255 -0
- package/src/components/charts/RadialBars.vue +99 -0
- package/src/components/charts/RankBars.vue +72 -0
- package/src/components/charts/Sparkline.vue +90 -0
- package/src/components/charts/StatCard.vue +84 -0
- package/src/components/charts/core/data.ts +95 -0
- package/src/components/charts/core/format.ts +64 -0
- package/src/components/charts/core/palette.ts +52 -0
- package/src/components/charts/core/uid.ts +6 -0
- package/src/components/charts/core/useChartAnim.ts +60 -0
- package/src/components/charts/core/useChartFrame.ts +49 -0
- package/src/components/charts/core/useScale.ts +39 -0
- package/src/components/charts/index.ts +12 -0
- package/src/components/form/inputs/RadioGroup.vue +2 -1
- package/src/components/form/inputs/RangeInput.vue +43 -15
- package/src/components/form/inputs/SelectInput.vue +1 -19
- package/src/components/index.ts +3 -1
- package/src/components/layout/Timeline.types.ts +9 -0
- package/src/components/layout/Timeline.vue +54 -0
- package/src/components/layout/TimelineItem.vue +93 -0
- package/src/components/layout/index.ts +3 -0
- package/src/dialog/Dialog.vue +29 -1
- package/src/styles/bagel.css +1 -0
- package/src/styles/gradients.css +181 -0
- package/src/styles/layout.css +9 -0
- package/src/styles/theme.css +1 -1
- package/dist/components/analytics/BarChart.vue.d.ts +0 -47
- package/dist/components/analytics/BarChart.vue.d.ts.map +0 -1
- package/dist/components/analytics/KpiCard.vue.d.ts +0 -24
- package/dist/components/analytics/KpiCard.vue.d.ts.map +0 -1
- package/dist/components/analytics/LineChart.vue.d.ts +0 -35
- package/dist/components/analytics/LineChart.vue.d.ts.map +0 -1
- package/dist/components/analytics/PieChart.vue.d.ts +0 -53
- package/dist/components/analytics/PieChart.vue.d.ts.map +0 -1
- package/dist/components/analytics/index.d.ts +0 -5
- package/dist/components/analytics/index.d.ts.map +0 -1
- package/src/components/analytics/BarChart.vue +0 -262
- package/src/components/analytics/KpiCard.vue +0 -84
- package/src/components/analytics/LineChart.vue +0 -357
- package/src/components/analytics/PieChart.vue +0 -544
- package/src/components/analytics/index.ts +0 -4
|
@@ -1,544 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { Icon } from '@bagelink/vue'
|
|
3
|
-
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
|
4
|
-
|
|
5
|
-
interface PieData {
|
|
6
|
-
label: string
|
|
7
|
-
value: number
|
|
8
|
-
color?: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
data: PieData[]
|
|
13
|
-
title?: string
|
|
14
|
-
icon?: string
|
|
15
|
-
color?: string
|
|
16
|
-
colors?: string[]
|
|
17
|
-
size?: number
|
|
18
|
-
showLegend?: boolean
|
|
19
|
-
donut?: boolean
|
|
20
|
-
thickness?: number
|
|
21
|
-
centerLabel?: string
|
|
22
|
-
animated?: boolean
|
|
23
|
-
animationDuration?: number
|
|
24
|
-
animationDelay?: number
|
|
25
|
-
animationStartDelay?: number
|
|
26
|
-
centerValue?: number
|
|
27
|
-
showCenterTotal?: boolean
|
|
28
|
-
percentageChange?: number
|
|
29
|
-
showLabelsOnChart?: boolean
|
|
30
|
-
showConnectorLines?: boolean
|
|
31
|
-
labelColor?: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
35
|
-
title: 'Pie Chart',
|
|
36
|
-
icon: 'bx-pie-chart-alt',
|
|
37
|
-
color: '#3B82F6',
|
|
38
|
-
size: 200,
|
|
39
|
-
showLegend: true,
|
|
40
|
-
donut: false,
|
|
41
|
-
thickness: 0.6,
|
|
42
|
-
animated: true,
|
|
43
|
-
animationDuration: 1500,
|
|
44
|
-
animationDelay: 200,
|
|
45
|
-
animationStartDelay: 0,
|
|
46
|
-
centerLabel: 'Total',
|
|
47
|
-
showCenterTotal: true,
|
|
48
|
-
percentageChange: 0,
|
|
49
|
-
showLabelsOnChart: false,
|
|
50
|
-
showConnectorLines: false,
|
|
51
|
-
labelColor: '#333'
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// Generate colors with different opacity based on the main color OR use custom colors
|
|
55
|
-
const colors = computed(() => {
|
|
56
|
-
// If custom colors array is provided, use it
|
|
57
|
-
if (props.colors && props.colors.length > 0) {
|
|
58
|
-
return props.colors
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Otherwise, use the existing logic with opacity variations
|
|
62
|
-
const baseColor = props.color
|
|
63
|
-
const opacities = [1, 0.8, 0.6, 0.4, 0.85, 0.7, 0.5, 0.3, 0.9, 0.75]
|
|
64
|
-
|
|
65
|
-
return opacities.map((opacity) => {
|
|
66
|
-
// Convert hex to rgba with opacity
|
|
67
|
-
const hex = baseColor.replace('#', '')
|
|
68
|
-
const r = Number.parseInt(hex.substr(0, 2), 16)
|
|
69
|
-
const g = Number.parseInt(hex.substr(2, 2), 16)
|
|
70
|
-
const b = Number.parseInt(hex.substr(4, 2), 16)
|
|
71
|
-
|
|
72
|
-
return `rgba(${r}, ${g}, ${b}, ${opacity})`
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
// Animation state
|
|
77
|
-
const animatedProgress = ref(0)
|
|
78
|
-
const isAnimating = ref(false)
|
|
79
|
-
const chartRef = ref<HTMLElement>()
|
|
80
|
-
const isInView = ref(false)
|
|
81
|
-
|
|
82
|
-
onMounted(() => {
|
|
83
|
-
if (props.animated) {
|
|
84
|
-
setupIntersectionObserver()
|
|
85
|
-
} else {
|
|
86
|
-
animatedProgress.value = 1
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
let observer: IntersectionObserver | null = null
|
|
91
|
-
|
|
92
|
-
onUnmounted(() => {
|
|
93
|
-
if (observer) {
|
|
94
|
-
observer.disconnect()
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
function setupIntersectionObserver() {
|
|
99
|
-
if (typeof window === 'undefined') { return }
|
|
100
|
-
|
|
101
|
-
observer = new IntersectionObserver(
|
|
102
|
-
(entries) => {
|
|
103
|
-
entries.forEach((entry) => {
|
|
104
|
-
if (entry.isIntersecting && !isInView.value) {
|
|
105
|
-
isInView.value = true
|
|
106
|
-
// Add start delay + small delay to ensure the component is fully rendered
|
|
107
|
-
setTimeout(() => {
|
|
108
|
-
startAnimation()
|
|
109
|
-
}, 100 + props.animationStartDelay)
|
|
110
|
-
}
|
|
111
|
-
})
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
threshold: 0.3, // Start animation when 30% of the component is visible
|
|
115
|
-
rootMargin: '50px'
|
|
116
|
-
}
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
// Use nextTick to ensure chartRef is available
|
|
120
|
-
if (chartRef.value) {
|
|
121
|
-
observer.observe(chartRef.value)
|
|
122
|
-
} else {
|
|
123
|
-
// Retry after a short delay if ref is not ready
|
|
124
|
-
setTimeout(() => {
|
|
125
|
-
if (chartRef.value && observer) {
|
|
126
|
-
observer.observe(chartRef.value)
|
|
127
|
-
}
|
|
128
|
-
}, 100)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function startAnimation() {
|
|
133
|
-
const delayType = props.animationDelay > 0 ? 'staggered with delay' : 'quick stagger'
|
|
134
|
-
console.log(`Starting animation for "${props.title}" - ${delayType} mode (duration: ${props.animationDuration}ms, start delay: ${props.animationStartDelay}ms)`)
|
|
135
|
-
isAnimating.value = true
|
|
136
|
-
animatedProgress.value = 0
|
|
137
|
-
|
|
138
|
-
const startTime = Date.now()
|
|
139
|
-
|
|
140
|
-
const animate = () => {
|
|
141
|
-
const elapsed = Date.now() - startTime
|
|
142
|
-
const progress = Math.min(elapsed / props.animationDuration, 1)
|
|
143
|
-
|
|
144
|
-
// Easing function for smooth animation
|
|
145
|
-
animatedProgress.value = easeOutCubic(progress)
|
|
146
|
-
|
|
147
|
-
if (progress < 1) {
|
|
148
|
-
requestAnimationFrame(animate)
|
|
149
|
-
} else {
|
|
150
|
-
isAnimating.value = false
|
|
151
|
-
console.log('Animation completed')
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
requestAnimationFrame(animate)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function easeOutCubic(t: number): number {
|
|
159
|
-
return 1 - (1 - t) ** 3
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Expose animation control methods
|
|
163
|
-
function restartAnimation() {
|
|
164
|
-
if (props.animated) {
|
|
165
|
-
startAnimation()
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
defineExpose({
|
|
170
|
-
restartAnimation
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
// Calculate SVG size based on connector lines
|
|
174
|
-
const svgSize = computed(() => {
|
|
175
|
-
if (props.showConnectorLines) {
|
|
176
|
-
return {
|
|
177
|
-
width: props.size + 200, // Increased even more
|
|
178
|
-
height: props.size + 100 // Increased even more
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return {
|
|
182
|
-
width: props.size,
|
|
183
|
-
height: props.size
|
|
184
|
-
}
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
// Calculate center offset for connector lines
|
|
188
|
-
const centerOffset = computed(() => {
|
|
189
|
-
if (props.showConnectorLines) {
|
|
190
|
-
return {
|
|
191
|
-
x: svgSize.value.width / 2 - 10, // Less offset
|
|
192
|
-
y: svgSize.value.height / 2
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return {
|
|
196
|
-
x: props.size / 2,
|
|
197
|
-
y: props.size / 2
|
|
198
|
-
}
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
const totalValue = computed(() => props.data.reduce((sum, item) => sum + item.value, 0)
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
// Calculate label positions for segments
|
|
205
|
-
function getLabelPosition(segment: any) {
|
|
206
|
-
const midAngle = (segment.startAngle + segment.endAngle) / 2
|
|
207
|
-
const midAngleRad = (midAngle * Math.PI) / 180
|
|
208
|
-
const radius = props.size / 2 - 10
|
|
209
|
-
const labelRadius = props.donut ? radius * 0.8 : radius * 0.7
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
x: Math.cos(midAngleRad) * labelRadius,
|
|
213
|
-
y: Math.sin(midAngleRad) * labelRadius
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Calculate connector line positions
|
|
218
|
-
function getConnectorLine(segment: any) {
|
|
219
|
-
const midAngle = (segment.startAngle + segment.endAngle) / 2
|
|
220
|
-
const midAngleRad = (midAngle * Math.PI) / 180
|
|
221
|
-
const radius = props.size / 2 - 10
|
|
222
|
-
const innerRadius = props.donut ? radius * props.thickness : radius * 0.5
|
|
223
|
-
const outerRadius = radius + 15 // Reduced from 20 to 15
|
|
224
|
-
|
|
225
|
-
const innerX = Math.cos(midAngleRad) * innerRadius
|
|
226
|
-
const innerY = Math.sin(midAngleRad) * innerRadius
|
|
227
|
-
const outerX = Math.cos(midAngleRad) * outerRadius
|
|
228
|
-
const outerY = Math.sin(midAngleRad) * outerRadius
|
|
229
|
-
|
|
230
|
-
// Extended line for label - reduced extension
|
|
231
|
-
const labelX = outerX + (outerX > 0 ? 8 : -8) // Reduced from 10 to 8
|
|
232
|
-
const labelY = outerY
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
innerX,
|
|
236
|
-
innerY,
|
|
237
|
-
outerX,
|
|
238
|
-
outerY,
|
|
239
|
-
labelX,
|
|
240
|
-
labelY,
|
|
241
|
-
textAnchor: outerX > 0 ? 'start' : 'end'
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const centerDisplayValue = computed(() => {
|
|
246
|
-
if (props.centerValue !== undefined) {
|
|
247
|
-
return props.centerValue
|
|
248
|
-
}
|
|
249
|
-
return props.showCenterTotal ? totalValue.value : 0
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
const pieSegments = computed(() => {
|
|
253
|
-
let cumulativeAngle = 0
|
|
254
|
-
return props.data.map((item, index) => {
|
|
255
|
-
const percentage = item.value / totalValue.value
|
|
256
|
-
const angle = percentage * 360
|
|
257
|
-
|
|
258
|
-
const startAngle = cumulativeAngle
|
|
259
|
-
const endAngle = cumulativeAngle + angle
|
|
260
|
-
cumulativeAngle += angle
|
|
261
|
-
|
|
262
|
-
// Calculate if this segment should be visible based on animation progress
|
|
263
|
-
// Add a small delay even for the first segment to avoid immediate appearance
|
|
264
|
-
const baseDelay = 100 // 100ms delay for the first segment
|
|
265
|
-
const segmentDelay = props.animationDelay > 0
|
|
266
|
-
? baseDelay + (index * props.animationDelay)
|
|
267
|
-
: baseDelay + (index * 50) // Small stagger even when animationDelay is 0
|
|
268
|
-
const totalElapsedTime = animatedProgress.value * props.animationDuration
|
|
269
|
-
const isVisible = !props.animated || totalElapsedTime >= segmentDelay
|
|
270
|
-
|
|
271
|
-
// Debug log for animation
|
|
272
|
-
if (props.animated && index === 0) {
|
|
273
|
-
console.log(`Animation progress: ${(animatedProgress.value * 100).toFixed(1)}%, elapsed: ${totalElapsedTime.toFixed(0)}ms`)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (!isVisible) {
|
|
277
|
-
if (index < 3) {
|
|
278
|
-
console.log(`Segment ${index} waiting - needs ${segmentDelay}ms, current: ${totalElapsedTime.toFixed(0)}ms`)
|
|
279
|
-
}
|
|
280
|
-
return {
|
|
281
|
-
...item,
|
|
282
|
-
percentage,
|
|
283
|
-
angle: 0,
|
|
284
|
-
startAngle,
|
|
285
|
-
endAngle: startAngle,
|
|
286
|
-
path: '',
|
|
287
|
-
color: item.color || colors.value[index % colors.value.length],
|
|
288
|
-
visible: false
|
|
289
|
-
}
|
|
290
|
-
} if (index < 3) {
|
|
291
|
-
console.log(`Segment ${index} visible! - delay: ${segmentDelay}ms, elapsed: ${totalElapsedTime.toFixed(0)}ms`)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const startAngleRad = (startAngle * Math.PI) / 180
|
|
295
|
-
const endAngleRad = (endAngle * Math.PI) / 180
|
|
296
|
-
|
|
297
|
-
const radius = props.size / 2 - 10
|
|
298
|
-
const innerRadius = props.donut ? radius * props.thickness : 0
|
|
299
|
-
|
|
300
|
-
const segmentAngle = endAngle - startAngle
|
|
301
|
-
const largeArcFlag = segmentAngle > 180 ? 1 : 0
|
|
302
|
-
|
|
303
|
-
const pathData = props.donut
|
|
304
|
-
? createDonutPath(startAngleRad, endAngleRad, radius, innerRadius, largeArcFlag)
|
|
305
|
-
: createPiePath(startAngleRad, endAngleRad, radius, largeArcFlag)
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
...item,
|
|
309
|
-
percentage,
|
|
310
|
-
angle: segmentAngle,
|
|
311
|
-
startAngle,
|
|
312
|
-
endAngle,
|
|
313
|
-
path: pathData,
|
|
314
|
-
color: item.color || colors.value[index % colors.value.length],
|
|
315
|
-
visible: true
|
|
316
|
-
}
|
|
317
|
-
})
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
function createPiePath(startAngle: number, endAngle: number, radius: number, largeArcFlag: number): string {
|
|
321
|
-
const x1 = Math.cos(startAngle) * radius
|
|
322
|
-
const y1 = Math.sin(startAngle) * radius
|
|
323
|
-
const x2 = Math.cos(endAngle) * radius
|
|
324
|
-
const y2 = Math.sin(endAngle) * radius
|
|
325
|
-
|
|
326
|
-
return `M 0 0 L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2} Z`
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function createDonutPath(startAngle: number, endAngle: number, outerRadius: number, innerRadius: number, largeArcFlag: number): string {
|
|
330
|
-
const x1Outer = Math.cos(startAngle) * outerRadius
|
|
331
|
-
const y1Outer = Math.sin(startAngle) * outerRadius
|
|
332
|
-
const x2Outer = Math.cos(endAngle) * outerRadius
|
|
333
|
-
const y2Outer = Math.sin(endAngle) * outerRadius
|
|
334
|
-
|
|
335
|
-
const x1Inner = Math.cos(startAngle) * innerRadius
|
|
336
|
-
const y1Inner = Math.sin(startAngle) * innerRadius
|
|
337
|
-
const x2Inner = Math.cos(endAngle) * innerRadius
|
|
338
|
-
const y2Inner = Math.sin(endAngle) * innerRadius
|
|
339
|
-
|
|
340
|
-
return `M ${x1Outer} ${y1Outer} A ${outerRadius} ${outerRadius} 0 ${largeArcFlag} 1 ${x2Outer} ${y2Outer} L ${x2Inner} ${y2Inner} A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${x1Inner} ${y1Inner} Z`
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function formatValue(value: number): string {
|
|
344
|
-
return value.toLocaleString()
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function formatPercentage(percentage: number): string {
|
|
348
|
-
return `${(percentage * 100).toFixed(1)}%`
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function formatTooltip(segment: any): string {
|
|
352
|
-
return `<div class="pieTooltip">
|
|
353
|
-
<div class="round w-10px h-10px w-100p inline-block" style="background-color: ${segment.color};"></div>
|
|
354
|
-
<p class="inline-block txt12">${segment.label}</p>
|
|
355
|
-
<div class="gap-025 flex">
|
|
356
|
-
<p class="bold txt18">${formatValue(segment.value)}</p>
|
|
357
|
-
<p>|</p>
|
|
358
|
-
<p class="bold txt18">${formatPercentage(segment.percentage)}</p></div>`
|
|
359
|
-
}
|
|
360
|
-
</script>
|
|
361
|
-
|
|
362
|
-
<template>
|
|
363
|
-
<div ref="chartRef" class="h-100p flex column flex-stretch">
|
|
364
|
-
<div class="flex space-between pb-1">
|
|
365
|
-
<div class="flex align-center gap-05">
|
|
366
|
-
<Icon :name="icon" size="1.2" :color="color" class="line-height-0" />
|
|
367
|
-
<p class="white-space light m_txt14">
|
|
368
|
-
{{ title }}
|
|
369
|
-
</p>
|
|
370
|
-
</div>
|
|
371
|
-
<div v-if="percentageChange !== 0" class="flex align-center gap-025">
|
|
372
|
-
<Icon class="line-height-1" :name="percentageChange > 0 ? 'trending_up' : 'trending_down'" size="1" :class="percentageChange > 0 ? 'color-success' : 'color-danger'" />
|
|
373
|
-
<span class="txt12 bold" :class="percentageChange > 0 ? 'color-success' : 'color-danger'">
|
|
374
|
-
{{ Math.abs(percentageChange) }}%
|
|
375
|
-
</span>
|
|
376
|
-
</div>
|
|
377
|
-
</div>
|
|
378
|
-
<div class="chart-container flex space-between gap-1 flex-wrap flex-grow" :class="{ 'with-legend': showLegend, 'with-connectors': showConnectorLines }">
|
|
379
|
-
<svg :width="svgSize.width" :height="svgSize.height" class="chart-svg mx-auto flex-shrink-0" :style="showConnectorLines ? 'overflow: visible;' : ''">
|
|
380
|
-
<g :transform="`translate(${centerOffset.x}, ${centerOffset.y})`">
|
|
381
|
-
<path
|
|
382
|
-
v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="index" v-tooltip="{ content: formatTooltip(segment), html: true }" :d="segment.path" :fill="segment.color"
|
|
383
|
-
class="pie-segment" stroke="white" stroke-width="2"
|
|
384
|
-
/>
|
|
385
|
-
|
|
386
|
-
<!-- Center text for donut chart -->
|
|
387
|
-
<g v-if="donut && (showCenterTotal || centerValue !== undefined)" class="center-text">
|
|
388
|
-
<text x="0" y="-8" text-anchor="middle" dominant-baseline="middle" class="center-label txt12 opacity-7">
|
|
389
|
-
{{ centerLabel }}
|
|
390
|
-
</text>
|
|
391
|
-
<text x="0" y="12" text-anchor="middle" dominant-baseline="middle" class="center-value txt20 bold">
|
|
392
|
-
{{ formatValue(centerDisplayValue) }}
|
|
393
|
-
</text>
|
|
394
|
-
</g>
|
|
395
|
-
|
|
396
|
-
<!-- Connector lines -->
|
|
397
|
-
<g v-if="showConnectorLines" class="connector-lines">
|
|
398
|
-
<g v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`line-${index}`">
|
|
399
|
-
<polyline
|
|
400
|
-
:points="`${getConnectorLine(segment).innerX},${getConnectorLine(segment).innerY} ${getConnectorLine(segment).outerX},${getConnectorLine(segment).outerY} ${getConnectorLine(segment).labelX},${getConnectorLine(segment).labelY}`"
|
|
401
|
-
stroke="#333" stroke-width="1.5" fill="none"
|
|
402
|
-
/>
|
|
403
|
-
</g>
|
|
404
|
-
</g>
|
|
405
|
-
|
|
406
|
-
<!-- Labels on chart -->
|
|
407
|
-
<g v-if="showLabelsOnChart" class="chart-labels">
|
|
408
|
-
<!-- Labels without connector lines -->
|
|
409
|
-
<g v-if="!showConnectorLines">
|
|
410
|
-
<text
|
|
411
|
-
v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`label-${index}`" :x="getLabelPosition(segment).x" :y="getLabelPosition(segment).y - 5"
|
|
412
|
-
text-anchor="middle" dominant-baseline="middle" class="chart-label txt12 pointer-events-none" :fill="labelColor"
|
|
413
|
-
>
|
|
414
|
-
{{ segment.label }}
|
|
415
|
-
</text>
|
|
416
|
-
<text
|
|
417
|
-
v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`value-${index}`" :x="getLabelPosition(segment).x" :y="getLabelPosition(segment).y + 12"
|
|
418
|
-
text-anchor="middle" dominant-baseline="middle" class="chart-value txt16 mt-1 line-height-2 bold pointer-events-none" :fill="labelColor"
|
|
419
|
-
>
|
|
420
|
-
{{ formatValue(segment.value) }}
|
|
421
|
-
</text>
|
|
422
|
-
</g>
|
|
423
|
-
|
|
424
|
-
<!-- Labels with connector lines -->
|
|
425
|
-
<template v-if="showConnectorLines">
|
|
426
|
-
<text
|
|
427
|
-
v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`label-line-${index}`" :x="getConnectorLine(segment).labelX"
|
|
428
|
-
:y="getConnectorLine(segment).labelY - 8" :text-anchor="getConnectorLine(segment).textAnchor" dominant-baseline="middle" class="chart-label txt12 bold" :fill="labelColor"
|
|
429
|
-
>
|
|
430
|
-
{{ segment.label }}
|
|
431
|
-
</text>
|
|
432
|
-
|
|
433
|
-
<!-- Value labels with connector lines -->
|
|
434
|
-
<text
|
|
435
|
-
v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`value-line-${index}`" :x="getConnectorLine(segment).labelX"
|
|
436
|
-
:y="getConnectorLine(segment).labelY + 2" :text-anchor="getConnectorLine(segment).textAnchor" dominant-baseline="middle" class="chart-value txt11" fill="#666"
|
|
437
|
-
>
|
|
438
|
-
{{ formatValue(segment.value) }}
|
|
439
|
-
</text>
|
|
440
|
-
|
|
441
|
-
<!-- Percentage labels with connector lines -->
|
|
442
|
-
<text
|
|
443
|
-
v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`percentage-${index}`" :x="getConnectorLine(segment).labelX"
|
|
444
|
-
:y="getConnectorLine(segment).labelY + 15" :text-anchor="getConnectorLine(segment).textAnchor" dominant-baseline="middle" class="chart-percentage txt10 pointer-events-none"
|
|
445
|
-
fill="#999"
|
|
446
|
-
>
|
|
447
|
-
({{ formatPercentage(segment.percentage) }})
|
|
448
|
-
</text>
|
|
449
|
-
</template>
|
|
450
|
-
</g>
|
|
451
|
-
</g>
|
|
452
|
-
</svg>
|
|
453
|
-
|
|
454
|
-
<div v-if="showLegend" class="legend mx-auto display-flex column gap-05 min-w-100px">
|
|
455
|
-
<div v-for="(segment, index) in pieSegments" :key="index" class="gap-1 flex align-items-center txt14 justify-content-start">
|
|
456
|
-
<div class="legend-color flex-shrink-0 radius-05" :style="{ backgroundColor: segment.color }" />
|
|
457
|
-
<span v-if="segment.label" class="flex-shrink-1 flex-grow-1">{{ segment.label }}</span>
|
|
458
|
-
<span class="bold">{{ formatValue(segment.value) }}</span>
|
|
459
|
-
<span class="opacity-6 txt12">({{ formatPercentage(segment.percentage) }})</span>
|
|
460
|
-
</div>
|
|
461
|
-
</div>
|
|
462
|
-
</div>
|
|
463
|
-
</div>
|
|
464
|
-
</template>
|
|
465
|
-
|
|
466
|
-
<style scoped>
|
|
467
|
-
|
|
468
|
-
.pie-segment {
|
|
469
|
-
transition: opacity 0.3s ease;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
[dir="rtl"] .chart-container.with-legend {
|
|
473
|
-
flex-direction: row-reverse;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
.pie-segment {
|
|
477
|
-
cursor: pointer;
|
|
478
|
-
transition: opacity 0.2s ease;
|
|
479
|
-
animation: fadeIn 0.3s ease-in-out;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
@keyframes fadeIn {
|
|
483
|
-
from {
|
|
484
|
-
opacity: 0;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
to {
|
|
488
|
-
opacity: 1;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
.pie-segment:hover {
|
|
493
|
-
opacity: 0.8;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/* RTL support for legend */
|
|
497
|
-
[dir="rtl"] .legend-item {
|
|
498
|
-
flex-direction: row-reverse;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
.legend-color {
|
|
502
|
-
width: 12px;
|
|
503
|
-
height: 12px;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
.connector-lines polyline {
|
|
507
|
-
opacity: 0.8;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
.chart-container.with-connectors {
|
|
511
|
-
overflow: visible;
|
|
512
|
-
padding: 10px;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
.chart-container.with-connectors .chart-svg {
|
|
516
|
-
overflow: visible;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
.chart-svg {
|
|
520
|
-
max-width: 100%;
|
|
521
|
-
max-height: 100%;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
.chart-container {
|
|
525
|
-
min-height: 0;
|
|
526
|
-
align-items: center;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
.chart-container.flex-grow {
|
|
530
|
-
flex: 1;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
</style>
|
|
534
|
-
|
|
535
|
-
<style>
|
|
536
|
-
|
|
537
|
-
.v-popper--theme-tooltip .v-popper__inner:has(.pieTooltip) {
|
|
538
|
-
background-color: var(--bgl-black) !important;
|
|
539
|
-
color: var(--bgl-white) !important;
|
|
540
|
-
border-radius: 0.5rem !important;
|
|
541
|
-
padding: 0.25rem 0.5rem !important;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
</style>
|