nuxt-charts 3.0.0-beta.2 → 3.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -8
- package/dist/module.json +1 -1
- package/dist/module.mjs +20 -2
- package/dist/runtime/assets/components.css +1 -1
- package/dist/runtime/assets/theme.css +1 -1
- package/dist/runtime/components/AreaChart.vue +91 -10
- package/dist/runtime/components/AreaChart.vue.d.ts +5 -1
- package/dist/runtime/components/BarChart.vue +132 -9
- package/dist/runtime/components/BarChart.vue.d.ts +5 -1
- package/dist/runtime/components/BubbleChart.vue +114 -27
- package/dist/runtime/components/BubbleChart.vue.d.ts +5 -1
- package/dist/runtime/components/CandlestickChart.vue +60 -18
- package/dist/runtime/components/DonutChart.vue +88 -17
- package/dist/runtime/components/FunnelChart.vue +156 -41
- package/dist/runtime/components/FunnelChart.vue.d.ts +8 -1
- package/dist/runtime/components/LineChart.vue +79 -8
- package/dist/runtime/components/LineChart.vue.d.ts +5 -1
- package/dist/runtime/components/RadarChart.vue +116 -14
- package/dist/runtime/components/RadialBarChart.vue +63 -13
- package/dist/runtime/components/SankeyChart.vue +169 -0
- package/dist/runtime/components/SankeyChart.vue.d.ts +36 -0
- package/dist/runtime/components/StatusTrackerChart.vue +33 -7
- package/dist/runtime/components/internal/AreaVariantDefs.d.ts +68 -0
- package/dist/runtime/components/internal/AreaVariantDefs.js +165 -0
- package/dist/runtime/components/internal/BarVariantDefs.d.ts +74 -0
- package/dist/runtime/components/internal/BarVariantDefs.js +200 -0
- package/dist/runtime/components/internal/CandlestickTooltip.vue +6 -4
- package/dist/runtime/components/internal/CandlestickTooltip.vue.d.ts +1 -0
- package/dist/runtime/components/internal/CartesianFrame.vue +98 -20
- package/dist/runtime/components/internal/CartesianFrame.vue.d.ts +18 -1
- package/dist/runtime/components/internal/ChartAccessibility.vue +20 -0
- package/dist/runtime/components/internal/ChartAccessibility.vue.d.ts +21 -0
- package/dist/runtime/components/internal/ChartBackground.d.ts +44 -0
- package/dist/runtime/components/internal/ChartBackground.js +53 -0
- package/dist/runtime/components/internal/ChartDot.d.ts +85 -0
- package/dist/runtime/components/internal/ChartDot.js +59 -0
- package/dist/runtime/components/internal/ChartEmptyState.vue +12 -0
- package/dist/runtime/components/internal/{ChartLegend.d.vue.ts → ChartEmptyState.vue.d.ts} +2 -6
- package/dist/runtime/components/internal/ChartLegend.vue +11 -3
- package/dist/runtime/components/internal/ChartLegend.vue.d.ts +24 -1
- package/dist/runtime/components/internal/ChartSkeleton.vue +64 -0
- package/dist/runtime/components/internal/ChartSkeleton.vue.d.ts +18 -0
- package/dist/runtime/components/internal/ChartTooltip.vue +10 -3
- package/dist/runtime/components/internal/CubeBarShape.vue +8 -8
- package/dist/runtime/components/internal/CubeBarShape.vue.d.ts +10 -2
- package/dist/runtime/components/internal/FunnelClassicShape.vue +98 -0
- package/dist/runtime/components/internal/FunnelClassicShape.vue.d.ts +81 -0
- package/dist/runtime/components/internal/FunnelLayeredShape.vue +72 -37
- package/dist/runtime/components/internal/FunnelLayeredShape.vue.d.ts +21 -0
- package/dist/runtime/components/internal/FunnelTooltip.vue +27 -0
- package/dist/runtime/components/internal/{CandlestickTooltip.d.vue.ts → FunnelTooltip.vue.d.ts} +11 -12
- package/dist/runtime/components/internal/PolarCenterSync.d.ts +38 -0
- package/dist/runtime/components/internal/PolarCenterSync.js +27 -0
- package/dist/runtime/components/internal/RadarGradientDefs.d.ts +46 -0
- package/dist/runtime/components/internal/RadarGradientDefs.js +46 -0
- package/dist/runtime/components/internal/SegmentVariantDefs.d.ts +67 -0
- package/dist/runtime/components/internal/SegmentVariantDefs.js +51 -0
- package/dist/runtime/components/internal/StrokeGradientDefs.d.ts +36 -0
- package/dist/runtime/components/internal/StrokeGradientDefs.js +35 -0
- package/dist/runtime/components/internal/VariantBarShape.vue +53 -0
- package/dist/runtime/components/internal/VariantBarShape.vue.d.ts +36 -0
- package/dist/runtime/components/internal/tooltipContent.d.ts +13 -0
- package/dist/runtime/components/internal/tooltipContent.js +9 -0
- package/dist/runtime/types/charts.d.ts +236 -46
- package/dist/runtime/types/shared.d.ts +37 -11
- package/dist/runtime/types.d.ts +3 -0
- package/dist/runtime/utils/axis.d.ts +12 -1
- package/dist/runtime/utils/axis.js +23 -0
- package/dist/runtime/utils/background.d.ts +36 -0
- package/dist/runtime/utils/background.js +140 -0
- package/dist/runtime/utils/data.d.ts +19 -0
- package/dist/runtime/utils/data.js +50 -0
- package/dist/runtime/utils/format.d.ts +5 -0
- package/dist/runtime/utils/format.js +12 -0
- package/dist/runtime/utils/gradient.d.ts +8 -0
- package/dist/runtime/utils/gradient.js +3 -0
- package/dist/runtime/utils/index.d.ts +2 -0
- package/dist/runtime/utils/index.js +2 -0
- package/dist/runtime/utils/theme.js +14 -0
- package/dist/runtime/utils/variants.d.ts +101 -0
- package/dist/runtime/utils/variants.js +48 -0
- package/package.json +16 -28
- package/dist/runtime/components/AreaChart.d.vue.ts +0 -19
- package/dist/runtime/components/BarChart.d.vue.ts +0 -19
- package/dist/runtime/components/BubbleChart.d.vue.ts +0 -19
- package/dist/runtime/components/CandlestickChart.d.vue.ts +0 -19
- package/dist/runtime/components/DonutChart.d.vue.ts +0 -21
- package/dist/runtime/components/FunnelChart.d.vue.ts +0 -19
- package/dist/runtime/components/LineChart.d.vue.ts +0 -19
- package/dist/runtime/components/RadarChart.d.vue.ts +0 -19
- package/dist/runtime/components/RadialBarChart.d.vue.ts +0 -19
- package/dist/runtime/components/StatusTrackerChart.d.vue.ts +0 -19
- package/dist/runtime/components/internal/CartesianFrame.d.vue.ts +0 -40
- package/dist/runtime/components/internal/ChartTooltip.d.vue.ts +0 -3
- package/dist/runtime/components/internal/CubeBarShape.d.vue.ts +0 -28
- package/dist/runtime/components/internal/FunnelLayeredShape.d.vue.ts +0 -42
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
2
|
+
import { computed, ref, useId } from "vue";
|
|
3
3
|
import { Bar, BarChart as VccsBarChart, LabelList, Rectangle } from "vccs";
|
|
4
4
|
import CartesianFrame from "./internal/CartesianFrame.vue";
|
|
5
5
|
import CubeBarShape from "./internal/CubeBarShape.vue";
|
|
6
|
+
import VariantBarShape from "./internal/VariantBarShape.vue";
|
|
7
|
+
import BarVariantDefs from "./internal/BarVariantDefs";
|
|
6
8
|
import {
|
|
7
9
|
categoriesToSeries,
|
|
8
10
|
PRIMARY_Y_AXIS_ID
|
|
@@ -13,10 +15,12 @@ import {
|
|
|
13
15
|
DEFAULT_CUBE_RADIUS,
|
|
14
16
|
DEFAULT_CUBE_SIZE
|
|
15
17
|
} from "../utils/cubes";
|
|
18
|
+
import { PATTERNED_BAR_VARIANTS, variantId } from "../utils/variants";
|
|
16
19
|
const props = defineProps({
|
|
17
20
|
yAxis: { type: Array, required: true },
|
|
18
21
|
xAxis: { type: null, required: false },
|
|
19
22
|
stacked: { type: Boolean, required: false },
|
|
23
|
+
percent: { type: Boolean, required: false },
|
|
20
24
|
orientation: { type: String, required: false },
|
|
21
25
|
radius: { type: Number, required: false },
|
|
22
26
|
barGap: { type: [Number, String], required: false },
|
|
@@ -31,6 +35,10 @@ const props = defineProps({
|
|
|
31
35
|
cubeMinSize: { type: Number, required: false },
|
|
32
36
|
cubeMinOpacity: { type: Number, required: false },
|
|
33
37
|
cubeEmptyColor: { type: String, required: false, default: DEFAULT_CUBE_EMPTY_COLOR },
|
|
38
|
+
glow: { type: Boolean, required: false },
|
|
39
|
+
bufferBar: { type: Boolean, required: false },
|
|
40
|
+
hoverHighlight: { type: Boolean, required: false },
|
|
41
|
+
maxHighlight: { type: Boolean, required: false },
|
|
34
42
|
stackAndGrouped: { type: Boolean, required: false },
|
|
35
43
|
stackedGroupedSpacing: { type: Number, required: false },
|
|
36
44
|
data: { type: Array, required: true },
|
|
@@ -62,16 +70,51 @@ const props = defineProps({
|
|
|
62
70
|
xAxisConfig: { type: Object, required: false },
|
|
63
71
|
yAxisConfig: { type: Object, required: false },
|
|
64
72
|
yAxes: { type: Object, required: false },
|
|
65
|
-
tooltip: { type: Object, required: false },
|
|
66
73
|
referenceLines: { type: Array, required: false },
|
|
67
74
|
syncId: { type: String, required: false },
|
|
68
75
|
minMaxTicksOnly: { type: Boolean, required: false },
|
|
69
76
|
xExplicitTicks: { type: Array, required: false },
|
|
70
77
|
yExplicitTicks: { type: Array, required: false },
|
|
71
78
|
crosshairConfig: { type: Object, required: false },
|
|
72
|
-
theme: { type: Object, required: false }
|
|
79
|
+
theme: { type: Object, required: false },
|
|
80
|
+
backgroundPattern: { type: String, required: false },
|
|
81
|
+
ariaLabel: { type: String, required: false },
|
|
82
|
+
ariaDescription: { type: String, required: false },
|
|
83
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
84
|
+
maxDataPoints: { type: Number, required: false },
|
|
85
|
+
emptyLabel: { type: String, required: false },
|
|
86
|
+
error: { type: String, required: false },
|
|
87
|
+
legendVariant: { type: String, required: false },
|
|
88
|
+
tooltipVariant: { type: String, required: false },
|
|
89
|
+
tooltipRoundness: { type: String, required: false },
|
|
90
|
+
loading: { type: Boolean, required: false },
|
|
91
|
+
loadingLabel: { type: String, required: false }
|
|
73
92
|
});
|
|
93
|
+
const variantScope = useId();
|
|
74
94
|
const isCubes = computed(() => props.variant === "cubes");
|
|
95
|
+
const isPatterned = computed(() => PATTERNED_BAR_VARIANTS.includes(props.variant));
|
|
96
|
+
const isStripped = computed(() => props.variant === "stripped");
|
|
97
|
+
const usesVariantShape = computed(
|
|
98
|
+
() => isStripped.value || props.bufferBar === true || props.glow === true
|
|
99
|
+
);
|
|
100
|
+
const hoveredIndex = ref(null);
|
|
101
|
+
const lastIndex = computed(() => props.data.length - 1);
|
|
102
|
+
const maxIndex = computed(() => {
|
|
103
|
+
if (!props.maxHighlight) return null;
|
|
104
|
+
let best = -1;
|
|
105
|
+
let bestTotal = Number.NEGATIVE_INFINITY;
|
|
106
|
+
props.data.forEach((row, index) => {
|
|
107
|
+
const total = series.value.reduce((sum, s) => {
|
|
108
|
+
const value = row[s.dataKey];
|
|
109
|
+
return sum + (typeof value === "number" ? value : 0);
|
|
110
|
+
}, 0);
|
|
111
|
+
if (total > bestTotal) {
|
|
112
|
+
bestTotal = total;
|
|
113
|
+
best = index;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return best >= 0 ? best : null;
|
|
117
|
+
});
|
|
75
118
|
const valueAccessor = computed(() => {
|
|
76
119
|
const fn = props.valueLabel?.label;
|
|
77
120
|
if (!fn) return void 0;
|
|
@@ -98,6 +141,40 @@ const series = computed(
|
|
|
98
141
|
};
|
|
99
142
|
})
|
|
100
143
|
);
|
|
144
|
+
const paintedSeries = computed(
|
|
145
|
+
() => series.value.filter((s) => typeof s.color === "string").map((s) => ({ dataKey: s.dataKey, color: s.color }))
|
|
146
|
+
);
|
|
147
|
+
const legendColors = computed(
|
|
148
|
+
() => Object.fromEntries(
|
|
149
|
+
series.value.filter((s) => s.color).map((s) => [s.name, s.color])
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
function fillFor(dataKey, color) {
|
|
153
|
+
if (!isPatterned.value || color === void 0) return color;
|
|
154
|
+
return `url(#${variantId(props.variant, dataKey, variantScope)})`;
|
|
155
|
+
}
|
|
156
|
+
function bufferFillFor(dataKey) {
|
|
157
|
+
return `url(#${variantId("buffer", dataKey, variantScope)})`;
|
|
158
|
+
}
|
|
159
|
+
function glowFor(dataKey) {
|
|
160
|
+
return props.glow ? `url(#${variantId("glow", dataKey, variantScope)})` : void 0;
|
|
161
|
+
}
|
|
162
|
+
const DIMMED_OPACITY = 0.3;
|
|
163
|
+
function opacityFor(index) {
|
|
164
|
+
if (props.hoverHighlight && hoveredIndex.value !== null) {
|
|
165
|
+
return index === hoveredIndex.value ? 1 : DIMMED_OPACITY;
|
|
166
|
+
}
|
|
167
|
+
if (maxIndex.value !== null) {
|
|
168
|
+
return index === maxIndex.value ? 1 : DIMMED_OPACITY;
|
|
169
|
+
}
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
function onBarEnter(index) {
|
|
173
|
+
if (props.hoverHighlight) hoveredIndex.value = index ?? null;
|
|
174
|
+
}
|
|
175
|
+
function onBarLeave() {
|
|
176
|
+
if (props.hoverHighlight) hoveredIndex.value = null;
|
|
177
|
+
}
|
|
101
178
|
const stackIdFor = (yAxisId) => props.stacked ? `stack-${yAxisId}` : void 0;
|
|
102
179
|
const xAxisKey = computed(() => props.xAxis !== void 0 ? String(props.xAxis) : void 0);
|
|
103
180
|
const chartContainerProps = computed(() => {
|
|
@@ -105,18 +182,23 @@ const chartContainerProps = computed(() => {
|
|
|
105
182
|
const barCategoryGap = props.barCategoryGap ?? props.groupPadding;
|
|
106
183
|
return {
|
|
107
184
|
...barGap !== void 0 ? { barGap } : {},
|
|
108
|
-
...barCategoryGap !== void 0 ? { barCategoryGap } : {}
|
|
185
|
+
...barCategoryGap !== void 0 ? { barCategoryGap } : {},
|
|
186
|
+
// Normalising only means something once the series are summed into a stack.
|
|
187
|
+
...props.stacked && props.percent ? { stackOffset: "expand" } : {}
|
|
109
188
|
};
|
|
110
189
|
});
|
|
111
190
|
function barRadius(index) {
|
|
112
191
|
const r = props.radius ?? 2;
|
|
192
|
+
const horizontal = props.orientation === "horizontal";
|
|
193
|
+
if (isStripped.value && !props.stacked) {
|
|
194
|
+
return horizontal ? [0, r, r, 0] : [r, r, 0, 0];
|
|
195
|
+
}
|
|
113
196
|
if (!props.stacked) return r;
|
|
114
197
|
const isLast = index === series.value.length - 1;
|
|
115
|
-
if (
|
|
116
|
-
return isLast ? [0, r, r, 0] : 0;
|
|
117
|
-
}
|
|
198
|
+
if (horizontal) return isLast ? [0, r, r, 0] : 0;
|
|
118
199
|
return isLast ? [r, r, 0, 0] : 0;
|
|
119
200
|
}
|
|
201
|
+
defineSlots();
|
|
120
202
|
</script>
|
|
121
203
|
|
|
122
204
|
<template>
|
|
@@ -124,8 +206,25 @@ function barRadius(index) {
|
|
|
124
206
|
:container="VccsBarChart"
|
|
125
207
|
:x-axis-key="xAxisKey"
|
|
126
208
|
:container-props="chartContainerProps"
|
|
209
|
+
:percent-axis="stacked === true && percent === true"
|
|
210
|
+
:legend-colors="legendColors"
|
|
127
211
|
v-bind="props"
|
|
128
212
|
>
|
|
213
|
+
<!--
|
|
214
|
+
Same namespace/`Customized` constraints as GradientDefs: these are built
|
|
215
|
+
with `h()` so they land in the SVG namespace and `url(#…)` resolves.
|
|
216
|
+
-->
|
|
217
|
+
<BarVariantDefs
|
|
218
|
+
v-if="isPatterned || glow || bufferBar"
|
|
219
|
+
:series="paintedSeries"
|
|
220
|
+
:variant="variant"
|
|
221
|
+
:scope="variantScope"
|
|
222
|
+
:glow="glow === true"
|
|
223
|
+
:buffer="bufferBar === true"
|
|
224
|
+
/>
|
|
225
|
+
<template v-if="$slots.tooltip" #tooltip="scope">
|
|
226
|
+
<slot name="tooltip" v-bind="scope" />
|
|
227
|
+
</template>
|
|
129
228
|
<Bar
|
|
130
229
|
v-for="(s, i) in series"
|
|
131
230
|
:key="s.dataKey"
|
|
@@ -133,10 +232,11 @@ function barRadius(index) {
|
|
|
133
232
|
:name="s.name"
|
|
134
233
|
:y-axis-id="s.yAxisId"
|
|
135
234
|
:stack-id="stackIdFor(s.yAxisId)"
|
|
136
|
-
:fill="s.color"
|
|
235
|
+
:fill="fillFor(s.dataKey, s.color)"
|
|
137
236
|
:radius="isCubes ? 0 : barRadius(i)"
|
|
138
237
|
:hide="s.hidden"
|
|
139
238
|
:is-animation-active="duration !== void 0 && duration !== 0"
|
|
239
|
+
:transition="{ duration: (duration ?? 400) / 1e3, ease: 'easeOut' }"
|
|
140
240
|
>
|
|
141
241
|
<!--
|
|
142
242
|
Always register `#shape`. vccs reads `slots.shape` once in Bar setup, so a
|
|
@@ -159,7 +259,30 @@ function barRadius(index) {
|
|
|
159
259
|
:empty-color="cubeEmptyColor"
|
|
160
260
|
:include-empty="!stacked || i === 0"
|
|
161
261
|
/>
|
|
162
|
-
<
|
|
262
|
+
<VariantBarShape
|
|
263
|
+
v-else-if="usesVariantShape"
|
|
264
|
+
:x="shapeProps.x"
|
|
265
|
+
:y="shapeProps.y"
|
|
266
|
+
:width="shapeProps.width"
|
|
267
|
+
:height="shapeProps.height"
|
|
268
|
+
:radius="barRadius(i)"
|
|
269
|
+
:fill="shapeProps.fill ?? s.color"
|
|
270
|
+
:color="s.color"
|
|
271
|
+
:capped="isStripped"
|
|
272
|
+
:is-buffer="bufferBar === true && shapeProps.index === lastIndex"
|
|
273
|
+
:buffer-fill="bufferFillFor(s.dataKey)"
|
|
274
|
+
:filter="glowFor(s.dataKey)"
|
|
275
|
+
:opacity="opacityFor(shapeProps.index)"
|
|
276
|
+
@mouseenter="onBarEnter(shapeProps.index)"
|
|
277
|
+
@mouseleave="onBarLeave"
|
|
278
|
+
/>
|
|
279
|
+
<Rectangle
|
|
280
|
+
v-else
|
|
281
|
+
v-bind="shapeProps"
|
|
282
|
+
:opacity="opacityFor(shapeProps.index)"
|
|
283
|
+
@mouseenter="onBarEnter(shapeProps.index)"
|
|
284
|
+
@mouseleave="onBarLeave"
|
|
285
|
+
/>
|
|
163
286
|
</template>
|
|
164
287
|
<LabelList
|
|
165
288
|
v-if="valueLabel && !isCubes"
|
|
@@ -5,7 +5,11 @@ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_se
|
|
|
5
5
|
} ? P : {});
|
|
6
6
|
expose: (exposed: {}) => void;
|
|
7
7
|
attrs: any;
|
|
8
|
-
slots: {
|
|
8
|
+
slots: {
|
|
9
|
+
tooltip?: (props: {
|
|
10
|
+
values: T | undefined;
|
|
11
|
+
}) => unknown;
|
|
12
|
+
};
|
|
9
13
|
emit: {};
|
|
10
14
|
}>) => import("vue").VNode & {
|
|
11
15
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
2
|
+
import { computed, h } from "vue";
|
|
3
3
|
import {
|
|
4
4
|
CartesianGrid,
|
|
5
5
|
Legend,
|
|
@@ -11,8 +11,11 @@ import {
|
|
|
11
11
|
ZAxis
|
|
12
12
|
} from "vccs";
|
|
13
13
|
import ChartContainer from "./internal/ChartContainer";
|
|
14
|
+
import ChartSkeleton from "./internal/ChartSkeleton.vue";
|
|
14
15
|
import ChartTooltip from "./internal/ChartTooltip.vue";
|
|
15
16
|
import ChartLegend from "./internal/ChartLegend.vue";
|
|
17
|
+
import ChartEmptyState from "./internal/ChartEmptyState.vue";
|
|
18
|
+
import ChartAccessibility from "./internal/ChartAccessibility.vue";
|
|
16
19
|
import { categoriesToSeries } from "../utils/categories";
|
|
17
20
|
import { legendPositionToLegendProps, resolveLegendWrapperStyle } from "../utils/legend";
|
|
18
21
|
import {
|
|
@@ -25,14 +28,8 @@ import {
|
|
|
25
28
|
} from "../utils/axis";
|
|
26
29
|
import { toAxisDomain, toCssProperties } from "../utils/style";
|
|
27
30
|
import { axisTickVars, resolveHoverRadius, resolveHoverVisible, themeToVars } from "../utils/theme";
|
|
31
|
+
import { DEFAULT_MAX_DATA_POINTS, sampleData, toFiniteNumber, warnDataLimit } from "../utils/data";
|
|
28
32
|
const props = defineProps({
|
|
29
|
-
xAccessor: { type: Function, required: true, skipCheck: true },
|
|
30
|
-
yAccessor: { type: Function, required: true, skipCheck: true },
|
|
31
|
-
sizeAccessor: { type: Function, required: false, skipCheck: true },
|
|
32
|
-
categoryKey: { type: null, required: true },
|
|
33
|
-
sizeRange: { type: Array, required: false },
|
|
34
|
-
opacity: { type: Number, required: false },
|
|
35
|
-
sizeOptions: { type: Object, required: false },
|
|
36
33
|
data: { type: Array, required: true },
|
|
37
34
|
height: { type: Number, required: true },
|
|
38
35
|
xLabel: { type: String, required: false },
|
|
@@ -61,24 +58,47 @@ const props = defineProps({
|
|
|
61
58
|
duration: { type: Number, required: false },
|
|
62
59
|
xAxisConfig: { type: Object, required: false },
|
|
63
60
|
yAxisConfig: { type: Object, required: false },
|
|
64
|
-
yAxes: { type: Object, required: false },
|
|
65
|
-
tooltip: { type: Object, required: false },
|
|
66
|
-
referenceLines: { type: Array, required: false },
|
|
67
|
-
syncId: { type: String, required: false },
|
|
68
61
|
minMaxTicksOnly: { type: Boolean, required: false },
|
|
69
62
|
xExplicitTicks: { type: Array, required: false },
|
|
70
|
-
yExplicitTicks: { type: Array, required: false },
|
|
71
63
|
crosshairConfig: { type: Object, required: false },
|
|
72
|
-
theme: { type: Object, required: false }
|
|
64
|
+
theme: { type: Object, required: false },
|
|
65
|
+
ariaLabel: { type: String, required: false },
|
|
66
|
+
ariaDescription: { type: String, required: false },
|
|
67
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
68
|
+
maxDataPoints: { type: Number, required: false },
|
|
69
|
+
emptyLabel: { type: String, required: false },
|
|
70
|
+
error: { type: String, required: false },
|
|
71
|
+
legendVariant: { type: String, required: false },
|
|
72
|
+
tooltipVariant: { type: String, required: false },
|
|
73
|
+
tooltipRoundness: { type: String, required: false },
|
|
74
|
+
loading: { type: Boolean, required: false },
|
|
75
|
+
loadingLabel: { type: String, required: false },
|
|
76
|
+
xAccessor: { type: Function, required: true, skipCheck: true },
|
|
77
|
+
yAccessor: { type: Function, required: true, skipCheck: true },
|
|
78
|
+
sizeAccessor: { type: Function, required: false, skipCheck: true },
|
|
79
|
+
categoryKey: { type: null, required: true },
|
|
80
|
+
sizeRange: { type: Array, required: false },
|
|
81
|
+
opacity: { type: Number, required: false },
|
|
82
|
+
sizeOptions: { type: Object, required: false }
|
|
73
83
|
});
|
|
84
|
+
const slots = defineSlots();
|
|
74
85
|
function resolveAccessor(accessor) {
|
|
75
86
|
if (accessor === void 0) return void 0;
|
|
76
87
|
return typeof accessor === "function" ? accessor : String(accessor);
|
|
77
88
|
}
|
|
89
|
+
function accessorName(accessor, fallback) {
|
|
90
|
+
if (accessor === void 0) return fallback;
|
|
91
|
+
if (typeof accessor !== "function") return String(accessor);
|
|
92
|
+
const match = /(?:=>|return)\s*[\w$]+(?:\?\.|\.)([\w$]+)\s*;?\s*\}?\s*$/.exec(String(accessor));
|
|
93
|
+
return match?.[1] ?? fallback;
|
|
94
|
+
}
|
|
78
95
|
const xKey = computed(() => resolveAccessor(props.xAccessor));
|
|
79
96
|
const yKey = computed(() => resolveAccessor(props.yAccessor));
|
|
80
97
|
const zKey = computed(() => resolveAccessor(props.sizeAccessor));
|
|
81
98
|
const categoryKey = computed(() => String(props.categoryKey));
|
|
99
|
+
const xName = computed(() => props.xLabel ?? accessorName(props.xAccessor, "x"));
|
|
100
|
+
const yName = computed(() => props.yLabel ?? accessorName(props.yAccessor, "y"));
|
|
101
|
+
const zName = computed(() => accessorName(props.sizeAccessor, "size"));
|
|
82
102
|
const legend = computed(() => legendPositionToLegendProps(props.legendPosition));
|
|
83
103
|
const sizeRange = computed(() => {
|
|
84
104
|
if (props.sizeRange) return props.sizeRange;
|
|
@@ -127,11 +147,33 @@ const cursor = computed(
|
|
|
127
147
|
radius: resolveHoverRadius(props.theme)
|
|
128
148
|
} : false
|
|
129
149
|
);
|
|
150
|
+
const colorByCategory = computed(() => {
|
|
151
|
+
const map = /* @__PURE__ */ new Map();
|
|
152
|
+
for (const s of categoriesToSeries(props.categories)) map.set(s.dataKey, s.color);
|
|
153
|
+
return map;
|
|
154
|
+
});
|
|
155
|
+
function accessorValue(row, accessor) {
|
|
156
|
+
return typeof accessor === "function" ? accessor(row) : accessor === void 0 ? void 0 : row[accessor];
|
|
157
|
+
}
|
|
158
|
+
const chartData = computed(() => {
|
|
159
|
+
const normalized = props.data.filter(
|
|
160
|
+
(row) => toFiniteNumber(accessorValue(row, props.xAccessor)) !== void 0 && toFiniteNumber(accessorValue(row, props.yAccessor)) !== void 0 && (props.sizeAccessor === void 0 || toFiniteNumber(accessorValue(row, props.sizeAccessor)) !== void 0)
|
|
161
|
+
);
|
|
162
|
+
const limit = props.maxDataPoints ?? DEFAULT_MAX_DATA_POINTS;
|
|
163
|
+
warnDataLimit("BubbleChart", normalized.length, limit);
|
|
164
|
+
return sampleData(normalized, limit);
|
|
165
|
+
});
|
|
166
|
+
const accessibleRows = computed(() => chartData.value.map((row, index) => ({
|
|
167
|
+
label: String(row[categoryKey.value] ?? index + 1),
|
|
168
|
+
values: [
|
|
169
|
+
{ label: xName.value, value: accessorValue(row, props.xAccessor) },
|
|
170
|
+
{ label: yName.value, value: accessorValue(row, props.yAccessor) },
|
|
171
|
+
...props.sizeAccessor === void 0 ? [] : [{ label: zName.value, value: accessorValue(row, props.sizeAccessor) }]
|
|
172
|
+
]
|
|
173
|
+
})));
|
|
130
174
|
const groups = computed(() => {
|
|
131
|
-
const colorByCat = /* @__PURE__ */ new Map();
|
|
132
|
-
for (const s of categoriesToSeries(props.categories)) colorByCat.set(s.dataKey, s.color);
|
|
133
175
|
const byCategory = /* @__PURE__ */ new Map();
|
|
134
|
-
for (const row of
|
|
176
|
+
for (const row of chartData.value) {
|
|
135
177
|
const cat = String(row[categoryKey.value]);
|
|
136
178
|
const bucket = byCategory.get(cat);
|
|
137
179
|
if (bucket) bucket.push(row);
|
|
@@ -140,26 +182,66 @@ const groups = computed(() => {
|
|
|
140
182
|
return Array.from(byCategory.entries()).map(([name, data]) => ({
|
|
141
183
|
name,
|
|
142
184
|
data,
|
|
143
|
-
color:
|
|
185
|
+
color: colorByCategory.value.get(name)
|
|
144
186
|
}));
|
|
145
187
|
});
|
|
188
|
+
const tooltipContent = computed(() => {
|
|
189
|
+
const fields = [
|
|
190
|
+
{ key: xKey.value, name: xName.value, format: xTickFormatter.value },
|
|
191
|
+
{ key: yKey.value, name: yName.value, format: yTickFormatter.value },
|
|
192
|
+
...zKey.value ? [{ key: zKey.value, name: zName.value, format: void 0 }] : []
|
|
193
|
+
];
|
|
194
|
+
return (tooltipProps) => {
|
|
195
|
+
const row = tooltipProps.payload?.[0]?.payload;
|
|
196
|
+
if (slots.tooltip) return slots.tooltip({ values: row });
|
|
197
|
+
const category = row != null ? String(row[categoryKey.value]) : void 0;
|
|
198
|
+
const color = category != null ? colorByCategory.value.get(category) : void 0;
|
|
199
|
+
const payload = (tooltipProps.payload ?? []).map((entry) => {
|
|
200
|
+
const field = fields.find((f) => f.key === entry.dataKey);
|
|
201
|
+
return {
|
|
202
|
+
...entry,
|
|
203
|
+
color,
|
|
204
|
+
name: field?.name ?? entry.name,
|
|
205
|
+
value: field?.format ? field.format(entry.value, 0) : entry.value
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
return h(ChartTooltip, {
|
|
209
|
+
...tooltipProps,
|
|
210
|
+
label: props.tooltipTitleFormatter && row ? props.tooltipTitleFormatter(row) : category,
|
|
211
|
+
payload,
|
|
212
|
+
variant: props.tooltipVariant,
|
|
213
|
+
roundness: props.tooltipRoundness
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
});
|
|
146
217
|
</script>
|
|
147
218
|
|
|
148
219
|
<template>
|
|
149
|
-
<div class="vue-chrts" :style="themeVars">
|
|
150
|
-
<
|
|
220
|
+
<div class="vue-chrts" role="group" :aria-label="ariaLabel ?? 'Bubble chart'" :style="themeVars">
|
|
221
|
+
<ChartAccessibility :label="ariaLabel ?? 'Bubble chart'" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
|
|
222
|
+
<ChartSkeleton
|
|
223
|
+
v-if="loading"
|
|
224
|
+
:height="height"
|
|
225
|
+
shape="bars"
|
|
226
|
+
:label="loadingLabel ?? 'Loading'"
|
|
227
|
+
/>
|
|
228
|
+
<ChartEmptyState v-else-if="error || chartData.length === 0" :height="height" :message="error || emptyLabel" />
|
|
229
|
+
<ChartContainer v-else width="100%" :height="height">
|
|
151
230
|
<ScatterChart>
|
|
152
231
|
<CartesianGrid
|
|
153
232
|
v-if="(xGridLine ?? true) || (yGridLine ?? true)"
|
|
233
|
+
:horizontal="yGridLine ?? true"
|
|
234
|
+
:vertical="xGridLine ?? true"
|
|
154
235
|
stroke="var(--vc-grid-color)"
|
|
155
236
|
stroke-dasharray="var(--vc-grid-dash)"
|
|
156
237
|
:stroke-width="'var(--vc-grid-width)'"
|
|
157
238
|
/>
|
|
239
|
+
<!-- Axes stay mounted when hidden: their `dataKey` is what maps rows to x/y. -->
|
|
158
240
|
<XAxis
|
|
159
|
-
v-if="!hideXAxis"
|
|
160
241
|
type="number"
|
|
161
242
|
:data-key="xKey"
|
|
162
|
-
:
|
|
243
|
+
:hide="hideXAxis"
|
|
244
|
+
:name="xName"
|
|
163
245
|
:height="xAxisHeight"
|
|
164
246
|
:tick="xTickProp"
|
|
165
247
|
:tick-margin="AXIS_TICK_MARGIN.x"
|
|
@@ -171,10 +253,10 @@ const groups = computed(() => {
|
|
|
171
253
|
:interval="xAxis.interval"
|
|
172
254
|
/>
|
|
173
255
|
<YAxis
|
|
174
|
-
v-if="!hideYAxis"
|
|
175
256
|
type="number"
|
|
176
257
|
:data-key="yKey"
|
|
177
|
-
:
|
|
258
|
+
:hide="hideYAxis"
|
|
259
|
+
:name="yName"
|
|
178
260
|
:width="yAxisWidth"
|
|
179
261
|
:tick="yTickProp"
|
|
180
262
|
:tick-margin="AXIS_TICK_MARGIN.y"
|
|
@@ -184,18 +266,23 @@ const groups = computed(() => {
|
|
|
184
266
|
:domain="yAxisDomain"
|
|
185
267
|
:interval="yAxis.interval"
|
|
186
268
|
/>
|
|
187
|
-
<ZAxis v-if="zKey" type="number" :data-key="zKey" :range="sizeRange" />
|
|
269
|
+
<ZAxis v-if="zKey" type="number" :data-key="zKey" :name="zName" :range="sizeRange" />
|
|
188
270
|
|
|
271
|
+
<!-- Distinct `dataKey` per series: `vccs` filters the item tooltip and the -->
|
|
272
|
+
<!-- active-symbol highlight by it, so shared keys show every series at once. -->
|
|
189
273
|
<Scatter
|
|
190
274
|
v-for="g in groups"
|
|
191
275
|
:key="g.name"
|
|
276
|
+
:data-key="g.name"
|
|
192
277
|
:name="g.name"
|
|
193
278
|
:data="g.data"
|
|
194
279
|
:fill="g.color"
|
|
195
280
|
:fill-opacity="opacity ?? 0.7"
|
|
281
|
+
:is-animation-active="duration !== void 0 && duration !== 0"
|
|
282
|
+
:transition="{ duration: (duration ?? 400) / 1e3, ease: 'easeOut' }"
|
|
196
283
|
/>
|
|
197
284
|
|
|
198
|
-
<Tooltip v-if="!hideTooltip" :content="
|
|
285
|
+
<Tooltip v-if="!hideTooltip" :content="tooltipContent" :cursor="cursor" :is-animation-active="false" />
|
|
199
286
|
<Legend
|
|
200
287
|
v-if="!hideLegend"
|
|
201
288
|
:align="legend.align"
|
|
@@ -204,7 +291,7 @@ const groups = computed(() => {
|
|
|
204
291
|
:wrapper-style="legendWrapperStyle"
|
|
205
292
|
>
|
|
206
293
|
<template #content="slotProps">
|
|
207
|
-
<ChartLegend v-bind="slotProps" />
|
|
294
|
+
<ChartLegend v-bind="slotProps" :variant="legendVariant" />
|
|
208
295
|
</template>
|
|
209
296
|
</Legend>
|
|
210
297
|
</ScatterChart>
|
|
@@ -5,7 +5,11 @@ declare const __VLS_export: <T extends Record<string, unknown>>(__VLS_props: Non
|
|
|
5
5
|
} ? P : {});
|
|
6
6
|
expose: (exposed: {}) => void;
|
|
7
7
|
attrs: any;
|
|
8
|
-
slots: {
|
|
8
|
+
slots: {
|
|
9
|
+
tooltip?: (props: {
|
|
10
|
+
values: T | undefined;
|
|
11
|
+
}) => unknown;
|
|
12
|
+
};
|
|
9
13
|
emit: {};
|
|
10
14
|
}>) => import("vue").VNode & {
|
|
11
15
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";
|
|
3
3
|
import CandlestickTooltip from "./internal/CandlestickTooltip.vue";
|
|
4
|
+
import ChartEmptyState from "./internal/ChartEmptyState.vue";
|
|
5
|
+
import ChartAccessibility from "./internal/ChartAccessibility.vue";
|
|
6
|
+
import ChartSkeleton from "./internal/ChartSkeleton.vue";
|
|
7
|
+
import { DEFAULT_MAX_DATA_POINTS, sampleData, toFiniteNumber, warnDataLimit } from "../utils/data";
|
|
8
|
+
import { formatNumber } from "../utils/format";
|
|
4
9
|
const props = defineProps({
|
|
5
10
|
data: { type: Array, required: true },
|
|
6
11
|
height: { type: Number, required: true },
|
|
@@ -27,7 +32,17 @@ const props = defineProps({
|
|
|
27
32
|
hideXAxis: { type: Boolean, required: false },
|
|
28
33
|
hideYAxis: { type: Boolean, required: false },
|
|
29
34
|
hideTooltip: { type: Boolean, required: false },
|
|
30
|
-
|
|
35
|
+
ariaLabel: { type: String, required: false },
|
|
36
|
+
ariaDescription: { type: String, required: false },
|
|
37
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
38
|
+
maxDataPoints: { type: Number, required: false },
|
|
39
|
+
emptyLabel: { type: String, required: false },
|
|
40
|
+
error: { type: String, required: false },
|
|
41
|
+
legendVariant: { type: String, required: false },
|
|
42
|
+
tooltipVariant: { type: String, required: false },
|
|
43
|
+
tooltipRoundness: { type: String, required: false },
|
|
44
|
+
loading: { type: Boolean, required: false },
|
|
45
|
+
loadingLabel: { type: String, required: false }
|
|
31
46
|
});
|
|
32
47
|
const xKey = computed(() => String(props.xAccessor ?? "label"));
|
|
33
48
|
const openKey = computed(() => String(props.openAccessor ?? "open"));
|
|
@@ -35,26 +50,38 @@ const highKey = computed(() => String(props.highAccessor ?? "high"));
|
|
|
35
50
|
const lowKey = computed(() => String(props.lowAccessor ?? "low"));
|
|
36
51
|
const closeKey = computed(() => String(props.closeAccessor ?? "close"));
|
|
37
52
|
const volumeKey = computed(() => String(props.volumeAccessor ?? "volume"));
|
|
38
|
-
const upColor = computed(() => props.upColor ?? "
|
|
39
|
-
const downColor = computed(() => props.downColor ?? "
|
|
53
|
+
const upColor = computed(() => props.upColor ?? "var(--vc-candle-up)");
|
|
54
|
+
const downColor = computed(() => props.downColor ?? "var(--vc-candle-down)");
|
|
40
55
|
const showVolume = computed(() => props.showVolume ?? false);
|
|
41
56
|
const maxCandleWidth = computed(() => props.candleWidth ?? 18);
|
|
42
57
|
const wickWidth = computed(() => props.wickWidth ?? 1.5);
|
|
43
58
|
const yGridLine = computed(() => props.yGridLine ?? true);
|
|
44
59
|
const xGridLine = computed(() => props.xGridLine ?? false);
|
|
45
60
|
const yNumTicks = computed(() => props.yNumTicks ?? 5);
|
|
46
|
-
const candles = computed(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
close
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
61
|
+
const candles = computed(() => {
|
|
62
|
+
const normalized = props.data.flatMap((row) => {
|
|
63
|
+
const open = toFiniteNumber(row[openKey.value]);
|
|
64
|
+
const high = toFiniteNumber(row[highKey.value]);
|
|
65
|
+
const low = toFiniteNumber(row[lowKey.value]);
|
|
66
|
+
const close = toFiniteNumber(row[closeKey.value]);
|
|
67
|
+
if (open === void 0 || high === void 0 || low === void 0 || close === void 0) return [];
|
|
68
|
+
if (low > Math.min(open, close) || high < Math.max(open, close) || low > high) return [];
|
|
69
|
+
const volume = toFiniteNumber(row[volumeKey.value]);
|
|
70
|
+
return [{ raw: row, label: String(row[xKey.value] ?? ""), open, high, low, close, volume }];
|
|
71
|
+
});
|
|
72
|
+
const limit = props.maxDataPoints ?? DEFAULT_MAX_DATA_POINTS;
|
|
73
|
+
warnDataLimit("CandlestickChart", normalized.length, limit);
|
|
74
|
+
return sampleData(normalized, limit);
|
|
75
|
+
});
|
|
76
|
+
const accessibleRows = computed(() => candles.value.map((candle) => ({
|
|
77
|
+
label: String(candle.label),
|
|
78
|
+
values: ["open", "high", "low", "close", "volume"].flatMap((key) => {
|
|
79
|
+
const value = candle[key];
|
|
80
|
+
return typeof value === "number" ? [{ label: key, value: fmtPrice(value) }] : [];
|
|
81
|
+
})
|
|
82
|
+
})));
|
|
56
83
|
const containerRef = useTemplateRef("containerRef");
|
|
57
|
-
const W = ref(
|
|
84
|
+
const W = ref(0);
|
|
58
85
|
const H = computed(() => props.height);
|
|
59
86
|
const PAD = computed(() => ({
|
|
60
87
|
top: 12,
|
|
@@ -135,7 +162,7 @@ const xTicks = computed(() => {
|
|
|
135
162
|
})).filter((_, i) => i === 0 || i === n - 1 || i % step === 0);
|
|
136
163
|
});
|
|
137
164
|
function fmtPrice(value) {
|
|
138
|
-
return props.yFormatter ? props.yFormatter(value) : value
|
|
165
|
+
return props.yFormatter ? props.yFormatter(value) : formatNumber(value);
|
|
139
166
|
}
|
|
140
167
|
const activeIndex = ref(null);
|
|
141
168
|
const pointer = ref({ x: 0, y: 0 });
|
|
@@ -161,12 +188,19 @@ function onMouseMove(e) {
|
|
|
161
188
|
function onMouseLeave() {
|
|
162
189
|
activeIndex.value = null;
|
|
163
190
|
}
|
|
191
|
+
function onKeydown(event) {
|
|
192
|
+
if (!candles.value.length) return;
|
|
193
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight" && event.key !== "Home" && event.key !== "End") return;
|
|
194
|
+
event.preventDefault();
|
|
195
|
+
const current = activeIndex.value ?? 0;
|
|
196
|
+
activeIndex.value = event.key === "Home" ? 0 : event.key === "End" ? candles.value.length - 1 : Math.min(Math.max(current + (event.key === "ArrowRight" ? 1 : -1), 0), candles.value.length - 1);
|
|
197
|
+
}
|
|
164
198
|
let ro;
|
|
165
199
|
onMounted(() => {
|
|
166
200
|
if (!containerRef.value) return;
|
|
167
201
|
ro = new ResizeObserver((entries) => {
|
|
168
202
|
const cr = entries[0]?.contentRect;
|
|
169
|
-
if (cr
|
|
203
|
+
if (cr) W.value = Math.max(0, cr.width);
|
|
170
204
|
});
|
|
171
205
|
ro.observe(containerRef.value);
|
|
172
206
|
});
|
|
@@ -177,10 +211,17 @@ onBeforeUnmount(() => ro?.disconnect());
|
|
|
177
211
|
<div
|
|
178
212
|
ref="containerRef"
|
|
179
213
|
class="vc-candlestick-chart vue-chrts"
|
|
214
|
+
role="group"
|
|
215
|
+
:aria-label="ariaLabel ?? 'Candlestick chart'"
|
|
216
|
+
tabindex="0"
|
|
180
217
|
@mousemove="onMouseMove"
|
|
181
218
|
@mouseleave="onMouseLeave"
|
|
219
|
+
@keydown="onKeydown"
|
|
182
220
|
>
|
|
183
|
-
<
|
|
221
|
+
<ChartAccessibility :label="ariaLabel ?? 'Candlestick chart'" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
|
|
222
|
+
<ChartSkeleton v-if="loading" :height="height" shape="bars" :label="loadingLabel ?? 'Loading'" />
|
|
223
|
+
<ChartEmptyState v-else-if="error || candles.length === 0" :height="height" :message="error || emptyLabel" />
|
|
224
|
+
<svg v-else :width="W" :height="H" class="vc-candlestick-chart__svg" aria-hidden="true">
|
|
184
225
|
<!-- Grid -->
|
|
185
226
|
<template v-if="yGridLine">
|
|
186
227
|
<line
|
|
@@ -252,7 +293,7 @@ onBeforeUnmount(() => ro?.disconnect());
|
|
|
252
293
|
:width="c.halfW * 2"
|
|
253
294
|
:height="c.bodyH"
|
|
254
295
|
:rx="Math.min(2, c.halfW / 2)"
|
|
255
|
-
:fill="c.rising ? c.color : 'var(--
|
|
296
|
+
:fill="c.rising ? c.color : 'var(--vc-surface-bg)'"
|
|
256
297
|
:stroke="c.color"
|
|
257
298
|
:stroke-width="activeIndex === i ? 2.5 : 1.5"
|
|
258
299
|
/>
|
|
@@ -322,6 +363,7 @@ onBeforeUnmount(() => ro?.disconnect());
|
|
|
322
363
|
:up-color="upColor"
|
|
323
364
|
:down-color="downColor"
|
|
324
365
|
:value-formatter="yFormatter"
|
|
366
|
+
:title="tooltipTitleFormatter ? tooltipTitleFormatter(activeCandle.raw) : activeCandle.label"
|
|
325
367
|
/>
|
|
326
368
|
</div>
|
|
327
369
|
</Teleport>
|