@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,31 +1,36 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import * as echarts from 'echarts/core';
|
|
3
3
|
import { LineChart as EChartsLineChart } from 'echarts/charts';
|
|
4
|
-
import {
|
|
5
|
-
GridComponent,
|
|
6
|
-
TooltipComponent,
|
|
7
|
-
LegendComponent,
|
|
8
|
-
TitleComponent,
|
|
9
|
-
} from 'echarts/components';
|
|
10
|
-
import { CanvasRenderer } from 'echarts/renderers';
|
|
4
|
+
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
|
|
11
5
|
import type { EChartsOption } from 'echarts';
|
|
12
6
|
|
|
13
|
-
import type { AreaChartProps
|
|
7
|
+
import type { AreaChartProps } 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
|
colorWithAlpha,
|
|
18
|
+
generateAreaChartDescription,
|
|
20
19
|
getChartColors,
|
|
21
20
|
hasPointLabels,
|
|
22
21
|
gridInset,
|
|
23
22
|
mergeChartOption,
|
|
23
|
+
pairWithCategories,
|
|
24
|
+
resolveValueFormatter,
|
|
24
25
|
seriesLabelLookup,
|
|
26
|
+
toPercentStack,
|
|
25
27
|
withPointLabels,
|
|
28
|
+
withReferenceMarks,
|
|
29
|
+
withZoomInset,
|
|
26
30
|
} from '@classic-homes/charts-core';
|
|
27
31
|
|
|
28
32
|
import { cn } from '../../utils.js';
|
|
33
|
+
import { chartEvents, toDataPointParams } from '../../events.js';
|
|
29
34
|
import { useChart } from '../../composables/useChart.svelte.js';
|
|
30
35
|
import {
|
|
31
36
|
useChartLegend,
|
|
@@ -34,14 +39,7 @@
|
|
|
34
39
|
import ChartContainer from '../base/ChartContainer.svelte';
|
|
35
40
|
|
|
36
41
|
// Register required ECharts modules
|
|
37
|
-
echarts.use([
|
|
38
|
-
EChartsLineChart,
|
|
39
|
-
GridComponent,
|
|
40
|
-
TooltipComponent,
|
|
41
|
-
LegendComponent,
|
|
42
|
-
TitleComponent,
|
|
43
|
-
CanvasRenderer,
|
|
44
|
-
]);
|
|
42
|
+
echarts.use([EChartsLineChart, GridComponent, TooltipComponent, LegendComponent]);
|
|
45
43
|
|
|
46
44
|
interface Props extends Omit<AreaChartProps, 'class'>, CustomLegendProps {
|
|
47
45
|
class?: string;
|
|
@@ -52,8 +50,11 @@
|
|
|
52
50
|
description,
|
|
53
51
|
data,
|
|
54
52
|
height = 400,
|
|
53
|
+
minHeight,
|
|
54
|
+
maxHeight,
|
|
55
55
|
loading,
|
|
56
56
|
error,
|
|
57
|
+
onRetry,
|
|
57
58
|
emptyMessage,
|
|
58
59
|
theme = 'auto',
|
|
59
60
|
animation = true,
|
|
@@ -63,33 +64,96 @@
|
|
|
63
64
|
smooth = false,
|
|
64
65
|
stacked = false,
|
|
65
66
|
gradient = true,
|
|
67
|
+
connectNulls = false,
|
|
66
68
|
showLabels = false,
|
|
69
|
+
valueFormat,
|
|
70
|
+
locale,
|
|
67
71
|
labelFormatter,
|
|
68
72
|
xAxisConfig,
|
|
69
73
|
yAxisConfig,
|
|
74
|
+
referenceLines,
|
|
75
|
+
referenceBands,
|
|
76
|
+
dataZoom,
|
|
70
77
|
tooltipFormatter,
|
|
71
78
|
tooltipValueFormatter,
|
|
72
79
|
legend: legendConfig,
|
|
73
80
|
renderLegend,
|
|
74
81
|
onLegendChange,
|
|
75
82
|
option: optionOverride,
|
|
83
|
+
onReady,
|
|
84
|
+
events,
|
|
85
|
+
showDataTable,
|
|
86
|
+
exportable,
|
|
87
|
+
patterns,
|
|
76
88
|
onClick,
|
|
89
|
+
onHover,
|
|
77
90
|
class: className,
|
|
78
91
|
}: Props = $props();
|
|
79
92
|
|
|
80
|
-
const isEmpty = $derived(
|
|
81
|
-
|
|
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
|
+
})
|
|
82
105
|
);
|
|
83
106
|
|
|
84
107
|
// Per-element colours that follow the resolved chart theme
|
|
85
108
|
const chartColors = $derived.by(() => getChartColors(chart.theme));
|
|
86
109
|
|
|
110
|
+
const builtInLegend = $derived(showLegend && !renderLegend);
|
|
111
|
+
|
|
87
112
|
const option: EChartsOption = $derived.by(() => {
|
|
88
113
|
if (isEmpty) return {};
|
|
89
114
|
|
|
115
|
+
const plotted = isPercent ? toPercentStack(data.series) : data.series;
|
|
90
116
|
const getLabel = seriesLabelLookup(data.series);
|
|
91
117
|
// ECharts hides line labels along with the symbol
|
|
92
118
|
const showSymbol = showLabels || hasPointLabels(data.series);
|
|
119
|
+
const legendBottom = builtInLegend && (legendConfig?.position ?? 'bottom') === 'bottom';
|
|
120
|
+
|
|
121
|
+
const series = data.series.map((source, index) => {
|
|
122
|
+
// Fade the series colour (theme palette unless overridden) downwards
|
|
123
|
+
const base = source.color || chartColors.series[index % chartColors.series.length];
|
|
124
|
+
return {
|
|
125
|
+
name: source.name,
|
|
126
|
+
type: 'line',
|
|
127
|
+
data: withPointLabels(
|
|
128
|
+
pairWithCategories(data.categories, plotted[index].data, xAxisConfig?.type),
|
|
129
|
+
source.labels,
|
|
130
|
+
formatters.label,
|
|
131
|
+
{ position: 'top' }
|
|
132
|
+
),
|
|
133
|
+
smooth,
|
|
134
|
+
connectNulls,
|
|
135
|
+
stack: source.stack ?? (stacked ? 'Total' : undefined),
|
|
136
|
+
areaStyle: gradient
|
|
137
|
+
? {
|
|
138
|
+
opacity: 0.8,
|
|
139
|
+
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
140
|
+
{ offset: 0, color: colorWithAlpha(base, 0.8) },
|
|
141
|
+
{ offset: 1, color: colorWithAlpha(base, 0.1) },
|
|
142
|
+
]),
|
|
143
|
+
}
|
|
144
|
+
: { opacity: 0.5 },
|
|
145
|
+
itemStyle: source.color ? { color: source.color } : undefined,
|
|
146
|
+
label: buildDataLabel({ show: true, position: 'top' }, undefined, {
|
|
147
|
+
show: showLabels,
|
|
148
|
+
formatter: formatters.label,
|
|
149
|
+
getLabel,
|
|
150
|
+
}),
|
|
151
|
+
emphasis: {
|
|
152
|
+
focus: 'series',
|
|
153
|
+
},
|
|
154
|
+
showSymbol,
|
|
155
|
+
};
|
|
156
|
+
});
|
|
93
157
|
|
|
94
158
|
return mergeChartOption(
|
|
95
159
|
{
|
|
@@ -98,7 +162,11 @@
|
|
|
98
162
|
right: '4%',
|
|
99
163
|
bottom: '3%',
|
|
100
164
|
containLabel: true,
|
|
101
|
-
...
|
|
165
|
+
...withZoomInset(
|
|
166
|
+
gridInset(legendConfig, builtInLegend, xAxisConfig, yAxisConfig),
|
|
167
|
+
dataZoom,
|
|
168
|
+
'x'
|
|
169
|
+
),
|
|
102
170
|
},
|
|
103
171
|
tooltip: buildTooltip(
|
|
104
172
|
{
|
|
@@ -113,7 +181,7 @@
|
|
|
113
181
|
{
|
|
114
182
|
show: showTooltip,
|
|
115
183
|
formatter: tooltipFormatter,
|
|
116
|
-
valueFormatter:
|
|
184
|
+
valueFormatter: formatters.tooltip,
|
|
117
185
|
getLabel,
|
|
118
186
|
}
|
|
119
187
|
),
|
|
@@ -122,6 +190,7 @@
|
|
|
122
190
|
legendConfig,
|
|
123
191
|
{ show: showLegend, custom: !!renderLegend }
|
|
124
192
|
),
|
|
193
|
+
dataZoom: buildDataZoom(dataZoom, 'x', legendBottom ? 36 : 0),
|
|
125
194
|
xAxis: buildAxis(
|
|
126
195
|
{
|
|
127
196
|
type: 'category',
|
|
@@ -133,41 +202,18 @@
|
|
|
133
202
|
yAxis: buildAxis(
|
|
134
203
|
{
|
|
135
204
|
type: 'value',
|
|
205
|
+
...(isPercent && { max: 100 }),
|
|
136
206
|
splitLine: {
|
|
137
207
|
show: showGrid,
|
|
138
208
|
},
|
|
139
209
|
},
|
|
140
|
-
yAxisConfig
|
|
210
|
+
formatters.axis(yAxisConfig)
|
|
141
211
|
),
|
|
142
|
-
series:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
type: 'line',
|
|
148
|
-
data: withPointLabels(series.data, series.labels, labelFormatter, { position: 'top' }),
|
|
149
|
-
smooth,
|
|
150
|
-
stack: stacked ? 'Total' : undefined,
|
|
151
|
-
areaStyle: gradient
|
|
152
|
-
? {
|
|
153
|
-
opacity: 0.8,
|
|
154
|
-
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
155
|
-
{ offset: 0, color: colorWithAlpha(base, 0.8) },
|
|
156
|
-
{ offset: 1, color: colorWithAlpha(base, 0.1) },
|
|
157
|
-
]),
|
|
158
|
-
}
|
|
159
|
-
: { opacity: 0.5 },
|
|
160
|
-
itemStyle: series.color ? { color: series.color } : undefined,
|
|
161
|
-
label: buildDataLabel({ show: true, position: 'top' }, undefined, {
|
|
162
|
-
show: showLabels,
|
|
163
|
-
formatter: labelFormatter,
|
|
164
|
-
getLabel,
|
|
165
|
-
}),
|
|
166
|
-
emphasis: {
|
|
167
|
-
focus: 'series',
|
|
168
|
-
},
|
|
169
|
-
showSymbol,
|
|
170
|
-
};
|
|
212
|
+
series: withReferenceMarks(series, referenceLines, referenceBands, {
|
|
213
|
+
valueAxis: 'y',
|
|
214
|
+
format: formatters.format,
|
|
215
|
+
color: chartColors.reference,
|
|
216
|
+
bandColor: chartColors.referenceBand,
|
|
171
217
|
}),
|
|
172
218
|
} as EChartsOption,
|
|
173
219
|
optionOverride
|
|
@@ -176,11 +222,11 @@
|
|
|
176
222
|
|
|
177
223
|
const accessibilityDescription = $derived(
|
|
178
224
|
description ||
|
|
179
|
-
(!isEmpty
|
|
180
|
-
? `${title}. Area chart with ${data.categories.length} data points across ${data.series.length} series.`
|
|
181
|
-
: undefined)
|
|
225
|
+
(!isEmpty ? generateAreaChartDescription(title, data, formatters.format) : undefined)
|
|
182
226
|
);
|
|
183
227
|
|
|
228
|
+
const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
|
|
229
|
+
|
|
184
230
|
// Legend selection state, shared with a custom legend
|
|
185
231
|
const legend = useChartLegend({
|
|
186
232
|
names: () => data?.series?.map((s) => s.name) ?? [],
|
|
@@ -197,23 +243,14 @@
|
|
|
197
243
|
theme: () => theme,
|
|
198
244
|
animation: () => animation,
|
|
199
245
|
isEmpty: () => isEmpty,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
seriesName: params.seriesName || '',
|
|
209
|
-
dataIndex: params.dataIndex || 0,
|
|
210
|
-
name: params.name || '',
|
|
211
|
-
value: params.value as number,
|
|
212
|
-
color: params.color as string,
|
|
213
|
-
} as DataPointEventParams);
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
},
|
|
246
|
+
patterns: () => patterns,
|
|
247
|
+
events: () =>
|
|
248
|
+
chartEvents(events, {
|
|
249
|
+
click: onClick && ((params) => onClick(toDataPointParams(params, 'click'))),
|
|
250
|
+
mouseover: onHover && ((params) => onHover(toDataPointParams(params, 'mouseover'))),
|
|
251
|
+
}),
|
|
252
|
+
onReady: (instance) => legend.attach(instance),
|
|
253
|
+
onCreated: () => onReady,
|
|
217
254
|
});
|
|
218
255
|
</script>
|
|
219
256
|
|
|
@@ -225,13 +262,21 @@
|
|
|
225
262
|
{title}
|
|
226
263
|
description={accessibilityDescription}
|
|
227
264
|
{height}
|
|
265
|
+
{minHeight}
|
|
266
|
+
{maxHeight}
|
|
228
267
|
{loading}
|
|
229
268
|
{error}
|
|
269
|
+
{onRetry}
|
|
230
270
|
empty={isEmpty}
|
|
231
271
|
{emptyMessage}
|
|
232
272
|
class={cn(className)}
|
|
233
273
|
legend={renderLegend ? customLegend : undefined}
|
|
234
274
|
legendPosition={legendConfig?.position}
|
|
275
|
+
{dataTable}
|
|
276
|
+
{showDataTable}
|
|
277
|
+
format={resolveValueFormatter(valueFormat, locale)}
|
|
278
|
+
{exportable}
|
|
279
|
+
exportImage={chart.exportImage}
|
|
235
280
|
>
|
|
236
281
|
<div bind:this={chart.container} class="h-full w-full"></div>
|
|
237
282
|
</ChartContainer>
|
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import * as echarts from 'echarts/core';
|
|
3
3
|
import { BarChart as EChartsBarChart } 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 { BarChartProps
|
|
7
|
+
import type { BarChartProps } 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
|
generateBarChartDescription,
|
|
20
18
|
getChartColors,
|
|
21
19
|
gridInset,
|
|
22
20
|
mergeChartOption,
|
|
21
|
+
pairWithCategories,
|
|
22
|
+
resolveChartHeight,
|
|
23
|
+
resolveValueFormatter,
|
|
23
24
|
seriesLabelLookup,
|
|
25
|
+
toPercentStack,
|
|
24
26
|
withPointLabels,
|
|
27
|
+
withReferenceMarks,
|
|
28
|
+
withZoomInset,
|
|
25
29
|
} from '@classic-homes/charts-core';
|
|
26
30
|
|
|
27
31
|
import { cn } from '../../utils.js';
|
|
32
|
+
import { chartEvents, toDataPointParams } from '../../events.js';
|
|
28
33
|
import { useChart } from '../../composables/useChart.svelte.js';
|
|
29
34
|
import {
|
|
30
35
|
useChartLegend,
|
|
@@ -33,14 +38,7 @@
|
|
|
33
38
|
import ChartContainer from '../base/ChartContainer.svelte';
|
|
34
39
|
|
|
35
40
|
// Register required ECharts modules
|
|
36
|
-
echarts.use([
|
|
37
|
-
EChartsBarChart,
|
|
38
|
-
GridComponent,
|
|
39
|
-
TooltipComponent,
|
|
40
|
-
LegendComponent,
|
|
41
|
-
TitleComponent,
|
|
42
|
-
CanvasRenderer,
|
|
43
|
-
]);
|
|
41
|
+
echarts.use([EChartsBarChart, GridComponent, TooltipComponent, LegendComponent]);
|
|
44
42
|
|
|
45
43
|
interface Props extends Omit<BarChartProps, 'class'>, CustomLegendProps {
|
|
46
44
|
class?: string;
|
|
@@ -51,8 +49,12 @@
|
|
|
51
49
|
description,
|
|
52
50
|
data,
|
|
53
51
|
height = 400,
|
|
52
|
+
minHeight,
|
|
53
|
+
maxHeight,
|
|
54
|
+
rowHeight,
|
|
54
55
|
loading,
|
|
55
56
|
error,
|
|
57
|
+
onRetry,
|
|
56
58
|
emptyMessage,
|
|
57
59
|
theme = 'auto',
|
|
58
60
|
animation = true,
|
|
@@ -62,31 +64,62 @@
|
|
|
62
64
|
orientation = 'vertical',
|
|
63
65
|
stacked = false,
|
|
64
66
|
showValues = false,
|
|
67
|
+
valueFormat,
|
|
68
|
+
locale,
|
|
65
69
|
labelFormatter,
|
|
66
70
|
xAxisConfig,
|
|
67
71
|
yAxisConfig,
|
|
72
|
+
referenceLines,
|
|
73
|
+
referenceBands,
|
|
74
|
+
dataZoom,
|
|
68
75
|
tooltipFormatter,
|
|
69
76
|
tooltipValueFormatter,
|
|
70
77
|
legend: legendConfig,
|
|
71
78
|
renderLegend,
|
|
72
79
|
onLegendChange,
|
|
73
80
|
option: optionOverride,
|
|
81
|
+
onReady,
|
|
82
|
+
events,
|
|
83
|
+
showDataTable,
|
|
84
|
+
exportable,
|
|
85
|
+
patterns,
|
|
74
86
|
onClick,
|
|
87
|
+
onHover,
|
|
75
88
|
class: className,
|
|
76
89
|
}: Props = $props();
|
|
77
90
|
|
|
78
|
-
const isEmpty = $derived(
|
|
79
|
-
!data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length)
|
|
80
|
-
);
|
|
91
|
+
const isEmpty = $derived(!data?.categories?.length || allSeriesEmpty(data?.series));
|
|
81
92
|
|
|
82
93
|
const isHorizontal = $derived(orientation === 'horizontal');
|
|
94
|
+
const isPercent = $derived(stacked === 'percent');
|
|
95
|
+
|
|
96
|
+
// Percent stacks plot shares, so they always read as percentages
|
|
97
|
+
const formatters = $derived(
|
|
98
|
+
chartFormatters({
|
|
99
|
+
valueFormat: isPercent ? 'percentValue' : valueFormat,
|
|
100
|
+
locale,
|
|
101
|
+
tooltipValueFormatter,
|
|
102
|
+
labelFormatter,
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Per-element colours that follow the resolved chart theme
|
|
107
|
+
const chartColors = $derived.by(() => getChartColors(chart.theme));
|
|
108
|
+
|
|
109
|
+
const builtInLegend = $derived(showLegend && !renderLegend);
|
|
83
110
|
|
|
84
111
|
const option: EChartsOption = $derived.by(() => {
|
|
85
112
|
if (isEmpty) return {};
|
|
86
113
|
|
|
114
|
+
const plotted = isPercent ? toPercentStack(data.series) : data.series;
|
|
115
|
+
// A time or value x-axis reads [x, y] points (vertical bars only)
|
|
116
|
+
const categoryAxisType = isHorizontal ? undefined : xAxisConfig?.type;
|
|
117
|
+
|
|
87
118
|
const categoryAxis = {
|
|
88
119
|
type: 'category' as const,
|
|
89
120
|
data: data.categories,
|
|
121
|
+
// Horizontal bars list the first category at the top
|
|
122
|
+
inverse: isHorizontal,
|
|
90
123
|
axisTick: {
|
|
91
124
|
alignWithLabel: true,
|
|
92
125
|
},
|
|
@@ -94,12 +127,37 @@
|
|
|
94
127
|
|
|
95
128
|
const valueAxis = {
|
|
96
129
|
type: 'value' as const,
|
|
130
|
+
...(isPercent && { max: 100 }),
|
|
97
131
|
splitLine: {
|
|
98
132
|
show: showGrid,
|
|
99
133
|
},
|
|
100
134
|
};
|
|
101
135
|
|
|
102
136
|
const getLabel = seriesLabelLookup(data.series);
|
|
137
|
+
const labelPosition = isHorizontal ? 'right' : 'top';
|
|
138
|
+
const zoomAxis = isHorizontal ? 'y' : 'x';
|
|
139
|
+
const legendBottom = builtInLegend && (legendConfig?.position ?? 'bottom') === 'bottom';
|
|
140
|
+
|
|
141
|
+
const series = data.series.map((source, index) => ({
|
|
142
|
+
name: source.name,
|
|
143
|
+
type: 'bar',
|
|
144
|
+
data: withPointLabels(
|
|
145
|
+
pairWithCategories(data.categories, plotted[index].data, categoryAxisType),
|
|
146
|
+
source.labels,
|
|
147
|
+
formatters.label,
|
|
148
|
+
{ position: labelPosition }
|
|
149
|
+
),
|
|
150
|
+
stack: source.stack ?? (stacked ? 'Total' : undefined),
|
|
151
|
+
itemStyle: source.color ? { color: source.color } : undefined,
|
|
152
|
+
label: buildDataLabel({ show: true, position: labelPosition }, undefined, {
|
|
153
|
+
show: showValues,
|
|
154
|
+
formatter: formatters.label,
|
|
155
|
+
getLabel,
|
|
156
|
+
}),
|
|
157
|
+
emphasis: {
|
|
158
|
+
focus: 'series',
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
103
161
|
|
|
104
162
|
return mergeChartOption(
|
|
105
163
|
{
|
|
@@ -108,7 +166,11 @@
|
|
|
108
166
|
right: '4%',
|
|
109
167
|
bottom: '3%',
|
|
110
168
|
containLabel: true,
|
|
111
|
-
...
|
|
169
|
+
...withZoomInset(
|
|
170
|
+
gridInset(legendConfig, builtInLegend, xAxisConfig, yAxisConfig),
|
|
171
|
+
dataZoom,
|
|
172
|
+
zoomAxis
|
|
173
|
+
),
|
|
112
174
|
},
|
|
113
175
|
tooltip: buildTooltip(
|
|
114
176
|
{
|
|
@@ -120,7 +182,7 @@
|
|
|
120
182
|
{
|
|
121
183
|
show: showTooltip,
|
|
122
184
|
formatter: tooltipFormatter,
|
|
123
|
-
valueFormatter:
|
|
185
|
+
valueFormatter: formatters.tooltip,
|
|
124
186
|
getLabel,
|
|
125
187
|
}
|
|
126
188
|
),
|
|
@@ -129,35 +191,46 @@
|
|
|
129
191
|
legendConfig,
|
|
130
192
|
{ show: showLegend, custom: !!renderLegend }
|
|
131
193
|
),
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
{ show: showValues, formatter: labelFormatter, getLabel }
|
|
149
|
-
),
|
|
150
|
-
emphasis: {
|
|
151
|
-
focus: 'series',
|
|
152
|
-
},
|
|
153
|
-
})),
|
|
194
|
+
dataZoom: buildDataZoom(dataZoom, zoomAxis, legendBottom ? 36 : 0),
|
|
195
|
+
xAxis: buildAxis(
|
|
196
|
+
isHorizontal ? valueAxis : categoryAxis,
|
|
197
|
+
isHorizontal ? formatters.axis(xAxisConfig) : xAxisConfig
|
|
198
|
+
),
|
|
199
|
+
yAxis: buildAxis(
|
|
200
|
+
isHorizontal ? categoryAxis : valueAxis,
|
|
201
|
+
isHorizontal ? yAxisConfig : formatters.axis(yAxisConfig)
|
|
202
|
+
),
|
|
203
|
+
series: withReferenceMarks(series, referenceLines, referenceBands, {
|
|
204
|
+
valueAxis: isHorizontal ? 'x' : 'y',
|
|
205
|
+
inverse: isHorizontal,
|
|
206
|
+
format: formatters.format,
|
|
207
|
+
color: chartColors.reference,
|
|
208
|
+
bandColor: chartColors.referenceBand,
|
|
209
|
+
}),
|
|
154
210
|
} as EChartsOption,
|
|
155
211
|
optionOverride
|
|
156
212
|
);
|
|
157
213
|
});
|
|
158
214
|
|
|
159
215
|
const accessibilityDescription = $derived(
|
|
160
|
-
description ||
|
|
216
|
+
description ||
|
|
217
|
+
(!isEmpty ? generateBarChartDescription(title, data, formatters.format) : undefined)
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
|
|
221
|
+
|
|
222
|
+
// One row per category when horizontal; a bottom legend needs its own row
|
|
223
|
+
const resolvedHeight = $derived(
|
|
224
|
+
isHorizontal
|
|
225
|
+
? resolveChartHeight(height, data?.categories?.length ?? 0, {
|
|
226
|
+
rowHeight,
|
|
227
|
+
minHeight,
|
|
228
|
+
maxHeight,
|
|
229
|
+
chrome: 56 + (builtInLegend ? 36 : 0) + (dataZoom ? 36 : 0),
|
|
230
|
+
})
|
|
231
|
+
: height === 'auto'
|
|
232
|
+
? 400
|
|
233
|
+
: height
|
|
161
234
|
);
|
|
162
235
|
|
|
163
236
|
// Legend selection state, shared with a custom legend
|
|
@@ -176,23 +249,14 @@
|
|
|
176
249
|
theme: () => theme,
|
|
177
250
|
animation: () => animation,
|
|
178
251
|
isEmpty: () => isEmpty,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
seriesName: params.seriesName || '',
|
|
188
|
-
dataIndex: params.dataIndex || 0,
|
|
189
|
-
name: params.name || '',
|
|
190
|
-
value: params.value as number,
|
|
191
|
-
color: params.color as string,
|
|
192
|
-
} as DataPointEventParams);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
},
|
|
252
|
+
patterns: () => patterns,
|
|
253
|
+
events: () =>
|
|
254
|
+
chartEvents(events, {
|
|
255
|
+
click: onClick && ((params) => onClick(toDataPointParams(params, 'click'))),
|
|
256
|
+
mouseover: onHover && ((params) => onHover(toDataPointParams(params, 'mouseover'))),
|
|
257
|
+
}),
|
|
258
|
+
onReady: (instance) => legend.attach(instance),
|
|
259
|
+
onCreated: () => onReady,
|
|
196
260
|
});
|
|
197
261
|
</script>
|
|
198
262
|
|
|
@@ -203,14 +267,22 @@
|
|
|
203
267
|
<ChartContainer
|
|
204
268
|
{title}
|
|
205
269
|
description={accessibilityDescription}
|
|
206
|
-
{
|
|
270
|
+
height={resolvedHeight}
|
|
271
|
+
{minHeight}
|
|
272
|
+
{maxHeight}
|
|
207
273
|
{loading}
|
|
208
274
|
{error}
|
|
275
|
+
{onRetry}
|
|
209
276
|
empty={isEmpty}
|
|
210
277
|
{emptyMessage}
|
|
211
278
|
class={cn(className)}
|
|
212
279
|
legend={renderLegend ? customLegend : undefined}
|
|
213
280
|
legendPosition={legendConfig?.position}
|
|
281
|
+
{dataTable}
|
|
282
|
+
{showDataTable}
|
|
283
|
+
format={resolveValueFormatter(valueFormat, locale)}
|
|
284
|
+
{exportable}
|
|
285
|
+
exportImage={chart.exportImage}
|
|
214
286
|
>
|
|
215
287
|
<div bind:this={chart.container} class="h-full w-full"></div>
|
|
216
288
|
</ChartContainer>
|