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