@classic-homes/charts-svelte 0.2.0 → 0.3.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 (54) hide show
  1. package/dist/lib/components/base/ChartContainer.svelte +141 -14
  2. package/dist/lib/components/base/ChartContainer.svelte.d.ts +19 -1
  3. package/dist/lib/components/base/ChartDataTable.svelte +59 -0
  4. package/dist/lib/components/base/ChartDataTable.svelte.d.ts +15 -0
  5. package/dist/lib/components/base/ChartEmpty.svelte +1 -0
  6. package/dist/lib/components/base/ChartSkeleton.svelte +7 -2
  7. package/dist/lib/components/base/ChartSkeleton.svelte.d.ts +2 -0
  8. package/dist/lib/components/base/index.d.ts +3 -0
  9. package/dist/lib/components/base/index.js +1 -0
  10. package/dist/lib/components/core/AreaChart.svelte +115 -70
  11. package/dist/lib/components/core/BarChart.svelte +134 -62
  12. package/dist/lib/components/core/DonutChart.svelte +41 -22
  13. package/dist/lib/components/core/LineChart.svelte +104 -73
  14. package/dist/lib/components/core/PieChart.svelte +41 -22
  15. package/dist/lib/components/core/ScatterChart.svelte +72 -68
  16. package/dist/lib/components/extended/CandlestickChart.svelte +67 -44
  17. package/dist/lib/components/extended/FunnelChart.svelte +41 -27
  18. package/dist/lib/components/extended/GaugeChart.svelte +61 -10
  19. package/dist/lib/components/extended/HeatmapChart.svelte +62 -33
  20. package/dist/lib/components/extended/RadarChart.svelte +39 -19
  21. package/dist/lib/components/extended/SankeyChart.svelte +50 -20
  22. package/dist/lib/components/extended/TreemapChart.svelte +53 -21
  23. package/dist/lib/components/index.d.ts +2 -1
  24. package/dist/lib/components/index.js +3 -1
  25. package/dist/lib/components/report/BoxPlotChart.svelte +253 -0
  26. package/dist/lib/components/report/BoxPlotChart.svelte.d.ts +7 -0
  27. package/dist/lib/components/report/BulletChart.svelte +231 -0
  28. package/dist/lib/components/report/BulletChart.svelte.d.ts +7 -0
  29. package/dist/lib/components/report/ComboChart.svelte +327 -0
  30. package/dist/lib/components/report/ComboChart.svelte.d.ts +8 -0
  31. package/dist/lib/components/report/DotPlotChart.svelte +325 -0
  32. package/dist/lib/components/report/DotPlotChart.svelte.d.ts +8 -0
  33. package/dist/lib/components/report/HistogramChart.svelte +226 -0
  34. package/dist/lib/components/report/HistogramChart.svelte.d.ts +7 -0
  35. package/dist/lib/components/report/Sparkline.svelte +181 -0
  36. package/dist/lib/components/report/Sparkline.svelte.d.ts +4 -0
  37. package/dist/lib/components/report/StatCard.svelte +146 -0
  38. package/dist/lib/components/report/StatCard.svelte.d.ts +4 -0
  39. package/dist/lib/components/report/StatGroup.svelte +35 -0
  40. package/dist/lib/components/report/StatGroup.svelte.d.ts +12 -0
  41. package/dist/lib/components/report/WaterfallChart.svelte +271 -0
  42. package/dist/lib/components/report/WaterfallChart.svelte.d.ts +7 -0
  43. package/dist/lib/components/report/index.d.ts +12 -0
  44. package/dist/lib/components/report/index.js +9 -0
  45. package/dist/lib/composables/index.d.ts +1 -1
  46. package/dist/lib/composables/index.js +1 -1
  47. package/dist/lib/composables/useChart.svelte.d.ts +34 -0
  48. package/dist/lib/composables/useChart.svelte.js +121 -9
  49. package/dist/lib/composables/useChartTheme.svelte.js +14 -11
  50. package/dist/lib/events.d.ts +15 -0
  51. package/dist/lib/events.js +56 -0
  52. package/dist/lib/index.d.ts +4 -3
  53. package/dist/lib/index.js +7 -3
  54. package/package.json +10 -4
