@classic-homes/charts-svelte 0.1.1

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 (53) hide show
  1. package/dist/lib/components/base/ChartContainer.svelte +63 -0
  2. package/dist/lib/components/base/ChartContainer.svelte.d.ts +17 -0
  3. package/dist/lib/components/base/ChartEmpty.svelte +39 -0
  4. package/dist/lib/components/base/ChartEmpty.svelte.d.ts +8 -0
  5. package/dist/lib/components/base/ChartError.svelte +49 -0
  6. package/dist/lib/components/base/ChartError.svelte.d.ts +9 -0
  7. package/dist/lib/components/base/ChartSkeleton.svelte +37 -0
  8. package/dist/lib/components/base/ChartSkeleton.svelte.d.ts +7 -0
  9. package/dist/lib/components/base/index.d.ts +4 -0
  10. package/dist/lib/components/base/index.js +4 -0
  11. package/dist/lib/components/core/AreaChart.svelte +198 -0
  12. package/dist/lib/components/core/AreaChart.svelte.d.ts +7 -0
  13. package/dist/lib/components/core/BarChart.svelte +186 -0
  14. package/dist/lib/components/core/BarChart.svelte.d.ts +7 -0
  15. package/dist/lib/components/core/DonutChart.svelte +207 -0
  16. package/dist/lib/components/core/DonutChart.svelte.d.ts +7 -0
  17. package/dist/lib/components/core/LineChart.svelte +203 -0
  18. package/dist/lib/components/core/LineChart.svelte.d.ts +7 -0
  19. package/dist/lib/components/core/PieChart.svelte +156 -0
  20. package/dist/lib/components/core/PieChart.svelte.d.ts +7 -0
  21. package/dist/lib/components/core/ScatterChart.svelte +224 -0
  22. package/dist/lib/components/core/ScatterChart.svelte.d.ts +7 -0
  23. package/dist/lib/components/core/index.d.ts +6 -0
  24. package/dist/lib/components/core/index.js +6 -0
  25. package/dist/lib/components/extended/CandlestickChart.svelte +200 -0
  26. package/dist/lib/components/extended/CandlestickChart.svelte.d.ts +7 -0
  27. package/dist/lib/components/extended/FunnelChart.svelte +142 -0
  28. package/dist/lib/components/extended/FunnelChart.svelte.d.ts +7 -0
  29. package/dist/lib/components/extended/GaugeChart.svelte +113 -0
  30. package/dist/lib/components/extended/GaugeChart.svelte.d.ts +7 -0
  31. package/dist/lib/components/extended/HeatmapChart.svelte +159 -0
  32. package/dist/lib/components/extended/HeatmapChart.svelte.d.ts +7 -0
  33. package/dist/lib/components/extended/RadarChart.svelte +131 -0
  34. package/dist/lib/components/extended/RadarChart.svelte.d.ts +7 -0
  35. package/dist/lib/components/extended/SankeyChart.svelte +129 -0
  36. package/dist/lib/components/extended/SankeyChart.svelte.d.ts +7 -0
  37. package/dist/lib/components/extended/TreemapChart.svelte +133 -0
  38. package/dist/lib/components/extended/TreemapChart.svelte.d.ts +7 -0
  39. package/dist/lib/components/extended/index.d.ts +7 -0
  40. package/dist/lib/components/extended/index.js +7 -0
  41. package/dist/lib/components/index.d.ts +3 -0
  42. package/dist/lib/components/index.js +6 -0
  43. package/dist/lib/composables/index.d.ts +2 -0
  44. package/dist/lib/composables/index.js +2 -0
  45. package/dist/lib/composables/useChartTheme.svelte.d.ts +11 -0
  46. package/dist/lib/composables/useChartTheme.svelte.js +66 -0
  47. package/dist/lib/composables/useReducedMotion.svelte.d.ts +6 -0
  48. package/dist/lib/composables/useReducedMotion.svelte.js +26 -0
  49. package/dist/lib/index.d.ts +9 -0
  50. package/dist/lib/index.js +17 -0
  51. package/dist/lib/utils.d.ts +2 -0
  52. package/dist/lib/utils.js +5 -0
  53. package/package.json +45 -0
