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.
Files changed (96) hide show
  1. package/README.md +99 -8
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +20 -2
  4. package/dist/runtime/assets/components.css +1 -1
  5. package/dist/runtime/assets/theme.css +1 -1
  6. package/dist/runtime/components/AreaChart.vue +91 -10
  7. package/dist/runtime/components/AreaChart.vue.d.ts +5 -1
  8. package/dist/runtime/components/BarChart.vue +132 -9
  9. package/dist/runtime/components/BarChart.vue.d.ts +5 -1
  10. package/dist/runtime/components/BubbleChart.vue +114 -27
  11. package/dist/runtime/components/BubbleChart.vue.d.ts +5 -1
  12. package/dist/runtime/components/CandlestickChart.vue +60 -18
  13. package/dist/runtime/components/DonutChart.vue +88 -17
  14. package/dist/runtime/components/FunnelChart.vue +156 -41
  15. package/dist/runtime/components/FunnelChart.vue.d.ts +8 -1
  16. package/dist/runtime/components/LineChart.vue +79 -8
  17. package/dist/runtime/components/LineChart.vue.d.ts +5 -1
  18. package/dist/runtime/components/RadarChart.vue +116 -14
  19. package/dist/runtime/components/RadialBarChart.vue +63 -13
  20. package/dist/runtime/components/SankeyChart.vue +169 -0
  21. package/dist/runtime/components/SankeyChart.vue.d.ts +36 -0
  22. package/dist/runtime/components/StatusTrackerChart.vue +33 -7
  23. package/dist/runtime/components/internal/AreaVariantDefs.d.ts +68 -0
  24. package/dist/runtime/components/internal/AreaVariantDefs.js +165 -0
  25. package/dist/runtime/components/internal/BarVariantDefs.d.ts +74 -0
  26. package/dist/runtime/components/internal/BarVariantDefs.js +200 -0
  27. package/dist/runtime/components/internal/CandlestickTooltip.vue +6 -4
  28. package/dist/runtime/components/internal/CandlestickTooltip.vue.d.ts +1 -0
  29. package/dist/runtime/components/internal/CartesianFrame.vue +98 -20
  30. package/dist/runtime/components/internal/CartesianFrame.vue.d.ts +18 -1
  31. package/dist/runtime/components/internal/ChartAccessibility.vue +20 -0
  32. package/dist/runtime/components/internal/ChartAccessibility.vue.d.ts +21 -0
  33. package/dist/runtime/components/internal/ChartBackground.d.ts +44 -0
  34. package/dist/runtime/components/internal/ChartBackground.js +53 -0
  35. package/dist/runtime/components/internal/ChartDot.d.ts +85 -0
  36. package/dist/runtime/components/internal/ChartDot.js +59 -0
  37. package/dist/runtime/components/internal/ChartEmptyState.vue +12 -0
  38. package/dist/runtime/components/internal/{ChartLegend.d.vue.ts → ChartEmptyState.vue.d.ts} +2 -6
  39. package/dist/runtime/components/internal/ChartLegend.vue +11 -3
  40. package/dist/runtime/components/internal/ChartLegend.vue.d.ts +24 -1
  41. package/dist/runtime/components/internal/ChartSkeleton.vue +64 -0
  42. package/dist/runtime/components/internal/ChartSkeleton.vue.d.ts +18 -0
  43. package/dist/runtime/components/internal/ChartTooltip.vue +10 -3
  44. package/dist/runtime/components/internal/CubeBarShape.vue +8 -8
  45. package/dist/runtime/components/internal/CubeBarShape.vue.d.ts +10 -2
  46. package/dist/runtime/components/internal/FunnelClassicShape.vue +98 -0
  47. package/dist/runtime/components/internal/FunnelClassicShape.vue.d.ts +81 -0
  48. package/dist/runtime/components/internal/FunnelLayeredShape.vue +72 -37
  49. package/dist/runtime/components/internal/FunnelLayeredShape.vue.d.ts +21 -0
  50. package/dist/runtime/components/internal/FunnelTooltip.vue +27 -0
  51. package/dist/runtime/components/internal/{CandlestickTooltip.d.vue.ts → FunnelTooltip.vue.d.ts} +11 -12
  52. package/dist/runtime/components/internal/PolarCenterSync.d.ts +38 -0
  53. package/dist/runtime/components/internal/PolarCenterSync.js +27 -0
  54. package/dist/runtime/components/internal/RadarGradientDefs.d.ts +46 -0
  55. package/dist/runtime/components/internal/RadarGradientDefs.js +46 -0
  56. package/dist/runtime/components/internal/SegmentVariantDefs.d.ts +67 -0
  57. package/dist/runtime/components/internal/SegmentVariantDefs.js +51 -0
  58. package/dist/runtime/components/internal/StrokeGradientDefs.d.ts +36 -0
  59. package/dist/runtime/components/internal/StrokeGradientDefs.js +35 -0
  60. package/dist/runtime/components/internal/VariantBarShape.vue +53 -0
  61. package/dist/runtime/components/internal/VariantBarShape.vue.d.ts +36 -0
  62. package/dist/runtime/components/internal/tooltipContent.d.ts +13 -0
  63. package/dist/runtime/components/internal/tooltipContent.js +9 -0
  64. package/dist/runtime/types/charts.d.ts +236 -46
  65. package/dist/runtime/types/shared.d.ts +37 -11
  66. package/dist/runtime/types.d.ts +3 -0
  67. package/dist/runtime/utils/axis.d.ts +12 -1
  68. package/dist/runtime/utils/axis.js +23 -0
  69. package/dist/runtime/utils/background.d.ts +36 -0
  70. package/dist/runtime/utils/background.js +140 -0
  71. package/dist/runtime/utils/data.d.ts +19 -0
  72. package/dist/runtime/utils/data.js +50 -0
  73. package/dist/runtime/utils/format.d.ts +5 -0
  74. package/dist/runtime/utils/format.js +12 -0
  75. package/dist/runtime/utils/gradient.d.ts +8 -0
  76. package/dist/runtime/utils/gradient.js +3 -0
  77. package/dist/runtime/utils/index.d.ts +2 -0
  78. package/dist/runtime/utils/index.js +2 -0
  79. package/dist/runtime/utils/theme.js +14 -0
  80. package/dist/runtime/utils/variants.d.ts +101 -0
  81. package/dist/runtime/utils/variants.js +48 -0
  82. package/package.json +16 -28
  83. package/dist/runtime/components/AreaChart.d.vue.ts +0 -19
  84. package/dist/runtime/components/BarChart.d.vue.ts +0 -19
  85. package/dist/runtime/components/BubbleChart.d.vue.ts +0 -19
  86. package/dist/runtime/components/CandlestickChart.d.vue.ts +0 -19
  87. package/dist/runtime/components/DonutChart.d.vue.ts +0 -21
  88. package/dist/runtime/components/FunnelChart.d.vue.ts +0 -19
  89. package/dist/runtime/components/LineChart.d.vue.ts +0 -19
  90. package/dist/runtime/components/RadarChart.d.vue.ts +0 -19
  91. package/dist/runtime/components/RadialBarChart.d.vue.ts +0 -19
  92. package/dist/runtime/components/StatusTrackerChart.d.vue.ts +0 -19
  93. package/dist/runtime/components/internal/CartesianFrame.d.vue.ts +0 -40
  94. package/dist/runtime/components/internal/ChartTooltip.d.vue.ts +0 -3
  95. package/dist/runtime/components/internal/CubeBarShape.d.vue.ts +0 -28
  96. 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 (props.orientation === "horizontal") {
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
- <Rectangle v-else v-bind="shapeProps" />
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 props.data) {
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: colorByCat.get(name)
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
- <ChartContainer width="100%" :height="height">
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
- :name="xLabel"
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
- :name="yLabel"
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="ChartTooltip" :cursor="cursor" :is-animation-active="false" />
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
- tooltip: { type: Object, required: false }
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 ?? "#10b981");
39
- const downColor = computed(() => props.downColor ?? "#ef4444");
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
- () => 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
- );
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(600);
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.toLocaleString();
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?.width) W.value = cr.width;
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
- <svg :width="W" :height="H" class="vc-candlestick-chart__svg">
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(--chart-bg, #ffffff)'"
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>