@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
|
@@ -6,16 +6,32 @@
|
|
|
6
6
|
import type { EChartsOption } from 'echarts';
|
|
7
7
|
|
|
8
8
|
import type { PieChartProps, 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
|
+
legendCenter,
|
|
19
|
+
mergeChartOption,
|
|
20
|
+
pointLabel,
|
|
21
|
+
} from '@classic-homes/charts-core';
|
|
10
22
|
|
|
11
23
|
import { cn } from '../../utils.js';
|
|
12
24
|
import { useChart } from '../../composables/useChart.svelte.js';
|
|
25
|
+
import {
|
|
26
|
+
useChartLegend,
|
|
27
|
+
type CustomLegendProps,
|
|
28
|
+
} from '../../composables/useChartLegend.svelte.js';
|
|
13
29
|
import ChartContainer from '../base/ChartContainer.svelte';
|
|
14
30
|
|
|
15
31
|
// Register required ECharts modules
|
|
16
32
|
echarts.use([EChartsPieChart, TooltipComponent, LegendComponent, TitleComponent, CanvasRenderer]);
|
|
17
33
|
|
|
18
|
-
interface Props extends Omit<PieChartProps, 'class'
|
|
34
|
+
interface Props extends Omit<PieChartProps, 'class'>, CustomLegendProps {
|
|
19
35
|
class?: string;
|
|
20
36
|
}
|
|
21
37
|
|
|
@@ -34,6 +50,13 @@
|
|
|
34
50
|
showLabels = true,
|
|
35
51
|
labelPosition = 'outside',
|
|
36
52
|
innerRadius = 0,
|
|
53
|
+
labelFormatter,
|
|
54
|
+
tooltipFormatter,
|
|
55
|
+
tooltipValueFormatter,
|
|
56
|
+
legend: legendConfig,
|
|
57
|
+
renderLegend,
|
|
58
|
+
onLegendChange,
|
|
59
|
+
option: optionOverride,
|
|
37
60
|
onClick,
|
|
38
61
|
class: className,
|
|
39
62
|
}: Props = $props();
|
|
@@ -43,56 +66,88 @@
|
|
|
43
66
|
const option: EChartsOption = $derived.by(() => {
|
|
44
67
|
if (isEmpty) return {};
|
|
45
68
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
const getLabel = itemLabelLookup(data);
|
|
70
|
+
|
|
71
|
+
return mergeChartOption(
|
|
72
|
+
{
|
|
73
|
+
tooltip: buildTooltip(
|
|
74
|
+
{
|
|
49
75
|
trigger: 'item',
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
// A string template ignores valueFormatter, so format the value here
|
|
77
|
+
formatter:
|
|
78
|
+
tooltipValueFormatter && !tooltipFormatter
|
|
79
|
+
? (params: unknown) => {
|
|
80
|
+
const p = params as {
|
|
81
|
+
seriesName?: string;
|
|
82
|
+
name?: string;
|
|
83
|
+
value?: unknown;
|
|
84
|
+
percent?: number;
|
|
85
|
+
};
|
|
86
|
+
return `${escapeHtml(p.seriesName)} <br/>${escapeHtml(p.name)}: ${escapeHtml(formatTooltipValue(p.value, tooltipValueFormatter))} (${p.percent}%)`;
|
|
87
|
+
}
|
|
88
|
+
: '{a} <br/>{b}: {c} ({d}%)',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
show: showTooltip,
|
|
92
|
+
formatter: tooltipFormatter,
|
|
93
|
+
valueFormatter: tooltipValueFormatter,
|
|
94
|
+
getLabel,
|
|
58
95
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
name:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
96
|
+
),
|
|
97
|
+
legend: buildLegend(
|
|
98
|
+
data.map((d) => d.name),
|
|
99
|
+
legendConfig,
|
|
100
|
+
{ show: showLegend, custom: !!renderLegend, defaults: { orient: 'horizontal' } }
|
|
101
|
+
),
|
|
102
|
+
series: [
|
|
103
|
+
{
|
|
104
|
+
name: title,
|
|
105
|
+
type: 'pie',
|
|
106
|
+
radius: innerRadius ? [innerRadius, '70%'] : '70%',
|
|
107
|
+
center: legendCenter(legendConfig, showLegend && !renderLegend, ['50%', '45%']),
|
|
108
|
+
data: data.map((item) => ({
|
|
109
|
+
name: item.name,
|
|
110
|
+
value: item.value,
|
|
111
|
+
itemStyle: item.color ? { color: item.color } : undefined,
|
|
112
|
+
label: pointLabel(item.label, labelFormatter, { position: labelPosition }),
|
|
113
|
+
})),
|
|
114
|
+
label: buildDataLabel(
|
|
115
|
+
{
|
|
73
116
|
show: true,
|
|
74
117
|
position: labelPosition,
|
|
75
118
|
formatter: labelPosition === 'inside' ? '{d}%' : '{b}: {d}%',
|
|
76
|
-
}
|
|
77
|
-
: {
|
|
78
|
-
show: false,
|
|
79
119
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
120
|
+
{ show: false },
|
|
121
|
+
{ show: showLabels, formatter: labelFormatter, getLabel }
|
|
122
|
+
),
|
|
123
|
+
emphasis: {
|
|
124
|
+
itemStyle: {
|
|
125
|
+
shadowBlur: 10,
|
|
126
|
+
shadowOffsetX: 0,
|
|
127
|
+
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
128
|
+
},
|
|
85
129
|
},
|
|
86
130
|
},
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
131
|
+
],
|
|
132
|
+
} as EChartsOption,
|
|
133
|
+
optionOverride
|
|
134
|
+
);
|
|
90
135
|
});
|
|
91
136
|
|
|
92
137
|
const accessibilityDescription = $derived(
|
|
93
138
|
description || (!isEmpty ? generatePieChartDescription(title, data, false) : undefined)
|
|
94
139
|
);
|
|
95
140
|
|
|
141
|
+
// Legend selection state, shared with a custom legend
|
|
142
|
+
const legend = useChartLegend({
|
|
143
|
+
names: () => data?.map((d) => d.name) ?? [],
|
|
144
|
+
colors: () => data?.map((d) => d.color) ?? [],
|
|
145
|
+
palette: () => getChartColors(chart.theme).series,
|
|
146
|
+
config: () => legendConfig,
|
|
147
|
+
title: () => title,
|
|
148
|
+
onLegendChange: () => onLegendChange,
|
|
149
|
+
});
|
|
150
|
+
|
|
96
151
|
// Manage ECharts instance lifecycle (init, theme, resize, option updates)
|
|
97
152
|
const chart = useChart({
|
|
98
153
|
option: () => option,
|
|
@@ -100,6 +155,7 @@
|
|
|
100
155
|
animation: () => animation,
|
|
101
156
|
isEmpty: () => isEmpty,
|
|
102
157
|
onReady: (instance) => {
|
|
158
|
+
legend.attach(instance);
|
|
103
159
|
if (onClick) {
|
|
104
160
|
instance.on('click', (params) => {
|
|
105
161
|
onClick({
|
|
@@ -116,6 +172,10 @@
|
|
|
116
172
|
});
|
|
117
173
|
</script>
|
|
118
174
|
|
|
175
|
+
{#snippet customLegend()}
|
|
176
|
+
{@render renderLegend?.(legend.renderProps)}
|
|
177
|
+
{/snippet}
|
|
178
|
+
|
|
119
179
|
<ChartContainer
|
|
120
180
|
{title}
|
|
121
181
|
description={accessibilityDescription}
|
|
@@ -125,6 +185,8 @@
|
|
|
125
185
|
empty={isEmpty}
|
|
126
186
|
{emptyMessage}
|
|
127
187
|
class={cn(className)}
|
|
188
|
+
legend={renderLegend ? customLegend : undefined}
|
|
189
|
+
legendPosition={legendConfig?.position}
|
|
128
190
|
>
|
|
129
191
|
<div bind:this={chart.container} class="h-full w-full"></div>
|
|
130
192
|
</ChartContainer>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PieChartProps } from '@classic-homes/charts-core';
|
|
2
|
-
|
|
2
|
+
import { type CustomLegendProps } from '../../composables/useChartLegend.svelte.js';
|
|
3
|
+
interface Props extends Omit<PieChartProps, 'class'>, CustomLegendProps {
|
|
3
4
|
class?: string;
|
|
4
5
|
}
|
|
5
6
|
declare const PieChart: import("svelte").Component<Props, {}, "">;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import * as echarts from 'echarts/core';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ScatterChart as EChartsScatterChart,
|
|
5
|
+
LineChart as EChartsLineChart,
|
|
6
|
+
} from 'echarts/charts';
|
|
4
7
|
import {
|
|
5
8
|
GridComponent,
|
|
6
9
|
TooltipComponent,
|
|
@@ -11,15 +14,34 @@
|
|
|
11
14
|
import type { EChartsOption } from 'echarts';
|
|
12
15
|
|
|
13
16
|
import type { ScatterChartProps, DataPointEventParams } from '@classic-homes/charts-core';
|
|
14
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
buildAxis,
|
|
19
|
+
buildDataLabel,
|
|
20
|
+
buildLegend,
|
|
21
|
+
buildTooltip,
|
|
22
|
+
escapeHtml,
|
|
23
|
+
formatTooltipValue,
|
|
24
|
+
generateScatterChartDescription,
|
|
25
|
+
getChartColors,
|
|
26
|
+
gridInset,
|
|
27
|
+
mergeChartOption,
|
|
28
|
+
seriesLabelLookup,
|
|
29
|
+
withPointLabels,
|
|
30
|
+
} from '@classic-homes/charts-core';
|
|
15
31
|
|
|
16
32
|
import { cn } from '../../utils.js';
|
|
17
33
|
import { useChart } from '../../composables/useChart.svelte.js';
|
|
34
|
+
import {
|
|
35
|
+
useChartLegend,
|
|
36
|
+
type CustomLegendProps,
|
|
37
|
+
} from '../../composables/useChartLegend.svelte.js';
|
|
18
38
|
import ChartContainer from '../base/ChartContainer.svelte';
|
|
19
39
|
|
|
20
40
|
// Register required ECharts modules
|
|
21
41
|
echarts.use([
|
|
22
42
|
EChartsScatterChart,
|
|
43
|
+
// Trend lines are line series
|
|
44
|
+
EChartsLineChart,
|
|
23
45
|
GridComponent,
|
|
24
46
|
TooltipComponent,
|
|
25
47
|
LegendComponent,
|
|
@@ -27,7 +49,7 @@
|
|
|
27
49
|
CanvasRenderer,
|
|
28
50
|
]);
|
|
29
51
|
|
|
30
|
-
interface Props extends Omit<ScatterChartProps, 'class'
|
|
52
|
+
interface Props extends Omit<ScatterChartProps, 'class'>, CustomLegendProps {
|
|
31
53
|
class?: string;
|
|
32
54
|
}
|
|
33
55
|
|
|
@@ -46,6 +68,16 @@
|
|
|
46
68
|
showGrid = true,
|
|
47
69
|
showTrendLine = false,
|
|
48
70
|
symbolSize = 10,
|
|
71
|
+
showLabels = false,
|
|
72
|
+
labelFormatter,
|
|
73
|
+
xAxisConfig,
|
|
74
|
+
yAxisConfig,
|
|
75
|
+
tooltipFormatter,
|
|
76
|
+
tooltipValueFormatter,
|
|
77
|
+
legend: legendConfig,
|
|
78
|
+
renderLegend,
|
|
79
|
+
onLegendChange,
|
|
80
|
+
option: optionOverride,
|
|
49
81
|
onClick,
|
|
50
82
|
class: className,
|
|
51
83
|
}: Props = $props();
|
|
@@ -55,12 +87,19 @@
|
|
|
55
87
|
const option: EChartsOption = $derived.by(() => {
|
|
56
88
|
if (isEmpty) return {};
|
|
57
89
|
|
|
90
|
+
const getLabel = seriesLabelLookup(data.series);
|
|
91
|
+
|
|
58
92
|
const series: EChartsOption['series'] = data.series.map((s) => ({
|
|
59
93
|
name: s.name,
|
|
60
94
|
type: 'scatter',
|
|
61
|
-
data: s.data,
|
|
95
|
+
data: withPointLabels(s.data, s.labels, labelFormatter, { position: 'top' }),
|
|
62
96
|
symbolSize: typeof symbolSize === 'function' ? symbolSize : symbolSize,
|
|
63
97
|
itemStyle: s.color ? { color: s.color } : undefined,
|
|
98
|
+
label: buildDataLabel({ show: true, position: 'top' }, undefined, {
|
|
99
|
+
show: showLabels,
|
|
100
|
+
formatter: labelFormatter,
|
|
101
|
+
getLabel,
|
|
102
|
+
}),
|
|
64
103
|
emphasis: {
|
|
65
104
|
focus: 'series',
|
|
66
105
|
itemStyle: {
|
|
@@ -91,9 +130,13 @@
|
|
|
91
130
|
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
|
|
92
131
|
const intercept = (sumY - slope * sumX) / n;
|
|
93
132
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
133
|
+
// A loop: spreading large arrays into Math.min/max overflows the stack
|
|
134
|
+
let minX = Infinity;
|
|
135
|
+
let maxX = -Infinity;
|
|
136
|
+
for (const [x] of s.data) {
|
|
137
|
+
if (x < minX) minX = x;
|
|
138
|
+
if (x > maxX) maxX = x;
|
|
139
|
+
}
|
|
97
140
|
|
|
98
141
|
(series as unknown[]).push({
|
|
99
142
|
name: `${s.name} Trend`,
|
|
@@ -116,49 +159,76 @@
|
|
|
116
159
|
});
|
|
117
160
|
}
|
|
118
161
|
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
162
|
+
return mergeChartOption(
|
|
163
|
+
{
|
|
164
|
+
grid: {
|
|
165
|
+
left: '3%',
|
|
166
|
+
right: '4%',
|
|
167
|
+
bottom: '3%',
|
|
168
|
+
containLabel: true,
|
|
169
|
+
...gridInset(legendConfig, showLegend && !renderLegend, xAxisConfig, yAxisConfig),
|
|
170
|
+
},
|
|
171
|
+
tooltip: buildTooltip(
|
|
172
|
+
{
|
|
128
173
|
trigger: 'item' as const,
|
|
129
174
|
formatter: (params: unknown) => {
|
|
130
175
|
const p = params as { seriesName?: string; value?: number[] };
|
|
131
176
|
if (!p.value) return '';
|
|
132
|
-
|
|
177
|
+
const x = formatTooltipValue(p.value[0], tooltipValueFormatter);
|
|
178
|
+
const y = formatTooltipValue(p.value[1], tooltipValueFormatter);
|
|
179
|
+
return `${escapeHtml(p.seriesName)}<br/>X: ${escapeHtml(x)}<br/>Y: ${escapeHtml(y)}`;
|
|
133
180
|
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
show: showTooltip,
|
|
184
|
+
formatter: tooltipFormatter,
|
|
185
|
+
valueFormatter: tooltipValueFormatter,
|
|
186
|
+
getLabel,
|
|
134
187
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
188
|
+
),
|
|
189
|
+
legend: buildLegend(
|
|
190
|
+
data.series.map((s) => s.name),
|
|
191
|
+
legendConfig,
|
|
192
|
+
{ show: showLegend, custom: !!renderLegend }
|
|
193
|
+
),
|
|
194
|
+
xAxis: buildAxis(
|
|
195
|
+
{
|
|
196
|
+
type: 'value',
|
|
197
|
+
splitLine: {
|
|
198
|
+
show: showGrid,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
xAxisConfig
|
|
202
|
+
),
|
|
203
|
+
yAxis: buildAxis(
|
|
204
|
+
{
|
|
205
|
+
type: 'value',
|
|
206
|
+
splitLine: {
|
|
207
|
+
show: showGrid,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
yAxisConfig
|
|
211
|
+
),
|
|
212
|
+
series,
|
|
213
|
+
} as EChartsOption,
|
|
214
|
+
optionOverride
|
|
215
|
+
);
|
|
156
216
|
});
|
|
157
217
|
|
|
158
218
|
const accessibilityDescription = $derived(
|
|
159
219
|
description || (!isEmpty ? generateScatterChartDescription(title, data) : undefined)
|
|
160
220
|
);
|
|
161
221
|
|
|
222
|
+
// Legend selection state, shared with a custom legend
|
|
223
|
+
const legend = useChartLegend({
|
|
224
|
+
names: () => data?.series?.map((s) => s.name) ?? [],
|
|
225
|
+
colors: () => data?.series?.map((s) => s.color) ?? [],
|
|
226
|
+
palette: () => getChartColors(chart.theme).series,
|
|
227
|
+
config: () => legendConfig,
|
|
228
|
+
title: () => title,
|
|
229
|
+
onLegendChange: () => onLegendChange,
|
|
230
|
+
});
|
|
231
|
+
|
|
162
232
|
// Manage ECharts instance lifecycle (init, theme, resize, option updates)
|
|
163
233
|
const chart = useChart({
|
|
164
234
|
option: () => option,
|
|
@@ -166,6 +236,7 @@
|
|
|
166
236
|
animation: () => animation,
|
|
167
237
|
isEmpty: () => isEmpty,
|
|
168
238
|
onReady: (instance) => {
|
|
239
|
+
legend.attach(instance);
|
|
169
240
|
if (onClick) {
|
|
170
241
|
instance.on('click', (params) => {
|
|
171
242
|
onClick({
|
|
@@ -184,6 +255,10 @@
|
|
|
184
255
|
});
|
|
185
256
|
</script>
|
|
186
257
|
|
|
258
|
+
{#snippet customLegend()}
|
|
259
|
+
{@render renderLegend?.(legend.renderProps)}
|
|
260
|
+
{/snippet}
|
|
261
|
+
|
|
187
262
|
<ChartContainer
|
|
188
263
|
{title}
|
|
189
264
|
description={accessibilityDescription}
|
|
@@ -193,6 +268,8 @@
|
|
|
193
268
|
empty={isEmpty}
|
|
194
269
|
{emptyMessage}
|
|
195
270
|
class={cn(className)}
|
|
271
|
+
legend={renderLegend ? customLegend : undefined}
|
|
272
|
+
legendPosition={legendConfig?.position}
|
|
196
273
|
>
|
|
197
274
|
<div bind:this={chart.container} class="h-full w-full"></div>
|
|
198
275
|
</ChartContainer>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ScatterChartProps } from '@classic-homes/charts-core';
|
|
2
|
-
|
|
2
|
+
import { type CustomLegendProps } from '../../composables/useChartLegend.svelte.js';
|
|
3
|
+
interface Props extends Omit<ScatterChartProps, 'class'>, CustomLegendProps {
|
|
3
4
|
class?: string;
|
|
4
5
|
}
|
|
5
6
|
declare const ScatterChart: import("svelte").Component<Props, {}, "">;
|
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
import type { EChartsOption } from 'echarts';
|
|
15
15
|
|
|
16
16
|
import type { CandlestickChartProps, CandlestickEventParams } from '@classic-homes/charts-core';
|
|
17
|
+
import {
|
|
18
|
+
buildAxis,
|
|
19
|
+
buildTooltip,
|
|
20
|
+
getChartColors,
|
|
21
|
+
mergeChartOption,
|
|
22
|
+
} from '@classic-homes/charts-core';
|
|
17
23
|
|
|
18
24
|
import { cn } from '../../utils.js';
|
|
19
25
|
import { useChart } from '../../composables/useChart.svelte.js';
|
|
@@ -45,12 +51,20 @@
|
|
|
45
51
|
animation = true,
|
|
46
52
|
showTooltip = true,
|
|
47
53
|
showVolume = false,
|
|
54
|
+
xAxisConfig,
|
|
55
|
+
yAxisConfig,
|
|
56
|
+
tooltipFormatter,
|
|
57
|
+
tooltipValueFormatter,
|
|
58
|
+
option: optionOverride,
|
|
48
59
|
onClick,
|
|
49
60
|
class: className,
|
|
50
61
|
}: Props = $props();
|
|
51
62
|
|
|
52
63
|
const isEmpty = $derived(!data?.dates?.length || !data?.data?.length);
|
|
53
64
|
|
|
65
|
+
// Per-element colours that follow the resolved chart theme
|
|
66
|
+
const chartColors = $derived.by(() => getChartColors(chart.theme));
|
|
67
|
+
|
|
54
68
|
const option: EChartsOption = $derived.by(() => {
|
|
55
69
|
if (isEmpty) return {};
|
|
56
70
|
|
|
@@ -62,10 +76,10 @@
|
|
|
62
76
|
type: 'candlestick',
|
|
63
77
|
data: candlestickData,
|
|
64
78
|
itemStyle: {
|
|
65
|
-
color:
|
|
66
|
-
color0:
|
|
67
|
-
borderColor:
|
|
68
|
-
borderColor0:
|
|
79
|
+
color: chartColors.destructive,
|
|
80
|
+
color0: chartColors.success,
|
|
81
|
+
borderColor: chartColors.destructive,
|
|
82
|
+
borderColor0: chartColors.success,
|
|
69
83
|
},
|
|
70
84
|
},
|
|
71
85
|
];
|
|
@@ -73,18 +87,24 @@
|
|
|
73
87
|
const gridArr: { left: string; right: string; top: number | string; height: string }[] = [
|
|
74
88
|
{ left: '10%', right: '10%', top: 60, height: showVolume ? '50%' : '70%' },
|
|
75
89
|
];
|
|
90
|
+
// Axis customisation applies to the main (price) axes only
|
|
76
91
|
const xAxisArr: unknown[] = [
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
buildAxis(
|
|
93
|
+
{
|
|
94
|
+
type: 'category',
|
|
95
|
+
data: data.dates,
|
|
96
|
+
boundaryGap: false,
|
|
97
|
+
axisLine: { onZero: false },
|
|
98
|
+
splitLine: { show: false },
|
|
99
|
+
min: 'dataMin',
|
|
100
|
+
max: 'dataMax',
|
|
101
|
+
},
|
|
102
|
+
xAxisConfig
|
|
103
|
+
),
|
|
104
|
+
];
|
|
105
|
+
const yAxisArr: unknown[] = [
|
|
106
|
+
buildAxis({ scale: true, splitArea: { show: true } }, yAxisConfig),
|
|
86
107
|
];
|
|
87
|
-
const yAxisArr: unknown[] = [{ scale: true, splitArea: { show: true } }];
|
|
88
108
|
|
|
89
109
|
if (showVolume && data.volume) {
|
|
90
110
|
gridArr.push({ left: '10%', right: '10%', top: '73%', height: '12%' });
|
|
@@ -115,21 +135,27 @@
|
|
|
115
135
|
xAxisIndex: 1,
|
|
116
136
|
yAxisIndex: 1,
|
|
117
137
|
data: data.volume,
|
|
118
|
-
itemStyle: { color:
|
|
138
|
+
itemStyle: { color: chartColors.mutedForeground },
|
|
119
139
|
});
|
|
120
140
|
}
|
|
121
141
|
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
return mergeChartOption(
|
|
143
|
+
{
|
|
144
|
+
tooltip: buildTooltip(
|
|
145
|
+
{ trigger: 'axis', axisPointer: { type: 'cross' } },
|
|
146
|
+
{ show: showTooltip, formatter: tooltipFormatter, valueFormatter: tooltipValueFormatter }
|
|
147
|
+
),
|
|
148
|
+
grid: gridArr,
|
|
149
|
+
xAxis: xAxisArr as EChartsOption['xAxis'],
|
|
150
|
+
yAxis: yAxisArr as EChartsOption['yAxis'],
|
|
151
|
+
dataZoom: [
|
|
152
|
+
{ type: 'inside', start: 80, end: 100 },
|
|
153
|
+
{ show: true, type: 'slider', top: '90%', start: 80, end: 100 },
|
|
154
|
+
],
|
|
155
|
+
series: seriesArr,
|
|
156
|
+
} as EChartsOption,
|
|
157
|
+
optionOverride
|
|
158
|
+
);
|
|
133
159
|
});
|
|
134
160
|
|
|
135
161
|
const accessibilityDescription = $derived(
|
|
@@ -148,6 +174,7 @@
|
|
|
148
174
|
instance.on('click', (params) => {
|
|
149
175
|
const dataIndex = params.dataIndex || 0;
|
|
150
176
|
const item = data.data[dataIndex];
|
|
177
|
+
if (!item) return;
|
|
151
178
|
onClick({
|
|
152
179
|
type: params.type || 'click',
|
|
153
180
|
componentType: params.componentType || 'series',
|