@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.
- package/dist/lib/components/base/ChartContainer.svelte +63 -0
- package/dist/lib/components/base/ChartContainer.svelte.d.ts +17 -0
- package/dist/lib/components/base/ChartEmpty.svelte +39 -0
- package/dist/lib/components/base/ChartEmpty.svelte.d.ts +8 -0
- package/dist/lib/components/base/ChartError.svelte +49 -0
- package/dist/lib/components/base/ChartError.svelte.d.ts +9 -0
- package/dist/lib/components/base/ChartSkeleton.svelte +37 -0
- package/dist/lib/components/base/ChartSkeleton.svelte.d.ts +7 -0
- package/dist/lib/components/base/index.d.ts +4 -0
- package/dist/lib/components/base/index.js +4 -0
- package/dist/lib/components/core/AreaChart.svelte +198 -0
- package/dist/lib/components/core/AreaChart.svelte.d.ts +7 -0
- package/dist/lib/components/core/BarChart.svelte +186 -0
- package/dist/lib/components/core/BarChart.svelte.d.ts +7 -0
- package/dist/lib/components/core/DonutChart.svelte +207 -0
- package/dist/lib/components/core/DonutChart.svelte.d.ts +7 -0
- package/dist/lib/components/core/LineChart.svelte +203 -0
- package/dist/lib/components/core/LineChart.svelte.d.ts +7 -0
- package/dist/lib/components/core/PieChart.svelte +156 -0
- package/dist/lib/components/core/PieChart.svelte.d.ts +7 -0
- package/dist/lib/components/core/ScatterChart.svelte +224 -0
- package/dist/lib/components/core/ScatterChart.svelte.d.ts +7 -0
- package/dist/lib/components/core/index.d.ts +6 -0
- package/dist/lib/components/core/index.js +6 -0
- package/dist/lib/components/extended/CandlestickChart.svelte +200 -0
- package/dist/lib/components/extended/CandlestickChart.svelte.d.ts +7 -0
- package/dist/lib/components/extended/FunnelChart.svelte +142 -0
- package/dist/lib/components/extended/FunnelChart.svelte.d.ts +7 -0
- package/dist/lib/components/extended/GaugeChart.svelte +113 -0
- package/dist/lib/components/extended/GaugeChart.svelte.d.ts +7 -0
- package/dist/lib/components/extended/HeatmapChart.svelte +159 -0
- package/dist/lib/components/extended/HeatmapChart.svelte.d.ts +7 -0
- package/dist/lib/components/extended/RadarChart.svelte +131 -0
- package/dist/lib/components/extended/RadarChart.svelte.d.ts +7 -0
- package/dist/lib/components/extended/SankeyChart.svelte +129 -0
- package/dist/lib/components/extended/SankeyChart.svelte.d.ts +7 -0
- package/dist/lib/components/extended/TreemapChart.svelte +133 -0
- package/dist/lib/components/extended/TreemapChart.svelte.d.ts +7 -0
- package/dist/lib/components/extended/index.d.ts +7 -0
- package/dist/lib/components/extended/index.js +7 -0
- package/dist/lib/components/index.d.ts +3 -0
- package/dist/lib/components/index.js +6 -0
- package/dist/lib/composables/index.d.ts +2 -0
- package/dist/lib/composables/index.js +2 -0
- package/dist/lib/composables/useChartTheme.svelte.d.ts +11 -0
- package/dist/lib/composables/useChartTheme.svelte.js +66 -0
- package/dist/lib/composables/useReducedMotion.svelte.d.ts +6 -0
- package/dist/lib/composables/useReducedMotion.svelte.js +26 -0
- package/dist/lib/index.d.ts +9 -0
- package/dist/lib/index.js +17 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/package.json +45 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as echarts from 'echarts/core';
|
|
3
|
+
import { GaugeChart as EChartsGaugeChart } from 'echarts/charts';
|
|
4
|
+
import { TitleComponent } from 'echarts/components';
|
|
5
|
+
import { CanvasRenderer } from 'echarts/renderers';
|
|
6
|
+
import type { EChartsOption } from 'echarts';
|
|
7
|
+
|
|
8
|
+
import type { GaugeChartProps } from '@classic-homes/charts-core';
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../utils.js';
|
|
11
|
+
import { useChartTheme } from '../../composables/useChartTheme.svelte.js';
|
|
12
|
+
import { useReducedMotion } from '../../composables/useReducedMotion.svelte.js';
|
|
13
|
+
import ChartContainer from '../base/ChartContainer.svelte';
|
|
14
|
+
|
|
15
|
+
echarts.use([EChartsGaugeChart, TitleComponent, CanvasRenderer]);
|
|
16
|
+
|
|
17
|
+
interface Props extends Omit<GaugeChartProps, 'class'> {
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
value,
|
|
25
|
+
min = 0,
|
|
26
|
+
max = 100,
|
|
27
|
+
height = 400,
|
|
28
|
+
loading,
|
|
29
|
+
error,
|
|
30
|
+
emptyMessage,
|
|
31
|
+
theme = 'auto',
|
|
32
|
+
animation = true,
|
|
33
|
+
splitNumber = 10,
|
|
34
|
+
showProgress = true,
|
|
35
|
+
formatValue,
|
|
36
|
+
class: className,
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
let containerEl: HTMLDivElement | null = $state(null);
|
|
40
|
+
let chart: echarts.ECharts | null = null;
|
|
41
|
+
|
|
42
|
+
const chartTheme = useChartTheme(() => theme);
|
|
43
|
+
const reducedMotion = useReducedMotion();
|
|
44
|
+
|
|
45
|
+
const option: EChartsOption = $derived({
|
|
46
|
+
series: [
|
|
47
|
+
{
|
|
48
|
+
type: 'gauge',
|
|
49
|
+
center: ['50%', '60%'],
|
|
50
|
+
radius: '80%',
|
|
51
|
+
min,
|
|
52
|
+
max,
|
|
53
|
+
splitNumber,
|
|
54
|
+
progress: showProgress ? { show: true, width: 18 } : undefined,
|
|
55
|
+
axisLine: { lineStyle: { width: 18 } },
|
|
56
|
+
axisTick: { distance: -30, splitNumber: 5, lineStyle: { width: 2, color: '#999' } },
|
|
57
|
+
splitLine: { distance: -32, length: 10, lineStyle: { width: 3, color: '#999' } },
|
|
58
|
+
axisLabel: { distance: -20, color: '#999', fontSize: 12 },
|
|
59
|
+
anchor: { show: true, size: 20, itemStyle: { borderWidth: 2 } },
|
|
60
|
+
pointer: { width: 6, length: '60%' },
|
|
61
|
+
title: { show: true, offsetCenter: [0, '70%'], fontSize: 14 },
|
|
62
|
+
detail: {
|
|
63
|
+
valueAnimation: animation && !reducedMotion.value,
|
|
64
|
+
fontSize: 28,
|
|
65
|
+
fontWeight: 'bold',
|
|
66
|
+
offsetCenter: [0, '40%'],
|
|
67
|
+
formatter: formatValue || '{value}',
|
|
68
|
+
},
|
|
69
|
+
data: [{ value, name: title }],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
animation: animation && !reducedMotion.value,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const accessibilityDescription = $derived(
|
|
76
|
+
description || `${title}. Gauge showing value ${value} on scale from ${min} to ${max}.`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Initialize chart instance (only depends on container and theme)
|
|
80
|
+
$effect(() => {
|
|
81
|
+
if (!containerEl) return;
|
|
82
|
+
if (chart) chart.dispose();
|
|
83
|
+
chart = echarts.init(containerEl, chartTheme.theme);
|
|
84
|
+
|
|
85
|
+
const handleResize = () => chart?.resize();
|
|
86
|
+
window.addEventListener('resize', handleResize);
|
|
87
|
+
|
|
88
|
+
return () => {
|
|
89
|
+
window.removeEventListener('resize', handleResize);
|
|
90
|
+
chart?.dispose();
|
|
91
|
+
chart = null;
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Update options when they change (handles both initial and subsequent updates)
|
|
96
|
+
$effect(() => {
|
|
97
|
+
if (chart) {
|
|
98
|
+
chart.setOption(option, { notMerge: true, lazyUpdate: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<ChartContainer
|
|
104
|
+
{title}
|
|
105
|
+
description={accessibilityDescription}
|
|
106
|
+
{height}
|
|
107
|
+
{loading}
|
|
108
|
+
{error}
|
|
109
|
+
{emptyMessage}
|
|
110
|
+
class={cn(className)}
|
|
111
|
+
>
|
|
112
|
+
<div bind:this={containerEl} class="h-full w-full"></div>
|
|
113
|
+
</ChartContainer>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { GaugeChartProps } from '@classic-homes/charts-core';
|
|
2
|
+
interface Props extends Omit<GaugeChartProps, 'class'> {
|
|
3
|
+
class?: string;
|
|
4
|
+
}
|
|
5
|
+
declare const GaugeChart: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type GaugeChart = ReturnType<typeof GaugeChart>;
|
|
7
|
+
export default GaugeChart;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as echarts from 'echarts/core';
|
|
3
|
+
import { HeatmapChart as EChartsHeatmapChart } from 'echarts/charts';
|
|
4
|
+
import {
|
|
5
|
+
GridComponent,
|
|
6
|
+
TooltipComponent,
|
|
7
|
+
TitleComponent,
|
|
8
|
+
VisualMapComponent,
|
|
9
|
+
} from 'echarts/components';
|
|
10
|
+
import { CanvasRenderer } from 'echarts/renderers';
|
|
11
|
+
import type { EChartsOption } from 'echarts';
|
|
12
|
+
|
|
13
|
+
import type { HeatmapChartProps, HeatmapEventParams } from '@classic-homes/charts-core';
|
|
14
|
+
|
|
15
|
+
import { cn } from '../../utils.js';
|
|
16
|
+
import { useChartTheme } from '../../composables/useChartTheme.svelte.js';
|
|
17
|
+
import { useReducedMotion } from '../../composables/useReducedMotion.svelte.js';
|
|
18
|
+
import ChartContainer from '../base/ChartContainer.svelte';
|
|
19
|
+
|
|
20
|
+
echarts.use([
|
|
21
|
+
EChartsHeatmapChart,
|
|
22
|
+
GridComponent,
|
|
23
|
+
TooltipComponent,
|
|
24
|
+
TitleComponent,
|
|
25
|
+
VisualMapComponent,
|
|
26
|
+
CanvasRenderer,
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
interface Props extends Omit<HeatmapChartProps, 'class'> {
|
|
30
|
+
class?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
title,
|
|
35
|
+
description,
|
|
36
|
+
data,
|
|
37
|
+
xAxis,
|
|
38
|
+
yAxis,
|
|
39
|
+
height = 400,
|
|
40
|
+
loading,
|
|
41
|
+
error,
|
|
42
|
+
emptyMessage,
|
|
43
|
+
theme = 'auto',
|
|
44
|
+
animation = true,
|
|
45
|
+
showTooltip = true,
|
|
46
|
+
showValues = false,
|
|
47
|
+
onClick,
|
|
48
|
+
class: className,
|
|
49
|
+
}: Props = $props();
|
|
50
|
+
|
|
51
|
+
let containerEl: HTMLDivElement | null = $state(null);
|
|
52
|
+
let chart: echarts.ECharts | null = null;
|
|
53
|
+
|
|
54
|
+
const chartTheme = useChartTheme(() => theme);
|
|
55
|
+
const reducedMotion = useReducedMotion();
|
|
56
|
+
|
|
57
|
+
const isEmpty = $derived(!data?.data?.length || !xAxis?.length || !yAxis?.length);
|
|
58
|
+
|
|
59
|
+
const values = $derived(data?.data?.map(([, , value]) => value) || []);
|
|
60
|
+
const minValue = $derived(data?.min ?? Math.min(...values));
|
|
61
|
+
const maxValue = $derived(data?.max ?? Math.max(...values));
|
|
62
|
+
|
|
63
|
+
const option: EChartsOption = $derived.by(() => {
|
|
64
|
+
if (isEmpty) return {};
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
tooltip: showTooltip
|
|
68
|
+
? {
|
|
69
|
+
position: 'top' as const,
|
|
70
|
+
formatter: (params: unknown) => {
|
|
71
|
+
const p = params as { value?: [number, number, number] };
|
|
72
|
+
if (!p.value) return '';
|
|
73
|
+
const [xIdx, yIdx, value] = p.value;
|
|
74
|
+
return `${xAxis[xIdx]} / ${yAxis[yIdx]}: ${value}`;
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
: undefined,
|
|
78
|
+
grid: { left: '3%', right: '10%', bottom: '3%', containLabel: true },
|
|
79
|
+
xAxis: { type: 'category', data: xAxis, splitArea: { show: true } },
|
|
80
|
+
yAxis: { type: 'category', data: yAxis, splitArea: { show: true } },
|
|
81
|
+
visualMap: {
|
|
82
|
+
min: minValue,
|
|
83
|
+
max: maxValue,
|
|
84
|
+
calculable: true,
|
|
85
|
+
orient: 'vertical',
|
|
86
|
+
right: '0%',
|
|
87
|
+
top: 'center',
|
|
88
|
+
},
|
|
89
|
+
series: [
|
|
90
|
+
{
|
|
91
|
+
name: title,
|
|
92
|
+
type: 'heatmap',
|
|
93
|
+
data: data.data,
|
|
94
|
+
label: showValues ? { show: true } : undefined,
|
|
95
|
+
emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0, 0, 0, 0.5)' } },
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
animation: animation && !reducedMotion.value,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const accessibilityDescription = $derived(
|
|
103
|
+
description ||
|
|
104
|
+
(!isEmpty
|
|
105
|
+
? `${title}. Heatmap with ${xAxis.length} columns and ${yAxis.length} rows.`
|
|
106
|
+
: undefined)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Initialize chart instance (only depends on container and theme)
|
|
110
|
+
$effect(() => {
|
|
111
|
+
if (!containerEl) return;
|
|
112
|
+
if (chart) chart.dispose();
|
|
113
|
+
chart = echarts.init(containerEl, chartTheme.theme);
|
|
114
|
+
|
|
115
|
+
if (onClick) {
|
|
116
|
+
chart.on('click', (params) => {
|
|
117
|
+
const [xIdx, yIdx, value] = (params.value as [number, number, number]) || [0, 0, 0];
|
|
118
|
+
onClick({
|
|
119
|
+
type: params.type || 'click',
|
|
120
|
+
componentType: params.componentType || 'series',
|
|
121
|
+
xIndex: xIdx,
|
|
122
|
+
yIndex: yIdx,
|
|
123
|
+
xLabel: xAxis[xIdx],
|
|
124
|
+
yLabel: yAxis[yIdx],
|
|
125
|
+
value,
|
|
126
|
+
} as HeatmapEventParams);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const handleResize = () => chart?.resize();
|
|
131
|
+
window.addEventListener('resize', handleResize);
|
|
132
|
+
|
|
133
|
+
return () => {
|
|
134
|
+
window.removeEventListener('resize', handleResize);
|
|
135
|
+
chart?.dispose();
|
|
136
|
+
chart = null;
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Update options when they change (handles both initial and subsequent updates)
|
|
141
|
+
$effect(() => {
|
|
142
|
+
if (chart && !isEmpty) {
|
|
143
|
+
chart.setOption(option, { notMerge: true, lazyUpdate: true });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<ChartContainer
|
|
149
|
+
{title}
|
|
150
|
+
description={accessibilityDescription}
|
|
151
|
+
{height}
|
|
152
|
+
{loading}
|
|
153
|
+
{error}
|
|
154
|
+
empty={isEmpty}
|
|
155
|
+
{emptyMessage}
|
|
156
|
+
class={cn(className)}
|
|
157
|
+
>
|
|
158
|
+
<div bind:this={containerEl} class="h-full w-full"></div>
|
|
159
|
+
</ChartContainer>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HeatmapChartProps } from '@classic-homes/charts-core';
|
|
2
|
+
interface Props extends Omit<HeatmapChartProps, 'class'> {
|
|
3
|
+
class?: string;
|
|
4
|
+
}
|
|
5
|
+
declare const HeatmapChart: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type HeatmapChart = ReturnType<typeof HeatmapChart>;
|
|
7
|
+
export default HeatmapChart;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as echarts from 'echarts/core';
|
|
3
|
+
import { RadarChart as EChartsRadarChart } from 'echarts/charts';
|
|
4
|
+
import {
|
|
5
|
+
TooltipComponent,
|
|
6
|
+
LegendComponent,
|
|
7
|
+
TitleComponent,
|
|
8
|
+
RadarComponent,
|
|
9
|
+
} from 'echarts/components';
|
|
10
|
+
import { CanvasRenderer } from 'echarts/renderers';
|
|
11
|
+
import type { EChartsOption } from 'echarts';
|
|
12
|
+
|
|
13
|
+
import type { RadarChartProps } from '@classic-homes/charts-core';
|
|
14
|
+
|
|
15
|
+
import { cn } from '../../utils.js';
|
|
16
|
+
import { useChartTheme } from '../../composables/useChartTheme.svelte.js';
|
|
17
|
+
import { useReducedMotion } from '../../composables/useReducedMotion.svelte.js';
|
|
18
|
+
import ChartContainer from '../base/ChartContainer.svelte';
|
|
19
|
+
|
|
20
|
+
echarts.use([
|
|
21
|
+
EChartsRadarChart,
|
|
22
|
+
TooltipComponent,
|
|
23
|
+
LegendComponent,
|
|
24
|
+
TitleComponent,
|
|
25
|
+
RadarComponent,
|
|
26
|
+
CanvasRenderer,
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
interface Props extends Omit<RadarChartProps, 'class'> {
|
|
30
|
+
class?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
title,
|
|
35
|
+
description,
|
|
36
|
+
data,
|
|
37
|
+
indicators,
|
|
38
|
+
height = 400,
|
|
39
|
+
loading,
|
|
40
|
+
error,
|
|
41
|
+
emptyMessage,
|
|
42
|
+
theme = 'auto',
|
|
43
|
+
animation = true,
|
|
44
|
+
showLegend = true,
|
|
45
|
+
showTooltip = true,
|
|
46
|
+
shape = 'polygon',
|
|
47
|
+
class: className,
|
|
48
|
+
}: Props = $props();
|
|
49
|
+
|
|
50
|
+
let containerEl: HTMLDivElement | null = $state(null);
|
|
51
|
+
let chart: echarts.ECharts | null = null;
|
|
52
|
+
|
|
53
|
+
const chartTheme = useChartTheme(() => theme);
|
|
54
|
+
const reducedMotion = useReducedMotion();
|
|
55
|
+
|
|
56
|
+
const isEmpty = $derived(
|
|
57
|
+
!data?.series?.length || !indicators?.length || data.series.every((s) => !s.data?.length)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const option: EChartsOption = $derived.by(() => {
|
|
61
|
+
if (isEmpty) return {};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
tooltip: showTooltip ? { trigger: 'item' } : undefined,
|
|
65
|
+
legend: showLegend ? { data: data.series.map((s) => s.name), bottom: 0 } : undefined,
|
|
66
|
+
radar: {
|
|
67
|
+
indicator: indicators.map((ind) => ({
|
|
68
|
+
name: ind.name,
|
|
69
|
+
max: ind.max,
|
|
70
|
+
min: ind.min,
|
|
71
|
+
})),
|
|
72
|
+
shape,
|
|
73
|
+
},
|
|
74
|
+
series: [
|
|
75
|
+
{
|
|
76
|
+
type: 'radar',
|
|
77
|
+
data: data.series.map((series) => ({
|
|
78
|
+
name: series.name,
|
|
79
|
+
value: series.data,
|
|
80
|
+
itemStyle: series.color ? { color: series.color } : undefined,
|
|
81
|
+
areaStyle: { opacity: 0.2 },
|
|
82
|
+
})),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
animation: animation && !reducedMotion.value,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const accessibilityDescription = $derived(
|
|
90
|
+
description ||
|
|
91
|
+
(!isEmpty
|
|
92
|
+
? `${title}. Radar chart with ${indicators.length} dimensions comparing ${data.series.length} series.`
|
|
93
|
+
: undefined)
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Initialize chart instance (only depends on container and theme)
|
|
97
|
+
$effect(() => {
|
|
98
|
+
if (!containerEl) return;
|
|
99
|
+
if (chart) chart.dispose();
|
|
100
|
+
chart = echarts.init(containerEl, chartTheme.theme);
|
|
101
|
+
|
|
102
|
+
const handleResize = () => chart?.resize();
|
|
103
|
+
window.addEventListener('resize', handleResize);
|
|
104
|
+
|
|
105
|
+
return () => {
|
|
106
|
+
window.removeEventListener('resize', handleResize);
|
|
107
|
+
chart?.dispose();
|
|
108
|
+
chart = null;
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Update options when they change (handles both initial and subsequent updates)
|
|
113
|
+
$effect(() => {
|
|
114
|
+
if (chart && !isEmpty) {
|
|
115
|
+
chart.setOption(option, { notMerge: true, lazyUpdate: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<ChartContainer
|
|
121
|
+
{title}
|
|
122
|
+
description={accessibilityDescription}
|
|
123
|
+
{height}
|
|
124
|
+
{loading}
|
|
125
|
+
{error}
|
|
126
|
+
empty={isEmpty}
|
|
127
|
+
{emptyMessage}
|
|
128
|
+
class={cn(className)}
|
|
129
|
+
>
|
|
130
|
+
<div bind:this={containerEl} class="h-full w-full"></div>
|
|
131
|
+
</ChartContainer>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RadarChartProps } from '@classic-homes/charts-core';
|
|
2
|
+
interface Props extends Omit<RadarChartProps, 'class'> {
|
|
3
|
+
class?: string;
|
|
4
|
+
}
|
|
5
|
+
declare const RadarChart: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type RadarChart = ReturnType<typeof RadarChart>;
|
|
7
|
+
export default RadarChart;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as echarts from 'echarts/core';
|
|
3
|
+
import { SankeyChart as EChartsSankeyChart } from 'echarts/charts';
|
|
4
|
+
import { TooltipComponent, TitleComponent } from 'echarts/components';
|
|
5
|
+
import { CanvasRenderer } from 'echarts/renderers';
|
|
6
|
+
import type { EChartsOption } from 'echarts';
|
|
7
|
+
|
|
8
|
+
import type { SankeyChartProps, SankeyEventParams } from '@classic-homes/charts-core';
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../utils.js';
|
|
11
|
+
import { useChartTheme } from '../../composables/useChartTheme.svelte.js';
|
|
12
|
+
import { useReducedMotion } from '../../composables/useReducedMotion.svelte.js';
|
|
13
|
+
import ChartContainer from '../base/ChartContainer.svelte';
|
|
14
|
+
|
|
15
|
+
echarts.use([EChartsSankeyChart, TooltipComponent, TitleComponent, CanvasRenderer]);
|
|
16
|
+
|
|
17
|
+
interface Props extends Omit<SankeyChartProps, 'class'> {
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
data,
|
|
25
|
+
height = 400,
|
|
26
|
+
loading,
|
|
27
|
+
error,
|
|
28
|
+
emptyMessage,
|
|
29
|
+
theme = 'auto',
|
|
30
|
+
animation = true,
|
|
31
|
+
showTooltip = true,
|
|
32
|
+
orient = 'horizontal',
|
|
33
|
+
onClick,
|
|
34
|
+
class: className,
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
let containerEl: HTMLDivElement | null = $state(null);
|
|
38
|
+
let chart: echarts.ECharts | null = null;
|
|
39
|
+
|
|
40
|
+
const chartTheme = useChartTheme(() => theme);
|
|
41
|
+
const reducedMotion = useReducedMotion();
|
|
42
|
+
|
|
43
|
+
const isEmpty = $derived(!data?.nodes?.length || !data?.links?.length);
|
|
44
|
+
|
|
45
|
+
const option: EChartsOption = $derived.by(() => {
|
|
46
|
+
if (isEmpty) return {};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
tooltip: showTooltip ? { trigger: 'item', triggerOn: 'mousemove' } : undefined,
|
|
50
|
+
series: [
|
|
51
|
+
{
|
|
52
|
+
type: 'sankey',
|
|
53
|
+
orient,
|
|
54
|
+
emphasis: { focus: 'adjacency' },
|
|
55
|
+
data: data.nodes.map((node) => ({
|
|
56
|
+
name: node.name,
|
|
57
|
+
itemStyle: node.color ? { color: node.color } : undefined,
|
|
58
|
+
})),
|
|
59
|
+
links: data.links.map((link) => ({
|
|
60
|
+
source: link.source,
|
|
61
|
+
target: link.target,
|
|
62
|
+
value: link.value,
|
|
63
|
+
})),
|
|
64
|
+
lineStyle: { color: 'gradient', curveness: 0.5 },
|
|
65
|
+
label: { position: orient === 'horizontal' ? 'right' : 'bottom' },
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
animation: animation && !reducedMotion.value,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const accessibilityDescription = $derived(
|
|
73
|
+
description ||
|
|
74
|
+
(!isEmpty
|
|
75
|
+
? `${title}. Sankey diagram with ${data.nodes.length} nodes and ${data.links.length} links.`
|
|
76
|
+
: undefined)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Initialize chart instance (only depends on container and theme)
|
|
80
|
+
$effect(() => {
|
|
81
|
+
if (!containerEl) return;
|
|
82
|
+
if (chart) chart.dispose();
|
|
83
|
+
chart = echarts.init(containerEl, chartTheme.theme);
|
|
84
|
+
|
|
85
|
+
if (onClick) {
|
|
86
|
+
chart.on('click', (params) => {
|
|
87
|
+
const isEdge = params.dataType === 'edge';
|
|
88
|
+
onClick({
|
|
89
|
+
type: params.type || 'click',
|
|
90
|
+
componentType: params.componentType || 'series',
|
|
91
|
+
dataType: isEdge ? 'edge' : 'node',
|
|
92
|
+
name: params.name || '',
|
|
93
|
+
value: isEdge ? (params.value as number) : undefined,
|
|
94
|
+
source: isEdge ? (params.data as { source?: string })?.source : undefined,
|
|
95
|
+
target: isEdge ? (params.data as { target?: string })?.target : undefined,
|
|
96
|
+
} as SankeyEventParams);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const handleResize = () => chart?.resize();
|
|
101
|
+
window.addEventListener('resize', handleResize);
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
window.removeEventListener('resize', handleResize);
|
|
105
|
+
chart?.dispose();
|
|
106
|
+
chart = null;
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Update options when they change (handles both initial and subsequent updates)
|
|
111
|
+
$effect(() => {
|
|
112
|
+
if (chart && !isEmpty) {
|
|
113
|
+
chart.setOption(option, { notMerge: true, lazyUpdate: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<ChartContainer
|
|
119
|
+
{title}
|
|
120
|
+
description={accessibilityDescription}
|
|
121
|
+
{height}
|
|
122
|
+
{loading}
|
|
123
|
+
{error}
|
|
124
|
+
empty={isEmpty}
|
|
125
|
+
{emptyMessage}
|
|
126
|
+
class={cn(className)}
|
|
127
|
+
>
|
|
128
|
+
<div bind:this={containerEl} class="h-full w-full"></div>
|
|
129
|
+
</ChartContainer>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SankeyChartProps } from '@classic-homes/charts-core';
|
|
2
|
+
interface Props extends Omit<SankeyChartProps, 'class'> {
|
|
3
|
+
class?: string;
|
|
4
|
+
}
|
|
5
|
+
declare const SankeyChart: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type SankeyChart = ReturnType<typeof SankeyChart>;
|
|
7
|
+
export default SankeyChart;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as echarts from 'echarts/core';
|
|
3
|
+
import { TreemapChart as EChartsTreemapChart } from 'echarts/charts';
|
|
4
|
+
import { TooltipComponent, TitleComponent } from 'echarts/components';
|
|
5
|
+
import { CanvasRenderer } from 'echarts/renderers';
|
|
6
|
+
import type { EChartsOption } from 'echarts';
|
|
7
|
+
|
|
8
|
+
import type { TreemapChartProps, TreemapEventParams } from '@classic-homes/charts-core';
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../utils.js';
|
|
11
|
+
import { useChartTheme } from '../../composables/useChartTheme.svelte.js';
|
|
12
|
+
import { useReducedMotion } from '../../composables/useReducedMotion.svelte.js';
|
|
13
|
+
import ChartContainer from '../base/ChartContainer.svelte';
|
|
14
|
+
|
|
15
|
+
echarts.use([EChartsTreemapChart, TooltipComponent, TitleComponent, CanvasRenderer]);
|
|
16
|
+
|
|
17
|
+
interface Props extends Omit<TreemapChartProps, 'class'> {
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
data,
|
|
25
|
+
levels,
|
|
26
|
+
height = 400,
|
|
27
|
+
loading,
|
|
28
|
+
error,
|
|
29
|
+
emptyMessage,
|
|
30
|
+
theme = 'auto',
|
|
31
|
+
animation = true,
|
|
32
|
+
showTooltip = true,
|
|
33
|
+
onClick,
|
|
34
|
+
class: className,
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
let containerEl: HTMLDivElement | null = $state(null);
|
|
38
|
+
let chart: echarts.ECharts | null = null;
|
|
39
|
+
|
|
40
|
+
const chartTheme = useChartTheme(() => theme);
|
|
41
|
+
const reducedMotion = useReducedMotion();
|
|
42
|
+
|
|
43
|
+
const isEmpty = $derived(!data?.length);
|
|
44
|
+
|
|
45
|
+
const option: EChartsOption = $derived.by(() => {
|
|
46
|
+
if (isEmpty) return {};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
tooltip: showTooltip
|
|
50
|
+
? {
|
|
51
|
+
formatter: (params: unknown) => {
|
|
52
|
+
const p = params as { name?: string; value?: number };
|
|
53
|
+
return `${p.name}: ${p.value}`;
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
: undefined,
|
|
57
|
+
series: [
|
|
58
|
+
{
|
|
59
|
+
type: 'treemap',
|
|
60
|
+
data,
|
|
61
|
+
roam: false,
|
|
62
|
+
levels: (levels || [
|
|
63
|
+
{ itemStyle: { borderColor: '#555', borderWidth: 4, gapWidth: 4 } },
|
|
64
|
+
{
|
|
65
|
+
colorSaturation: [0.3, 0.6],
|
|
66
|
+
itemStyle: { borderColorSaturation: 0.7, gapWidth: 2, borderWidth: 2 },
|
|
67
|
+
},
|
|
68
|
+
{ colorSaturation: [0.3, 0.5], itemStyle: { borderColorSaturation: 0.6, gapWidth: 1 } },
|
|
69
|
+
{ colorSaturation: [0.3, 0.5] },
|
|
70
|
+
]) as unknown[],
|
|
71
|
+
label: { show: true, formatter: '{b}' },
|
|
72
|
+
upperLabel: { show: true, height: 30 },
|
|
73
|
+
breadcrumb: { show: true },
|
|
74
|
+
},
|
|
75
|
+
] as EChartsOption['series'],
|
|
76
|
+
animation: animation && !reducedMotion.value,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const accessibilityDescription = $derived(
|
|
81
|
+
description || (!isEmpty ? `${title}. Treemap chart showing hierarchical data.` : undefined)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Initialize chart instance (only depends on container and theme)
|
|
85
|
+
$effect(() => {
|
|
86
|
+
if (!containerEl) return;
|
|
87
|
+
if (chart) chart.dispose();
|
|
88
|
+
chart = echarts.init(containerEl, chartTheme.theme);
|
|
89
|
+
|
|
90
|
+
if (onClick) {
|
|
91
|
+
chart.on('click', (params) => {
|
|
92
|
+
const treePathInfo = params.treePathInfo || [];
|
|
93
|
+
onClick({
|
|
94
|
+
type: params.type || 'click',
|
|
95
|
+
componentType: params.componentType || 'series',
|
|
96
|
+
name: params.name || '',
|
|
97
|
+
value: params.value as number,
|
|
98
|
+
path: treePathInfo.map((item: { name?: string }) => item.name || ''),
|
|
99
|
+
depth: treePathInfo.length - 1,
|
|
100
|
+
} as TreemapEventParams);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const handleResize = () => chart?.resize();
|
|
105
|
+
window.addEventListener('resize', handleResize);
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
window.removeEventListener('resize', handleResize);
|
|
109
|
+
chart?.dispose();
|
|
110
|
+
chart = null;
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Update options when they change (handles both initial and subsequent updates)
|
|
115
|
+
$effect(() => {
|
|
116
|
+
if (chart && !isEmpty) {
|
|
117
|
+
chart.setOption(option, { notMerge: true, lazyUpdate: true });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<ChartContainer
|
|
123
|
+
{title}
|
|
124
|
+
description={accessibilityDescription}
|
|
125
|
+
{height}
|
|
126
|
+
{loading}
|
|
127
|
+
{error}
|
|
128
|
+
empty={isEmpty}
|
|
129
|
+
{emptyMessage}
|
|
130
|
+
class={cn(className)}
|
|
131
|
+
>
|
|
132
|
+
<div bind:this={containerEl} class="h-full w-full"></div>
|
|
133
|
+
</ChartContainer>
|