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.
Files changed (62) hide show
  1. package/README.md +46 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +0 -1
  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 +7 -1
  7. package/dist/runtime/components/BarChart.vue +7 -1
  8. package/dist/runtime/components/BubbleChart.vue +43 -17
  9. package/dist/runtime/components/CandlestickChart.vue +60 -18
  10. package/dist/runtime/components/DonutChart.vue +28 -9
  11. package/dist/runtime/components/FunnelChart.vue +31 -8
  12. package/dist/runtime/components/LineChart.vue +7 -1
  13. package/dist/runtime/components/RadarChart.vue +25 -4
  14. package/dist/runtime/components/RadialBarChart.vue +48 -8
  15. package/dist/runtime/components/SankeyChart.vue +32 -9
  16. package/dist/runtime/components/StatusTrackerChart.vue +33 -7
  17. package/dist/runtime/components/internal/CandlestickTooltip.vue +6 -4
  18. package/dist/runtime/components/internal/CandlestickTooltip.vue.d.ts +1 -0
  19. package/dist/runtime/components/internal/CartesianFrame.vue +37 -9
  20. package/dist/runtime/components/internal/ChartAccessibility.vue +20 -0
  21. package/dist/runtime/components/internal/{ChartSkeleton.d.vue.ts → ChartAccessibility.vue.d.ts} +13 -10
  22. package/dist/runtime/components/internal/ChartEmptyState.vue +12 -0
  23. package/dist/runtime/components/internal/{CandlestickTooltip.d.vue.ts → ChartEmptyState.vue.d.ts} +2 -12
  24. package/dist/runtime/components/internal/ChartLegend.vue.d.ts +1 -1
  25. package/dist/runtime/components/internal/ChartTooltip.vue +2 -1
  26. package/dist/runtime/components/internal/CubeBarShape.vue.d.ts +1 -1
  27. package/dist/runtime/components/internal/FunnelClassicShape.vue +2 -1
  28. package/dist/runtime/components/internal/FunnelLayeredShape.vue +2 -1
  29. package/dist/runtime/components/internal/FunnelTooltip.vue +2 -1
  30. package/dist/runtime/components/internal/tooltipContent.d.ts +1 -1
  31. package/dist/runtime/components/internal/tooltipContent.js +6 -2
  32. package/dist/runtime/types/charts.d.ts +44 -34
  33. package/dist/runtime/types/shared.d.ts +5 -11
  34. package/dist/runtime/utils/categories.d.ts +1 -1
  35. package/dist/runtime/utils/categories.js +2 -2
  36. package/dist/runtime/utils/data.d.ts +19 -0
  37. package/dist/runtime/utils/data.js +50 -0
  38. package/dist/runtime/utils/format.d.ts +5 -0
  39. package/dist/runtime/utils/format.js +12 -0
  40. package/dist/runtime/utils/index.d.ts +2 -0
  41. package/dist/runtime/utils/index.js +2 -0
  42. package/dist/runtime/utils/theme.js +3 -0
  43. package/package.json +2 -2
  44. package/dist/runtime/components/AreaChart.d.vue.ts +0 -23
  45. package/dist/runtime/components/BarChart.d.vue.ts +0 -23
  46. package/dist/runtime/components/BubbleChart.d.vue.ts +0 -23
  47. package/dist/runtime/components/CandlestickChart.d.vue.ts +0 -19
  48. package/dist/runtime/components/DonutChart.d.vue.ts +0 -21
  49. package/dist/runtime/components/FunnelChart.d.vue.ts +0 -26
  50. package/dist/runtime/components/LineChart.d.vue.ts +0 -23
  51. package/dist/runtime/components/RadarChart.d.vue.ts +0 -19
  52. package/dist/runtime/components/RadialBarChart.d.vue.ts +0 -19
  53. package/dist/runtime/components/SankeyChart.d.vue.ts +0 -36
  54. package/dist/runtime/components/StatusTrackerChart.d.vue.ts +0 -19
  55. package/dist/runtime/components/internal/CartesianFrame.d.vue.ts +0 -57
  56. package/dist/runtime/components/internal/ChartLegend.d.vue.ts +0 -34
  57. package/dist/runtime/components/internal/ChartTooltip.d.vue.ts +0 -3
  58. package/dist/runtime/components/internal/CubeBarShape.d.vue.ts +0 -36
  59. package/dist/runtime/components/internal/FunnelClassicShape.d.vue.ts +0 -81
  60. package/dist/runtime/components/internal/FunnelLayeredShape.d.vue.ts +0 -63
  61. package/dist/runtime/components/internal/FunnelTooltip.d.vue.ts +0 -16
  62. package/dist/runtime/components/internal/VariantBarShape.d.vue.ts +0 -36
package/README.md CHANGED
@@ -4,6 +4,11 @@ Beautiful chart components for Nuxt — Area, Bar, Line, Donut, Bubble, Radar, R
4
4
 
