nuxt-charts 2.2.1 → 3.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +80 -79
  3. package/dist/module.d.mts +4 -4
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +64 -95
  6. package/dist/runtime/assets/components.css +1 -0
  7. package/dist/runtime/assets/theme.css +1 -0
  8. package/dist/runtime/components/AreaChart.d.vue.ts +19 -0
  9. package/dist/runtime/components/AreaChart.vue +149 -0
  10. package/dist/runtime/components/AreaChart.vue.d.ts +19 -0
  11. package/dist/runtime/components/BarChart.d.vue.ts +19 -0
  12. package/dist/runtime/components/BarChart.vue +175 -0
  13. package/dist/runtime/components/BarChart.vue.d.ts +19 -0
  14. package/dist/runtime/components/BubbleChart.d.vue.ts +19 -0
  15. package/dist/runtime/components/BubbleChart.vue +213 -0
  16. package/dist/runtime/components/BubbleChart.vue.d.ts +19 -0
  17. package/dist/runtime/components/CandlestickChart.d.vue.ts +19 -0
  18. package/dist/runtime/components/CandlestickChart.vue +329 -0
  19. package/dist/runtime/components/CandlestickChart.vue.d.ts +19 -0
  20. package/dist/runtime/components/DonutChart.d.vue.ts +21 -0
  21. package/dist/runtime/components/DonutChart.vue +116 -0
  22. package/dist/runtime/components/DonutChart.vue.d.ts +21 -0
  23. package/dist/runtime/components/FunnelChart.d.vue.ts +19 -0
  24. package/dist/runtime/components/FunnelChart.vue +152 -0
  25. package/dist/runtime/components/FunnelChart.vue.d.ts +19 -0
  26. package/dist/runtime/components/LineChart.d.vue.ts +19 -0
  27. package/dist/runtime/components/LineChart.vue +84 -0
  28. package/dist/runtime/components/LineChart.vue.d.ts +19 -0
  29. package/dist/runtime/components/RadarChart.d.vue.ts +19 -0
  30. package/dist/runtime/components/RadarChart.vue +90 -0
  31. package/dist/runtime/components/RadarChart.vue.d.ts +19 -0
  32. package/dist/runtime/components/RadialBarChart.d.vue.ts +19 -0
  33. package/dist/runtime/components/RadialBarChart.vue +81 -0
  34. package/dist/runtime/components/RadialBarChart.vue.d.ts +19 -0
  35. package/dist/runtime/components/StatusTrackerChart.d.vue.ts +19 -0
  36. package/dist/runtime/components/StatusTrackerChart.vue +147 -0
  37. package/dist/runtime/components/StatusTrackerChart.vue.d.ts +19 -0
  38. package/dist/runtime/components/internal/CandlestickTooltip.d.vue.ts +17 -0
  39. package/dist/runtime/components/internal/CandlestickTooltip.vue +38 -0
  40. package/dist/runtime/components/internal/CandlestickTooltip.vue.d.ts +17 -0
  41. package/dist/runtime/components/internal/CartesianFrame.d.vue.ts +40 -0
  42. package/dist/runtime/components/internal/CartesianFrame.vue +321 -0
  43. package/dist/runtime/components/internal/CartesianFrame.vue.d.ts +40 -0
  44. package/dist/runtime/components/internal/ChartLegend.d.vue.ts +11 -0
  45. package/dist/runtime/components/internal/ChartLegend.vue +22 -0
  46. package/dist/runtime/components/internal/ChartLegend.vue.d.ts +11 -0
  47. package/dist/runtime/components/internal/ChartTooltip.d.vue.ts +3 -0
  48. package/dist/runtime/components/internal/ChartTooltip.vue +39 -0
  49. package/dist/runtime/components/internal/ChartTooltip.vue.d.ts +3 -0
  50. package/dist/runtime/components/internal/CubeBarShape.d.vue.ts +28 -0
  51. package/dist/runtime/components/internal/CubeBarShape.vue +69 -0
  52. package/dist/runtime/components/internal/CubeBarShape.vue.d.ts +28 -0
  53. package/dist/runtime/components/internal/DitherDefs.d.ts +98 -0
  54. package/dist/runtime/components/internal/DitherDefs.js +158 -0
  55. package/dist/runtime/components/internal/FunnelLayeredShape.d.vue.ts +42 -0
  56. package/dist/runtime/components/internal/FunnelLayeredShape.vue +124 -0
  57. package/dist/runtime/components/internal/FunnelLayeredShape.vue.d.ts +42 -0
  58. package/dist/runtime/components/internal/GradientDefs.d.ts +46 -0
  59. package/dist/runtime/components/internal/GradientDefs.js +38 -0
  60. package/dist/runtime/enums.d.ts +65 -0
  61. package/dist/runtime/enums.js +40 -0
  62. package/dist/runtime/types/charts.d.ts +642 -0
  63. package/dist/runtime/types/charts.js +0 -0
  64. package/dist/runtime/types/shared.d.ts +202 -0
  65. package/dist/runtime/types/shared.js +0 -0
  66. package/dist/runtime/types.d.ts +8 -0
  67. package/dist/runtime/types.js +0 -0
  68. package/dist/runtime/utils/axis.d.ts +132 -0
  69. package/dist/runtime/utils/axis.js +93 -0
  70. package/dist/runtime/utils/categories.d.ts +36 -0
  71. package/dist/runtime/utils/categories.js +18 -0
  72. package/dist/runtime/utils/cubes.d.ts +100 -0
  73. package/dist/runtime/utils/cubes.js +73 -0
  74. package/dist/runtime/utils/curve.d.ts +12 -0
  75. package/dist/runtime/utils/curve.js +26 -0
  76. package/dist/runtime/utils/dither.d.ts +55 -0
  77. package/dist/runtime/utils/dither.js +45 -0
  78. package/dist/runtime/utils/gradient.d.ts +12 -0
  79. package/dist/runtime/utils/gradient.js +8 -0
  80. package/dist/runtime/utils/index.d.ts +6 -0
  81. package/dist/runtime/utils/index.js +6 -0
  82. package/dist/runtime/utils/legend.d.ts +27 -0
  83. package/dist/runtime/utils/legend.js +19 -0
  84. package/dist/runtime/utils/marker.d.ts +20 -0
  85. package/dist/runtime/utils/marker.js +21 -0
  86. package/dist/runtime/utils/style.d.ts +14 -0
  87. package/dist/runtime/utils/style.js +10 -0
  88. package/dist/runtime/utils/theme.d.ts +36 -0
  89. package/dist/runtime/utils/theme.js +45 -0
  90. package/package.json +56 -12
  91. package/dist/runtime/vue-chrts.d.ts +0 -1
  92. package/dist/runtime/vue-chrts.js +0 -13