@@ -1,31 +1,36 @@
1
1
  <script lang="ts">
2
2
  import * as echarts from 'echarts/core';
3
3
  import { LineChart as EChartsLineChart } from 'echarts/charts';
4
- import {
5
- GridComponent,
6
- TooltipComponent,
7
- LegendComponent,
8
- TitleComponent,
9
- } from 'echarts/components';
10
- import { CanvasRenderer } from 'echarts/renderers';
4
+ import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
11
5
  import type { EChartsOption } from 'echarts';
12
6
 
13
- import type { AreaChartProps, DataPointEventParams } from '@classic-homes/charts-core';
7
+ import type { AreaChartProps } from '@classic-homes/charts-core';
14
8
  import {
9
+ allSeriesEmpty,
15
10
  buildAxis,
16
11
  buildDataLabel,
12
+ buildDataZoom,
17
13
  buildLegend,
18
14
  buildTooltip,
15
+ categoryTable,
16
+ chartFormatters,
19
17
  colorWithAlpha,
18
+ generateAreaChartDescription,
20
19
  getChartColors,
21
20
  hasPointLabels,
22
21
  gridInset,
23
22
  mergeChartOption,
23
+ pairWithCategories,
24
+ resolveValueFormatter,
24
25
  seriesLabelLookup,
26
+ toPercentStack,
25
27
  withPointLabels,
28
+ withReferenceMarks,
29
+ withZoomInset,
26
30
  } from '@classic-homes/charts-core';
27
31
 
28
32
  import { cn } from '../../utils.js';
33
+ import { chartEvents, toDataPointParams } from '../../events.js';
29
34
  import { useChart } from '../../composables/useChart.svelte.js';
