@classic-homes/charts-svelte 0.1.37 → 0.2.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.
- package/dist/lib/components/base/ChartContainer.svelte +51 -11
- package/dist/lib/components/base/ChartContainer.svelte.d.ts +8 -0
- package/dist/lib/components/base/ChartError.svelte +3 -1
- package/dist/lib/components/base/ChartLegend.svelte +59 -0
- package/dist/lib/components/base/ChartLegend.svelte.d.ts +10 -0
- package/dist/lib/components/base/index.d.ts +12 -0
- package/dist/lib/components/base/index.js +1 -0
- package/dist/lib/components/core/AreaChart.svelte +126 -61
- package/dist/lib/components/core/AreaChart.svelte.d.ts +2 -1
- package/dist/lib/components/core/BarChart.svelte +91 -35
- package/dist/lib/components/core/BarChart.svelte.d.ts +2 -1
- package/dist/lib/components/core/DonutChart.svelte +155 -82
- package/dist/lib/components/core/DonutChart.svelte.d.ts +2 -1
- package/dist/lib/components/core/LineChart.svelte +118 -48
- package/dist/lib/components/core/LineChart.svelte.d.ts +2 -1
- package/dist/lib/components/core/PieChart.svelte +100 -38
- package/dist/lib/components/core/PieChart.svelte.d.ts +2 -1
- package/dist/lib/components/core/ScatterChart.svelte +115 -38
- package/dist/lib/components/core/ScatterChart.svelte.d.ts +2 -1
- package/dist/lib/components/extended/CandlestickChart.svelte +53 -26
- package/dist/lib/components/extended/FunnelChart.svelte +102 -32
- package/dist/lib/components/extended/FunnelChart.svelte.d.ts +2 -1
- package/dist/lib/components/extended/GaugeChart.svelte +62 -29
- package/dist/lib/components/extended/HeatmapChart.svelte +59 -29
- package/dist/lib/components/extended/RadarChart.svelte +76 -22
- package/dist/lib/components/extended/RadarChart.svelte.d.ts +2 -1
- package/dist/lib/components/extended/SankeyChart.svelte +50 -21
- package/dist/lib/components/extended/TreemapChart.svelte +71 -26
- package/dist/lib/components/index.d.ts +1 -1
- package/dist/lib/components/index.js +1 -1
- package/dist/lib/composables/index.d.ts +1 -0
- package/dist/lib/composables/index.js +1 -0
- package/dist/lib/composables/useChart.svelte.js +20 -3
- package/dist/lib/composables/useChartLegend.svelte.d.ts +37 -0
- package/dist/lib/composables/useChartLegend.svelte.js +54 -0
- package/dist/lib/composables/useChartTheme.svelte.d.ts +2 -1
- package/dist/lib/composables/useChartTheme.svelte.js +2 -6
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +2 -2
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BarChartProps } from '@classic-homes/charts-core';
|
|
2
|
-
|
|
2
|
+
import { type CustomLegendProps } from '../../composables/useChartLegend.svelte.js';
|
|
3
|
+
interface Props extends Omit<BarChartProps, 'class'>, CustomLegendProps {
|
|
3
4
|
class?: string;
|
|
4
5
|
}
|
|
5
6
|
declare const BarChart: import("svelte").Component<Props, {}, "">;
|
|
@@ -6,16 +6,33 @@
|
|
|
6
6
|
import type { EChartsOption } from 'echarts';
|
|
7
7
|
|
|
8
8
|
import type { DonutChartProps, SliceEventParams } from '@classic-homes/charts-core';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
buildDataLabel,
|
|
11
|
+
buildLegend,
|
|
12
|
+
buildTooltip,
|
|
13
|
+
escapeHtml,
|
|
14
|
+
formatTooltipValue,
|
|
15
|
+
generatePieChartDescription,
|
|
16
|
+
getChartColors,
|
|
17
|
+
itemLabelLookup,
|
|
18
|
+
anchorCenterGraphic,
|
|
19
|
+
legendCenter,
|
|
20
|
+
mergeChartOption,
|
|
21
|
+
pointLabel,
|
|
22
|
+
} from '@classic-homes/charts-core';
|
|
10
23
|
|
|
11
24
|
import { cn } from '../../utils.js';
|
|
12
25
|
import { useChart } from '../../composables/useChart.svelte.js';
|
|
26
|
+
import {
|
|
27
|
+
useChartLegend,
|
|
28
|
+
type CustomLegendProps,
|
|
29
|
+
} from '../../composables/useChartLegend.svelte.js';
|
|
13
30
|
import ChartContainer from '../base/ChartContainer.svelte';
|
|
14
31
|
|
|
15
32
|
// Register required ECharts modules
|
|
16
33
|
echarts.use([EChartsPieChart, TooltipComponent, LegendComponent, TitleComponent, CanvasRenderer]);
|
|
17
34
|
|
|
18
|
-
interface Props extends Omit<DonutChartProps, 'class'
|
|
35
|
+
interface Props extends Omit<DonutChartProps, 'class'>, CustomLegendProps {
|
|
19
36
|
class?: string;
|
|
20
37
|
}
|
|
21
38
|
|
|
@@ -36,114 +53,163 @@
|
|
|
36
53
|
innerRadius = '50%',
|
|
37
54
|
centerLabel,
|
|
38
55
|
centerValue,
|
|
56
|
+
labelFormatter,
|
|
57
|
+
tooltipFormatter,
|
|
58
|
+
tooltipValueFormatter,
|
|
59
|
+
legend: legendConfig,
|
|
60
|
+
renderLegend,
|
|
61
|
+
onLegendChange,
|
|
62
|
+
option: optionOverride,
|
|
39
63
|
onClick,
|
|
40
64
|
class: className,
|
|
41
65
|
}: Props = $props();
|
|
42
66
|
|
|
43
67
|
const isEmpty = $derived(!data?.length);
|
|
44
68
|
|
|
69
|
+
// Per-element colours that follow the resolved chart theme
|
|
70
|
+
const chartColors = $derived.by(() => getChartColors(chart.theme));
|
|
71
|
+
|
|
45
72
|
const option: EChartsOption = $derived.by(() => {
|
|
46
73
|
if (isEmpty) return {};
|
|
47
74
|
|
|
75
|
+
const getLabel = itemLabelLookup(data);
|
|
76
|
+
|
|
77
|
+
const pieCenter = legendCenter(legendConfig, showLegend && !renderLegend, ['50%', '45%']);
|
|
48
78
|
const graphicElements: unknown[] = [];
|
|
49
79
|
|
|
50
80
|
if (centerLabel || centerValue) {
|
|
51
|
-
graphicElements.push(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
graphicElements.push(
|
|
82
|
+
anchorCenterGraphic(
|
|
83
|
+
{
|
|
84
|
+
type: 'group',
|
|
85
|
+
left: 'center',
|
|
86
|
+
top: '40%',
|
|
87
|
+
children: [
|
|
88
|
+
...(centerValue !== undefined
|
|
89
|
+
? [
|
|
90
|
+
{
|
|
91
|
+
type: 'text',
|
|
92
|
+
style: {
|
|
93
|
+
text: String(centerValue),
|
|
94
|
+
fontSize: 28,
|
|
95
|
+
fontWeight: 'bold',
|
|
96
|
+
fill: chartColors.foreground,
|
|
97
|
+
align: 'center',
|
|
98
|
+
},
|
|
99
|
+
top: 0,
|
|
100
|
+
left: 'center',
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
: []),
|
|
104
|
+
...(centerLabel
|
|
105
|
+
? [
|
|
106
|
+
{
|
|
107
|
+
type: 'text',
|
|
108
|
+
style: {
|
|
109
|
+
text: centerLabel,
|
|
110
|
+
fontSize: 14,
|
|
111
|
+
fill: chartColors.mutedForeground,
|
|
112
|
+
align: 'center',
|
|
113
|
+
},
|
|
114
|
+
top: centerValue !== undefined ? 35 : 0,
|
|
115
|
+
left: 'center',
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
: []),
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
pieCenter,
|
|
122
|
+
['50%', '45%']
|
|
123
|
+
)
|
|
124
|
+
);
|
|
89
125
|
}
|
|
90
126
|
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
|
|
127
|
+
return mergeChartOption(
|
|
128
|
+
{
|
|
129
|
+
tooltip: buildTooltip(
|
|
130
|
+
{
|
|
94
131
|
trigger: 'item',
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
132
|
+
// A string template ignores valueFormatter, so format the value here
|
|
133
|
+
formatter:
|
|
134
|
+
tooltipValueFormatter && !tooltipFormatter
|
|
135
|
+
? (params: unknown) => {
|
|
136
|
+
const p = params as {
|
|
137
|
+
seriesName?: string;
|
|
138
|
+
name?: string;
|
|
139
|
+
value?: unknown;
|
|
140
|
+
percent?: number;
|
|
141
|
+
};
|
|
142
|
+
return `${escapeHtml(p.seriesName)} <br/>${escapeHtml(p.name)}: ${escapeHtml(formatTooltipValue(p.value, tooltipValueFormatter))} (${p.percent}%)`;
|
|
143
|
+
}
|
|
144
|
+
: '{a} <br/>{b}: {c} ({d}%)',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
show: showTooltip,
|
|
148
|
+
formatter: tooltipFormatter,
|
|
149
|
+
valueFormatter: tooltipValueFormatter,
|
|
150
|
+
getLabel,
|
|
103
151
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
152
|
+
),
|
|
153
|
+
legend: buildLegend(
|
|
154
|
+
data.map((d) => d.name),
|
|
155
|
+
legendConfig,
|
|
156
|
+
{ show: showLegend, custom: !!renderLegend, defaults: { orient: 'horizontal' } }
|
|
157
|
+
),
|
|
158
|
+
graphic:
|
|
159
|
+
graphicElements.length > 0 ? (graphicElements as EChartsOption['graphic']) : undefined,
|
|
160
|
+
series: [
|
|
161
|
+
{
|
|
162
|
+
name: title,
|
|
163
|
+
type: 'pie',
|
|
164
|
+
radius: [innerRadius, '70%'],
|
|
165
|
+
center: pieCenter,
|
|
166
|
+
avoidLabelOverlap: true,
|
|
167
|
+
data: data.map((item) => ({
|
|
168
|
+
name: item.name,
|
|
169
|
+
value: item.value,
|
|
170
|
+
itemStyle: item.color ? { color: item.color } : undefined,
|
|
171
|
+
label: pointLabel(item.label, labelFormatter, { position: labelPosition }),
|
|
172
|
+
})),
|
|
173
|
+
label: buildDataLabel(
|
|
174
|
+
{
|
|
121
175
|
show: true,
|
|
122
176
|
position: labelPosition,
|
|
123
177
|
formatter: labelPosition === 'inside' ? '{d}%' : '{b}: {d}%',
|
|
124
|
-
}
|
|
125
|
-
: {
|
|
126
|
-
show: false,
|
|
127
178
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
179
|
+
{ show: false },
|
|
180
|
+
{ show: showLabels, formatter: labelFormatter, getLabel }
|
|
181
|
+
),
|
|
182
|
+
labelLine: {
|
|
183
|
+
show: showLabels && labelPosition === 'outside',
|
|
184
|
+
},
|
|
185
|
+
emphasis: {
|
|
186
|
+
itemStyle: {
|
|
187
|
+
shadowBlur: 10,
|
|
188
|
+
shadowOffsetX: 0,
|
|
189
|
+
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
190
|
+
},
|
|
136
191
|
},
|
|
137
192
|
},
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
193
|
+
],
|
|
194
|
+
} as EChartsOption,
|
|
195
|
+
optionOverride
|
|
196
|
+
);
|
|
141
197
|
});
|
|
142
198
|
|
|
143
199
|
const accessibilityDescription = $derived(
|
|
144
200
|
description || (!isEmpty ? generatePieChartDescription(title, data, true) : undefined)
|
|
145
201
|
);
|
|
146
202
|
|
|
203
|
+
// Legend selection state, shared with a custom legend
|
|
204
|
+
const legend = useChartLegend({
|
|
205
|
+
names: () => data?.map((d) => d.name) ?? [],
|
|
206
|
+
colors: () => data?.map((d) => d.color) ?? [],
|
|
207
|
+
palette: () => getChartColors(chart.theme).series,
|
|
208
|
+
config: () => legendConfig,
|
|
209
|
+
title: () => title,
|
|
210
|
+
onLegendChange: () => onLegendChange,
|
|
211
|
+
});
|
|
212
|
+
|
|
147
213
|
// Manage ECharts instance lifecycle (init, theme, resize, option updates)
|
|
148
214
|
const chart = useChart({
|
|
149
215
|
option: () => option,
|
|
@@ -151,6 +217,7 @@
|
|
|
151
217
|
animation: () => animation,
|
|
152
218
|
isEmpty: () => isEmpty,
|
|
153
219
|
onReady: (instance) => {
|
|
220
|
+
legend.attach(instance);
|
|
154
221
|
if (onClick) {
|
|
155
222
|
instance.on('click', (params) => {
|
|
156
223
|
onClick({
|
|
@@ -167,6 +234,10 @@
|
|
|
167
234
|
});
|
|
168
235
|
</script>
|
|
169
236
|
|
|
237
|
+
{#snippet customLegend()}
|
|
238
|
+
{@render renderLegend?.(legend.renderProps)}
|
|
239
|
+
{/snippet}
|
|
240
|
+
|
|
170
241
|
<ChartContainer
|
|
171
242
|
{title}
|
|
172
243
|
description={accessibilityDescription}
|
|
@@ -176,6 +247,8 @@
|
|
|
176
247
|
empty={isEmpty}
|
|
177
248
|
{emptyMessage}
|
|
178
249
|
class={cn(className)}
|
|
250
|
+
legend={renderLegend ? customLegend : undefined}
|
|
251
|
+
legendPosition={legendConfig?.position}
|
|
179
252
|
>
|
|
180
253
|
<div bind:this={chart.container} class="h-full w-full"></div>
|
|
181
254
|
</ChartContainer>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DonutChartProps } from '@classic-homes/charts-core';
|
|
2
|
-
|
|
2
|
+
import { type CustomLegendProps } from '../../composables/useChartLegend.svelte.js';
|
|
3
|
+
interface Props extends Omit<DonutChartProps, 'class'>, CustomLegendProps {
|
|
3
4
|
class?: string;
|
|
4
5
|
}
|
|
5
6
|
declare const DonutChart: import("svelte").Component<Props, {}, "">;
|
|
@@ -11,10 +11,26 @@
|
|
|
11
11
|
import type { EChartsOption } from 'echarts';
|
|
12
12
|
|
|
13
13
|
import type { LineChartProps, DataPointEventParams } from '@classic-homes/charts-core';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
buildAxis,
|
|
16
|
+
buildDataLabel,
|
|
17
|
+
buildLegend,
|
|
18
|
+
buildTooltip,
|
|
19
|
+
generateLineChartDescription,
|
|
20
|
+
getChartColors,
|
|
21
|
+
hasPointLabels,
|
|
22
|
+
gridInset,
|
|
23
|
+
mergeChartOption,
|
|
24
|
+
seriesLabelLookup,
|
|
25
|
+
withPointLabels,
|
|
26
|
+
} from '@classic-homes/charts-core';
|
|
15
27
|
|
|
16
28
|
import { cn } from '../../utils.js';
|
|
17
29
|
import { useChart } from '../../composables/useChart.svelte.js';
|
|
30
|
+
import {
|
|
31
|
+
useChartLegend,
|
|
32
|
+
type CustomLegendProps,
|
|
33
|
+
} from '../../composables/useChartLegend.svelte.js';
|
|
18
34
|
import ChartContainer from '../base/ChartContainer.svelte';
|
|
19
35
|
|
|
20
36
|
// Register required ECharts modules
|
|
@@ -27,7 +43,7 @@
|
|
|
27
43
|
CanvasRenderer,
|
|
28
44
|
]);
|
|
29
45
|
|
|
30
|
-
interface Props extends Omit<LineChartProps, 'class'
|
|
46
|
+
interface Props extends Omit<LineChartProps, 'class'>, CustomLegendProps {
|
|
31
47
|
class?: string;
|
|
32
48
|
}
|
|
33
49
|
|
|
@@ -48,6 +64,16 @@
|
|
|
48
64
|
areaFilled = false,
|
|
49
65
|
showDataPoints = false,
|
|
50
66
|
stacked = false,
|
|
67
|
+
showLabels = false,
|
|
68
|
+
labelFormatter,
|
|
69
|
+
xAxisConfig,
|
|
70
|
+
yAxisConfig,
|
|
71
|
+
tooltipFormatter,
|
|
72
|
+
tooltipValueFormatter,
|
|
73
|
+
legend: legendConfig,
|
|
74
|
+
renderLegend,
|
|
75
|
+
onLegendChange,
|
|
76
|
+
option: optionOverride,
|
|
51
77
|
onClick,
|
|
52
78
|
onHover,
|
|
53
79
|
class: className,
|
|
@@ -57,68 +83,105 @@
|
|
|
57
83
|
!data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length)
|
|
58
84
|
);
|
|
59
85
|
|
|
86
|
+
// Per-element colours that follow the resolved chart theme
|
|
87
|
+
const chartColors = $derived.by(() => getChartColors(chart.theme));
|
|
88
|
+
|
|
60
89
|
const option: EChartsOption = $derived.by(() => {
|
|
61
90
|
if (isEmpty) return {};
|
|
62
91
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
const getLabel = seriesLabelLookup(data.series);
|
|
93
|
+
// ECharts hides line labels along with the symbol
|
|
94
|
+
const showSymbol = showDataPoints || showLabels || hasPointLabels(data.series);
|
|
95
|
+
|
|
96
|
+
return mergeChartOption(
|
|
97
|
+
{
|
|
98
|
+
grid: {
|
|
99
|
+
left: '3%',
|
|
100
|
+
right: '4%',
|
|
101
|
+
bottom: '3%',
|
|
102
|
+
containLabel: true,
|
|
103
|
+
...gridInset(legendConfig, showLegend && !renderLegend, xAxisConfig, yAxisConfig),
|
|
104
|
+
},
|
|
105
|
+
tooltip: buildTooltip(
|
|
106
|
+
{
|
|
72
107
|
trigger: 'axis',
|
|
73
108
|
axisPointer: {
|
|
74
109
|
type: 'cross',
|
|
75
110
|
label: {
|
|
76
|
-
backgroundColor:
|
|
111
|
+
backgroundColor: chartColors.axisPointerLabel,
|
|
77
112
|
},
|
|
78
113
|
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
show: showTooltip,
|
|
117
|
+
formatter: tooltipFormatter,
|
|
118
|
+
valueFormatter: tooltipValueFormatter,
|
|
119
|
+
getLabel,
|
|
79
120
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
),
|
|
122
|
+
legend: buildLegend(
|
|
123
|
+
data.series.map((s) => s.name),
|
|
124
|
+
legendConfig,
|
|
125
|
+
{ show: showLegend, custom: !!renderLegend }
|
|
126
|
+
),
|
|
127
|
+
xAxis: buildAxis(
|
|
128
|
+
{
|
|
129
|
+
type: 'category',
|
|
130
|
+
boundaryGap: false,
|
|
131
|
+
data: data.categories,
|
|
132
|
+
splitLine: {
|
|
133
|
+
show: showGrid,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
xAxisConfig
|
|
137
|
+
),
|
|
138
|
+
yAxis: buildAxis(
|
|
139
|
+
{
|
|
140
|
+
type: 'value',
|
|
141
|
+
splitLine: {
|
|
142
|
+
show: showGrid,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
yAxisConfig
|
|
146
|
+
),
|
|
147
|
+
series: data.series.map((series) => ({
|
|
148
|
+
name: series.name,
|
|
149
|
+
type: 'line',
|
|
150
|
+
data: withPointLabels(series.data, series.labels, labelFormatter, { position: 'top' }),
|
|
151
|
+
smooth,
|
|
152
|
+
showSymbol,
|
|
153
|
+
symbolSize: showDataPoints ? 6 : 0,
|
|
154
|
+
stack: stacked ? 'Total' : undefined,
|
|
155
|
+
areaStyle: areaFilled ? { opacity: 0.3 } : undefined,
|
|
156
|
+
itemStyle: series.color ? { color: series.color } : undefined,
|
|
157
|
+
label: buildDataLabel({ show: true, position: 'top' }, undefined, {
|
|
158
|
+
show: showLabels,
|
|
159
|
+
formatter: labelFormatter,
|
|
160
|
+
getLabel,
|
|
161
|
+
}),
|
|
162
|
+
emphasis: {
|
|
163
|
+
focus: 'series',
|
|
164
|
+
},
|
|
165
|
+
})),
|
|
166
|
+
} as EChartsOption,
|
|
167
|
+
optionOverride
|
|
168
|
+
);
|
|
116
169
|
});
|
|
117
170
|
|
|
118
171
|
const accessibilityDescription = $derived(
|
|
119
172
|
description || (!isEmpty ? generateLineChartDescription(title, data) : undefined)
|
|
120
173
|
);
|
|
121
174
|
|
|
175
|
+
// Legend selection state, shared with a custom legend
|
|
176
|
+
const legend = useChartLegend({
|
|
177
|
+
names: () => data?.series?.map((s) => s.name) ?? [],
|
|
178
|
+
colors: () => data?.series?.map((s) => s.color) ?? [],
|
|
179
|
+
palette: () => getChartColors(chart.theme).series,
|
|
180
|
+
config: () => legendConfig,
|
|
181
|
+
title: () => title,
|
|
182
|
+
onLegendChange: () => onLegendChange,
|
|
183
|
+
});
|
|
184
|
+
|
|
122
185
|
// Manage ECharts instance lifecycle (init, theme, resize, option updates)
|
|
123
186
|
const chart = useChart({
|
|
124
187
|
option: () => option,
|
|
@@ -126,6 +189,7 @@
|
|
|
126
189
|
animation: () => animation,
|
|
127
190
|
isEmpty: () => isEmpty,
|
|
128
191
|
onReady: (instance) => {
|
|
192
|
+
legend.attach(instance);
|
|
129
193
|
if (onClick) {
|
|
130
194
|
instance.on('click', (params) => {
|
|
131
195
|
onClick({
|
|
@@ -159,6 +223,10 @@
|
|
|
159
223
|
});
|
|
160
224
|
</script>
|
|
161
225
|
|
|
226
|
+
{#snippet customLegend()}
|
|
227
|
+
{@render renderLegend?.(legend.renderProps)}
|
|
228
|
+
{/snippet}
|
|
229
|
+
|
|
162
230
|
<ChartContainer
|
|
163
231
|
{title}
|
|
164
232
|
description={accessibilityDescription}
|
|
@@ -168,6 +236,8 @@
|
|
|
168
236
|
empty={isEmpty}
|
|
169
237
|
{emptyMessage}
|
|
170
238
|
class={cn(className)}
|
|
239
|
+
legend={renderLegend ? customLegend : undefined}
|
|
240
|
+
legendPosition={legendConfig?.position}
|
|
171
241
|
>
|
|
172
242
|
<div bind:this={chart.container} class="h-full w-full"></div>
|
|
173
243
|
</ChartContainer>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LineChartProps } from '@classic-homes/charts-core';
|
|
2
|
-
|
|
2
|
+
import { type CustomLegendProps } from '../../composables/useChartLegend.svelte.js';
|
|
3
|
+
interface Props extends Omit<LineChartProps, 'class'>, CustomLegendProps {
|
|
3
4
|
class?: string;
|
|
4
5
|
}
|
|
5
6
|
declare const LineChart: import("svelte").Component<Props, {}, "">;
|