@@ -0,0 +1,329 @@
1
+ <script setup>
2
+ import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";
3
+ import CandlestickTooltip from "./internal/CandlestickTooltip.vue";
4
+ const props = defineProps({
5
+ data: { type: Array, required: true },
6
+ height: { type: Number, required: true },
7
+ xAccessor: { type: null, required: false },
8
+ openAccessor: { type: null, required: false },
9
+ highAccessor: { type: null, required: false },
10
+ lowAccessor: { type: null, required: false },
11
+ closeAccessor: { type: null, required: false },
12
+ volumeAccessor: { type: null, required: false },
13
+ upColor: { type: String, required: false },
14
+ downColor: { type: String, required: false },
15
+ candleWidth: { type: Number, required: false },
16
+ wickWidth: { type: Number, required: false },
17
+ showVolume: { type: Boolean, required: false },
18
+ xLabel: { type: String, required: false },
19
+ yLabel: { type: String, required: false },
20
+ yFormatter: { type: Function, required: false },
21
+ xFormatter: { type: Function, required: false },
22
+ tooltipTitleFormatter: { type: Function, required: false },
23
+ yNumTicks: { type: Number, required: false },
24
+ yDomain: { type: Array, required: false },
25
+ xGridLine: { type: Boolean, required: false },
26
+ yGridLine: { type: Boolean, required: false },
27
+ hideXAxis: { type: Boolean, required: false },
28
+ hideYAxis: { type: Boolean, required: false },
29
+ hideTooltip: { type: Boolean, required: false },
30
+ tooltip: { type: Object, required: false }
31
+ });
32
+ const xKey = computed(() => String(props.xAccessor ?? "label"));
33
+ const openKey = computed(() => String(props.openAccessor ?? "open"));
34
+ const highKey = computed(() => String(props.highAccessor ?? "high"));
35
+ const lowKey = computed(() => String(props.lowAccessor ?? "low"));
36
+ const closeKey = computed(() => String(props.closeAccessor ?? "close"));
37
+ const volumeKey = computed(() => String(props.volumeAccessor ?? "volume"));
38
+ const upColor = computed(() => props.upColor ?? "#10b981");
39
+ const downColor = computed(() => props.downColor ?? "#ef4444");
40
+ const showVolume = computed(() => props.showVolume ?? false);
41
+ const maxCandleWidth = computed(() => props.candleWidth ?? 18);
42
+ const wickWidth = computed(() => props.wickWidth ?? 1.5);
43
+ const yGridLine = computed(() => props.yGridLine ?? true);
44
+ const xGridLine = computed(() => props.xGridLine ?? false);
45
+ const yNumTicks = computed(() => props.yNumTicks ?? 5);
46
+ const candles = computed(
47
+ () => props.data.map((row) => ({
48
+ label: row[xKey.value],
49
+ open: Number(row[openKey.value]),
50
+ high: Number(row[highKey.value]),
51
+ low: Number(row[lowKey.value]),
52
+ close: Number(row[closeKey.value]),
53
+ volume: props.volumeAccessor || volumeKey.value in row ? Number(row[volumeKey.value]) : void 0
54
+ }))
55
+ );
56
+ const containerRef = useTemplateRef("containerRef");
57
+ const W = ref(600);
58
+ const H = computed(() => props.height);
59
+ const PAD = computed(() => ({
60
+ top: 12,
61
+ right: props.hideYAxis ? 4 : 12,
62
+ bottom: props.hideXAxis ? 4 : 36,
63
+ left: props.hideYAxis ? 4 : 56
64
+ }));
65
+ const chartW = computed(() => Math.max(W.value - PAD.value.left - PAD.value.right, 1));
66
+ const VOL_GAP = 8;
67
+ const volumeH = computed(() => showVolume.value ? H.value * 0.22 : 0);
68
+ const chartH = computed(
69
+ () => Math.max(
70
+ H.value - PAD.value.top - PAD.value.bottom - volumeH.value - (showVolume.value ? VOL_GAP : 0),
71
+ 1
72
+ )
73
+ );
74
+ const volumeTop = computed(() => PAD.value.top + chartH.value + VOL_GAP);
75
+ const priceMin = computed(() => {
76
+ const dataMin = candles.value.length ? Math.min(...candles.value.map((c) => c.low)) : 0;
77
+ return props.yDomain?.[0] ?? dataMin;
78
+ });
79
+ const priceMax = computed(() => {
80
+ const dataMax = candles.value.length ? Math.max(...candles.value.map((c) => c.high)) : 1;
81
+ return props.yDomain?.[1] ?? dataMax;
82
+ });
83
+ const priceRange = computed(() => Math.max(priceMax.value - priceMin.value, Number.EPSILON));
84
+ function priceToY(price) {
85
+ return PAD.value.top + (priceMax.value - price) / priceRange.value * chartH.value;
86
+ }
87
+ function indexToX(i) {
88
+ const n = candles.value.length || 1;
89
+ return PAD.value.left + (i + 0.5) * (chartW.value / n);
90
+ }
91
+ const volumeMax = computed(
92
+ () => showVolume.value ? Math.max(...candles.value.map((c) => c.volume ?? 0), 1) : 1
93
+ );
94
+ const layouts = computed(() => {
95
+ const n = candles.value.length || 1;
96
+ const step = chartW.value / n;
97
+ const halfW = Math.max(Math.min(maxCandleWidth.value, step * 0.7) / 2, 1);
98
+ return candles.value.map((c, i) => {
99
+ const rising = c.close >= c.open;
100
+ const color = rising ? upColor.value : downColor.value;
101
+ const centerX = indexToX(i);
102
+ const bodyTop = priceToY(Math.max(c.open, c.close));
103
+ const bodyBottom = priceToY(Math.min(c.open, c.close));
104
+ const volH = showVolume.value ? (c.volume ?? 0) / volumeMax.value * volumeH.value : 0;
105
+ return {
106
+ key: c.label,
107
+ x: centerX - halfW,
108
+ centerX,
109
+ halfW,
110
+ bodyTop,
111
+ bodyH: Math.max(bodyBottom - bodyTop, 1.5),
112
+ wickTop: priceToY(c.high),
113
+ wickBottom: priceToY(c.low),
114
+ color,
115
+ rising,
116
+ volH,
117
+ volY: volumeTop.value + volumeH.value - volH
118
+ };
119
+ });
120
+ });
121
+ const yTicks = computed(() => {
122
+ const count = Math.max(yNumTicks.value, 2);
123
+ return Array.from({ length: count }, (_, i) => {
124
+ const price = priceMin.value + priceRange.value / (count - 1) * i;
125
+ return { price, y: priceToY(price) };
126
+ });
127
+ });
128
+ const xTicks = computed(() => {
129
+ const n = candles.value.length;
130
+ const step = Math.max(1, Math.ceil(n / 8));
131
+ return candles.value.map((c, i) => ({
132
+ label: props.xFormatter ? props.xFormatter(i) : c.label,
133
+ x: indexToX(i),
134
+ i
135
+ })).filter((_, i) => i === 0 || i === n - 1 || i % step === 0);
136
+ });
137
+ function fmtPrice(value) {
138
+ return props.yFormatter ? props.yFormatter(value) : value.toLocaleString();
139
+ }
140
+ const activeIndex = ref(null);
141
+ const pointer = ref({ x: 0, y: 0 });
142
+ const activeCandle = computed(
143
+ () => activeIndex.value !== null ? candles.value[activeIndex.value] : null
144
+ );
145
+ const activeLayout = computed(
146
+ () => activeIndex.value !== null ? layouts.value[activeIndex.value] : null
147
+ );
148
+ function onMouseMove(e) {
149
+ if (props.hideTooltip) return;
150
+ const rect = containerRef.value.getBoundingClientRect();
151
+ const mx = e.clientX - rect.left;
152
+ const stepW = chartW.value / (candles.value.length || 1);
153
+ const idx = Math.floor((mx - PAD.value.left) / stepW);
154
+ if (idx >= 0 && idx < candles.value.length) {
155
+ activeIndex.value = idx;
156
+ pointer.value = { x: e.clientX, y: e.clientY };
157
+ } else {
158
+ activeIndex.value = null;
159
+ }
160
+ }
161
+ function onMouseLeave() {
162
+ activeIndex.value = null;
163
+ }
164
+ let ro;
165
+ onMounted(() => {
166
+ if (!containerRef.value) return;
167
+ ro = new ResizeObserver((entries) => {
168
+ const cr = entries[0]?.contentRect;
169
+ if (cr?.width) W.value = cr.width;
170
+ });
171
+ ro.observe(containerRef.value);
172
+ });
173
+ onBeforeUnmount(() => ro?.disconnect());
174
+ </script>
175
+
176
+ <template>
177
+ <div
178
+ ref="containerRef"
179
+ class="vc-candlestick-chart vue-chrts"
180
+ @mousemove="onMouseMove"
181
+ @mouseleave="onMouseLeave"
182
+ >
183
+ <svg :width="W" :height="H" class="vc-candlestick-chart__svg">
184
+ <!-- Grid -->
185
+ <template v-if="yGridLine">
186
+ <line
187
+ v-for="tick in yTicks"
188
+ :key="`yg-${tick.price}`"
189
+ :x1="PAD.left"
190
+ :x2="PAD.left + chartW"
191
+ :y1="tick.y"
192
+ :y2="tick.y"
193
+ stroke="currentColor"
194
+ stroke-opacity="0.15"
195
+ stroke-dasharray="3 3"
196
+ />
197
+ </template>
198
+ <template v-if="xGridLine">
199
+ <line
200
+ v-for="tick in xTicks"
201
+ :key="`xg-${tick.i}`"
202
+ :x1="tick.x"
203
+ :x2="tick.x"
204
+ :y1="PAD.top"
205
+ :y2="PAD.top + chartH"
206
+ stroke="currentColor"
207
+ stroke-opacity="0.15"
208
+ stroke-dasharray="3 3"
209
+ />
210
+ </template>
211
+
212
+ <!-- Hover column highlight -->
213
+ <rect
214
+ v-if="activeLayout"
215
+ :x="activeLayout.x - 4"
216
+ :y="PAD.top"
217
+ :width="activeLayout.halfW * 2 + 8"
218
+ :height="chartH"
219
+ rx="4"
220
+ fill="currentColor"
221
+ fill-opacity="0.06"
222
+ />
223
+
224
+ <!-- Volume histogram -->
225
+ <template v-if="showVolume">
226
+ <rect
227
+ v-for="c in layouts"
228
+ :key="`vol-${c.key}`"
229
+ :x="c.x"
230
+ :y="c.volY"
231
+ :width="c.halfW * 2"
232
+ :height="c.volH"
233
+ :fill="c.color"
234
+ fill-opacity="0.25"
235
+ rx="1"
236
+ />
237
+ </template>
238
+
239
+ <!-- Candles -->
240
+ <g v-for="(c, i) in layouts" :key="c.key">
241
+ <line
242
+ :x1="c.centerX"
243
+ :x2="c.centerX"
244
+ :y1="c.wickTop"
245
+ :y2="c.wickBottom"
246
+ :stroke="c.color"
247
+ :stroke-width="activeIndex === i ? wickWidth + 1 : wickWidth"
248
+ />
249
+ <rect
250
+ :x="c.x"
251
+ :y="c.bodyTop"
252
+ :width="c.halfW * 2"
253
+ :height="c.bodyH"
254
+ :rx="Math.min(2, c.halfW / 2)"
255
+ :fill="c.rising ? c.color : 'var(--chart-bg, #ffffff)'"
256
+ :stroke="c.color"
257
+ :stroke-width="activeIndex === i ? 2.5 : 1.5"
258
+ />
259
+ </g>
260
+
261
+ <!-- Y axis -->
262
+ <template v-if="!hideYAxis">
263
+ <text
264
+ v-for="tick in yTicks"
265
+ :key="`yt-${tick.price}`"
266
+ :x="PAD.left - 8"
267
+ :y="tick.y"
268
+ text-anchor="end"
269
+ dominant-baseline="middle"
270
+ fill="currentColor"
271
+ opacity="0.6"
272
+ font-size="11"
273
+ >{{ fmtPrice(tick.price) }}</text>
274
+ <text
275
+ v-if="yLabel"
276
+ :transform="`translate(14,${PAD.top + chartH / 2}) rotate(-90)`"
277
+ text-anchor="middle"
278
+ fill="currentColor"
279
+ opacity="0.7"
280
+ font-size="12"
281
+ >{{ yLabel }}</text>
282
+ </template>
283
+
284
+ <!-- X axis -->
285
+ <template v-if="!hideXAxis">
286
+ <text
287
+ v-for="tick in xTicks"
288
+ :key="`xt-${tick.i}`"
289
+ :x="tick.x"
290
+ :y="PAD.top + chartH + (showVolume ? volumeH + VOL_GAP : 0) + 18"
291
+ text-anchor="middle"
292
+ fill="currentColor"
293
+ opacity="0.6"
294
+ font-size="11"
295
+ >{{ tick.label }}</text>
296
+ <text
297
+ v-if="xLabel"
298
+ :x="PAD.left + chartW / 2"
299
+ :y="H - 2"
300
+ text-anchor="middle"
301
+ fill="currentColor"
302
+ opacity="0.7"
303
+ font-size="12"
304
+ >{{ xLabel }}</text>
305
+ </template>
306
+ </svg>
307
+
308
+ <Teleport to="body">
309
+ <div
310
+ v-if="!hideTooltip && activeCandle"
311
+ class="candlestick-chart__tooltip"
312
+ :style="{
313
+ position: 'fixed',
314
+ left: `${pointer.x + 12}px`,
315
+ top: `${pointer.y}px`,
316
+ pointerEvents: 'none',
317
+ zIndex: 9999
318
+ }"
319
+ >
320
+ <CandlestickTooltip
321
+ :row="activeCandle"
322
+ :up-color="upColor"
323
+ :down-color="downColor"
324
+ :value-formatter="yFormatter"
325
+ />
326
+ </div>
327
+ </Teleport>
328
+ </div>
329
+ </template>
@@ -0,0 +1,19 @@
1
+ import type { CandlestickChartProps } from "../types/charts.js";
2
+ declare const __VLS_export: <T extends Record<string, unknown>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<CandlestickChartProps<T>> & (typeof globalThis extends {
4
+ __VLS_PROPS_FALLBACK: infer P;
5
+ } ? P : {});
6
+ expose: (exposed: {}) => void;
7
+ attrs: any;
8
+ slots: {};
9
+ emit: {};
10
+ }>) => import("vue").VNode & {
11
+ __ctx?: Awaited<typeof __VLS_setup>;
12
+ };
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
15
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
16
+ [K in keyof T]: T[K];
17
+ } : {
18
+ [K in keyof T as K]: T[K];
19
+ }) & {};
@@ -0,0 +1,21 @@
1
+ import type { DonutChartProps } from "../types/charts.js";
2
+ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<DonutChartProps<T>> & (typeof globalThis extends {
4
+ __VLS_PROPS_FALLBACK: infer P;
5
+ } ? P : {});
6
+ expose: (exposed: {}) => void;
7
+ attrs: any;
8
+ slots: {
9
+ default?: (props: {}) => any;
10
+ };
11
+ emit: {};
12
+ }>) => import("vue").VNode & {
13
+ __ctx?: Awaited<typeof __VLS_setup>;
14
+ };
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
17
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
18
+ [K in keyof T]: T[K];
19
+ } : {
20
+ [K in keyof T as K]: T[K];
21
+ }) & {};
@@ -0,0 +1,116 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { Legend, Pie, PieChart, ResponsiveContainer, Tooltip } from "vccs";
4
+ import ChartTooltip from "./internal/ChartTooltip.vue";
5
+ import ChartLegend from "./internal/ChartLegend.vue";
6
+ import { DonutType } from "../enums";
7
+ import { categoriesToSeries } from "../utils/categories";
8
+ import { legendPositionToLegendProps, resolveLegendWrapperStyle } from "../utils/legend";
9
+ import { toCssProperties } from "../utils/style";
10
+ import { themeToVars } from "../utils/theme";
11
+ const props = defineProps({
12
+ data: { type: Array, required: true },
13
+ categories: { type: Object, required: true },
14
+ type: { type: String, required: false },
15
+ height: { type: Number, required: false },
16
+ radius: { type: Number, required: false },
17
+ arcWidth: { type: Number, required: false },
18
+ padAngle: { type: Number, required: false },
19
+ stroke: { type: String, required: false },
20
+ hideLegend: { type: Boolean, required: false },
21
+ legendPosition: { type: String, required: false },
22
+ legendStyle: { type: [String, Object], required: false },
23
+ hideTooltip: { type: Boolean, required: false },
24
+ tooltipTitleFormatter: { type: Function, required: false },
25
+ tooltip: { type: Object, required: false },
26
+ theme: { type: Object, required: false },
27
+ duration: { type: Number, required: false }
28
+ });
29
+ const themeVars = computed(() => themeToVars(props.theme));
30
+ const segments = computed(() => {
31
+ const cats = categoriesToSeries(props.categories);
32
+ return props.data.map((value, i) => ({
33
+ name: cats[i]?.name ?? String(i),
34
+ value,
35
+ // vccs reads `fill` directly from the data entry to color each sector
36
+ fill: cats[i]?.color ?? `var(--chart-color-${i})`
37
+ }));
38
+ });
39
+ const DEFAULT_ARC_WIDTH = 40;
40
+ const AUTO_OUTER_FRACTION = 0.9;
41
+ const radii = computed(() => {
42
+ const arcWidth = props.arcWidth ?? DEFAULT_ARC_WIDTH;
43
+ if (props.radius && props.radius > 0) {
44
+ return {
45
+ outerRadius: props.radius,
46
+ innerRadius: Math.max(props.radius - arcWidth, 0)
47
+ };
48
+ }
49
+ if (props.height && props.height > 0) {
50
+ const bound = props.type === DonutType.Half ? props.height : props.height / 2;
51
+ const outerRadius2 = bound * AUTO_OUTER_FRACTION;
52
+ return {
53
+ outerRadius: outerRadius2,
54
+ innerRadius: Math.max(outerRadius2 - arcWidth, 0)
55
+ };
56
+ }
57
+ return { outerRadius: "90%", innerRadius: "60%" };
58
+ });
59
+ const outerRadius = computed(() => radii.value.outerRadius);
60
+ const innerRadius = computed(() => radii.value.innerRadius);
61
+ const angles = computed(
62
+ () => props.type === DonutType.Half ? { startAngle: 180, endAngle: 0 } : { startAngle: 0, endAngle: 360 }
63
+ );
64
+ const legend = computed(() => legendPositionToLegendProps(props.legendPosition));
65
+ const legendWrapperStyle = computed(
66
+ () => resolveLegendWrapperStyle(props.legendPosition, toCssProperties(props.legendStyle))
67
+ );
68
+ </script>
69
+
70
+ <template>
71
+ <div class="donut-chart vue-chrts" :style="{ position: 'relative', width: '100%', height: `${height ?? (radius ? radius * 2 : 200)}px`, ...themeVars }">
72
+ <ResponsiveContainer width="100%" height="100%">
73
+ <PieChart>
74
+ <Pie
75
+ :data="segments"
76
+ data-key="value"
77
+ name-key="name"
78
+ :inner-radius="innerRadius"
79
+ :outer-radius="outerRadius"
80
+ :start-angle="angles.startAngle"
81
+ :end-angle="angles.endAngle"
82
+ :padding-angle="padAngle ?? 0"
83
+ :stroke="stroke ?? 'none'"
84
+ :is-animation-active="duration !== void 0 && duration !== 0"
85
+ />
86
+ <Tooltip v-if="!hideTooltip" :content="ChartTooltip" :is-animation-active="false" />
87
+ <Legend
88
+ v-if="!hideLegend"
89
+ :align="legend.align"
90
+ :vertical-align="legend.verticalAlign"
91
+ :layout="legend.layout"
92
+ :wrapper-style="legendWrapperStyle"
93
+ >
94
+ <template #content="slotProps">
95
+ <ChartLegend v-bind="slotProps" />
96
+ </template>
97
+ </Legend>
98
+ </PieChart>
99
+ </ResponsiveContainer>
100
+
101
+ <!-- Centered overlay for custom content (v2 parity: default slot). -->
102
+ <div
103
+ v-if="$slots.default"
104
+ class="donut-chart__center"
105
+ :style="{
106
+ position: 'absolute',
107
+ top: '50%',
108
+ left: '50%',
109
+ transform: 'translate(-50%, -50%)',
110
+ pointerEvents: 'none'
111
+ }"
112
+ >
113
+ <slot />
114
+ </div>
115
+ </div>
116
+ </template>
@@ -0,0 +1,21 @@
1
+ import type { DonutChartProps } from "../types/charts.js";
2
+ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<DonutChartProps<T>> & (typeof globalThis extends {
4
+ __VLS_PROPS_FALLBACK: infer P;
5
+ } ? P : {});
6
+ expose: (exposed: {}) => void;
7
+ attrs: any;
8
+ slots: {
9
+ default?: (props: {}) => any;
10
+ };
11
+ emit: {};
12
+ }>) => import("vue").VNode & {
13
+ __ctx?: Awaited<typeof __VLS_setup>;
14
+ };
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
17
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
18
+ [K in keyof T]: T[K];
19
+ } : {
20
+ [K in keyof T as K]: T[K];
21
+ }) & {};
@@ -0,0 +1,19 @@
1
+ import type { FunnelChartProps } from "../types/charts.js";
2
+ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<FunnelChartProps<T>> & (typeof globalThis extends {
4
+ __VLS_PROPS_FALLBACK: infer P;
5
+ } ? P : {});
6
+ expose: (exposed: {}) => void;
7
+ attrs: any;
8
+ slots: {};
9
+ emit: {};
10
+ }>) => import("vue").VNode & {
11
+ __ctx?: Awaited<typeof __VLS_setup>;
12
+ };
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
15
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
16
+ [K in keyof T]: T[K];
17
+ } : {
18
+ [K in keyof T as K]: T[K];
19
+ }) & {};
@@ -0,0 +1,152 @@
1
+ <script setup>
2
+ import { computed, ref, useTemplateRef } from "vue";
3
+ import {
4
+ Funnel,
5
+ FunnelChart as VccsFunnelChart,
6
+ LabelList,
7
+ Legend,
8
+ ResponsiveContainer,
9
+ Tooltip
10
+ } from "vccs";
11
+ import ChartTooltip from "./internal/ChartTooltip.vue";
12
+ import ChartLegend from "./internal/ChartLegend.vue";
13
+ import FunnelLayeredShape, {
14
+ } from "./internal/FunnelLayeredShape.vue";
15
+ import { categoriesToSeries } from "../utils/categories";
16
+ import { legendPositionToLegendProps, resolveLegendWrapperStyle } from "../utils/legend";
17
+ import { toCssProperties } from "../utils/style";
18
+ import { themeToVars } from "../utils/theme";
19
+ const props = defineProps({
20
+ data: { type: Array, required: true },
21
+ categories: { type: Object, required: true },
22
+ height: { type: Number, required: true },
23
+ variant: { type: String, required: false },
24
+ lastShapeType: { type: String, required: false },
25
+ showValueLabel: { type: Boolean, required: false },
26
+ hideLegend: { type: Boolean, required: false },
27
+ legendPosition: { type: String, required: false },
28
+ legendStyle: { type: [String, Object], required: false },
29
+ hideTooltip: { type: Boolean, required: false },
30
+ tooltipTitleFormatter: { type: Function, required: false },
31
+ tooltip: { type: Object, required: false },
32
+ theme: { type: Object, required: false },
33
+ duration: { type: Number, required: false }
34
+ });
35
+ const themeVars = computed(() => themeToVars(props.theme));
36
+ const stages = computed(() => {
37
+ const cats = categoriesToSeries(props.categories);
38
+ return props.data.map((value, i) => ({
39
+ name: cats[i]?.name ?? String(i),
40
+ value,
41
+ fill: cats[i]?.color ?? `var(--chart-color-${i})`,
42
+ color: cats[i]?.color ?? `var(--chart-color-${i})`
43
+ }));
44
+ });
45
+ const isLayered = computed(() => props.variant === "layered");
46
+ const layeredData = computed(() => {
47
+ const maxValue = stages.value[0]?.value ?? 0;
48
+ return stages.value.map((stage, stageIndex) => ({
49
+ ...stage,
50
+ stageIndex,
51
+ stageCount: stages.value.length,
52
+ maxValue,
53
+ nextValue: stages.value[stageIndex + 1]?.value ?? stage.value
54
+ }));
55
+ });
56
+ const showValueLabel = computed(() => props.showValueLabel ?? true);
57
+ const legend = computed(() => legendPositionToLegendProps(props.legendPosition));
58
+ const legendWrapperStyle = computed(
59
+ () => resolveLegendWrapperStyle(props.legendPosition, toCssProperties(props.legendStyle))
60
+ );
61
+ const chartContainer = useTemplateRef("chartContainer");
62
+ const activeDatum = ref(null);
63
+ const tooltipPosition = ref({ x: 0, y: 0 });
64
+ function updateTooltipPosition(event) {
65
+ const bounds = chartContainer.value?.getBoundingClientRect();
66
+ if (!bounds) return;
67
+ tooltipPosition.value = {
68
+ x: Math.min(Math.max(event.clientX - bounds.left, 8), bounds.width - 8),
69
+ y: Math.min(Math.max(event.clientY - bounds.top, 8), bounds.height - 8)
70
+ };
71
+ }
72
+ function showTooltip(event, datum) {
73
+ activeDatum.value = datum;
74
+ updateTooltipPosition(event);
75
+ }
76
+ function hideTooltip() {
77
+ activeDatum.value = null;
78
+ }
79
+ </script>
80
+
81
+ <template>
82
+ <div
83
+ v-if="isLayered"
84
+ ref="chartContainer"
85
+ class="vc-funnel-layered vue-chrts"
86
+ :style="{ height: `${height}px`, ...themeVars }"
87
+ >
88
+ <ResponsiveContainer width="100%" height="100%">
89
+ <VccsFunnelChart :margin="{ top: 16, right: 0, bottom: 8, left: 0 }">
90
+ <Funnel
91
+ :data="layeredData"
92
+ data-key="value"
93
+ name-key="name"
94
+ last-shape-type="rectangle"
95
+ :is-animation-active="duration !== void 0 && duration !== 0"
96
+ >
97
+ <template #shape="shapeProps">
98
+ <FunnelLayeredShape
99
+ v-bind="shapeProps"
100
+ @mouseenter="showTooltip"
101
+ @mousemove="updateTooltipPosition"
102
+ @mouseleave="hideTooltip"
103
+ />
104
+ </template>
105
+ </Funnel>
106
+ </VccsFunnelChart>
107
+ </ResponsiveContainer>
108
+
109
+ <div
110
+ v-if="!hideTooltip && activeDatum"
111
+ class="vc-funnel-layered__tooltip"
112
+ :style="{ left: `${tooltipPosition.x}px`, top: `${tooltipPosition.y}px` }"
113
+ >
114
+ <span
115
+ class="vc-funnel-layered__dot"
116
+ :style="{ background: activeDatum.color }"
117
+ />
118
+ <span class="vc-funnel-layered__name">{{ activeDatum.name }}</span>
119
+ <strong class="vc-funnel-layered__value">{{
120
+ activeDatum.value.toLocaleString()
121
+ }}</strong>
122
+ </div>
123
+ </div>
124
+
125
+ <div v-else class="vue-chrts" :style="themeVars">
126
+ <ResponsiveContainer width="100%" :height="height">
127
+ <VccsFunnelChart>
128
+ <Funnel
129
+ :data="stages"
130
+ data-key="value"
131
+ name-key="name"
132
+ :last-shape-type="lastShapeType ?? 'triangle'"
133
+ :is-animation-active="duration !== void 0 && duration !== 0"
134
+ >
135
+ <LabelList v-if="showValueLabel" data-key="value" position="right" />
136
+ </Funnel>
137
+ <Tooltip v-if="!hideTooltip" :content="ChartTooltip" :is-animation-active="false" />
138
+ <Legend
139
+ v-if="!hideLegend"
140
+ :align="legend.align"
141
+ :vertical-align="legend.verticalAlign"
142
+ :layout="legend.layout"
143
+ :wrapper-style="legendWrapperStyle"
144
+ >
145
+ <template #content="slotProps">
146
+ <ChartLegend v-bind="slotProps" />
147
+ </template>
148
+ </Legend>
149
+ </VccsFunnelChart>
150
+ </ResponsiveContainer>
151
+ </div>
152
+ </template>