@@ -0,0 +1,207 @@
1
+ <script lang="ts">
2
+ import * as echarts from 'echarts/core';
3
+ import { PieChart as EChartsPieChart } from 'echarts/charts';
4
+ import { TooltipComponent, LegendComponent, TitleComponent } from 'echarts/components';
5
+ import { CanvasRenderer } from 'echarts/renderers';
6
+ import type { EChartsOption } from 'echarts';
7
+
8
+ import type { DonutChartProps, SliceEventParams } from '@classic-homes/charts-core';
9
+ import { generatePieChartDescription } from '@classic-homes/charts-core';
10
+
11
+ import { cn } from '../../utils.js';
12
+ import { useChartTheme } from '../../composables/useChartTheme.svelte.js';
13
+ import { useReducedMotion } from '../../composables/useReducedMotion.svelte.js';
14
+ import ChartContainer from '../base/ChartContainer.svelte';
15
+
16
+ // Register required ECharts modules
17
+ echarts.use([EChartsPieChart, TooltipComponent, LegendComponent, TitleComponent, CanvasRenderer]);
18
+
19
+ interface Props extends Omit<DonutChartProps, 'class'> {
20
+ class?: string;
21
+ }
22
+
23
+ let {
24
+ title,
25
+ description,
26
+ data,
27
+ height = 400,
28
+ loading,
29
+ error,
30
+ emptyMessage,
31
+ theme = 'auto',
32
+ animation = true,
33
+ showLegend = true,
34
+ showTooltip = true,
35
+ showLabels = true,
36
+ labelPosition = 'outside',
37
+ innerRadius = '50%',
38
+ centerLabel,
39
+ centerValue,
40
+ onClick,
41
+ class: className,
42
+ }: Props = $props();
43
+
44
+ let containerEl: HTMLDivElement | null = $state(null);
45
+ let chart: echarts.ECharts | null = null;
46
+
47
+ const chartTheme = useChartTheme(() => theme);
48
+ const reducedMotion = useReducedMotion();
49
+
50
+ const isEmpty = $derived(!data?.length);
51
+
52
+ const option: EChartsOption = $derived.by(() => {
53
+ if (isEmpty) return {};
54
+
55
+ const graphicElements: unknown[] = [];
56
+
57
+ if (centerLabel || centerValue) {
58
+ graphicElements.push({
59
+ type: 'group',
60
+ left: 'center',
61
+ top: '40%',
62
+ children: [
63
+ ...(centerValue !== undefined
64
+ ? [
65
+ {
66
+ type: 'text',
67
+ style: {
68
+ text: String(centerValue),
69
+ fontSize: 28,
70
+ fontWeight: 'bold',
71
+ fill: 'currentColor',
72
+ align: 'center',
73
+ },
74
+ top: 0,
75
+ left: 'center',
76
+ },
77
+ ]
78
+ : []),
79
+ ...(centerLabel
80
+ ? [
81
+ {
82
+ type: 'text',
83
+ style: {
84
+ text: centerLabel,
85
+ fontSize: 14,
86
+ fill: '#888',
87
+ align: 'center',
88
+ },
89
+ top: centerValue !== undefined ? 35 : 0,
90
+ left: 'center',
91
+ },
92
+ ]
93
+ : []),
94
+ ],
95
+ });
96
+ }
97
+
98
+ return {
99
+ tooltip: showTooltip
100
+ ? {
101
+ trigger: 'item',
102
+ formatter: '{a} <br/>{b}: {c} ({d}%)',
103
+ }
104
+ : undefined,
105
+ legend: showLegend
106
+ ? {
107
+ orient: 'horizontal',
108
+ bottom: 0,
109
+ data: data.map((d) => d.name),
110
+ }
111
+ : undefined,
112
+ graphic:
113
+ graphicElements.length > 0 ? (graphicElements as EChartsOption['graphic']) : undefined,
114
+ series: [
115
+ {
116
+ name: title,
117
+ type: 'pie',
118
+ radius: [innerRadius, '70%'],
119
+ center: ['50%', '45%'],
120
+ avoidLabelOverlap: true,
121
+ data: data.map((item) => ({
122
+ name: item.name,
123
+ value: item.value,
124
+ itemStyle: item.color ? { color: item.color } : undefined,
125
+ })),
126
+ label: showLabels
127
+ ? {
128
+ show: true,
129
+ position: labelPosition,
130
+ formatter: labelPosition === 'inside' ? '{d}%' : '{b}: {d}%',
131
+ }
132
+ : {
133
+ show: false,
134
+ },
135
+ labelLine: {
136
+ show: showLabels && labelPosition === 'outside',
137
+ },
138
+ emphasis: {
139
+ itemStyle: {
140
+ shadowBlur: 10,
141
+ shadowOffsetX: 0,
142
+ shadowColor: 'rgba(0, 0, 0, 0.5)',
143
+ },
144
+ },
145
+ },
146
+ ],
147
+ animation: animation && !reducedMotion.value,
148
+ };
149
+ });
150
+
151
+ const accessibilityDescription = $derived(
152
+ description || (!isEmpty ? generatePieChartDescription(title, data, true) : undefined)
153
+ );
154
+
155
+ // Initialize chart instance (only depends on container and theme)
156
+ $effect(() => {
157
+ if (!containerEl) return;
158
+
159
+ if (chart) {
160
+ chart.dispose();
161
+ }
162
+
163
+ chart = echarts.init(containerEl, chartTheme.theme);
164
+
165
+ if (onClick) {
166
+ chart.on('click', (params) => {
167
+ onClick({
168
+ type: params.type || 'click',
169
+ componentType: params.componentType || 'series',
170
+ name: params.name || '',
171
+ value: params.value as number,
172
+ percent: params.percent,
173
+ color: params.color as string,
174
+ } as SliceEventParams);
175
+ });
176
+ }
177
+
178
+ const handleResize = () => chart?.resize();
179
+ window.addEventListener('resize', handleResize);
180
+
181
+ return () => {
182
+ window.removeEventListener('resize', handleResize);
183
+ chart?.dispose();
184
+ chart = null;
185
+ };
186
+ });
187
+
188
+ // Update options when they change (handles both initial and subsequent updates)
189
+ $effect(() => {
190
+ if (chart && !isEmpty) {
191
+ chart.setOption(option, { notMerge: true, lazyUpdate: true });
192
+ }
193
+ });
194
+ </script>
195
+
196
+ <ChartContainer
197
+ {title}
198
+ description={accessibilityDescription}
199
+ {height}
200
+ {loading}
201
+ {error}
202
+ empty={isEmpty}
203
+ {emptyMessage}
204
+ class={cn(className)}
205
+ >
206
+ <div bind:this={containerEl} class="h-full w-full"></div>
207
+ </ChartContainer>
@@ -0,0 +1,7 @@
1
+ import type { DonutChartProps } from '@classic-homes/charts-core';
2
+ interface Props extends Omit<DonutChartProps, 'class'> {
3
+ class?: string;
4
+ }
5
+ declare const DonutChart: import("svelte").Component<Props, {}, "">;
6
+ type DonutChart = ReturnType<typeof DonutChart>;
7
+ export default DonutChart;
@@ -0,0 +1,203 @@
1
+ <script lang="ts">
2
+ import * as echarts from 'echarts/core';
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';
11
+ import type { EChartsOption } from 'echarts';
12
+
13
+ import type { LineChartProps, DataPointEventParams } from '@classic-homes/charts-core';
14
+ import { generateLineChartDescription } from '@classic-homes/charts-core';
15
+
16
+ import { cn } from '../../utils.js';
17
+ import { useChartTheme } from '../../composables/useChartTheme.svelte.js';
18
+ import { useReducedMotion } from '../../composables/useReducedMotion.svelte.js';
19
+ import ChartContainer from '../base/ChartContainer.svelte';
20
+
21
+ // Register required ECharts modules
22
+ echarts.use([
23
+ EChartsLineChart,
24
+ GridComponent,
25
+ TooltipComponent,
26
+ LegendComponent,
27
+ TitleComponent,
28
+ CanvasRenderer,
29
+ ]);
30
+
31
+ interface Props extends Omit<LineChartProps, 'class'> {
32
+ class?: string;
33
+ }
34
+
35
+ let {
36
+ title,
37
+ description,
38
+ data,
39
+ height = 400,
40
+ loading,
41
+ error,
42
+ emptyMessage,
43
+ theme = 'auto',
44
+ animation = true,
45
+ showLegend = true,
46
+ showTooltip = true,
47
+ showGrid = true,
48
+ smooth = false,
49
+ areaFilled = false,
50
+ showDataPoints = false,
51
+ stacked = false,
52
+ onClick,
53
+ onHover,
54
+ class: className,
55
+ }: Props = $props();
56
+
57
+ let containerEl: HTMLDivElement | null = $state(null);
58
+ let chart: echarts.ECharts | null = null;
59
+
60
+ const chartTheme = useChartTheme(() => theme);
61
+ const reducedMotion = useReducedMotion();
62
+
63
+ const isEmpty = $derived(
64
+ !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length)
65
+ );
66
+
67
+ const option: EChartsOption = $derived.by(() => {
68
+ if (isEmpty) return {};
69
+
70
+ return {
71
+ grid: {
72
+ left: '3%',
73
+ right: '4%',
74
+ bottom: '3%',
75
+ containLabel: true,
76
+ },
77
+ tooltip: showTooltip
78
+ ? {
79
+ trigger: 'axis',
80
+ axisPointer: {
81
+ type: 'cross',
82
+ label: {
83
+ backgroundColor: '#6a7985',
84
+ },
85
+ },
86
+ }
87
+ : undefined,
88
+ legend: showLegend
89
+ ? {
90
+ data: data.series.map((s) => s.name),
91
+ bottom: 0,
92
+ }
93
+ : undefined,
94
+ xAxis: {
95
+ type: 'category',
96
+ boundaryGap: false,
97
+ data: data.categories,
98
+ splitLine: {
99
+ show: showGrid,
100
+ },
101
+ },
102
+ yAxis: {
103
+ type: 'value',
104
+ splitLine: {
105
+ show: showGrid,
106
+ },
107
+ },
108
+ series: data.series.map((series) => ({
109
+ name: series.name,
110
+ type: 'line',
111
+ data: series.data,
112
+ smooth,
113
+ showSymbol: showDataPoints,
114
+ symbolSize: showDataPoints ? 6 : 0,
115
+ stack: stacked ? 'Total' : undefined,
116
+ areaStyle: areaFilled ? { opacity: 0.3 } : undefined,
117
+ itemStyle: series.color ? { color: series.color } : undefined,
118
+ emphasis: {
119
+ focus: 'series',
120
+ },
121
+ })),
122
+ animation: animation && !reducedMotion.value,
123
+ };
124
+ });
125
+
126
+ const accessibilityDescription = $derived(
127
+ description || (!isEmpty ? generateLineChartDescription(title, data) : undefined)
128
+ );
129
+
130
+ // Initialize chart instance (only depends on container and theme)
131
+ $effect(() => {
132
+ if (!containerEl) return;
133
+
134
+ // Dispose existing chart if any
135
+ if (chart) {
136
+ chart.dispose();
137
+ }
138
+
139
+ // Create new chart instance
140
+ chart = echarts.init(containerEl, chartTheme.theme);
141
+
142
+ // Setup event handlers
143
+ if (onClick) {
144
+ chart.on('click', (params) => {
145
+ onClick({
146
+ type: params.type || 'click',
147
+ componentType: params.componentType || 'series',
148
+ seriesIndex: params.seriesIndex,
149
+ seriesName: params.seriesName || '',
150
+ dataIndex: params.dataIndex || 0,
151
+ name: params.name || '',
152
+ value: params.value as number,
153
+ color: params.color as string,
154
+ } as DataPointEventParams);
155
+ });
156
+ }
157
+
158
+ if (onHover) {
159
+ chart.on('mouseover', (params) => {
160
+ onHover({
161
+ type: params.type || 'mouseover',
162
+ componentType: params.componentType || 'series',
163
+ seriesIndex: params.seriesIndex,
164
+ seriesName: params.seriesName || '',
165
+ dataIndex: params.dataIndex || 0,
166
+ name: params.name || '',
167
+ value: params.value as number,
168
+ color: params.color as string,
169
+ } as DataPointEventParams);
170
+ });
171
+ }
172
+
173
+ // Resize handler
174
+ const handleResize = () => chart?.resize();
175
+ window.addEventListener('resize', handleResize);
176
+
177
+ return () => {
178
+ window.removeEventListener('resize', handleResize);
179
+ chart?.dispose();
180
+ chart = null;
181
+ };
182
+ });
183
+
184
+ // Update options when they change (handles both initial and subsequent updates)
185
+ $effect(() => {
186
+ if (chart && !isEmpty) {
187
+ chart.setOption(option, { notMerge: true, lazyUpdate: true });
188
+ }
189
+ });
190
+ </script>
191
+
192
+ <ChartContainer
193
+ {title}
194
+ description={accessibilityDescription}
195
+ {height}
196
+ {loading}
197
+ {error}
198
+ empty={isEmpty}
199
+ {emptyMessage}
200
+ class={cn(className)}
201
+ >
202
+ <div bind:this={containerEl} class="h-full w-full"></div>
203
+ </ChartContainer>
@@ -0,0 +1,7 @@
1
+ import type { LineChartProps } from '@classic-homes/charts-core';
2
+ interface Props extends Omit<LineChartProps, 'class'> {
3
+ class?: string;
4
+ }
5
+ declare const LineChart: import("svelte").Component<Props, {}, "">;
6
+ type LineChart = ReturnType<typeof LineChart>;
7
+ export default LineChart;
@@ -0,0 +1,156 @@
1
+ <script lang="ts">
2
+ import * as echarts from 'echarts/core';
3
+ import { PieChart as EChartsPieChart } from 'echarts/charts';
4
+ import { TooltipComponent, LegendComponent, TitleComponent } from 'echarts/components';
5
+ import { CanvasRenderer } from 'echarts/renderers';
6
+ import type { EChartsOption } from 'echarts';
7
+
8
+ import type { PieChartProps, SliceEventParams } from '@classic-homes/charts-core';
9
+ import { generatePieChartDescription } from '@classic-homes/charts-core';
10
+
11
+ import { cn } from '../../utils.js';
12
+ import { useChartTheme } from '../../composables/useChartTheme.svelte.js';
13
+ import { useReducedMotion } from '../../composables/useReducedMotion.svelte.js';
14
+ import ChartContainer from '../base/ChartContainer.svelte';
15
+
16
+ // Register required ECharts modules
17
+ echarts.use([EChartsPieChart, TooltipComponent, LegendComponent, TitleComponent, CanvasRenderer]);
18
+
19
+ interface Props extends Omit<PieChartProps, 'class'> {
20
+ class?: string;
21
+ }
22
+
23
+ let {
24
+ title,
25
+ description,
26
+ data,
27
+ height = 400,
28
+ loading,
29
+ error,
30
+ emptyMessage,
31
+ theme = 'auto',
32
+ animation = true,
33
+ showLegend = true,
34
+ showTooltip = true,
35
+ showLabels = true,
36
+ labelPosition = 'outside',
37
+ innerRadius = 0,
38
+ onClick,
39
+ class: className,
40
+ }: Props = $props();
41
+
42
+ let containerEl: HTMLDivElement | null = $state(null);
43
+ let chart: echarts.ECharts | null = null;
44
+
45
+ const chartTheme = useChartTheme(() => theme);
46
+ const reducedMotion = useReducedMotion();
47
+
48
+ const isEmpty = $derived(!data?.length);
49
+
50
+ const option: EChartsOption = $derived.by(() => {
51
+ if (isEmpty) return {};
52
+
53
+ return {
54
+ tooltip: showTooltip
55
+ ? {
56
+ trigger: 'item',
57
+ formatter: '{a} <br/>{b}: {c} ({d}%)',
58
+ }
59
+ : undefined,
60
+ legend: showLegend
61
+ ? {
62
+ orient: 'horizontal',
63
+ bottom: 0,
64
+ data: data.map((d) => d.name),
65
+ }
66
+ : undefined,
67
+ series: [
68
+ {
69
+ name: title,
70
+ type: 'pie',
71
+ radius: innerRadius === 0 ? '70%' : ['50%', '70%'],
72
+ center: ['50%', '45%'],
73
+ data: data.map((item) => ({
74
+ name: item.name,
75
+ value: item.value,
76
+ itemStyle: item.color ? { color: item.color } : undefined,
77
+ })),
78
+ label: showLabels
79
+ ? {
80
+ show: true,
81
+ position: labelPosition,
82
+ formatter: labelPosition === 'inside' ? '{d}%' : '{b}: {d}%',
83
+ }
84
+ : {
85
+ show: false,
86
+ },
87
+ emphasis: {
88
+ itemStyle: {
89
+ shadowBlur: 10,
90
+ shadowOffsetX: 0,
91
+ shadowColor: 'rgba(0, 0, 0, 0.5)',
92
+ },
93
+ },
94
+ },
95
+ ],
96
+ animation: animation && !reducedMotion.value,
97
+ };
98
+ });
99
+
100
+ const accessibilityDescription = $derived(
101
+ description || (!isEmpty ? generatePieChartDescription(title, data, false) : undefined)
102
+ );
103
+
104
+ // Initialize chart instance (only depends on container and theme)
105
+ $effect(() => {
106
+ if (!containerEl) return;
107
+
108
+ if (chart) {
109
+ chart.dispose();
110
+ }
111
+
112
+ chart = echarts.init(containerEl, chartTheme.theme);
113
+
114
+ if (onClick) {
115
+ chart.on('click', (params) => {
116
+ onClick({
117
+ type: params.type || 'click',
118
+ componentType: params.componentType || 'series',
119
+ name: params.name || '',
120
+ value: params.value as number,
121
+ percent: params.percent,
122
+ color: params.color as string,
123
+ } as SliceEventParams);
124
+ });
125
+ }
126
+
127
+ const handleResize = () => chart?.resize();
128
+ window.addEventListener('resize', handleResize);
129
+
130
+ return () => {
131
+ window.removeEventListener('resize', handleResize);
132
+ chart?.dispose();
133
+ chart = null;
134
+ };
135
+ });
136
+
137
+ // Update options when they change (handles both initial and subsequent updates)
138
+ $effect(() => {
139
+ if (chart && !isEmpty) {
140
+ chart.setOption(option, { notMerge: true, lazyUpdate: true });
141
+ }
142
+ });
143
+ </script>
144
+
145
+ <ChartContainer
146
+ {title}
147
+ description={accessibilityDescription}
148
+ {height}
149
+ {loading}
150
+ {error}
151
+ empty={isEmpty}
152
+ {emptyMessage}
153
+ class={cn(className)}
154
+ >
155
+ <div bind:this={containerEl} class="h-full w-full"></div>
156
+ </ChartContainer>
@@ -0,0 +1,7 @@
1
+ import type { PieChartProps } from '@classic-homes/charts-core';
2
+ interface Props extends Omit<PieChartProps, 'class'> {
3
+ class?: string;
4
+ }
5
+ declare const PieChart: import("svelte").Component<Props, {}, "">;
6
+ type PieChart = ReturnType<typeof PieChart>;
7
+ export default PieChart;