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.
- package/LICENSE +1 -1
- package/README.md +80 -79
- package/dist/module.d.mts +4 -4
- package/dist/module.json +1 -1
- package/dist/module.mjs +64 -95
- package/dist/runtime/assets/components.css +1 -0
- package/dist/runtime/assets/theme.css +1 -0
- package/dist/runtime/components/AreaChart.d.vue.ts +19 -0
- package/dist/runtime/components/AreaChart.vue +149 -0
- package/dist/runtime/components/AreaChart.vue.d.ts +19 -0
- package/dist/runtime/components/BarChart.d.vue.ts +19 -0
- package/dist/runtime/components/BarChart.vue +175 -0
- package/dist/runtime/components/BarChart.vue.d.ts +19 -0
- package/dist/runtime/components/BubbleChart.d.vue.ts +19 -0
- package/dist/runtime/components/BubbleChart.vue +213 -0
- package/dist/runtime/components/BubbleChart.vue.d.ts +19 -0
- package/dist/runtime/components/CandlestickChart.d.vue.ts +19 -0
- package/dist/runtime/components/CandlestickChart.vue +329 -0
- package/dist/runtime/components/CandlestickChart.vue.d.ts +19 -0
- package/dist/runtime/components/DonutChart.d.vue.ts +21 -0
- package/dist/runtime/components/DonutChart.vue +116 -0
- package/dist/runtime/components/DonutChart.vue.d.ts +21 -0
- package/dist/runtime/components/FunnelChart.d.vue.ts +19 -0
- package/dist/runtime/components/FunnelChart.vue +152 -0
- package/dist/runtime/components/FunnelChart.vue.d.ts +19 -0
- package/dist/runtime/components/LineChart.d.vue.ts +19 -0
- package/dist/runtime/components/LineChart.vue +84 -0
- package/dist/runtime/components/LineChart.vue.d.ts +19 -0
- package/dist/runtime/components/RadarChart.d.vue.ts +19 -0
- package/dist/runtime/components/RadarChart.vue +90 -0
- package/dist/runtime/components/RadarChart.vue.d.ts +19 -0
- package/dist/runtime/components/RadialBarChart.d.vue.ts +19 -0
- package/dist/runtime/components/RadialBarChart.vue +81 -0
- package/dist/runtime/components/RadialBarChart.vue.d.ts +19 -0
- package/dist/runtime/components/StatusTrackerChart.d.vue.ts +19 -0
- package/dist/runtime/components/StatusTrackerChart.vue +147 -0
- package/dist/runtime/components/StatusTrackerChart.vue.d.ts +19 -0
- package/dist/runtime/components/internal/CandlestickTooltip.d.vue.ts +17 -0
- package/dist/runtime/components/internal/CandlestickTooltip.vue +38 -0
- package/dist/runtime/components/internal/CandlestickTooltip.vue.d.ts +17 -0
- package/dist/runtime/components/internal/CartesianFrame.d.vue.ts +40 -0
- package/dist/runtime/components/internal/CartesianFrame.vue +321 -0
- package/dist/runtime/components/internal/CartesianFrame.vue.d.ts +40 -0
- package/dist/runtime/components/internal/ChartLegend.d.vue.ts +11 -0
- package/dist/runtime/components/internal/ChartLegend.vue +22 -0
- package/dist/runtime/components/internal/ChartLegend.vue.d.ts +11 -0
- package/dist/runtime/components/internal/ChartTooltip.d.vue.ts +3 -0
- package/dist/runtime/components/internal/ChartTooltip.vue +39 -0
- package/dist/runtime/components/internal/ChartTooltip.vue.d.ts +3 -0
- package/dist/runtime/components/internal/CubeBarShape.d.vue.ts +28 -0
- package/dist/runtime/components/internal/CubeBarShape.vue +69 -0
- package/dist/runtime/components/internal/CubeBarShape.vue.d.ts +28 -0
- package/dist/runtime/components/internal/DitherDefs.d.ts +98 -0
- package/dist/runtime/components/internal/DitherDefs.js +158 -0
- package/dist/runtime/components/internal/FunnelLayeredShape.d.vue.ts +42 -0
- package/dist/runtime/components/internal/FunnelLayeredShape.vue +124 -0
- package/dist/runtime/components/internal/FunnelLayeredShape.vue.d.ts +42 -0
- package/dist/runtime/components/internal/GradientDefs.d.ts +46 -0
- package/dist/runtime/components/internal/GradientDefs.js +38 -0
- package/dist/runtime/enums.d.ts +65 -0
- package/dist/runtime/enums.js +40 -0
- package/dist/runtime/types/charts.d.ts +642 -0
- package/dist/runtime/types/charts.js +0 -0
- package/dist/runtime/types/shared.d.ts +202 -0
- package/dist/runtime/types/shared.js +0 -0
- package/dist/runtime/types.d.ts +8 -0
- package/dist/runtime/types.js +0 -0
- package/dist/runtime/utils/axis.d.ts +132 -0
- package/dist/runtime/utils/axis.js +93 -0
- package/dist/runtime/utils/categories.d.ts +36 -0
- package/dist/runtime/utils/categories.js +18 -0
- package/dist/runtime/utils/cubes.d.ts +100 -0
- package/dist/runtime/utils/cubes.js +73 -0
- package/dist/runtime/utils/curve.d.ts +12 -0
- package/dist/runtime/utils/curve.js +26 -0
- package/dist/runtime/utils/dither.d.ts +55 -0
- package/dist/runtime/utils/dither.js +45 -0
- package/dist/runtime/utils/gradient.d.ts +12 -0
- package/dist/runtime/utils/gradient.js +8 -0
- package/dist/runtime/utils/index.d.ts +6 -0
- package/dist/runtime/utils/index.js +6 -0
- package/dist/runtime/utils/legend.d.ts +27 -0
- package/dist/runtime/utils/legend.js +19 -0
- package/dist/runtime/utils/marker.d.ts +20 -0
- package/dist/runtime/utils/marker.js +21 -0
- package/dist/runtime/utils/style.d.ts +14 -0
- package/dist/runtime/utils/style.js +10 -0
- package/dist/runtime/utils/theme.d.ts +36 -0
- package/dist/runtime/utils/theme.js +45 -0
- package/package.json +56 -12
- package/dist/runtime/vue-chrts.d.ts +0 -1
- 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>
|