30
35
  import {
31
36
  useChartLegend,
@@ -34,14 +39,7 @@
34
39
  import ChartContainer from '../base/ChartContainer.svelte';
35
40
 
36
41
  // Register required ECharts modules
37
- echarts.use([
38
- EChartsLineChart,
39
- GridComponent,
40
- TooltipComponent,
41
- LegendComponent,
42
- TitleComponent,
43
- CanvasRenderer,
44
- ]);
42
+ echarts.use([EChartsLineChart, GridComponent, TooltipComponent, LegendComponent]);
45
43
 
46
44
  interface Props extends Omit<AreaChartProps, 'class'>, CustomLegendProps {
47
45
  class?: string;
@@ -52,8 +50,11 @@
52
50
  description,
53
51
  data,
54
52
  height = 400,
53
+ minHeight,
54
+ maxHeight,
55
55
  loading,
56
56
  error,
57
+ onRetry,
57
58
  emptyMessage,
58
59
  theme = 'auto',
59
60
  animation = true,
@@ -63,33 +64,96 @@
63
64
  smooth = false,
64
65
  stacked = false,
65
66
  gradient = true,
67
+ connectNulls = false,
66
68
  showLabels = false,
69
+ valueFormat,
70
+ locale,
67
71
  labelFormatter,
68
72
  xAxisConfig,
69
73
  yAxisConfig,
74
+ referenceLines,
75
+ referenceBands,
76
+ dataZoom,
70
77
  tooltipFormatter,
71
78
  tooltipValueFormatter,
72
79
  legend: legendConfig,
73
80
  renderLegend,
74
81
  onLegendChange,
75
82
  option: optionOverride,
83
+ onReady,
84
+ events,
85
+ showDataTable,
86
+ exportable,
87
+ patterns,
76
88
  onClick,
89
+ onHover,
77
90
  class: className,
78
91
  }: Props = $props();
79
92
 
80
- const isEmpty = $derived(
81
- !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length)
93
+ const isEmpty = $derived(!data?.categories?.length || allSeriesEmpty(data?.series));
94
+
95
+ const isPercent = $derived(stacked === 'percent');
96
+
97
+ // Percent stacks plot shares, so they always read as percentages
98
+ const formatters = $derived(
99
+ chartFormatters({
100
+ valueFormat: isPercent ? 'percentValue' : valueFormat,
101
+ locale,
102
+ tooltipValueFormatter,
103
+ labelFormatter,
104
+ })
82
105
  );
83
106
 
84
107
  // Per-element colours that follow the resolved chart theme
85
108
  const chartColors = $derived.by(() => getChartColors(chart.theme));
86
109
 
110
+ const builtInLegend = $derived(showLegend && !renderLegend);
111
+
87
112
  const option: EChartsOption = $derived.by(() => {
88
113
  if (isEmpty) return {};
89
114
 
115
+ const plotted = isPercent ? toPercentStack(data.series) : data.series;
90
116
  const getLabel = seriesLabelLookup(data.series);
91
117
  // ECharts hides line labels along with the symbol
92
118
  const showSymbol = showLabels || hasPointLabels(data.series);
119
+ const legendBottom = builtInLegend && (legendConfig?.position ?? 'bottom') === 'bottom';
120
+
121
+ const series = data.series.map((source, index) => {
122
+ // Fade the series colour (theme palette unless overridden) downwards
123
+ const base = source.color || chartColors.series[index % chartColors.series.length];
124
+ return {
125
+ name: source.name,
126
+ type: 'line',
127
+ data: withPointLabels(
128
+ pairWithCategories(data.categories, plotted[index].data, xAxisConfig?.type),
129
+ source.labels,
130
+ formatters.label,
131
+ { position: 'top' }
132
+ ),
133
+ smooth,
134
+ connectNulls,
135
+ stack: source.stack ?? (stacked ? 'Total' : undefined),
136
+ areaStyle: gradient
137
+ ? {
138
+ opacity: 0.8,
139
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
140
+ { offset: 0, color: colorWithAlpha(base, 0.8) },
141
+ { offset: 1, color: colorWithAlpha(base, 0.1) },
142
+ ]),
143
+ }
144
+ : { opacity: 0.5 },
145
+ itemStyle: source.color ? { color: source.color } : undefined,
146
+ label: buildDataLabel({ show: true, position: 'top' }, undefined, {
147
+ show: showLabels,
148
+ formatter: formatters.label,
149
+ getLabel,
150
+ }),
151
+ emphasis: {
152
+ focus: 'series',
153
+ },
154
+ showSymbol,
155
+ };
156
+ });
93
157
 
94
158
  return mergeChartOption(
95
159
  {
@@ -98,7 +162,11 @@
98
162
  right: '4%',
99
163
  bottom: '3%',
100
164
  containLabel: true,
101
- ...gridInset(legendConfig, showLegend && !renderLegend, xAxisConfig, yAxisConfig),
165
+ ...withZoomInset(
166
+ gridInset(legendConfig, builtInLegend, xAxisConfig, yAxisConfig),
167
+ dataZoom,
168
+ 'x'
169
+ ),
102
170
  },
103
171
  tooltip: buildTooltip(
104
172
  {
@@ -113,7 +181,7 @@
113
181
  {
114
182
  show: showTooltip,
115
183
  formatter: tooltipFormatter,
116
- valueFormatter: tooltipValueFormatter,
184
+ valueFormatter: formatters.tooltip,
117
185
  getLabel,
118
186
  }
119
187
  ),
@@ -122,6 +190,7 @@
122
190
  legendConfig,
123
191
  { show: showLegend, custom: !!renderLegend }
124
192
  ),
193
+ dataZoom: buildDataZoom(dataZoom, 'x', legendBottom ? 36 : 0),
125
194
  xAxis: buildAxis(
126
195
  {
127
196
  type: 'category',
@@ -133,41 +202,18 @@
133
202
  yAxis: buildAxis(
134
203
  {
135
204
  type: 'value',
205
+ ...(isPercent && { max: 100 }),
136
206
  splitLine: {
137
207
  show: showGrid,
138
208
  },
139
209
  },
140
- yAxisConfig
210
+ formatters.axis(yAxisConfig)
141
211
  ),
142
- series: data.series.map((series, index) => {
143
- // Fade the series colour (theme palette unless overridden) downwards
144
- const base = series.color || chartColors.series[index % chartColors.series.length];
145
- return {
146
- name: series.name,
147
- type: 'line',
148
- data: withPointLabels(series.data, series.labels, labelFormatter, { position: 'top' }),
149
- smooth,
150
- stack: stacked ? 'Total' : undefined,
151
- areaStyle: gradient
152
- ? {
153
- opacity: 0.8,
154
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
155
- { offset: 0, color: colorWithAlpha(base, 0.8) },
156
- { offset: 1, color: colorWithAlpha(base, 0.1) },
157
- ]),
158
- }
159
- : { opacity: 0.5 },
160
- itemStyle: series.color ? { color: series.color } : undefined,
161
- label: buildDataLabel({ show: true, position: 'top' }, undefined, {
162
- show: showLabels,
163
- formatter: labelFormatter,
164
- getLabel,
165
- }),
166
- emphasis: {
167
- focus: 'series',
168
- },
169
- showSymbol,
170
- };
212
+ series: withReferenceMarks(series, referenceLines, referenceBands, {
213
+ valueAxis: 'y',
214
+ format: formatters.format,
215
+ color: chartColors.reference,
216
+ bandColor: chartColors.referenceBand,
171
217
  }),
172
218
  } as EChartsOption,
173
219
  optionOverride
@@ -176,11 +222,11 @@
176
222
 
177
223
  const accessibilityDescription = $derived(
178
224
  description ||
179
- (!isEmpty
180
- ? `${title}. Area chart with ${data.categories.length} data points across ${data.series.length} series.`
181
- : undefined)
225
+ (!isEmpty ? generateAreaChartDescription(title, data, formatters.format) : undefined)
182
226
  );
183
227
 
228
+ const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
229
+
184
230
  // Legend selection state, shared with a custom legend
185
231
  const legend = useChartLegend({
186
232
  names: () => data?.series?.map((s) => s.name) ?? [],
@@ -197,23 +243,14 @@
197
243
  theme: () => theme,
198
244
  animation: () => animation,
199
245
  isEmpty: () => isEmpty,
200
- onReady: (instance) => {
201
- legend.attach(instance);
202
- if (onClick) {
203
- instance.on('click', (params) => {
204
- onClick({
205
- type: params.type || 'click',
206
- componentType: params.componentType || 'series',
207
- seriesIndex: params.seriesIndex,
208
- seriesName: params.seriesName || '',
209
- dataIndex: params.dataIndex || 0,
210
- name: params.name || '',
211
- value: params.value as number,
212
- color: params.color as string,
213
- } as DataPointEventParams);
214
- });
215
- }
216
- },
246
+ patterns: () => patterns,
247
+ events: () =>
248
+ chartEvents(events, {
249
+ click: onClick && ((params) => onClick(toDataPointParams(params, 'click'))),
250
+ mouseover: onHover && ((params) => onHover(toDataPointParams(params, 'mouseover'))),
251
+ }),
252
+ onReady: (instance) => legend.attach(instance),
253
+ onCreated: () => onReady,
217
254
  });
218
255
  </script>
219
256
 
@@ -225,13 +262,21 @@
225
262
  {title}
226
263
  description={accessibilityDescription}
227
264
  {height}
265
+ {minHeight}
266
+ {maxHeight}
228
267
  {loading}
229
268
  {error}
269
+ {onRetry}
230
270
  empty={isEmpty}
231
271
  {emptyMessage}
232
272
  class={cn(className)}
233
273
  legend={renderLegend ? customLegend : undefined}
234
274
  legendPosition={legendConfig?.position}
275
+ {dataTable}
276
+ {showDataTable}
277
+ format={resolveValueFormatter(valueFormat, locale)}
278
+ {exportable}
279
+ exportImage={chart.exportImage}
235
280
  >
236
281
  <div bind:this={chart.container} class="h-full w-full"></div>
237
282
  </ChartContainer>
@@ -1,30 +1,35 @@
1
1
  <script lang="ts">
2
2
  import * as echarts from 'echarts/core';
3
3
  import { BarChart as EChartsBarChart } from 'echarts/charts';
4
- import {
5
- GridComponent,
6
- TooltipComponent,
7
- LegendComponent,
8
- TitleComponent,
9
- } from 'echarts/components';
10
- import { CanvasRenderer } from 'echarts/renderers';
4
+ import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
11
5
  import type { EChartsOption } from 'echarts';
12
6
 
13
- import type { BarChartProps, DataPointEventParams } from '@classic-homes/charts-core';
7
+ import type { BarChartProps } from '@classic-homes/charts-core';
14
8
  import {
9
+ allSeriesEmpty,
15
10
  buildAxis,
16
11
  buildDataLabel,
12
+ buildDataZoom,
17
13
  buildLegend,
18
14
  buildTooltip,
15
+ categoryTable,
16
+ chartFormatters,
19
17
  generateBarChartDescription,
20
18
  getChartColors,
21
19
  gridInset,
22
20
  mergeChartOption,
21
+ pairWithCategories,
22
+ resolveChartHeight,
23
+ resolveValueFormatter,
23
24
  seriesLabelLookup,
25
+ toPercentStack,
24
26
  withPointLabels,
27
+ withReferenceMarks,
28
+ withZoomInset,
25
29
  } from '@classic-homes/charts-core';
26
30
 
27
31
  import { cn } from '../../utils.js';
32
+ import { chartEvents, toDataPointParams } from '../../events.js';
28
33
  import { useChart } from '../../composables/useChart.svelte.js';
29
34
  import {
30
35
  useChartLegend,
@@ -33,14 +38,7 @@
33
38
  import ChartContainer from '../base/ChartContainer.svelte';
34
39
 
35
40
  // Register required ECharts modules
36
- echarts.use([
37
- EChartsBarChart,
38
- GridComponent,
39
- TooltipComponent,
40
- LegendComponent,
41
- TitleComponent,
42
- CanvasRenderer,
43
- ]);
41
+ echarts.use([EChartsBarChart, GridComponent, TooltipComponent, LegendComponent]);
44
42
 
45
43
  interface Props extends Omit<BarChartProps, 'class'>, CustomLegendProps {
46
44
  class?: string;
@@ -51,8 +49,12 @@
51
49
  description,
52
50
  data,
53
51
  height = 400,
52
+ minHeight,
53
+ maxHeight,
54
+ rowHeight,
54
55
  loading,
55
56
  error,
57
+ onRetry,
56
58
  emptyMessage,
57
59
  theme = 'auto',
58
60
  animation = true,
@@ -62,31 +64,62 @@
62
64
  orientation = 'vertical',
63
65
  stacked = false,
64
66
  showValues = false,
67
+ valueFormat,
68
+ locale,
65
69
  labelFormatter,
66
70
  xAxisConfig,
67
71
  yAxisConfig,
72
+ referenceLines,
73
+ referenceBands,
74
+ dataZoom,
68
75
  tooltipFormatter,
69
76
  tooltipValueFormatter,
70
77
  legend: legendConfig,
71
78
  renderLegend,
72
79
  onLegendChange,
73
80
  option: optionOverride,
81
+ onReady,
82
+ events,
83
+ showDataTable,
84
+ exportable,
85
+ patterns,
74
86
  onClick,
87
+ onHover,
75
88
  class: className,
76
89
  }: Props = $props();
77
90
 
78
- const isEmpty = $derived(
79
- !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length)
80
- );
91
+ const isEmpty = $derived(!data?.categories?.length || allSeriesEmpty(data?.series));
81
92
 
82
93
  const isHorizontal = $derived(orientation === 'horizontal');
94
+ const isPercent = $derived(stacked === 'percent');
95
+
96
+ // Percent stacks plot shares, so they always read as percentages
97
+ const formatters = $derived(
98
+ chartFormatters({
99
+ valueFormat: isPercent ? 'percentValue' : valueFormat,
100
+ locale,
101
+ tooltipValueFormatter,
102
+ labelFormatter,
103
+ })
104
+ );
105
+
106
+ // Per-element colours that follow the resolved chart theme
107
+ const chartColors = $derived.by(() => getChartColors(chart.theme));
108
+
109
+ const builtInLegend = $derived(showLegend && !renderLegend);
83
110
 
84
111
  const option: EChartsOption = $derived.by(() => {
85
112
  if (isEmpty) return {};
86
113
 
114
+ const plotted = isPercent ? toPercentStack(data.series) : data.series;
115
+ // A time or value x-axis reads [x, y] points (vertical bars only)
116
+ const categoryAxisType = isHorizontal ? undefined : xAxisConfig?.type;
117
+
87
118
  const categoryAxis = {
88
119
  type: 'category' as const,
89
120
  data: data.categories,
121
+ // Horizontal bars list the first category at the top
122
+ inverse: isHorizontal,
90
123
  axisTick: {
91
124
  alignWithLabel: true,
92
125
  },
@@ -94,12 +127,37 @@
94
127
 
95
128
  const valueAxis = {
96
129
  type: 'value' as const,
130
+ ...(isPercent && { max: 100 }),
97
131
  splitLine: {
98
132
  show: showGrid,
99
133
  },
100
134
  };
101
135
 
102
136
  const getLabel = seriesLabelLookup(data.series);
137
+ const labelPosition = isHorizontal ? 'right' : 'top';
138
+ const zoomAxis = isHorizontal ? 'y' : 'x';
139
+ const legendBottom = builtInLegend && (legendConfig?.position ?? 'bottom') === 'bottom';
140
+
141
+ const series = data.series.map((source, index) => ({
142
+ name: source.name,
143
+ type: 'bar',
144
+ data: withPointLabels(
145
+ pairWithCategories(data.categories, plotted[index].data, categoryAxisType),
146
+ source.labels,
147
+ formatters.label,
148
+ { position: labelPosition }
149
+ ),
150
+ stack: source.stack ?? (stacked ? 'Total' : undefined),
151
+ itemStyle: source.color ? { color: source.color } : undefined,
152
+ label: buildDataLabel({ show: true, position: labelPosition }, undefined, {
153
+ show: showValues,
154
+ formatter: formatters.label,
155
+ getLabel,
156
+ }),
157
+ emphasis: {
158
+ focus: 'series',
159
+ },
160
+ }));
103
161
 
104
162
  return mergeChartOption(
105
163
  {
@@ -108,7 +166,11 @@
108
166
  right: '4%',
109
167
  bottom: '3%',
110
168
  containLabel: true,
111
- ...gridInset(legendConfig, showLegend && !renderLegend, xAxisConfig, yAxisConfig),
169
+ ...withZoomInset(
170
+ gridInset(legendConfig, builtInLegend, xAxisConfig, yAxisConfig),
171
+ dataZoom,
172
+ zoomAxis
173
+ ),
112
174
  },
113
175
  tooltip: buildTooltip(
114
176
  {
@@ -120,7 +182,7 @@
120
182
  {
121
183
  show: showTooltip,
122
184
  formatter: tooltipFormatter,
123
- valueFormatter: tooltipValueFormatter,
185
+ valueFormatter: formatters.tooltip,
124
186
  getLabel,
125
187
  }
126
188
  ),
@@ -129,35 +191,46 @@
129
191
  legendConfig,
130
192
  { show: showLegend, custom: !!renderLegend }
131
193
  ),
132
- xAxis: buildAxis(isHorizontal ? valueAxis : categoryAxis, xAxisConfig),
133
- yAxis: buildAxis(isHorizontal ? categoryAxis : valueAxis, yAxisConfig),
134
- series: data.series.map((series) => ({
135
- name: series.name,
136
- type: 'bar',
137
- data: withPointLabels(series.data, series.labels, labelFormatter, {
138
- position: isHorizontal ? 'right' : 'top',
139
- }),
140
- stack: stacked ? 'Total' : undefined,
141
- itemStyle: series.color ? { color: series.color } : undefined,
142
- label: buildDataLabel(
143
- {
144
- show: true,
145
- position: isHorizontal ? 'right' : 'top',
146
- },
147
- undefined,
148
- { show: showValues, formatter: labelFormatter, getLabel }
149
- ),
150
- emphasis: {
151
- focus: 'series',
152
- },
153
- })),
194
+ dataZoom: buildDataZoom(dataZoom, zoomAxis, legendBottom ? 36 : 0),
195
+ xAxis: buildAxis(
196
+ isHorizontal ? valueAxis : categoryAxis,
197
+ isHorizontal ? formatters.axis(xAxisConfig) : xAxisConfig
198
+ ),
199
+ yAxis: buildAxis(
200
+ isHorizontal ? categoryAxis : valueAxis,
201
+ isHorizontal ? yAxisConfig : formatters.axis(yAxisConfig)
202
+ ),
203
+ series: withReferenceMarks(series, referenceLines, referenceBands, {
204
+ valueAxis: isHorizontal ? 'x' : 'y',
205
+ inverse: isHorizontal,
206
+ format: formatters.format,
207
+ color: chartColors.reference,
208
+ bandColor: chartColors.referenceBand,
209
+ }),
154
210
  } as EChartsOption,
155
211
  optionOverride
156
212
  );
157
213
  });
158
214
 
159
215
  const accessibilityDescription = $derived(
160
- description || (!isEmpty ? generateBarChartDescription(title, data) : undefined)
216
+ description ||
217
+ (!isEmpty ? generateBarChartDescription(title, data, formatters.format) : undefined)
218
+ );
219
+
220
+ const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
221
+
222
+ // One row per category when horizontal; a bottom legend needs its own row
223
+ const resolvedHeight = $derived(
224
+ isHorizontal
225
+ ? resolveChartHeight(height, data?.categories?.length ?? 0, {
226
+ rowHeight,
227
+ minHeight,
228
+ maxHeight,
229
+ chrome: 56 + (builtInLegend ? 36 : 0) + (dataZoom ? 36 : 0),
230
+ })
231
+ : height === 'auto'
232
+ ? 400
233
+ : height
161
234
  );
162
235
 
163
236
  // Legend selection state, shared with a custom legend
@@ -176,23 +249,14 @@
176
249
  theme: () => theme,
177
250
  animation: () => animation,
178
251
  isEmpty: () => isEmpty,
179
- onReady: (instance) => {
180
- legend.attach(instance);
181
- if (onClick) {
182
- instance.on('click', (params) => {
183
- onClick({
184
- type: params.type || 'click',
185
- componentType: params.componentType || 'series',
186
- seriesIndex: params.seriesIndex,
187
- seriesName: params.seriesName || '',
188
- dataIndex: params.dataIndex || 0,
189
- name: params.name || '',
190
- value: params.value as number,
191
- color: params.color as string,
192
- } as DataPointEventParams);
193
- });
194
- }
195
- },
252
+ patterns: () => patterns,
253
+ events: () =>
254
+ chartEvents(events, {
255
+ click: onClick && ((params) => onClick(toDataPointParams(params, 'click'))),
256
+ mouseover: onHover && ((params) => onHover(toDataPointParams(params, 'mouseover'))),
257
+ }),
258
+ onReady: (instance) => legend.attach(instance),
259
+ onCreated: () => onReady,
196
260
  });
197
261
  </script>
198
262
 
@@ -203,14 +267,22 @@
203
267
  <ChartContainer
204
268
  {title}
205
269
  description={accessibilityDescription}
206
- {height}
270
+ height={resolvedHeight}
271
+ {minHeight}
272
+ {maxHeight}
207
273
  {loading}
208
274
  {error}
275
+ {onRetry}
209
276
  empty={isEmpty}
210
277
  {emptyMessage}
211
278
  class={cn(className)}
212
279
  legend={renderLegend ? customLegend : undefined}
213
280
  legendPosition={legendConfig?.position}
281
+ {dataTable}
282
+ {showDataTable}
283
+ format={resolveValueFormatter(valueFormat, locale)}
284
+ {exportable}
285
+ exportImage={chart.exportImage}
214
286
  >
215
287
  <div bind:this={chart.container} class="h-full w-full"></div>
216
288
  </ChartContainer>