5
5
  Powered by [vccs](https://vue-charts.com) (Vue port of Recharts). Drop-in config-prop API from nuxt-charts v2.
6
6
 
7
+ Nuxt UI v4 is a first-class, zero-config integration: chart series, surfaces,
8
+ text, borders, radius, typography, and dark mode inherit its semantic design
9
+ tokens. Nuxt UI is not a dependency; every token has a standalone fallback, so
10
+ the same components work in any Nuxt project.
11
+
7
12
  ## Install
8
13
 
9
14
  ```bash
@@ -158,6 +163,46 @@ export default defineNuxtConfig({
158
163
  })
159
164
  ```
160
165
 
166
+ ## Data safety, accessibility, and limits
167
+
168
+ This package is Nuxt-only. Vue/Vite applications should use the separate
169
+ `vue-chrts` package.
170
+
171
+ All numeric chart input is validated before it reaches a scale or SVG
172
+ attribute. `null`, missing values, `NaN`, and positive/negative infinity are
173
+ treated as gaps; a chart with no valid values shows `emptyLabel` (default:
174
+ `"No valid data"`). Donut, Radial Bar, and Funnel accept record data with
175
+ `nameKey` and `valueKey`; positional numeric arrays remain supported for v2
176
+ compatibility.
177
+
178
+ Every chart accepts `ariaLabel`, `ariaDescription`, and
179
+ `accessibleDataTable`. The visually hidden table is enabled by default and is
180
+ the keyboard/screen-reader equivalent of pointer-only SVG tooltips.
181
+
182
+ SVG charts render at most 2,000 data rows by default. Larger inputs are sampled
183
+ evenly (preserving both endpoints) and emit one development warning. Override
184
+ this with `maxDataPoints`, but use pre-aggregation for genuinely dense data:
185
+ SVG creates DOM work per mark, so raising the limit can block the UI.
186
+
187
+ ## Theming
188
+
189
+ With Nuxt UI, no chart-specific setup is required. Categories without an
190
+ explicit `color` use `--ui-primary`, `--ui-secondary`, `--ui-success`,
191
+ `--ui-info`, `--ui-warning`, and `--ui-error`; chart chrome follows Nuxt UI's
192
+ background, text, border, radius, and font tokens.
193
+
194
+ Without Nuxt UI, built-in light and dark defaults are used. Customize globally
195
+ with `--vc-*` variables (or the backwards-compatible `--chart-color-0` through
196
+ `--chart-color-7`), and customize one chart with the `theme` prop:
197
+
198
+ ```vue
199
+ <LineChart
200
+ :data="data"
201
+ :categories="categories"
202
+ :theme="{ colors: ['var(--brand)', 'var(--accent)'] }"
203
+ />
204
+ ```
205
+
161
206
  ## Auto-imported enums & types
162
207
 
163
208
  - Enums: `CurveType`, `LegendPosition`, `Orientation`, `DonutType`
@@ -166,7 +211,7 @@ export default defineNuxtConfig({
166
211
  `CandlestickChartProps`,
167
212
  `SankeyChartProps`, `SankeyInputNode`, `SankeyInputLink`,
168
213
  `StatusTrackerChartProps`, `StatusTrackerDatum`, `BulletLegendItemInterface`,
169
- `AxisConfig`, `TooltipConfig`, `ValueLabel`, `ReferenceLineConfig`
214
+ `AxisConfig`, `ValueLabel`, `ReferenceLineConfig`, `NumericKeys`
170
215
  - Style variant unions: `BarVariant`, `AreaFillVariant`, `StrokeVariant`,
171
216
  `DotVariant`, `DitherVariant`, `BackgroundVariant`, `LegendIndicatorVariant`,
172
217
  `TooltipVariant`, `TooltipRoundness`, `RadarVariant`, `RadialVariant`
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3"
6
6
  },
7
- "version": "3.0.0-beta.3",
7
+ "version": "3.0.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -58,7 +58,6 @@ const resolveImports = (config, resolverUrl) => {
58
58
  "StatusTrackerDatum",
59
59
  "BulletLegendItemInterface",
60
60
  "AxisConfig",
61
- "TooltipConfig",
62
61
  "ValueLabel",
63
62
  "ReferenceLineConfig",
64
63
  // v2 parity: these auto-imported in nuxt-charts v2. The types still exist
@@ -1 +1 @@
1
- @layer vue-chrts{.vc-tooltip{background:var(--vc-tooltip-bg,oklch(1 0 0));border:1px solid var(--vc-tooltip-border,oklch(.922 0 0));border-radius:var(--vc-tooltip-radius,.5rem);box-shadow:var(--vc-tooltip-shadow,0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1));color:var(--vc-tooltip-fg,oklch(.205 0 0));font-size:var(--vc-legend-size,.75rem);line-height:1.5;min-width:8rem;padding:var(--vc-tooltip-padding,.375rem .625rem);pointer-events:none}.vc-tooltip--frosted-glass{backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);background:color-mix(in oklab,var(--vc-tooltip-bg,oklch(1 0 0)) 70%,transparent)}.vc-tooltip--sm{border-radius:.125rem}.vc-tooltip--md{border-radius:.375rem}.vc-tooltip--lg{border-radius:.5rem}.vc-tooltip--xl{border-radius:.75rem}.vc-tooltip__label{color:var(--vc-tooltip-fg,oklch(.205 0 0));font-weight:500;margin:0 0 .25rem}.vc-tooltip__list{display:flex;flex-direction:column;gap:.125rem;list-style:none;margin:0;padding:0}.vc-tooltip__item{align-items:center;display:flex;gap:.375rem}.vc-tooltip__dot{border-radius:9999px;display:inline-block;flex-shrink:0;height:.5rem;width:.5rem}.vc-tooltip__name{color:var(--vc-tooltip-muted,oklch(.556 0 0))}.vc-tooltip__value{color:var(--vc-tooltip-fg,oklch(.205 0 0));font-variant-numeric:tabular-nums;font-weight:500;margin-left:auto}.vc-legend{flex-wrap:wrap;font-size:var(--vc-legend-size,.75rem);font-weight:var(--vc-legend-weight,400);gap:var(--vc-legend-gap,.75rem);justify-content:center;line-height:1.5;list-style:none;margin:0;padding:0}.vc-legend,.vc-legend__item{align-items:center;display:flex}.vc-legend__item{gap:.375rem}.vc-legend__item--inactive{opacity:.4}.vc-legend__dot{border-radius:9999px;display:inline-block;flex-shrink:0;height:var(--vc-legend-dot-size,.5rem);width:var(--vc-legend-dot-size,.5rem)}.vc-legend__dot--square{border-radius:0}.vc-legend__dot--rounded-square{border-radius:2px}.vc-legend__dot--circle{border-radius:9999px}.vc-legend__dot--vertical-bar{border-radius:2px;height:calc(var(--vc-legend-dot-size, .5rem)*1.5);width:calc(var(--vc-legend-dot-size, .5rem)*.5)}.vc-legend__dot--horizontal-bar{border-radius:2px;height:calc(var(--vc-legend-dot-size, .5rem)*.5);width:calc(var(--vc-legend-dot-size, .5rem)*1.5)}.vc-legend__dot--circle-outline,.vc-legend__dot--rounded-square-outline{box-sizing:border-box;height:calc(var(--vc-legend-dot-size, .5rem)*1.25);-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;padding:1.5px;width:calc(var(--vc-legend-dot-size, .5rem)*1.25)}.vc-legend__dot--circle-outline{border-radius:9999px}.vc-legend__dot--rounded-square-outline{border-radius:3px}.vc-legend__label{color:var(--vc-legend-color,oklch(.556 0 0))}.vc-skeleton{align-items:stretch;box-sizing:border-box;color:var(--vc-tick-color,oklch(.556 0 0));display:flex;overflow:hidden;padding:8% 4%;position:relative;width:100%}.vc-skeleton__bars{align-items:flex-end;display:flex;gap:2%;height:100%;width:100%}.vc-skeleton__bar{background:currentColor;border-radius:2px 2px 0 0;flex:1;opacity:.14}.vc-skeleton__wave{height:100%;opacity:.14;width:100%}.vc-skeleton__ring{aspect-ratio:1;border:1.75rem solid;border-radius:9999px;height:100%;margin:auto;opacity:.14}.vc-skeleton__shimmer{animation:vc-skeleton-sweep 1.8s linear infinite;background:linear-gradient(105deg,transparent 35%,currentColor 50%,transparent 65%);inset:0;opacity:.1;position:absolute}@keyframes vc-skeleton-sweep{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}.vc-skeleton__pill{align-items:center;background:var(--vc-surface-bg,oklch(1 0 0));border:1px solid var(--vc-tooltip-border,oklch(.922 0 0));border-radius:.375rem;color:var(--vc-tick-color,oklch(.556 0 0));display:flex;font-size:var(--vc-legend-size,.75rem);gap:.375rem;height:-moz-fit-content;height:fit-content;inset:0;line-height:1.5;margin:auto;padding:.125rem .5rem;position:absolute;width:-moz-fit-content;width:fit-content}.vc-skeleton__spinner{animation:vc-skeleton-spin .7s linear infinite;border:1px solid var(--vc-tooltip-border,oklch(.922 0 0));border-radius:9999px;border-top:1px solid;height:.75rem;width:.75rem}@keyframes vc-skeleton-spin{to{transform:rotate(1turn)}}@media (prefers-reduced-motion:reduce){.vc-skeleton__shimmer,.vc-skeleton__spinner{animation:none}}.vc-candlestick-chart{cursor:crosshair;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.vc-candlestick-chart__svg{display:block;overflow:visible;width:100%}.vc-funnel-layered{position:relative;width:100%}.vc-funnel{position:relative}.vc-funnel-layered__tooltip{align-items:center;background:var(--vc-tooltip-bg,oklch(1 0 0));border:1px solid var(--vc-tooltip-border,oklch(.922 0 0));border-radius:var(--vc-tooltip-radius,.5rem);box-shadow:var(--vc-tooltip-shadow,0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1));color:var(--vc-tooltip-fg,oklch(.205 0 0));display:flex;font-size:var(--vc-legend-size,.75rem);gap:.375rem;padding:var(--vc-tooltip-padding,.375rem .625rem);pointer-events:none;position:absolute;transform:translate(-50%,calc(-100% - 10px));white-space:nowrap}.vc-funnel-layered__name{color:var(--vc-tooltip-muted,oklch(.556 0 0))}.vc-funnel-layered__value{font-variant-numeric:tabular-nums;font-weight:600}.vc-funnel-layered__dot{border-radius:9999px;display:inline-block;flex-shrink:0;height:var(--vc-legend-dot-size,.5rem);width:var(--vc-legend-dot-size,.5rem)}.vc-funnel-layered-shape{opacity:.96;transition:opacity .15s}.vc-funnel-layered-shape:hover{opacity:1}.vc-funnel .v-charts-label-list .v-charts-label{fill:var(--vc-funnel-value-color,oklch(.205 0 0));font-size:var(--vc-funnel-value-size,.75rem);font-variant-numeric:tabular-nums;font-weight:var(--vc-funnel-value-weight,600)}.vc-funnel-classic-shape{cursor:default}.vc-funnel-classic-shape__shadow{opacity:var(--vc-funnel-shadow-opacity,.16)}.vc-funnel-classic-shape__body{opacity:var(--vc-funnel-shape-opacity,.92);transition:opacity .15s ease}.vc-funnel-classic-shape__highlight{fill:none;stroke:hsla(0,0%,100%,.24);stroke-width:1;vector-effect:non-scaling-stroke}.vc-funnel-classic-shape:hover .vc-funnel-classic-shape__body{opacity:var(--vc-funnel-shape-hover-opacity,1)}.vc-funnel-classic-shape__name,.vc-funnel-classic-shape__value{font-family:inherit;paint-order:stroke;pointer-events:none;stroke:rgba(0,0,0,.12);stroke-linejoin:round;stroke-width:2px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.vc-funnel-classic-shape__value{fill:var(--vc-funnel-inside-value-color,#fff);font-variant-numeric:tabular-nums}.vc-funnel-classic-shape__name{fill:var(--vc-funnel-inside-label-color,#fff);opacity:.78}.vc-funnel-layered-shape__divider{stroke:var(--vc-surface-bg,oklch(1 0 0))}.vc-funnel-layered-shape__badge-text,.vc-funnel-layered-shape__name,.vc-funnel-layered-shape__value{font-family:inherit;pointer-events:none;stroke:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.vc-funnel-layered-shape__value{fill:var(--vc-funnel-value-color,var(--vc-axis-label-color,oklch(.205 0 0)));font-variant-numeric:tabular-nums;font-weight:var(--vc-funnel-value-weight,600)}.vc-funnel-layered-shape__badge{fill:var(--vc-funnel-badge-bg,var(--vc-surface-bg,oklch(1 0 0)));fill-opacity:var(--vc-funnel-badge-opacity,.82);stroke:var(--vc-funnel-badge-border,var(--vc-axis-line-color,oklch(.922 0 0)));stroke-opacity:.7}.vc-funnel-layered-shape__badge-text{fill:var(--vc-funnel-badge-color,var(--vc-tick-color,oklch(.556 0 0)));font-variant-numeric:tabular-nums;font-weight:var(--vc-funnel-badge-weight,600)}.vc-funnel-layered-shape__name{fill:var(--vc-funnel-label-color,var(--vc-tick-color,oklch(.556 0 0)));font-weight:var(--vc-funnel-label-weight,500)}.vc-status-tracker{color:var(--vc-axis-label-color,var(--ui-text-highlighted,var(--foreground,oklch(.205 0 0))));font-family:inherit;width:100%}.vc-status-tracker__header{align-items:baseline;display:flex;gap:1rem;justify-content:space-between;margin-bottom:.75rem}.vc-status-tracker__summary,.vc-status-tracker__title{font-size:.875rem;line-height:1.25rem;margin:0}.vc-status-tracker__title{font-weight:600}.vc-status-tracker__summary{color:var(--vc-tick-color,var(--ui-text-muted,var(--muted-foreground,oklch(.556 0 0))));font-variant-numeric:tabular-nums}.vc-status-tracker__track{display:flex;overflow:hidden;width:100%}.vc-status-tracker__bar{display:block;flex:0 0 auto;height:100%;min-width:1px;transition:opacity .16s ease}.vc-status-tracker__bar:hover{opacity:.78}.vc-status-tracker__bar--rounded-start{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.vc-status-tracker__bar--rounded-end{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.vc-status-tracker__bar--empty:hover{opacity:1}.vc-status-tracker__labels{gap:1rem;justify-content:space-between;margin-top:.5rem}.vc-status-tracker__labels,.vc-status-tracker__legend{color:var(--vc-legend-color,oklch(.556 0 0));display:flex;font-size:var(--vc-legend-size,.75rem);line-height:1rem}.vc-status-tracker__legend{flex-wrap:wrap;gap:.5rem 1rem;list-style:none;margin:var(--vc-legend-inset,.75rem) 0 0;padding:0}.vc-status-tracker__legend-item{align-items:center;display:inline-flex;gap:.375rem}.vc-status-tracker__legend-dot{border-radius:9999px;flex:0 0 auto;height:var(--vc-legend-dot-size,.5rem);width:var(--vc-legend-dot-size,.5rem)}}
1
+ @layer vue-chrts{.vc-tooltip{background:var(--vc-tooltip-bg,oklch(1 0 0));border:1px solid var(--vc-tooltip-border,oklch(.922 0 0));border-radius:var(--vc-tooltip-radius,.5rem);box-shadow:var(--vc-tooltip-shadow,0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1));color:var(--vc-tooltip-fg,oklch(.205 0 0));font-size:var(--vc-legend-size,.75rem);line-height:1.5;min-width:8rem;padding:var(--vc-tooltip-padding,.375rem .625rem);pointer-events:none}.vc-tooltip--frosted-glass{backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);background:color-mix(in oklab,var(--vc-tooltip-bg,oklch(1 0 0)) 70%,transparent)}.vc-tooltip--sm{border-radius:.125rem}.vc-tooltip--md{border-radius:.375rem}.vc-tooltip--lg{border-radius:.5rem}.vc-tooltip--xl{border-radius:.75rem}.vc-tooltip__label{color:var(--vc-tooltip-fg,oklch(.205 0 0));font-weight:500;margin:0 0 .25rem}.vc-tooltip__list{display:flex;flex-direction:column;gap:.125rem;list-style:none;margin:0;padding:0}.vc-tooltip__item{align-items:center;display:flex;gap:.375rem}.vc-tooltip__dot{border-radius:9999px;display:inline-block;flex-shrink:0;height:.5rem;width:.5rem}.vc-tooltip__name{color:var(--vc-tooltip-muted,oklch(.556 0 0))}.vc-tooltip__value{color:var(--vc-tooltip-fg,oklch(.205 0 0));font-variant-numeric:tabular-nums;font-weight:500;margin-left:auto}.vc-legend{flex-wrap:wrap;font-size:var(--vc-legend-size,.75rem);font-weight:var(--vc-legend-weight,400);gap:var(--vc-legend-gap,.75rem);justify-content:center;line-height:1.5;list-style:none;margin:0;padding:0}.vc-legend,.vc-legend__item{align-items:center;display:flex}.vc-legend__item{gap:.375rem}.vc-legend__item--inactive{opacity:.4}.vc-legend__dot{border-radius:9999px;display:inline-block;flex-shrink:0;height:var(--vc-legend-dot-size,.5rem);width:var(--vc-legend-dot-size,.5rem)}.vc-legend__dot--square{border-radius:0}.vc-legend__dot--rounded-square{border-radius:2px}.vc-legend__dot--circle{border-radius:9999px}.vc-legend__dot--vertical-bar{border-radius:2px;height:calc(var(--vc-legend-dot-size, .5rem)*1.5);width:calc(var(--vc-legend-dot-size, .5rem)*.5)}.vc-legend__dot--horizontal-bar{border-radius:2px;height:calc(var(--vc-legend-dot-size, .5rem)*.5);width:calc(var(--vc-legend-dot-size, .5rem)*1.5)}.vc-legend__dot--circle-outline,.vc-legend__dot--rounded-square-outline{box-sizing:border-box;height:calc(var(--vc-legend-dot-size, .5rem)*1.25);-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;padding:1.5px;width:calc(var(--vc-legend-dot-size, .5rem)*1.25)}.vc-legend__dot--circle-outline{border-radius:9999px}.vc-legend__dot--rounded-square-outline{border-radius:3px}.vc-legend__label{color:var(--vc-legend-color,oklch(.556 0 0))}.vc-skeleton{align-items:stretch;box-sizing:border-box;color:var(--vc-tick-color,oklch(.556 0 0));display:flex;overflow:hidden;padding:8% 4%;position:relative;width:100%}.vc-skeleton__bars{align-items:flex-end;display:flex;gap:2%;height:100%;width:100%}.vc-skeleton__bar{background:currentColor;border-radius:2px 2px 0 0;flex:1;opacity:.14}.vc-skeleton__wave{height:100%;opacity:.14;width:100%}.vc-skeleton__ring{aspect-ratio:1;border:1.75rem solid;border-radius:9999px;height:100%;margin:auto;opacity:.14}.vc-skeleton__shimmer{animation:vc-skeleton-sweep 1.8s linear infinite;background:linear-gradient(105deg,transparent 35%,currentColor 50%,transparent 65%);inset:0;opacity:.1;position:absolute}@keyframes vc-skeleton-sweep{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}.vc-skeleton__pill{align-items:center;background:var(--vc-surface-bg,oklch(1 0 0));border:1px solid var(--vc-tooltip-border,oklch(.922 0 0));border-radius:.375rem;color:var(--vc-tick-color,oklch(.556 0 0));display:flex;font-size:var(--vc-legend-size,.75rem);gap:.375rem;height:-moz-fit-content;height:fit-content;inset:0;line-height:1.5;margin:auto;padding:.125rem .5rem;position:absolute;width:-moz-fit-content;width:fit-content}.vc-skeleton__spinner{animation:vc-skeleton-spin .7s linear infinite;border:1px solid var(--vc-tooltip-border,oklch(.922 0 0));border-radius:9999px;border-top:1px solid;height:.75rem;width:.75rem}@keyframes vc-skeleton-spin{to{transform:rotate(1turn)}}@media (prefers-reduced-motion:reduce){.vc-skeleton__shimmer,.vc-skeleton__spinner{animation:none}}.vc-candlestick-chart{cursor:crosshair;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.vc-candlestick-chart__svg{display:block;overflow:visible;width:100%}.vc-funnel-layered{position:relative;width:100%}.vc-funnel{position:relative}.vc-funnel-layered__tooltip{align-items:center;background:var(--vc-tooltip-bg,oklch(1 0 0));border:1px solid var(--vc-tooltip-border,oklch(.922 0 0));border-radius:var(--vc-tooltip-radius,.5rem);box-shadow:var(--vc-tooltip-shadow,0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1));color:var(--vc-tooltip-fg,oklch(.205 0 0));display:flex;font-size:var(--vc-legend-size,.75rem);gap:.375rem;padding:var(--vc-tooltip-padding,.375rem .625rem);pointer-events:none;position:absolute;transform:translate(-50%,calc(-100% - 10px));white-space:nowrap}.vc-funnel-layered__name{color:var(--vc-tooltip-muted,oklch(.556 0 0))}.vc-funnel-layered__value{font-variant-numeric:tabular-nums;font-weight:600}.vc-funnel-layered__dot{border-radius:9999px;display:inline-block;flex-shrink:0;height:var(--vc-legend-dot-size,.5rem);width:var(--vc-legend-dot-size,.5rem)}.vc-funnel-layered-shape{opacity:.96;transition:opacity .15s}.vc-funnel-layered-shape:hover{opacity:1}.vc-funnel .v-charts-label-list .v-charts-label{fill:var(--vc-funnel-value-color,oklch(.205 0 0));font-size:var(--vc-funnel-value-size,.75rem);font-variant-numeric:tabular-nums;font-weight:var(--vc-funnel-value-weight,600)}.vc-funnel-classic-shape{cursor:default}.vc-funnel-classic-shape__shadow{opacity:var(--vc-funnel-shadow-opacity,.16)}.vc-funnel-classic-shape__body{opacity:var(--vc-funnel-shape-opacity,.92);transition:opacity .15s ease}.vc-funnel-classic-shape__highlight{fill:none;stroke:hsla(0,0%,100%,.24);stroke-width:1;vector-effect:non-scaling-stroke}.vc-funnel-classic-shape:hover .vc-funnel-classic-shape__body{opacity:var(--vc-funnel-shape-hover-opacity,1)}.vc-funnel-classic-shape__name,.vc-funnel-classic-shape__value{font-family:inherit;paint-order:stroke;pointer-events:none;stroke:rgba(0,0,0,.12);stroke-linejoin:round;stroke-width:2px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.vc-funnel-classic-shape__value{fill:var(--vc-funnel-inside-value-color,#fff);font-variant-numeric:tabular-nums}.vc-funnel-classic-shape__name{fill:var(--vc-funnel-inside-label-color,#fff);opacity:.78}.vc-funnel-layered-shape__divider{stroke:var(--vc-surface-bg,oklch(1 0 0))}.vc-funnel-layered-shape__badge-text,.vc-funnel-layered-shape__name,.vc-funnel-layered-shape__value{font-family:inherit;pointer-events:none;stroke:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.vc-funnel-layered-shape__value{fill:var(--vc-funnel-value-color,var(--vc-axis-label-color,oklch(.205 0 0)));font-variant-numeric:tabular-nums;font-weight:var(--vc-funnel-value-weight,600)}.vc-funnel-layered-shape__badge{fill:var(--vc-funnel-badge-bg,var(--vc-surface-bg,oklch(1 0 0)));fill-opacity:var(--vc-funnel-badge-opacity,.82);stroke:var(--vc-funnel-badge-border,var(--vc-axis-line-color,oklch(.922 0 0)));stroke-opacity:.7}.vc-funnel-layered-shape__badge-text{fill:var(--vc-funnel-badge-color,var(--vc-tick-color,oklch(.556 0 0)));font-variant-numeric:tabular-nums;font-weight:var(--vc-funnel-badge-weight,600)}.vc-funnel-layered-shape__name{fill:var(--vc-funnel-label-color,var(--vc-tick-color,oklch(.556 0 0)));font-weight:var(--vc-funnel-label-weight,500)}.vc-status-tracker{color:var(--vc-axis-label-color,var(--ui-text-highlighted,var(--foreground,oklch(.205 0 0))));font-family:inherit;width:100%}.vc-status-tracker__header{align-items:baseline;display:flex;gap:1rem;justify-content:space-between;margin-bottom:.75rem}.vc-status-tracker__summary,.vc-status-tracker__title{font-size:.875rem;line-height:1.25rem;margin:0}.vc-status-tracker__title{font-weight:600}.vc-status-tracker__summary{color:var(--vc-tick-color,var(--ui-text-muted,var(--muted-foreground,oklch(.556 0 0))));font-variant-numeric:tabular-nums}.vc-status-tracker__track{display:flex;overflow:hidden;width:100%}.vc-status-tracker__bar{display:block;flex:0 0 auto;height:100%;min-width:1px;transition:opacity .16s ease}.vc-status-tracker__bar:hover{opacity:.78}.vc-status-tracker__bar--rounded-start{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.vc-status-tracker__bar--rounded-end{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.vc-status-tracker__bar--empty:hover{opacity:1}.vc-status-tracker__labels{gap:1rem;justify-content:space-between;margin-top:.5rem}.vc-status-tracker__labels,.vc-status-tracker__legend{color:var(--vc-legend-color,oklch(.556 0 0));display:flex;font-size:var(--vc-legend-size,.75rem);line-height:1rem}.vc-status-tracker__legend{flex-wrap:wrap;gap:.5rem 1rem;list-style:none;margin:var(--vc-legend-inset,.75rem) 0 0;padding:0}.vc-status-tracker__legend-item{align-items:center;display:inline-flex;gap:.375rem}.vc-status-tracker__legend-dot{border-radius:9999px;flex:0 0 auto;height:var(--vc-legend-dot-size,.5rem);width:var(--vc-legend-dot-size,.5rem)}}.vc-sr-only{height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;clip:rect(0,0,0,0)!important;border:0!important;white-space:nowrap!important}.vc-empty-state{color:var(--vc-tick-color);display:grid;font-size:var(--vc-legend-size);place-items:center}
@@ -1 +1 @@
1
- @layer vue-chrts{.vue-chrts :focus,.vue-chrts :focus-visible,.vue-chrts:focus,.vue-chrts:focus-visible{outline:none}.vue-chrts{--vc-grid-color:var(--ui-border,var(--border,oklch(0.922 0 0)));--vc-grid-dash:3 3;--vc-grid-width:1px;--vc-axis-line-color:var(--ui-border,var(--border,oklch(0.922 0 0)));--vc-axis-line-width:1px;--vc-surface-bg:var(--ui-bg,var(--background,oklch(1 0 0)));--vc-bg-pattern-color:var(--vc-grid-color);--vc-legend-size:0.75rem;--vc-legend-weight:400;--vc-tick-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.556 0 0)));--vc-tick-size:var(--vc-legend-size);--vc-tick-weight:var(--vc-legend-weight);--vc-axis-label-color:var(--ui-text-highlighted,var(--foreground,oklch(0.205 0 0)));--vc-axis-label-size:var(--vc-legend-size);--vc-axis-label-weight:500}.vue-chrts .v-charts-cartesian-axis .v-charts-label{fill:var(--vc-axis-label-color);font-size:var(--vc-axis-label-size);font-weight:var(--vc-axis-label-weight)}.vue-chrts{--vc-legend-gap:0.75rem;--vc-legend-inset:0.75rem;--vc-legend-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.556 0 0)));--vc-legend-dot-size:0.5rem;--vc-funnel-value-size:var(--vc-legend-size);--vc-funnel-value-color:var(--vc-axis-label-color);--vc-funnel-value-weight:600;--vc-funnel-label-color:var(--vc-tick-color);--vc-funnel-label-weight:500;--vc-funnel-badge-bg:var(--vc-surface-bg);--vc-funnel-badge-color:var(--vc-tick-color);--vc-funnel-badge-border:var(--vc-axis-line-color);--vc-funnel-badge-weight:600;--vc-funnel-badge-opacity:0.82;--vc-hover-fill:color-mix(in oklab,var(--ui-text-highlighted,var(--foreground,oklch(0.205 0 0))) 6%,transparent);--vc-hover-stroke:transparent;--vc-hover-radius:6px;--vc-tooltip-bg:var(--ui-bg,var(--popover,oklch(1 0 0)));--vc-tooltip-fg:var(--ui-text-highlighted,var(--popover-foreground,oklch(0.205 0 0)));--vc-tooltip-muted:var(--ui-text-muted,var(--muted-foreground,oklch(0.556 0 0)));--vc-tooltip-border:var(--ui-border,var(--border,oklch(0.922 0 0)));--vc-tooltip-radius:0.5rem;--vc-tooltip-padding:0.375rem 0.625rem;--vc-tooltip-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1)}.vue-chrts .v-charts-xAxis .v-charts-cartesian-axis-tick-value{fill:var(--vc-x-tick-color,var(--vc-tick-color));font-size:var(--vc-x-tick-size,var(--vc-tick-size));font-weight:var(--vc-x-tick-weight,var(--vc-tick-weight))}.vue-chrts .v-charts-yAxis .v-charts-cartesian-axis-tick-value{fill:var(--vc-y-tick-color,var(--vc-tick-color));font-size:var(--vc-y-tick-size,var(--vc-tick-size));font-weight:var(--vc-y-tick-weight,var(--vc-tick-weight))}.vue-chrts .v-charts-polar-angle-axis-tick-value,.vue-chrts .v-charts-polar-radius-axis-tick-value{fill:var(--vc-tick-color);font-size:var(--vc-tick-size);font-weight:var(--vc-tick-weight)}.vue-chrts.vc-dash-animated .v-charts-area-curve,.vue-chrts.vc-dash-animated .v-charts-line-curve{animation:vc-dash-march 1s linear infinite}@keyframes vc-dash-march{to{stroke-dashoffset:-6}}@media (prefers-reduced-motion:reduce){.vue-chrts.vc-dash-animated .v-charts-area-curve,.vue-chrts.vc-dash-animated .v-charts-line-curve{animation:none}}.vue-chrts:is(.dark,[data-theme=dark],.dark *,[data-theme=dark] *),:is(.dark,[data-theme=dark]) .vue-chrts{--vc-grid-color:var(--ui-border,var(--border,oklch(0.269 0 0)));--vc-axis-line-color:var(--ui-border,var(--border,oklch(0.269 0 0)));--vc-surface-bg:var(--ui-bg,var(--background,oklch(0.145 0 0)));--vc-tick-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.708 0 0)));--vc-axis-label-color:var(--ui-text-highlighted,var(--foreground,oklch(0.985 0 0)));--vc-legend-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.708 0 0)));--vc-hover-fill:color-mix(in oklab,var(--ui-text-highlighted,oklch(0.985 0 0)) 8%,transparent);--vc-tooltip-bg:var(--ui-bg,var(--popover,oklch(0.205 0 0)));--vc-tooltip-fg:var(--ui-text-highlighted,var(--popover-foreground,oklch(0.985 0 0)));--vc-tooltip-muted:var(--ui-text-muted,var(--muted-foreground,oklch(0.708 0 0)));--vc-tooltip-border:var(--ui-border,var(--border,oklch(0.269 0 0)))}}
1
+ @layer vue-chrts{.vue-chrts :focus,.vue-chrts :focus-visible,.vue-chrts:focus,.vue-chrts:focus-visible{outline:none}.vue-chrts{--vc-font-family:var(--font-sans,ui-sans-serif,system-ui,sans-serif);--vc-series-0:var(--chart-color-0,var(--ui-primary,oklch(0.63 0.18 155)));--vc-series-1:var(--chart-color-1,var(--ui-secondary,oklch(0.62 0.19 255)));--vc-series-2:var(--chart-color-2,var(--ui-success,oklch(0.64 0.17 145)));--vc-series-3:var(--chart-color-3,var(--ui-info,oklch(0.68 0.15 235)));--vc-series-4:var(--chart-color-4,var(--ui-warning,oklch(0.75 0.16 75)));--vc-series-5:var(--chart-color-5,var(--ui-error,oklch(0.63 0.22 25)));--vc-series-6:var(--chart-color-6,oklch(0.66 0.18 305));--vc-series-7:var(--chart-color-7,oklch(0.7 0.15 195));font-family:var(--vc-font-family);--vc-grid-color:var(--ui-border,var(--border,oklch(0.922 0 0)));--vc-grid-dash:3 3;--vc-grid-width:1px;--vc-axis-line-color:var(--ui-border,var(--border,oklch(0.922 0 0)));--vc-axis-line-width:1px;--vc-surface-bg:var(--ui-bg,var(--background,oklch(1 0 0)));--vc-reference-line-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.556 0 0)));--vc-candle-up:var(--ui-success,oklch(0.696 0.17 162.48));--vc-candle-down:var(--ui-error,oklch(0.637 0.237 25.331));--vc-bg-pattern-color:var(--vc-grid-color);--vc-legend-size:0.75rem;--vc-legend-weight:400;--vc-tick-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.556 0 0)));--vc-tick-size:var(--vc-legend-size);--vc-tick-weight:var(--vc-legend-weight);--vc-axis-label-color:var(--ui-text-highlighted,var(--foreground,oklch(0.205 0 0)));--vc-axis-label-size:var(--vc-legend-size);--vc-axis-label-weight:500}.vue-chrts .v-charts-cartesian-axis .v-charts-label{fill:var(--vc-axis-label-color);font-size:var(--vc-axis-label-size);font-weight:var(--vc-axis-label-weight)}.vue-chrts{--vc-legend-gap:0.75rem;--vc-legend-inset:0.75rem;--vc-legend-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.556 0 0)));--vc-legend-dot-size:0.5rem;--vc-funnel-value-size:var(--vc-legend-size);--vc-funnel-value-color:var(--vc-axis-label-color);--vc-funnel-value-weight:600;--vc-funnel-label-color:var(--vc-tick-color);--vc-funnel-label-weight:500;--vc-funnel-badge-bg:var(--vc-surface-bg);--vc-funnel-badge-color:var(--vc-tick-color);--vc-funnel-badge-border:var(--vc-axis-line-color);--vc-funnel-badge-weight:600;--vc-funnel-badge-opacity:0.82;--vc-hover-fill:color-mix(in oklab,var(--ui-text-highlighted,var(--foreground,oklch(0.205 0 0))) 6%,transparent);--vc-hover-stroke:transparent;--vc-hover-radius:6px;--vc-tooltip-bg:var(--ui-bg-elevated,var(--popover,oklch(1 0 0)));--vc-tooltip-fg:var(--ui-text-highlighted,var(--popover-foreground,oklch(0.205 0 0)));--vc-tooltip-muted:var(--ui-text-muted,var(--muted-foreground,oklch(0.556 0 0)));--vc-tooltip-border:var(--ui-border,var(--border,oklch(0.922 0 0)));--vc-tooltip-radius:var(--ui-radius,0.5rem);--vc-tooltip-padding:0.375rem 0.625rem;--vc-tooltip-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1)}.vue-chrts .v-charts-xAxis .v-charts-cartesian-axis-tick-value{fill:var(--vc-x-tick-color,var(--vc-tick-color));font-size:var(--vc-x-tick-size,var(--vc-tick-size));font-weight:var(--vc-x-tick-weight,var(--vc-tick-weight))}.vue-chrts .v-charts-yAxis .v-charts-cartesian-axis-tick-value{fill:var(--vc-y-tick-color,var(--vc-tick-color));font-size:var(--vc-y-tick-size,var(--vc-tick-size));font-weight:var(--vc-y-tick-weight,var(--vc-tick-weight))}.vue-chrts .v-charts-polar-angle-axis-tick-value,.vue-chrts .v-charts-polar-radius-axis-tick-value{fill:var(--vc-tick-color);font-size:var(--vc-tick-size);font-weight:var(--vc-tick-weight)}.vue-chrts.vc-dash-animated .v-charts-area-curve,.vue-chrts.vc-dash-animated .v-charts-line-curve{animation:vc-dash-march 1s linear infinite}@keyframes vc-dash-march{to{stroke-dashoffset:-6}}@media (prefers-reduced-motion:reduce){.vue-chrts.vc-dash-animated .v-charts-area-curve,.vue-chrts.vc-dash-animated .v-charts-line-curve{animation:none}}.vue-chrts:is(.dark,[data-theme=dark],.dark *,[data-theme=dark] *),:is(.dark,[data-theme=dark]) .vue-chrts{--vc-grid-color:var(--ui-border,var(--border,oklch(0.269 0 0)));--vc-axis-line-color:var(--ui-border,var(--border,oklch(0.269 0 0)));--vc-surface-bg:var(--ui-bg,var(--background,oklch(0.145 0 0)));--vc-tick-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.708 0 0)));--vc-axis-label-color:var(--ui-text-highlighted,var(--foreground,oklch(0.985 0 0)));--vc-legend-color:var(--ui-text-muted,var(--muted-foreground,oklch(0.708 0 0)));--vc-hover-fill:color-mix(in oklab,var(--ui-text-highlighted,oklch(0.985 0 0)) 8%,transparent);--vc-tooltip-bg:var(--ui-bg-elevated,var(--popover,oklch(0.205 0 0)));--vc-tooltip-fg:var(--ui-text-highlighted,var(--popover-foreground,oklch(0.985 0 0)));--vc-tooltip-muted:var(--ui-text-muted,var(--muted-foreground,oklch(0.708 0 0)));--vc-tooltip-border:var(--ui-border,var(--border,oklch(0.269 0 0)))}}
@@ -62,7 +62,6 @@ const props = defineProps({
62
62
  xAxisConfig: { type: Object, required: false },
63
63
  yAxisConfig: { type: Object, required: false },
64
64
  yAxes: { type: Object, required: false },
65
- tooltip: { type: Object, required: false },
66
65
  referenceLines: { type: Array, required: false },
67
66
  syncId: { type: String, required: false },
68
67
  minMaxTicksOnly: { type: Boolean, required: false },
@@ -71,6 +70,12 @@ const props = defineProps({
71
70
  crosshairConfig: { type: Object, required: false },
72
71
  theme: { type: Object, required: false },
73
72
  backgroundPattern: { type: String, required: false },
73
+ ariaLabel: { type: String, required: false },
74
+ ariaDescription: { type: String, required: false },
75
+ accessibleDataTable: { type: Boolean, required: false, default: true },
76
+ maxDataPoints: { type: Number, required: false },
77
+ emptyLabel: { type: String, required: false },
78
+ error: { type: String, required: false },
74
79
  legendVariant: { type: String, required: false },
75
80
  tooltipVariant: { type: String, required: false },
76
81
  tooltipRoundness: { type: String, required: false },
@@ -209,6 +214,7 @@ defineSlots();
209
214
  :dot="showDots || s.dot"
210
215
  :hide="s.hidden"
211
216
  :is-animation-active="duration !== void 0 && duration !== 0"
217
+ :transition="{ duration: (duration ?? 800) / 1e3, ease: 'easeOut' }"
212
218
  >
213
219
  <template v-if="showDots" #dot="dotProps">
214
220
  <ChartDot
@@ -70,7 +70,6 @@ const props = defineProps({
70
70
  xAxisConfig: { type: Object, required: false },
71
71
  yAxisConfig: { type: Object, required: false },
72
72
  yAxes: { type: Object, required: false },
73
- tooltip: { type: Object, required: false },
74
73
  referenceLines: { type: Array, required: false },
75
74
  syncId: { type: String, required: false },
76
75
  minMaxTicksOnly: { type: Boolean, required: false },
@@ -79,6 +78,12 @@ const props = defineProps({
79
78
  crosshairConfig: { type: Object, required: false },
80
79
  theme: { type: Object, required: false },
81
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 },
82
87
  legendVariant: { type: String, required: false },
83
88
  tooltipVariant: { type: String, required: false },
84
89
  tooltipRoundness: { type: String, required: false },
@@ -231,6 +236,7 @@ defineSlots();
231
236
  :radius="isCubes ? 0 : barRadius(i)"
232
237
  :hide="s.hidden"
233
238
  :is-animation-active="duration !== void 0 && duration !== 0"
239
+ :transition="{ duration: (duration ?? 400) / 1e3, ease: 'easeOut' }"
234
240
  >
235
241
  <!--
236
242
  Always register `#shape`. vccs reads `slots.shape` once in Bar setup, so a
@@ -14,6 +14,8 @@ import ChartContainer from "./internal/ChartContainer";
14
14
  import ChartSkeleton from "./internal/ChartSkeleton.vue";
15
15
  import ChartTooltip from "./internal/ChartTooltip.vue";
16
16
  import ChartLegend from "./internal/ChartLegend.vue";
17
+ import ChartEmptyState from "./internal/ChartEmptyState.vue";
18
+ import ChartAccessibility from "./internal/ChartAccessibility.vue";
17
19
  import { categoriesToSeries } from "../utils/categories";
18
20
  import { legendPositionToLegendProps, resolveLegendWrapperStyle } from "../utils/legend";
19
21
  import {
@@ -26,14 +28,8 @@ import {
26
28
  } from "../utils/axis";
27
29
  import { toAxisDomain, toCssProperties } from "../utils/style";
28
30
  import { axisTickVars, resolveHoverRadius, resolveHoverVisible, themeToVars } from "../utils/theme";
31
+ import { DEFAULT_MAX_DATA_POINTS, sampleData, toFiniteNumber, warnDataLimit } from "../utils/data";
29
32
  const props = defineProps({
30
- xAccessor: { type: Function, required: true, skipCheck: true },
31
- yAccessor: { type: Function, required: true, skipCheck: true },
32
- sizeAccessor: { type: Function, required: false, skipCheck: true },
33
- categoryKey: { type: null, required: true },
34
- sizeRange: { type: Array, required: false },
35
- opacity: { type: Number, required: false },
36
- sizeOptions: { type: Object, required: false },
37
33
  data: { type: Array, required: true },
38
34
  height: { type: Number, required: true },
39
35
  xLabel: { type: String, required: false },
@@ -62,21 +58,28 @@ const props = defineProps({
62
58
  duration: { type: Number, required: false },
63
59
  xAxisConfig: { type: Object, required: false },
64
60
  yAxisConfig: { type: Object, required: false },
65
- yAxes: { type: Object, required: false },
66
- tooltip: { type: Object, required: false },
67
- referenceLines: { type: Array, required: false },
68
- syncId: { type: String, required: false },
69
61
  minMaxTicksOnly: { type: Boolean, required: false },
70
62
  xExplicitTicks: { type: Array, required: false },
71
- yExplicitTicks: { type: Array, required: false },
72
63
  crosshairConfig: { type: Object, required: false },
73
64
  theme: { type: Object, required: false },
74
- backgroundPattern: { type: String, 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 },
75
71
  legendVariant: { type: String, required: false },
76
72
  tooltipVariant: { type: String, required: false },
77
73
  tooltipRoundness: { type: String, required: false },
78
74
  loading: { type: Boolean, required: false },
79
- loadingLabel: { type: String, 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 }
80
83
  });
81
84
  const slots = defineSlots();
82
85
  function resolveAccessor(accessor) {
@@ -149,9 +152,28 @@ const colorByCategory = computed(() => {
149
152
  for (const s of categoriesToSeries(props.categories)) map.set(s.dataKey, s.color);
150
153
  return map;
151
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
+ })));
152
174
  const groups = computed(() => {
153
175
  const byCategory = /* @__PURE__ */ new Map();
154
- for (const row of props.data) {
176
+ for (const row of chartData.value) {
155
177
  const cat = String(row[categoryKey.value]);
156
178
  const bucket = byCategory.get(cat);
157
179
  if (bucket) bucket.push(row);
@@ -185,7 +207,7 @@ const tooltipContent = computed(() => {
185
207
  });
186
208
  return h(ChartTooltip, {
187
209
  ...tooltipProps,
188
- label: category,
210
+ label: props.tooltipTitleFormatter && row ? props.tooltipTitleFormatter(row) : category,
189
211
  payload,
190
212
  variant: props.tooltipVariant,
191
213
  roundness: props.tooltipRoundness
@@ -195,13 +217,15 @@ const tooltipContent = computed(() => {
195
217
  </script>
196
218
 
197
219
  <template>
198
- <div class="vue-chrts" :style="themeVars">
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" />
199
222
  <ChartSkeleton
200
223
  v-if="loading"
201
224
  :height="height"
202
225
  shape="bars"
203
226
  :label="loadingLabel ?? 'Loading'"
204
227
  />
228
+ <ChartEmptyState v-else-if="error || chartData.length === 0" :height="height" :message="error || emptyLabel" />
205
229
  <ChartContainer v-else width="100%" :height="height">
206
230
  <ScatterChart>
207
231
  <CartesianGrid
@@ -254,6 +278,8 @@ const tooltipContent = computed(() => {
254
278
  :data="g.data"
255
279
  :fill="g.color"
256
280
  :fill-opacity="opacity ?? 0.7"
281
+ :is-animation-active="duration !== void 0 && duration !== 0"
282
+ :transition="{ duration: (duration ?? 400) / 1e3, ease: 'easeOut' }"
257
283
  />
258
284
 
259
285
  <Tooltip v-if="!hideTooltip" :content="tooltipContent" :cursor="cursor" :is-animation-active="false" />
@@ -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>
@@ -4,6 +4,8 @@ import { Legend, Pie, PieChart, Tooltip } from "vccs";
4
4
  import ChartContainer from "./internal/ChartContainer";
5
5
  import ChartSkeleton from "./internal/ChartSkeleton.vue";
6
6
  import ChartLegend from "./internal/ChartLegend.vue";
7
+ import ChartEmptyState from "./internal/ChartEmptyState.vue";
8
+ import ChartAccessibility from "./internal/ChartAccessibility.vue";
7
9
  import SegmentVariantDefs, { segmentGradientId } from "./internal/SegmentVariantDefs";
8
10
  import { tooltipContentFor } from "./internal/tooltipContent";
9
11
  import { variantId } from "../utils/variants";
@@ -12,8 +14,11 @@ import { categoriesToSeries } from "../utils/categories";
12
14
  import { legendPositionToLegendProps, resolveLegendWrapperStyle } from "../utils/legend";
13
15
  import { toCssProperties } from "../utils/style";
14
16
  import { themeToVars } from "../utils/theme";
17
+ import { DEFAULT_MAX_DATA_POINTS, normalizeNamedValues, sampleData, warnDataLimit } from "../utils/data";
15
18
  const props = defineProps({
16
19
  data: { type: Array, required: true },
20
+ nameKey: { type: null, required: false },
21
+ valueKey: { type: null, required: false },
17
22
  categories: { type: Object, required: true },
18
23
  type: { type: String, required: false },
19
24
  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 },
@@ -45,16 +55,21 @@ const variantScope = useId();
45
55
  const useGradient = computed(() => props.variant === "gradient");
46
56
  const segments = computed(() => {
47
57
  const cats = categoriesToSeries(props.categories);
48
- return props.data.map((value, i) => {
49
- const color = cats[i]?.color ?? `var(--chart-color-${i})`;
58
+ const normalized = normalizeNamedValues(props.data, cats.map((cat) => String(cat.name)), props.nameKey, props.valueKey);
59
+ const limit = props.maxDataPoints ?? DEFAULT_MAX_DATA_POINTS;
60
+ warnDataLimit("DonutChart", normalized.length, limit);
61
+ const values = sampleData(normalized, limit);
62
+ return values.map(({ row, value, sourceIndex, name }) => {
63
+ const color = cats[sourceIndex]?.color ?? `var(--vc-series-${sourceIndex % 8})`;
50
64
  return {
51
- name: cats[i]?.name ?? String(i),
65
+ ...row && typeof row === "object" ? row : {},
66
+ name,
52
67
  value,
53
- dataKey: String(i),
68
+ dataKey: String(sourceIndex),
54
69
  color,
55
70
  // vccs reads `fill` directly from the data entry to color each sector
56
- fill: useGradient.value ? `url(#${segmentGradientId(String(i), variantScope)})` : color,
57
- filter: props.glow ? `url(#${variantId("glow", String(i), variantScope)})` : void 0
71
+ fill: useGradient.value ? `url(#${segmentGradientId(String(sourceIndex), variantScope)})` : color,
72
+ filter: props.glow ? `url(#${variantId("glow", String(sourceIndex), variantScope)})` : void 0
58
73
  };
59
74
  });
60
75
  });
@@ -65,8 +80,9 @@ const legendColors = computed(
65
80
  () => Object.fromEntries(segments.value.map((s) => [s.name, s.color]))
66
81
  );
67
82
  const tooltipContent = computed(
68
- () => tooltipContentFor(props.tooltipVariant, props.tooltipRoundness)
83
+ () => tooltipContentFor(props.tooltipVariant, props.tooltipRoundness, props.tooltipTitleFormatter)
69
84
  );
85
+ const accessibleRows = computed(() => segments.value.map((segment) => ({ label: segment.name, values: [{ label: "Value", value: segment.value }] })));
70
86
  const DEFAULT_ARC_WIDTH = 40;
71
87
  const AUTO_OUTER_FRACTION = 0.9;
72
88
  const radii = computed(() => {
@@ -103,13 +119,15 @@ const legendWrapperStyle = computed(
103
119
  </script>
104
120
 
105
121
  <template>
106
- <div class="donut-chart vue-chrts" :style="{ position: 'relative', width: '100%', height: `${height ?? (radius ? radius * 2 : 200)}px`, ...themeVars }">
122
+ <div class="donut-chart vue-chrts" role="group" :aria-label="ariaLabel ?? 'Donut chart'" :style="{ position: 'relative', width: '100%', height: `${height ?? (radius ? radius * 2 : 200)}px`, ...themeVars }">
123
+ <ChartAccessibility :label="ariaLabel ?? 'Donut chart'" :description="ariaDescription" :rows="accessibleRows" :show-table="accessibleDataTable !== false" />
107
124
  <ChartSkeleton
108
125
  v-if="loading"
109
126
  :height="height ?? 200"
110
127
  shape="ring"
111
128
  :label="loadingLabel ?? 'Loading'"
112
129
  />
130
+ <ChartEmptyState v-else-if="error || segments.length === 0" :height="height ?? 200" :message="error || emptyLabel" />
113
131
  <ChartContainer v-else width="100%" height="100%">
114
132
  <PieChart>
115
133
  <!--
@@ -135,6 +153,7 @@ const legendWrapperStyle = computed(
135
153
  :corner-radius="cornerRadius ?? 0"
136
154
  :stroke="stroke ?? 'none'"
137
155
  :is-animation-active="duration !== void 0 && duration !== 0"
156
+ :transition="{ duration: (duration ?? 1200) / 1e3, ease: 'easeOut' }"
138
157
  />
139
158
  <Tooltip v-if="!hideTooltip" :content="tooltipContent" :is-animation-active="false" />
140
159
  <Legend