nuxt-charts 3.0.0-beta.3 → 3.0.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/README.md +46 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +0 -1
- package/dist/runtime/assets/components.css +1 -1
- package/dist/runtime/assets/theme.css +1 -1
- package/dist/runtime/components/AreaChart.vue +7 -1
- package/dist/runtime/components/BarChart.vue +7 -1
- package/dist/runtime/components/BubbleChart.vue +43 -17
- package/dist/runtime/components/CandlestickChart.vue +60 -18
- package/dist/runtime/components/DonutChart.vue +28 -9
- package/dist/runtime/components/FunnelChart.vue +31 -8
- package/dist/runtime/components/LineChart.vue +7 -1
- package/dist/runtime/components/RadarChart.vue +25 -4
- package/dist/runtime/components/RadialBarChart.vue +48 -8
- package/dist/runtime/components/SankeyChart.vue +32 -9
- package/dist/runtime/components/StatusTrackerChart.vue +33 -7
- 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 +37 -9
- package/dist/runtime/components/internal/ChartAccessibility.vue +20 -0
- package/dist/runtime/components/internal/{ChartSkeleton.d.vue.ts → ChartAccessibility.vue.d.ts} +13 -10
- package/dist/runtime/components/internal/ChartEmptyState.vue +12 -0
- package/dist/runtime/components/internal/{CandlestickTooltip.d.vue.ts → ChartEmptyState.vue.d.ts} +2 -12
- package/dist/runtime/components/internal/ChartLegend.vue.d.ts +1 -1
- package/dist/runtime/components/internal/ChartTooltip.vue +2 -1
- package/dist/runtime/components/internal/CubeBarShape.vue.d.ts +1 -1
- package/dist/runtime/components/internal/FunnelClassicShape.vue +2 -1
- package/dist/runtime/components/internal/FunnelLayeredShape.vue +2 -1
- package/dist/runtime/components/internal/FunnelTooltip.vue +2 -1
- package/dist/runtime/components/internal/tooltipContent.d.ts +1 -1
- package/dist/runtime/components/internal/tooltipContent.js +6 -2
- package/dist/runtime/types/charts.d.ts +44 -34
- package/dist/runtime/types/shared.d.ts +5 -11
- package/dist/runtime/utils/categories.d.ts +1 -1
- package/dist/runtime/utils/categories.js +2 -2
- 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/index.d.ts +2 -0
- package/dist/runtime/utils/index.js +2 -0
- package/dist/runtime/utils/theme.js +3 -0
- package/package.json +2 -2
- package/dist/runtime/components/AreaChart.d.vue.ts +0 -23
- package/dist/runtime/components/BarChart.d.vue.ts +0 -23
- package/dist/runtime/components/BubbleChart.d.vue.ts +0 -23
- 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 -26
- package/dist/runtime/components/LineChart.d.vue.ts +0 -23
- package/dist/runtime/components/RadarChart.d.vue.ts +0 -19
- package/dist/runtime/components/RadialBarChart.d.vue.ts +0 -19
- package/dist/runtime/components/SankeyChart.d.vue.ts +0 -36
- package/dist/runtime/components/StatusTrackerChart.d.vue.ts +0 -19
- package/dist/runtime/components/internal/CartesianFrame.d.vue.ts +0 -57
- package/dist/runtime/components/internal/ChartLegend.d.vue.ts +0 -34
- package/dist/runtime/components/internal/ChartTooltip.d.vue.ts +0 -3
- package/dist/runtime/components/internal/CubeBarShape.d.vue.ts +0 -36
- package/dist/runtime/components/internal/FunnelClassicShape.d.vue.ts +0 -81
- package/dist/runtime/components/internal/FunnelLayeredShape.d.vue.ts +0 -63
- package/dist/runtime/components/internal/FunnelTooltip.d.vue.ts +0 -16
- package/dist/runtime/components/internal/VariantBarShape.d.vue.ts +0 -36
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
import ChartContainer from "./internal/ChartContainer";
|
|
9
9
|
import ChartSkeleton from "./internal/ChartSkeleton.vue";
|
|
10
10
|
import ChartLegend from "./internal/ChartLegend.vue";
|
|
11
|
+
import ChartEmptyState from "./internal/ChartEmptyState.vue";
|
|
12
|
+
import ChartAccessibility from "./internal/ChartAccessibility.vue";
|
|
11
13
|
import FunnelClassicShape from "./internal/FunnelClassicShape.vue";
|
|
12
14
|
import FunnelTooltip from "./internal/FunnelTooltip.vue";
|
|
13
15
|
import FunnelLayeredShape, {
|
|
@@ -16,8 +18,11 @@ import { categoriesToSeries } from "../utils/categories";
|
|
|
16
18
|
import { legendPositionToLegendProps, resolveLegendWrapperStyle } from "../utils/legend";
|
|
17
19
|
import { toCssProperties } from "../utils/style";
|
|
18
20
|
import { themeToVars } from "../utils/theme";
|
|
21
|
+
import { DEFAULT_MAX_DATA_POINTS, normalizeNamedValues, sampleData, warnDataLimit } from "../utils/data";
|
|
19
22
|
const props = defineProps({
|
|
20
23
|
data: { type: Array, required: true },
|
|
24
|
+
nameKey: { type: null, required: false },
|
|
25
|
+
valueKey: { type: null, required: false },
|
|
21
26
|
categories: { type: Object, required: true },
|
|
22
27
|
height: { type: Number, required: true },
|
|
23
28
|
variant: { type: String, required: false },
|
|
@@ -33,9 +38,14 @@ const props = defineProps({
|
|
|
33
38
|
legendStyle: { type: [String, Object], required: false },
|
|
34
39
|
hideTooltip: { type: Boolean, required: false },
|
|
35
40
|
tooltipTitleFormatter: { type: Function, required: false },
|
|
36
|
-
tooltip: { type: Object, required: false },
|
|
37
41
|
theme: { type: Object, required: false },
|
|
38
42
|
duration: { type: Number, required: false },
|
|
43
|
+
ariaLabel: { type: String, required: false },
|
|
44
|
+
ariaDescription: { type: String, required: false },
|
|
45
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
46
|
+
maxDataPoints: { type: Number, required: false },
|
|
47
|
+
emptyLabel: { type: String, required: false },
|
|
48
|
+
error: { type: String, required: false },
|
|
39
49
|
legendVariant: { type: String, required: false },
|
|
40
50
|
tooltipVariant: { type: String, required: false },
|
|
41
51
|
tooltipRoundness: { type: String, required: false },
|
|
@@ -46,14 +56,19 @@ defineSlots();
|
|
|
46
56
|
const themeVars = computed(() => themeToVars(props.theme));
|
|
47
57
|
const stages = computed(() => {
|
|
48
58
|
const cats = categoriesToSeries(props.categories);
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
const normalized = normalizeNamedValues(props.data, cats.map((cat) => String(cat.name)), props.nameKey, props.valueKey);
|
|
60
|
+
const limit = props.maxDataPoints ?? DEFAULT_MAX_DATA_POINTS;
|
|
61
|
+
warnDataLimit("FunnelChart", normalized.length, limit);
|
|
62
|
+
return sampleData(normalized, limit).map(({ row, value, sourceIndex, name }) => ({
|
|
63
|
+
...row && typeof row === "object" ? row : {},
|
|
64
|
+
name,
|
|
51
65
|
value,
|
|
52
|
-
formattedValue: props.valueFormatter?.(value,
|
|
53
|
-
fill: cats[
|
|
54
|
-
color: cats[
|
|
66
|
+
formattedValue: props.valueFormatter?.(value, sourceIndex),
|
|
67
|
+
fill: cats[sourceIndex]?.color ?? `var(--vc-series-${sourceIndex % 8})`,
|
|
68
|
+
color: cats[sourceIndex]?.color ?? `var(--vc-series-${sourceIndex % 8})`
|
|
55
69
|
}));
|
|
56
70
|
});
|
|
71
|
+
const accessibleRows = computed(() => stages.value.map((stage) => ({ label: stage.name, values: [{ label: "Value", value: stage.formattedValue ?? stage.value }] })));
|
|
57
72
|
const isLayered = computed(() => props.variant === "layered");
|
|
58
73
|
const showValueLabel = computed(() => props.showValueLabel ?? true);
|
|
59
74
|
const NAMED_LABEL_SIZES = { sm: 10, md: 12, lg: 14 };
|
|
@@ -112,7 +127,7 @@ function updateTooltipPosition(event) {
|
|
|
112
127
|
};
|
|
113
128
|
}
|
|
114
129
|
function showTooltip(event, datum) {
|
|
115
|
-
activeDatum.value = datum;
|
|
130
|
+
activeDatum.value = props.tooltipTitleFormatter ? { ...datum, name: String(props.tooltipTitleFormatter(datum)) } : datum;
|
|
116
131
|
updateTooltipPosition(event);
|
|
117
132
|
}
|
|
118
133
|
function clearTooltip() {
|
|
@@ -125,14 +140,18 @@ function clearTooltip() {
|
|
|
125
140
|
v-if="isLayered"
|
|
126
141
|
ref="chartContainer"
|
|
127
142
|
class="vc-funnel-layered vue-chrts"
|
|
143
|
+
role="group"
|
|
144
|
+
:aria-label="ariaLabel ?? 'Funnel chart'"
|
|
128
145
|
:style="{ height: `${height}px`, ...themeVars }"
|
|
129
146
|
>
|
|
147
|
+
<ChartAccessibility :label="ariaLabel ?? 'Funnel chart'" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
|
|
130
148
|
<ChartSkeleton
|
|
131
149
|
v-if="loading"
|
|
132
150
|
:height="height"
|
|
133
151
|
shape="bars"
|
|
134
152
|
:label="loadingLabel ?? 'Loading'"
|
|
135
153
|
/>
|
|
154
|
+
<ChartEmptyState v-else-if="error || stages.length === 0" :height="height" :message="error || emptyLabel" />
|
|
136
155
|
<ChartContainer v-else width="100%" height="100%">
|
|
137
156
|
<VccsFunnelChart :margin="{ top: 16, right: 0, bottom: 8, left: 0 }">
|
|
138
157
|
<Funnel
|
|
@@ -142,6 +161,7 @@ function clearTooltip() {
|
|
|
142
161
|
last-shape-type="rectangle"
|
|
143
162
|
stroke="none"
|
|
144
163
|
:is-animation-active="duration !== void 0 && duration !== 0"
|
|
164
|
+
:transition="{ duration: (duration ?? 800) / 1e3, ease: 'easeOut' }"
|
|
145
165
|
>
|
|
146
166
|
<template #shape="shapeProps">
|
|
147
167
|
<!--
|
|
@@ -180,13 +200,15 @@ function clearTooltip() {
|
|
|
180
200
|
/>
|
|
181
201
|
</div>
|
|
182
202
|
|
|
183
|
-
<div v-else ref="chartContainer" class="vc-funnel vue-chrts" :style="themeVars">
|
|
203
|
+
<div v-else ref="chartContainer" class="vc-funnel vue-chrts" role="group" :aria-label="ariaLabel ?? 'Funnel chart'" :style="themeVars">
|
|
204
|
+
<ChartAccessibility :label="ariaLabel ?? 'Funnel chart'" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
|
|
184
205
|
<ChartSkeleton
|
|
185
206
|
v-if="loading"
|
|
186
207
|
:height="height"
|
|
187
208
|
shape="bars"
|
|
188
209
|
:label="loadingLabel ?? 'Loading'"
|
|
189
210
|
/>
|
|
211
|
+
<ChartEmptyState v-else-if="error || stages.length === 0" :height="height" :message="error || emptyLabel" />
|
|
190
212
|
<ChartContainer v-else width="100%" :height="height">
|
|
191
213
|
<VccsFunnelChart>
|
|
192
214
|
<Funnel
|
|
@@ -196,6 +218,7 @@ function clearTooltip() {
|
|
|
196
218
|
:last-shape-type="lastShapeType ?? 'triangle'"
|
|
197
219
|
stroke="none"
|
|
198
220
|
:is-animation-active="duration !== void 0 && duration !== 0"
|
|
221
|
+
:transition="{ duration: (duration ?? 800) / 1e3, ease: 'easeOut' }"
|
|
199
222
|
>
|
|
200
223
|
<template #shape="shapeProps">
|
|
201
224
|
<slot
|
|
@@ -52,7 +52,6 @@ const props = defineProps({
|
|
|
52
52
|
xAxisConfig: { type: Object, required: false },
|
|
53
53
|
yAxisConfig: { type: Object, required: false },
|
|
54
54
|
yAxes: { type: Object, required: false },
|
|
55
|
-
tooltip: { type: Object, required: false },
|
|
56
55
|
referenceLines: { type: Array, required: false },
|
|
57
56
|
syncId: { type: String, required: false },
|
|
58
57
|
minMaxTicksOnly: { type: Boolean, required: false },
|
|
@@ -61,6 +60,12 @@ const props = defineProps({
|
|
|
61
60
|
crosshairConfig: { type: Object, required: false },
|
|
62
61
|
theme: { type: Object, required: false },
|
|
63
62
|
backgroundPattern: { type: String, required: false },
|
|
63
|
+
ariaLabel: { type: String, required: false },
|
|
64
|
+
ariaDescription: { type: String, required: false },
|
|
65
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
66
|
+
maxDataPoints: { type: Number, required: false },
|
|
67
|
+
emptyLabel: { type: String, required: false },
|
|
68
|
+
error: { type: String, required: false },
|
|
64
69
|
legendVariant: { type: String, required: false },
|
|
65
70
|
tooltipVariant: { type: String, required: false },
|
|
66
71
|
tooltipRoundness: { type: String, required: false },
|
|
@@ -134,6 +139,7 @@ defineSlots();
|
|
|
134
139
|
:dot="showDots || s.dot"
|
|
135
140
|
:hide="s.hidden"
|
|
136
141
|
:is-animation-active="duration !== void 0 && duration !== 0"
|
|
142
|
+
:transition="{ duration: (duration ?? 800) / 1e3, ease: 'easeOut' }"
|
|
137
143
|
>
|
|
138
144
|
<template v-if="showDots" #dot="dotProps">
|
|
139
145
|
<ChartDot
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
import ChartContainer from "./internal/ChartContainer";
|
|
13
13
|
import ChartSkeleton from "./internal/ChartSkeleton.vue";
|
|
14
14
|
import ChartLegend from "./internal/ChartLegend.vue";
|
|
15
|
+
import ChartEmptyState from "./internal/ChartEmptyState.vue";
|
|
16
|
+
import ChartAccessibility from "./internal/ChartAccessibility.vue";
|
|
15
17
|
import ChartDot from "./internal/ChartDot";
|
|
16
18
|
import { tooltipContentFor } from "./internal/tooltipContent";
|
|
17
19
|
import PolarCenterSync from "./internal/PolarCenterSync";
|
|
@@ -20,6 +22,7 @@ import { categoriesToSeries } from "../utils/categories";
|
|
|
20
22
|
import { legendPositionToLegendProps, resolveLegendWrapperStyle } from "../utils/legend";
|
|
21
23
|
import { toCssProperties } from "../utils/style";
|
|
22
24
|
import { themeToVars } from "../utils/theme";
|
|
25
|
+
import { DEFAULT_MAX_DATA_POINTS, normalizeNumericRows, sampleData, warnDataLimit } from "../utils/data";
|
|
23
26
|
const props = defineProps({
|
|
24
27
|
data: { type: Array, required: true },
|
|
25
28
|
height: { type: Number, required: true },
|
|
@@ -41,9 +44,14 @@ const props = defineProps({
|
|
|
41
44
|
outerRadius: { type: [Number, String], required: false },
|
|
42
45
|
angleAxisTickSize: { type: Number, required: false },
|
|
43
46
|
margin: { type: Object, required: false },
|
|
44
|
-
tooltip: { type: Object, required: false },
|
|
45
47
|
theme: { type: Object, required: false },
|
|
46
48
|
duration: { type: Number, required: false },
|
|
49
|
+
ariaLabel: { type: String, required: false },
|
|
50
|
+
ariaDescription: { type: String, required: false },
|
|
51
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
52
|
+
maxDataPoints: { type: Number, required: false },
|
|
53
|
+
emptyLabel: { type: String, required: false },
|
|
54
|
+
error: { type: String, required: false },
|
|
47
55
|
legendVariant: { type: String, required: false },
|
|
48
56
|
tooltipVariant: { type: String, required: false },
|
|
49
57
|
tooltipRoundness: { type: String, required: false },
|
|
@@ -52,6 +60,16 @@ const props = defineProps({
|
|
|
52
60
|
});
|
|
53
61
|
const gradientScope = useId();
|
|
54
62
|
const series = computed(() => categoriesToSeries(props.categories).filter((s) => !s.hidden));
|
|
63
|
+
const chartData = computed(() => {
|
|
64
|
+
const normalized = normalizeNumericRows(props.data, series.value.map((item) => item.dataKey));
|
|
65
|
+
const limit = props.maxDataPoints ?? DEFAULT_MAX_DATA_POINTS;
|
|
66
|
+
warnDataLimit("RadarChart", normalized.length, limit);
|
|
67
|
+
return sampleData(normalized, limit);
|
|
68
|
+
});
|
|
69
|
+
const accessibleRows = computed(() => chartData.value.map((row, index) => ({
|
|
70
|
+
label: String(row[props.dataKey] ?? index + 1),
|
|
71
|
+
values: series.value.map((item) => ({ label: String(item.name), value: row[item.dataKey] }))
|
|
72
|
+
})));
|
|
55
73
|
const polygonFillOpacity = computed(
|
|
56
74
|
() => props.variant === "lines" ? 0 : isGradient.value ? 1 : props.fillOpacity ?? 0.6
|
|
57
75
|
);
|
|
@@ -63,7 +81,7 @@ function polygonFill(dataKey, color) {
|
|
|
63
81
|
}
|
|
64
82
|
const showDots = computed(() => props.dotVariant !== void 0);
|
|
65
83
|
const tooltipContent = computed(
|
|
66
|
-
() => tooltipContentFor(props.tooltipVariant, props.tooltipRoundness)
|
|
84
|
+
() => tooltipContentFor(props.tooltipVariant, props.tooltipRoundness, props.tooltipTitleFormatter)
|
|
67
85
|
);
|
|
68
86
|
const angleKey = computed(() => String(props.dataKey));
|
|
69
87
|
const legend = computed(() => legendPositionToLegendProps(props.legendPosition));
|
|
@@ -89,16 +107,18 @@ const resolvedOuterRadius = computed(() => {
|
|
|
89
107
|
</script>
|
|
90
108
|
|
|
91
109
|
<template>
|
|
92
|
-
<div class="vue-chrts" :style="themeVars">
|
|
110
|
+
<div class="vue-chrts" role="group" :aria-label="ariaLabel ?? 'Radar chart'" :style="themeVars">
|
|
111
|
+
<ChartAccessibility :label="ariaLabel ?? 'Radar chart'" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
|
|
93
112
|
<ChartSkeleton
|
|
94
113
|
v-if="loading"
|
|
95
114
|
:height="height ?? 200"
|
|
96
115
|
shape="ring"
|
|
97
116
|
:label="loadingLabel ?? 'Loading'"
|
|
98
117
|
/>
|
|
118
|
+
<ChartEmptyState v-else-if="error || chartData.length === 0" :height="height" :message="error || emptyLabel" />
|
|
99
119
|
<ChartContainer v-else width="100%" :height="height">
|
|
100
120
|
<VccsRadarChart
|
|
101
|
-
:data="
|
|
121
|
+
:data="chartData"
|
|
102
122
|
:outer-radius="resolvedOuterRadius"
|
|
103
123
|
:cx="center?.cx"
|
|
104
124
|
:cy="center?.cy"
|
|
@@ -141,6 +161,7 @@ const resolvedOuterRadius = computed(() => {
|
|
|
141
161
|
:fill-opacity="polygonFillOpacity"
|
|
142
162
|
:dot="showDots"
|
|
143
163
|
:is-animation-active="duration !== void 0 && duration !== 0"
|
|
164
|
+
:transition="{ duration: (duration ?? 800) / 1e3, ease: 'easeOut' }"
|
|
144
165
|
>
|
|
145
166
|
<template v-if="showDots" #dot="dotProps">
|
|
146
167
|
<ChartDot
|
|
@@ -9,14 +9,19 @@ import {
|
|
|
9
9
|
import ChartContainer from "./internal/ChartContainer";
|
|
10
10
|
import ChartSkeleton from "./internal/ChartSkeleton.vue";
|
|
11
11
|
import ChartLegend from "./internal/ChartLegend.vue";
|
|
12
|
+
import ChartEmptyState from "./internal/ChartEmptyState.vue";
|
|
13
|
+
import ChartAccessibility from "./internal/ChartAccessibility.vue";
|
|
12
14
|
import { tooltipContentFor } from "./internal/tooltipContent";
|
|
13
15
|
import PolarCenterSync from "./internal/PolarCenterSync";
|
|
14
16
|
import { categoriesToSeries } from "../utils/categories";
|
|
15
17
|
import { legendPositionToLegendProps, resolveLegendWrapperStyle } from "../utils/legend";
|
|
16
18
|
import { toCssProperties } from "../utils/style";
|
|
17
|
-
import { themeToVars } from "../utils/theme";
|
|
19
|
+
import { resolveHoverVisible, themeToVars } from "../utils/theme";
|
|
20
|
+
import { DEFAULT_MAX_DATA_POINTS, normalizeNamedValues, sampleData, warnDataLimit } from "../utils/data";
|
|
18
21
|
const props = defineProps({
|
|
19
22
|
data: { type: Array, required: true },
|
|
23
|
+
nameKey: { type: null, required: false },
|
|
24
|
+
valueKey: { type: null, required: false },
|
|
20
25
|
categories: { type: Object, required: true },
|
|
21
26
|
height: { type: Number, required: true },
|
|
22
27
|
variant: { type: String, required: false },
|
|
@@ -31,9 +36,14 @@ const props = defineProps({
|
|
|
31
36
|
legendStyle: { type: [String, Object], required: false },
|
|
32
37
|
hideTooltip: { type: Boolean, required: false },
|
|
33
38
|
tooltipTitleFormatter: { type: Function, required: false },
|
|
34
|
-
tooltip: { type: Object, required: false },
|
|
35
39
|
theme: { type: Object, required: false },
|
|
36
40
|
duration: { type: Number, required: false },
|
|
41
|
+
ariaLabel: { type: String, required: false },
|
|
42
|
+
ariaDescription: { type: String, required: false },
|
|
43
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
44
|
+
maxDataPoints: { type: Number, required: false },
|
|
45
|
+
emptyLabel: { type: String, required: false },
|
|
46
|
+
error: { type: String, required: false },
|
|
37
47
|
legendVariant: { type: String, required: false },
|
|
38
48
|
tooltipVariant: { type: String, required: false },
|
|
39
49
|
tooltipRoundness: { type: String, required: false },
|
|
@@ -43,10 +53,14 @@ const props = defineProps({
|
|
|
43
53
|
const themeVars = computed(() => themeToVars(props.theme));
|
|
44
54
|
const bars = computed(() => {
|
|
45
55
|
const cats = categoriesToSeries(props.categories);
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
const normalized = normalizeNamedValues(props.data, cats.map((cat) => String(cat.name)), props.nameKey, props.valueKey);
|
|
57
|
+
const limit = props.maxDataPoints ?? DEFAULT_MAX_DATA_POINTS;
|
|
58
|
+
warnDataLimit("RadialBarChart", normalized.length, limit);
|
|
59
|
+
return sampleData(normalized, limit).map(({ row, value, sourceIndex, name }) => ({
|
|
60
|
+
...row && typeof row === "object" ? row : {},
|
|
61
|
+
name,
|
|
48
62
|
value,
|
|
49
|
-
fill: cats[
|
|
63
|
+
fill: cats[sourceIndex]?.color ?? `var(--vc-series-${sourceIndex % 8})`
|
|
50
64
|
}));
|
|
51
65
|
});
|
|
52
66
|
const angles = computed(() => {
|
|
@@ -57,7 +71,26 @@ const angles = computed(() => {
|
|
|
57
71
|
};
|
|
58
72
|
});
|
|
59
73
|
const tooltipContent = computed(
|
|
60
|
-
() => tooltipContentFor(props.tooltipVariant, props.tooltipRoundness)
|
|
74
|
+
() => tooltipContentFor(props.tooltipVariant, props.tooltipRoundness, props.tooltipTitleFormatter)
|
|
75
|
+
);
|
|
76
|
+
const accessibleRows = computed(() => bars.value.map((bar) => ({ label: bar.name, values: [{ label: "Value", value: bar.value }] })));
|
|
77
|
+
const hiddenTooltipContent = () => null;
|
|
78
|
+
const radiusInPixels = (radius, fallback) => {
|
|
79
|
+
if (typeof radius === "number") return radius;
|
|
80
|
+
const percentage = Number.parseFloat(radius);
|
|
81
|
+
return Number.isFinite(percentage) ? props.height / 2 * percentage / 100 : fallback;
|
|
82
|
+
};
|
|
83
|
+
const hoverStrokeWidth = computed(() => {
|
|
84
|
+
const outer = radiusInPixels(props.outerRadius ?? "100%", props.height / 2);
|
|
85
|
+
const inner = radiusInPixels(props.innerRadius ?? "30%", props.height * 0.15);
|
|
86
|
+
return Math.max((outer - inner) / Math.max(bars.value.length, 1) * 0.9, 1);
|
|
87
|
+
});
|
|
88
|
+
const cursor = computed(
|
|
89
|
+
() => resolveHoverVisible(props.theme) ? {
|
|
90
|
+
fill: "none",
|
|
91
|
+
stroke: "var(--vc-hover-fill)",
|
|
92
|
+
strokeWidth: hoverStrokeWidth.value
|
|
93
|
+
} : false
|
|
61
94
|
);
|
|
62
95
|
const legend = computed(() => legendPositionToLegendProps(props.legendPosition));
|
|
63
96
|
const legendWrapperStyle = computed(
|
|
@@ -71,13 +104,15 @@ const onCenter = (next) => {
|
|
|
71
104
|
</script>
|
|
72
105
|
|
|
73
106
|
<template>
|
|
74
|
-
<div class="vue-chrts" :style="themeVars">
|
|
107
|
+
<div class="vue-chrts" role="group" :aria-label="ariaLabel ?? 'Radial bar chart'" :style="themeVars">
|
|
108
|
+
<ChartAccessibility :label="ariaLabel ?? 'Radial bar chart'" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
|
|
75
109
|
<ChartSkeleton
|
|
76
110
|
v-if="loading"
|
|
77
111
|
:height="height ?? 200"
|
|
78
112
|
shape="ring"
|
|
79
113
|
:label="loadingLabel ?? 'Loading'"
|
|
80
114
|
/>
|
|
115
|
+
<ChartEmptyState v-else-if="error || bars.length === 0" :height="height" :message="error || emptyLabel" />
|
|
81
116
|
<ChartContainer v-else width="100%" :height="height">
|
|
82
117
|
<VccsRadialBarChart
|
|
83
118
|
:data="bars"
|
|
@@ -94,8 +129,13 @@ const onCenter = (next) => {
|
|
|
94
129
|
:background="background ?? true"
|
|
95
130
|
:corner-radius="cornerRadius"
|
|
96
131
|
:is-animation-active="duration !== void 0 && duration !== 0"
|
|
132
|
+
:transition="{ duration: (duration ?? 400) / 1e3, ease: 'easeOut' }"
|
|
133
|
+
/>
|
|
134
|
+
<Tooltip
|
|
135
|
+
:content="hideTooltip ? hiddenTooltipContent : tooltipContent"
|
|
136
|
+
:cursor="cursor"
|
|
137
|
+
:is-animation-active="false"
|
|
97
138
|
/>
|
|
98
|
-
<Tooltip v-if="!hideTooltip" :content="tooltipContent" :is-animation-active="false" />
|
|
99
139
|
<Legend
|
|
100
140
|
v-if="!hideLegend"
|
|
101
141
|
:align="legend.align"
|
|
@@ -4,7 +4,10 @@ import { Sankey, Tooltip } from "vccs";
|
|
|
4
4
|
import ChartContainer from "./internal/ChartContainer";
|
|
5
5
|
import ChartSkeleton from "./internal/ChartSkeleton.vue";
|
|
6
6
|
import ChartTooltip from "./internal/ChartTooltip.vue";
|
|
7
|
+
import ChartEmptyState from "./internal/ChartEmptyState.vue";
|
|
8
|
+
import ChartAccessibility from "./internal/ChartAccessibility.vue";
|
|
7
9
|
import { themeToVars } from "../utils/theme";
|
|
10
|
+
import { DEFAULT_MAX_DATA_POINTS, sampleData, toFiniteNumber, warnDataLimit } from "../utils/data";
|
|
8
11
|
const props = defineProps({
|
|
9
12
|
data: { type: Object, required: true },
|
|
10
13
|
height: { type: Number, required: true },
|
|
@@ -22,9 +25,14 @@ const props = defineProps({
|
|
|
22
25
|
nodePadding: { type: Number, required: false, default: 10 },
|
|
23
26
|
iterations: { type: Number, required: false, default: 32 },
|
|
24
27
|
hideTooltip: { type: Boolean, required: false },
|
|
25
|
-
tooltip: { type: Object, required: false },
|
|
26
28
|
theme: { type: Object, required: false },
|
|
27
29
|
duration: { type: Number, required: false, default: 800 },
|
|
30
|
+
ariaLabel: { type: String, required: false },
|
|
31
|
+
ariaDescription: { type: String, required: false },
|
|
32
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
33
|
+
maxDataPoints: { type: Number, required: false },
|
|
34
|
+
emptyLabel: { type: String, required: false },
|
|
35
|
+
error: { type: String, required: false },
|
|
28
36
|
legendVariant: { type: String, required: false },
|
|
29
37
|
tooltipVariant: { type: String, required: false },
|
|
30
38
|
tooltipRoundness: { type: String, required: false },
|
|
@@ -47,11 +55,11 @@ const nodeLabel = (node) => {
|
|
|
47
55
|
const input = node;
|
|
48
56
|
return props.label?.(input) ?? String(input.name ?? input.id ?? "");
|
|
49
57
|
};
|
|
50
|
-
const resolvedNodeColor = (node) => props.nodeColor?.(node) ?? "var(--
|
|
58
|
+
const resolvedNodeColor = (node) => props.nodeColor?.(node) ?? "var(--vc-series-0)";
|
|
51
59
|
const resolvedLinkColor = (link) => {
|
|
52
60
|
const layoutLink = link;
|
|
53
61
|
const inputLink = layoutLink.__source === void 0 ? layoutLink : { ...layoutLink, source: layoutLink.__source };
|
|
54
|
-
return props.linkColor?.(inputLink) ?? "var(--
|
|
62
|
+
return props.linkColor?.(inputLink) ?? "var(--vc-series-0)";
|
|
55
63
|
};
|
|
56
64
|
function handleMouseEnter(item, type) {
|
|
57
65
|
activeColor.value = type === "node" ? resolvedNodeColor(item) : resolvedLinkColor(item);
|
|
@@ -66,30 +74,45 @@ const chartData = computed(() => {
|
|
|
66
74
|
if (node.id !== void 0) ids.set(node.id, index);
|
|
67
75
|
});
|
|
68
76
|
const resolveEndpoint = (endpoint) => ids.has(endpoint) ? ids.get(endpoint) : endpoint;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
const links = props.data.links.flatMap((link) => {
|
|
78
|
+
const value = toFiniteNumber(props.linkValue?.(link) ?? link.value ?? 0);
|
|
79
|
+
if (value === void 0 || value < 0) return [];
|
|
80
|
+
return [{
|
|
72
81
|
...link,
|
|
73
82
|
__source: link.source,
|
|
74
83
|
source: resolveEndpoint(link.source),
|
|
75
84
|
target: resolveEndpoint(link.target),
|
|
76
|
-
value
|
|
77
|
-
}
|
|
85
|
+
value
|
|
86
|
+
}];
|
|
87
|
+
});
|
|
88
|
+
const limit = props.maxDataPoints ?? DEFAULT_MAX_DATA_POINTS;
|
|
89
|
+
warnDataLimit("SankeyChart", links.length, limit);
|
|
90
|
+
return {
|
|
91
|
+
nodes,
|
|
92
|
+
links: sampleData(links, limit)
|
|
78
93
|
};
|
|
79
94
|
});
|
|
95
|
+
const accessibleRows = computed(() => chartData.value.links.map((link) => ({
|
|
96
|
+
label: `${String(link.__source)} to ${String(link.target)}`,
|
|
97
|
+
values: [{ label: "Value", value: link.value }]
|
|
98
|
+
})));
|
|
80
99
|
</script>
|
|
81
100
|
|
|
82
101
|
<template>
|
|
83
102
|
<div
|
|
84
103
|
class="vc-sankey vue-chrts"
|
|
104
|
+
role="group"
|
|
105
|
+
:aria-label="ariaLabel ?? 'Sankey chart'"
|
|
85
106
|
:style="{ position: 'relative', width: '100%', height: `${height}px`, ...themeVars }"
|
|
86
107
|
>
|
|
108
|
+
<ChartAccessibility :label="ariaLabel ?? 'Sankey chart'" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
|
|
87
109
|
<ChartSkeleton
|
|
88
110
|
v-if="loading"
|
|
89
111
|
:height="height"
|
|
90
112
|
shape="bars"
|
|
91
113
|
:label="loadingLabel ?? 'Loading'"
|
|
92
114
|
/>
|
|
115
|
+
<ChartEmptyState v-else-if="error || chartData.links.length === 0" :height="height" :message="error || emptyLabel" />
|
|
93
116
|
<ChartContainer v-else width="100%" height="100%">
|
|
94
117
|
<Sankey
|
|
95
118
|
:data="chartData"
|
|
@@ -112,7 +135,7 @@ const chartData = computed(() => {
|
|
|
112
135
|
:width="nodeProps.width"
|
|
113
136
|
:height="nodeProps.height"
|
|
114
137
|
:fill="resolvedNodeColor(nodeProps.payload)"
|
|
115
|
-
stroke="var(--vc-surface, #fff)"
|
|
138
|
+
stroke="var(--vc-surface-bg, #fff)"
|
|
116
139
|
/>
|
|
117
140
|
<text
|
|
118
141
|
:x="nodeProps.payload.depth === 0 ? nodeProps.x - 8 : nodeProps.x + nodeProps.width + 8"
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, onMounted, onUnmounted, ref } from "vue";
|
|
3
3
|
import { categoriesToSeries } from "../utils/categories";
|
|
4
|
+
import ChartAccessibility from "./internal/ChartAccessibility.vue";
|
|
5
|
+
import ChartEmptyState from "./internal/ChartEmptyState.vue";
|
|
6
|
+
import ChartSkeleton from "./internal/ChartSkeleton.vue";
|
|
7
|
+
import { toFiniteNumber } from "../utils/data";
|
|
8
|
+
import { formatNumber } from "../utils/format";
|
|
4
9
|
const props = defineProps({
|
|
5
10
|
data: { type: Array, required: true },
|
|
6
11
|
categories: { type: Object, required: true },
|
|
@@ -21,7 +26,18 @@ const props = defineProps({
|
|
|
21
26
|
hideHeader: { type: Boolean, required: false, default: false },
|
|
22
27
|
hideLegend: { type: Boolean, required: false, default: false },
|
|
23
28
|
hideTooltip: { type: Boolean, required: false, default: false },
|
|
24
|
-
rounded: { type: Boolean, required: false, default: true }
|
|
29
|
+
rounded: { type: Boolean, required: false, default: true },
|
|
30
|
+
ariaLabel: { type: String, required: false },
|
|
31
|
+
ariaDescription: { type: String, required: false },
|
|
32
|
+
accessibleDataTable: { type: Boolean, required: false, default: true },
|
|
33
|
+
maxDataPoints: { type: Number, required: false },
|
|
34
|
+
emptyLabel: { type: String, required: false },
|
|
35
|
+
error: { type: String, required: false },
|
|
36
|
+
legendVariant: { type: String, required: false },
|
|
37
|
+
tooltipVariant: { type: String, required: false },
|
|
38
|
+
tooltipRoundness: { type: String, required: false },
|
|
39
|
+
loading: { type: Boolean, required: false },
|
|
40
|
+
loadingLabel: { type: String, required: false }
|
|
25
41
|
});
|
|
26
42
|
const containerRef = ref(null);
|
|
27
43
|
const containerWidth = ref(0);
|
|
@@ -59,9 +75,9 @@ const visibleBars = computed(() => {
|
|
|
59
75
|
const dataIndex = start + visibleIndex;
|
|
60
76
|
const status = props.statusAccessor?.(datum, dataIndex) ?? datum.status;
|
|
61
77
|
const category = categoryMap.value.get(status);
|
|
62
|
-
const value = props.valueAccessor?.(datum, dataIndex) ?? datum.value;
|
|
78
|
+
const value = toFiniteNumber(props.valueAccessor?.(datum, dataIndex) ?? datum.value);
|
|
63
79
|
const label = props.labelAccessor?.(datum, dataIndex) ?? datum.label ?? category?.name ?? status;
|
|
64
|
-
const formattedValue = typeof value === "number" ? props.valueFormatter?.(value, datum, dataIndex) ?? value
|
|
80
|
+
const formattedValue = typeof value === "number" ? props.valueFormatter?.(value, datum, dataIndex) ?? formatNumber(value) : void 0;
|
|
65
81
|
return {
|
|
66
82
|
key: `${dataIndex}-${status}`,
|
|
67
83
|
status,
|
|
@@ -81,10 +97,15 @@ const barStyle = computed(() => ({
|
|
|
81
97
|
width: `${Math.max(1, props.barWidth)}px`
|
|
82
98
|
}));
|
|
83
99
|
const ariaLabel = computed(() => {
|
|
100
|
+
if (props.ariaLabel) return props.ariaLabel;
|
|
84
101
|
const prefix = props.title ? `${props.title}: ` : "";
|
|
85
102
|
const samples = props.data.length === 1 ? "1 status sample" : `${props.data.length} status samples`;
|
|
86
103
|
return `${prefix}${samples}`;
|
|
87
104
|
});
|
|
105
|
+
const accessibleRows = computed(() => visibleBars.value.filter((bar) => !bar.isEmpty).map((bar) => ({
|
|
106
|
+
label: bar.label,
|
|
107
|
+
values: [{ label: "Status", value: bar.status }]
|
|
108
|
+
})));
|
|
88
109
|
function updateContainerWidth() {
|
|
89
110
|
containerWidth.value = containerRef.value?.offsetWidth ?? 0;
|
|
90
111
|
}
|
|
@@ -101,6 +122,7 @@ onUnmounted(() => {
|
|
|
101
122
|
|
|
102
123
|
<template>
|
|
103
124
|
<section class="vc-status-tracker vue-chrts" :aria-label="ariaLabel">
|
|
125
|
+
<ChartAccessibility :label="ariaLabel" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
|
|
104
126
|
<header v-if="!hideHeader && (title || summary !== void 0)" class="vc-status-tracker__header">
|
|
105
127
|
<h3 v-if="title" class="vc-status-tracker__title">
|
|
106
128
|
{{ title }}
|
|
@@ -110,11 +132,13 @@ onUnmounted(() => {
|
|
|
110
132
|
</p>
|
|
111
133
|
</header>
|
|
112
134
|
|
|
135
|
+
<ChartSkeleton v-if="loading" :height="height" shape="bars" :label="loadingLabel ?? 'Loading'" />
|
|
136
|
+
<ChartEmptyState v-else-if="error || data.length === 0" :height="height" :message="error || emptyLabel" />
|
|
113
137
|
<div
|
|
138
|
+
v-else
|
|
114
139
|
ref="containerRef"
|
|
115
140
|
class="vc-status-tracker__track"
|
|
116
|
-
role="
|
|
117
|
-
:aria-label="ariaLabel"
|
|
141
|
+
role="list"
|
|
118
142
|
:style="trackerStyle"
|
|
119
143
|
>
|
|
120
144
|
<span
|
|
@@ -129,15 +153,17 @@ onUnmounted(() => {
|
|
|
129
153
|
:style="{ ...barStyle, backgroundColor: bar.color }"
|
|
130
154
|
:title="hideTooltip ? void 0 : bar.title"
|
|
131
155
|
:aria-label="bar.title"
|
|
156
|
+
:tabindex="bar.isEmpty ? void 0 : 0"
|
|
157
|
+
role="listitem"
|
|
132
158
|
/>
|
|
133
159
|
</div>
|
|
134
160
|
|
|
135
|
-
<div v-if="startLabel || endLabel" class="vc-status-tracker__labels">
|
|
161
|
+
<div v-if="!loading && !error && data.length && (startLabel || endLabel)" class="vc-status-tracker__labels">
|
|
136
162
|
<span>{{ startLabel }}</span>
|
|
137
163
|
<span>{{ endLabel }}</span>
|
|
138
164
|
</div>
|
|
139
165
|
|
|
140
|
-
<ul v-if="!hideLegend && legendItems.length" class="vc-status-tracker__legend">
|
|
166
|
+
<ul v-if="!loading && !error && data.length && !hideLegend && legendItems.length" class="vc-status-tracker__legend">
|
|
141
167
|
<li v-for="item in legendItems" :key="item.dataKey" class="vc-status-tracker__legend-item">
|
|
142
168
|
<span class="vc-status-tracker__legend-dot" :style="{ backgroundColor: item.color }" />
|
|
143
169
|
<span>{{ item.name }}</span>
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { formatNumber } from "../../utils/format";
|
|
3
4
|
const props = defineProps({
|
|
4
5
|
row: { type: Object, required: true },
|
|
5
6
|
upColor: { type: String, required: true },
|
|
6
7
|
downColor: { type: String, required: true },
|
|
7
|
-
valueFormatter: { type: Function, required: false }
|
|
8
|
+
valueFormatter: { type: Function, required: false },
|
|
9
|
+
title: { type: [String, Number], required: false }
|
|
8
10
|
});
|
|
9
11
|
const directionColor = computed(
|
|
10
12
|
() => props.row.close >= props.row.open ? props.upColor : props.downColor
|
|
11
13
|
);
|
|
12
14
|
function fmt(value) {
|
|
13
|
-
return props.valueFormatter ? props.valueFormatter(value) : value
|
|
15
|
+
return props.valueFormatter ? props.valueFormatter(value) : formatNumber(value);
|
|
14
16
|
}
|
|
15
17
|
const rows = computed(() => {
|
|
16
18
|
const r = props.row;
|
|
@@ -19,14 +21,14 @@ const rows = computed(() => {
|
|
|
19
21
|
{ name: "High", value: fmt(r.high), color: props.upColor },
|
|
20
22
|
{ name: "Low", value: fmt(r.low), color: props.downColor },
|
|
21
23
|
{ name: "Close", value: fmt(r.close), color: directionColor.value },
|
|
22
|
-
...r.volume != null && !Number.isNaN(r.volume) ? [{ name: "Volume", value: r.volume
|
|
24
|
+
...r.volume != null && !Number.isNaN(r.volume) ? [{ name: "Volume", value: formatNumber(r.volume), color: "var(--vc-tooltip-muted, oklch(0.556 0 0))" }] : []
|
|
23
25
|
];
|
|
24
26
|
});
|
|
25
27
|
</script>
|
|
26
28
|
|
|
27
29
|
<template>
|
|
28
30
|
<div class="vc-tooltip">
|
|
29
|
-
<p class="vc-tooltip__label">{{ row.label }}</p>
|
|
31
|
+
<p class="vc-tooltip__label">{{ title ?? row.label }}</p>
|
|
30
32
|
<ul class="vc-tooltip__list">
|
|
31
33
|
<li v-for="item in rows" :key="item.name" class="vc-tooltip__item">
|
|
32
34
|
<span class="vc-tooltip__dot" :style="{ background: item.color }" />
|
|
@@ -11,6 +11,7 @@ type __VLS_Props = {
|
|
|
11
11
|
upColor: string;
|
|
12
12
|
downColor: string;
|
|
13
13
|
valueFormatter?: (value: number) => string;
|
|
14
|
+
title?: string | number;
|
|
14
15
|
};
|
|
15
16
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
17
|
declare const _default: typeof __VLS_export;
|