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