@wavemaker/react-native-echarts 1.0.0-dev.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/.npmignore +4 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/area/area-chart.d.ts +12 -0
- package/area/area-chart.d.ts.map +1 -0
- package/area/area-chart.js +404 -0
- package/area/area-chart.props.d.ts +64 -0
- package/area/area-chart.props.d.ts.map +1 -0
- package/area/area-chart.props.js +0 -0
- package/area/index.d.ts +3 -0
- package/area/index.d.ts.map +1 -0
- package/area/index.js +1 -0
- package/axis.d.ts +3 -0
- package/axis.d.ts.map +1 -0
- package/axis.js +16 -0
- package/bar/bar-chart.d.ts +11 -0
- package/bar/bar-chart.d.ts.map +1 -0
- package/bar/bar-chart.js +6 -0
- package/bar/bar-chart.props.d.ts +7 -0
- package/bar/bar-chart.props.d.ts.map +1 -0
- package/bar/bar-chart.props.js +0 -0
- package/bar/index.d.ts +3 -0
- package/bar/index.d.ts.map +1 -0
- package/bar/index.js +1 -0
- package/bubble/bubble-chart.d.ts +13 -0
- package/bubble/bubble-chart.d.ts.map +1 -0
- package/bubble/bubble-chart.js +305 -0
- package/bubble/bubble-chart.props.d.ts +26 -0
- package/bubble/bubble-chart.props.d.ts.map +1 -0
- package/bubble/bubble-chart.props.js +0 -0
- package/bubble/index.d.ts +3 -0
- package/bubble/index.d.ts.map +1 -0
- package/bubble/index.js +1 -0
- package/candlestick/candlestick-chart.d.ts +12 -0
- package/candlestick/candlestick-chart.d.ts.map +1 -0
- package/candlestick/candlestick-chart.js +292 -0
- package/candlestick/candlestick-chart.props.d.ts +51 -0
- package/candlestick/candlestick-chart.props.d.ts.map +1 -0
- package/candlestick/candlestick-chart.props.js +0 -0
- package/candlestick/index.d.ts +3 -0
- package/candlestick/index.d.ts.map +1 -0
- package/candlestick/index.js +1 -0
- package/chart-container.d.ts +6 -0
- package/chart-container.d.ts.map +1 -0
- package/chart-container.js +63 -0
- package/chart-theme.context.d.ts +191 -0
- package/chart-theme.context.d.ts.map +1 -0
- package/chart-theme.context.js +276 -0
- package/column/column-chart.d.ts +13 -0
- package/column/column-chart.d.ts.map +1 -0
- package/column/column-chart.js +481 -0
- package/column/column-chart.props.d.ts +83 -0
- package/column/column-chart.props.d.ts.map +1 -0
- package/column/column-chart.props.js +0 -0
- package/column/index.d.ts +3 -0
- package/column/index.d.ts.map +1 -0
- package/column/index.js +1 -0
- package/gauge/digital/digital.gauge.d.ts +28 -0
- package/gauge/digital/digital.gauge.d.ts.map +1 -0
- package/gauge/digital/digital.gauge.js +213 -0
- package/gauge/gauge.types.d.ts +51 -0
- package/gauge/gauge.types.d.ts.map +1 -0
- package/gauge/gauge.types.js +0 -0
- package/gauge/index.d.ts +6 -0
- package/gauge/index.d.ts.map +1 -0
- package/gauge/index.js +4 -0
- package/gauge/radial/radial.gauge.d.ts +18 -0
- package/gauge/radial/radial.gauge.d.ts.map +1 -0
- package/gauge/radial/radial.gauge.js +284 -0
- package/gauge/simple/simple.gauge.d.ts +28 -0
- package/gauge/simple/simple.gauge.d.ts.map +1 -0
- package/gauge/simple/simple.gauge.js +102 -0
- package/gauge/speedometer/speedometer.gauge.d.ts +35 -0
- package/gauge/speedometer/speedometer.gauge.d.ts.map +1 -0
- package/gauge/speedometer/speedometer.gauge.js +241 -0
- package/geo/geo-chart.d.ts +15 -0
- package/geo/geo-chart.d.ts.map +1 -0
- package/geo/geo-chart.js +200 -0
- package/geo/geo-chart.props.d.ts +96 -0
- package/geo/geo-chart.props.d.ts.map +1 -0
- package/geo/geo-chart.props.js +0 -0
- package/geo/index.d.ts +7 -0
- package/geo/index.d.ts.map +1 -0
- package/geo/index.js +3 -0
- package/geo/us-chart.d.ts +15 -0
- package/geo/us-chart.d.ts.map +1 -0
- package/geo/us-chart.js +15 -0
- package/geo/world-chart.d.ts +15 -0
- package/geo/world-chart.d.ts.map +1 -0
- package/geo/world-chart.js +10 -0
- package/index.d.ts +17 -0
- package/index.d.ts.map +1 -0
- package/index.js +15 -0
- package/line/index.d.ts +3 -0
- package/line/index.d.ts.map +1 -0
- package/line/index.js +1 -0
- package/line/line-chart.d.ts +9 -0
- package/line/line-chart.d.ts.map +1 -0
- package/line/line-chart.js +8 -0
- package/line/line-chart.props.d.ts +12 -0
- package/line/line-chart.props.d.ts.map +1 -0
- package/line/line-chart.props.js +0 -0
- package/package.json +39 -0
- package/pie/index.d.ts +4 -0
- package/pie/index.d.ts.map +1 -0
- package/pie/index.js +2 -0
- package/pie/pie-chart.d.ts +13 -0
- package/pie/pie-chart.d.ts.map +1 -0
- package/pie/pie-chart.js +222 -0
- package/pie/pie-chart.props.d.ts +97 -0
- package/pie/pie-chart.props.d.ts.map +1 -0
- package/pie/pie-chart.props.js +12 -0
- package/props/cartesian.d.ts +120 -0
- package/props/cartesian.d.ts.map +1 -0
- package/props/cartesian.js +0 -0
- package/props/common.d.ts +28 -0
- package/props/common.d.ts.map +1 -0
- package/props/common.js +0 -0
- package/radar/index.d.ts +3 -0
- package/radar/index.d.ts.map +1 -0
- package/radar/index.js +1 -0
- package/radar/radar-chart.d.ts +12 -0
- package/radar/radar-chart.d.ts.map +1 -0
- package/radar/radar-chart.js +197 -0
- package/radar/radar-chart.props.d.ts +80 -0
- package/radar/radar-chart.props.d.ts.map +1 -0
- package/radar/radar-chart.props.js +0 -0
- package/radial/index.d.ts +3 -0
- package/radial/index.d.ts.map +1 -0
- package/radial/index.js +1 -0
- package/radial/radial-chart.d.ts +12 -0
- package/radial/radial-chart.d.ts.map +1 -0
- package/radial/radial-chart.js +235 -0
- package/radial/radial-chart.props.d.ts +74 -0
- package/radial/radial-chart.props.d.ts.map +1 -0
- package/radial/radial-chart.props.js +0 -0
- package/scatter/index.d.ts +3 -0
- package/scatter/index.d.ts.map +1 -0
- package/scatter/index.js +1 -0
- package/scatter/scatter-chart.d.ts +13 -0
- package/scatter/scatter-chart.d.ts.map +1 -0
- package/scatter/scatter-chart.js +310 -0
- package/scatter/scatter-chart.props.d.ts +36 -0
- package/scatter/scatter-chart.props.d.ts.map +1 -0
- package/scatter/scatter-chart.props.js +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { withResponsiveContainer } from '../chart-container';
|
|
2
|
+
import { useChartTheme, withChartTheme } from '../chart-theme.context';
|
|
3
|
+
import { SkiaChart, SkiaRenderer } from '@wuba/react-native-echarts';
|
|
4
|
+
import { PieChart as EChartsPieChart } from 'echarts/charts';
|
|
5
|
+
import { LegendComponent, TitleComponent, TooltipComponent } from 'echarts/components';
|
|
6
|
+
import * as echarts from 'echarts/core';
|
|
7
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
8
|
+
echarts.use([TooltipComponent, TitleComponent, LegendComponent, SkiaRenderer, EChartsPieChart]);
|
|
9
|
+
/** Percent of height reserved for horizontal legend (top/bottom) to avoid overlap */
|
|
10
|
+
const LEGEND_RESERVE_PCT = 22;
|
|
11
|
+
/** Percent of width reserved for vertical legend (left/right) to avoid overlap */
|
|
12
|
+
const LEGEND_RESERVE_WIDTH_PCT = 26;
|
|
13
|
+
/** Default blue gradient (outer dark → inner light) for concentric rings */
|
|
14
|
+
const DEFAULT_RING_COLORS = [
|
|
15
|
+
'#1e3a8a',
|
|
16
|
+
'#1e40af',
|
|
17
|
+
'#2563eb',
|
|
18
|
+
'#3b82f6',
|
|
19
|
+
'#60a5fa',
|
|
20
|
+
'#93c5fd',
|
|
21
|
+
'#bfdbfe',
|
|
22
|
+
];
|
|
23
|
+
const ChartComponent = ({ data, width = 220, height = 350, innerRadius = '20%', backgroundColor = '#e8e8e899', centerText, centerSubtext, showLegend = true, legendPosition = 'bottom', startAngle = 0, clockwise = false, ringGap = '4%', onSelect, ...props }) => {
|
|
24
|
+
const { theme } = useChartTheme(props.theme, props.colors);
|
|
25
|
+
const chartRef = useRef(null);
|
|
26
|
+
const onSelectRef = useRef(onSelect);
|
|
27
|
+
onSelectRef.current = onSelect;
|
|
28
|
+
const selectContextRef = useRef([]);
|
|
29
|
+
const normalizedData = useMemo(() => {
|
|
30
|
+
if (!Array.isArray(data) || data.length === 0)
|
|
31
|
+
return [];
|
|
32
|
+
return data.map((d) => ({
|
|
33
|
+
label: d.label,
|
|
34
|
+
value: Math.min(100, Math.max(0, Number(d.value))),
|
|
35
|
+
}));
|
|
36
|
+
}, [data]);
|
|
37
|
+
selectContextRef.current = normalizedData;
|
|
38
|
+
const option = useMemo(() => {
|
|
39
|
+
if (normalizedData.length === 0)
|
|
40
|
+
return { series: [] };
|
|
41
|
+
const seriesColors = props.colors && props.colors.length > 0
|
|
42
|
+
? props.colors
|
|
43
|
+
: theme.series.map((s) => s.color);
|
|
44
|
+
const ringColors = seriesColors.length >= normalizedData.length
|
|
45
|
+
? seriesColors
|
|
46
|
+
: DEFAULT_RING_COLORS.slice(0, normalizedData.length);
|
|
47
|
+
const innerPct = typeof innerRadius === 'number'
|
|
48
|
+
? innerRadius
|
|
49
|
+
: typeof innerRadius === 'string' && innerRadius.endsWith('%')
|
|
50
|
+
? parseFloat(innerRadius)
|
|
51
|
+
: 20;
|
|
52
|
+
const gapPct = typeof ringGap === 'number'
|
|
53
|
+
? ringGap
|
|
54
|
+
: typeof ringGap === 'string' && ringGap.endsWith('%')
|
|
55
|
+
? parseFloat(ringGap)
|
|
56
|
+
: 0;
|
|
57
|
+
const n = normalizedData.length;
|
|
58
|
+
const totalGap = (n - 1) * gapPct;
|
|
59
|
+
const available = 100 - innerPct - totalGap;
|
|
60
|
+
const ringStep = n > 0 ? available / n : 0;
|
|
61
|
+
const legendTopBottom = showLegend && (legendPosition === 'top' || legendPosition === 'bottom');
|
|
62
|
+
const legendLeftRight = showLegend && (legendPosition === 'left' || legendPosition === 'right');
|
|
63
|
+
const radiusScaleY = legendTopBottom
|
|
64
|
+
? (100 - LEGEND_RESERVE_PCT) / 100
|
|
65
|
+
: 1;
|
|
66
|
+
const radiusScaleX = legendLeftRight
|
|
67
|
+
? (100 - LEGEND_RESERVE_WIDTH_PCT) / 100
|
|
68
|
+
: 1;
|
|
69
|
+
const radiusScale = Math.min(radiusScaleY, radiusScaleX);
|
|
70
|
+
const centerY = legendTopBottom
|
|
71
|
+
? legendPosition === 'bottom'
|
|
72
|
+
? (100 - LEGEND_RESERVE_PCT) / 2
|
|
73
|
+
: 100 - (100 - LEGEND_RESERVE_PCT) / 2
|
|
74
|
+
: 50;
|
|
75
|
+
const centerX = legendLeftRight
|
|
76
|
+
? legendPosition === 'left'
|
|
77
|
+
? 100 - (100 - LEGEND_RESERVE_WIDTH_PCT) / 2
|
|
78
|
+
: (100 - LEGEND_RESERVE_WIDTH_PCT) / 2
|
|
79
|
+
: 50;
|
|
80
|
+
const seriesConfigs = normalizedData.map((item, index) => {
|
|
81
|
+
const fillPct = item.value;
|
|
82
|
+
const restPct = 100 - fillPct;
|
|
83
|
+
const ringInner = innerPct + index * (ringStep + gapPct);
|
|
84
|
+
const ringOuter = ringInner + ringStep;
|
|
85
|
+
const color = ringColors[index % ringColors.length];
|
|
86
|
+
const name = item.label ?? `Ring ${index + 1}`;
|
|
87
|
+
const scaledInner = ringInner * radiusScale;
|
|
88
|
+
const scaledOuter = ringOuter * radiusScale;
|
|
89
|
+
return {
|
|
90
|
+
type: 'pie',
|
|
91
|
+
radius: [`${scaledInner}%`, `${scaledOuter}%`],
|
|
92
|
+
center: [`${centerX}%`, `${centerY}%`],
|
|
93
|
+
startAngle,
|
|
94
|
+
clockwise,
|
|
95
|
+
data: [
|
|
96
|
+
{
|
|
97
|
+
name: `${name} (${fillPct}%)`,
|
|
98
|
+
value: fillPct,
|
|
99
|
+
itemStyle: { color },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: '',
|
|
103
|
+
value: restPct,
|
|
104
|
+
itemStyle: { color: backgroundColor },
|
|
105
|
+
label: { show: false },
|
|
106
|
+
labelLine: { show: false },
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
label: { show: false },
|
|
110
|
+
labelLine: { show: false },
|
|
111
|
+
emphasis: {
|
|
112
|
+
scale: false,
|
|
113
|
+
itemStyle: {
|
|
114
|
+
shadowBlur: 8,
|
|
115
|
+
shadowOffsetX: 0,
|
|
116
|
+
shadowColor: 'rgba(0, 0, 0, 0.2)',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
const legendData = normalizedData.map((item, index) => `${item.label ?? `Ring ${index + 1}`} (${item.value}%)`);
|
|
122
|
+
const config = {
|
|
123
|
+
tooltip: {
|
|
124
|
+
trigger: 'item',
|
|
125
|
+
formatter: (params) => params.name ? params.name : '',
|
|
126
|
+
},
|
|
127
|
+
series: seriesConfigs,
|
|
128
|
+
};
|
|
129
|
+
if (showLegend) {
|
|
130
|
+
const isVertical = legendPosition === 'left' || legendPosition === 'right';
|
|
131
|
+
config.legend = {
|
|
132
|
+
show: true,
|
|
133
|
+
data: legendData,
|
|
134
|
+
orient: isVertical ? 'vertical' : 'horizontal',
|
|
135
|
+
...(legendPosition === 'top' && { top: 8 }),
|
|
136
|
+
...(legendPosition === 'bottom' && { bottom: 8 }),
|
|
137
|
+
...(legendPosition === 'left' && { left: 8 }),
|
|
138
|
+
...(legendPosition === 'right' && { right: 8 }),
|
|
139
|
+
textStyle: {
|
|
140
|
+
color: theme.legend?.textColor ?? '#333333',
|
|
141
|
+
fontSize: theme.legend?.fontSize ?? 11,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (centerText != null && centerText !== '' || centerSubtext != null && centerSubtext !== '') {
|
|
146
|
+
const textColor = theme.legend?.textColor ?? '#333333';
|
|
147
|
+
config.title = {
|
|
148
|
+
text: centerText ?? '',
|
|
149
|
+
subtext: centerSubtext ?? '',
|
|
150
|
+
left: 'center',
|
|
151
|
+
top: 'middle',
|
|
152
|
+
textStyle: {
|
|
153
|
+
fontSize: 16,
|
|
154
|
+
color: textColor,
|
|
155
|
+
fontWeight: 'bold',
|
|
156
|
+
},
|
|
157
|
+
subtextStyle: {
|
|
158
|
+
fontSize: 12,
|
|
159
|
+
color: textColor,
|
|
160
|
+
},
|
|
161
|
+
itemGap: 4,
|
|
162
|
+
z: 100,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return config;
|
|
166
|
+
}, [
|
|
167
|
+
normalizedData,
|
|
168
|
+
theme,
|
|
169
|
+
innerRadius,
|
|
170
|
+
backgroundColor,
|
|
171
|
+
centerText,
|
|
172
|
+
centerSubtext,
|
|
173
|
+
showLegend,
|
|
174
|
+
legendPosition,
|
|
175
|
+
startAngle,
|
|
176
|
+
clockwise,
|
|
177
|
+
ringGap,
|
|
178
|
+
props.colors,
|
|
179
|
+
]);
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
let chart;
|
|
182
|
+
if (chartRef.current) {
|
|
183
|
+
try {
|
|
184
|
+
chart = echarts.init(chartRef.current, 'light', {
|
|
185
|
+
width,
|
|
186
|
+
height,
|
|
187
|
+
});
|
|
188
|
+
chart.setOption(option);
|
|
189
|
+
const handlePieClick = (params) => {
|
|
190
|
+
const cb = onSelectRef.current;
|
|
191
|
+
if (typeof cb !== 'function')
|
|
192
|
+
return;
|
|
193
|
+
if (params.componentType !== 'series')
|
|
194
|
+
return;
|
|
195
|
+
if (params.seriesType !== 'pie')
|
|
196
|
+
return;
|
|
197
|
+
if (params.dataIndex !== 0)
|
|
198
|
+
return;
|
|
199
|
+
const ringIndex = params.seriesIndex;
|
|
200
|
+
if (typeof ringIndex !== 'number' || ringIndex < 0)
|
|
201
|
+
return;
|
|
202
|
+
const row = selectContextRef.current[ringIndex];
|
|
203
|
+
if (!row)
|
|
204
|
+
return;
|
|
205
|
+
const event = {
|
|
206
|
+
seriesIndex: ringIndex,
|
|
207
|
+
dataIndex: 0,
|
|
208
|
+
label: row.label ?? `Ring ${ringIndex + 1}`,
|
|
209
|
+
value: row.value,
|
|
210
|
+
};
|
|
211
|
+
cb(event);
|
|
212
|
+
};
|
|
213
|
+
chart.on('click', handlePieClick);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.warn('Radial chart initialization error:', error);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return () => {
|
|
220
|
+
if (chart) {
|
|
221
|
+
try {
|
|
222
|
+
chart.dispose();
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.warn('Radial chart disposal error:', error);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}, [option, width, height]);
|
|
230
|
+
return <SkiaChart ref={chartRef} useRNGH/>;
|
|
231
|
+
};
|
|
232
|
+
const RadialChartComponent = withResponsiveContainer(withChartTheme(ChartComponent));
|
|
233
|
+
export const RadialChart = Object.assign(RadialChartComponent, {
|
|
234
|
+
displayName: 'RadialChart',
|
|
235
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { CommonChartProps } from '../props/common';
|
|
2
|
+
/** Emitted when the user taps/clicks a ring’s filled segment. */
|
|
3
|
+
export interface RadialChartSelectEvent {
|
|
4
|
+
seriesIndex: number;
|
|
5
|
+
dataIndex: number;
|
|
6
|
+
label: string;
|
|
7
|
+
value: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Single data item for radial chart: one concentric ring.
|
|
11
|
+
* value = fill percentage (0–100) for that ring; label optional (tooltip).
|
|
12
|
+
*/
|
|
13
|
+
export interface RadialDataItem {
|
|
14
|
+
label?: string;
|
|
15
|
+
value: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Props for RadialChart (concentric ring chart).
|
|
19
|
+
* common -> radial
|
|
20
|
+
*/
|
|
21
|
+
export interface RadialChartProps extends CommonChartProps {
|
|
22
|
+
/**
|
|
23
|
+
* Chart data: one item per concentric ring. value = fill percentage (0–100).
|
|
24
|
+
*/
|
|
25
|
+
data: RadialDataItem[];
|
|
26
|
+
/**
|
|
27
|
+
* Inner radius of the chart (center hole), e.g. '20%' or 40.
|
|
28
|
+
* @default '20%'
|
|
29
|
+
*/
|
|
30
|
+
innerRadius?: string | number;
|
|
31
|
+
/**
|
|
32
|
+
* Color for the unfilled portion of each ring.
|
|
33
|
+
* @default '#e8e8e899'
|
|
34
|
+
*/
|
|
35
|
+
backgroundColor?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Optional center text (e.g. main value or title).
|
|
38
|
+
*/
|
|
39
|
+
centerText?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Optional center subtext shown below center text (e.g. label or unit).
|
|
42
|
+
*/
|
|
43
|
+
centerSubtext?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Start angle of the fill (and animation), in degrees. 0 = right (3 o'clock), 90 = top (12 o'clock).
|
|
46
|
+
* @default 0
|
|
47
|
+
*/
|
|
48
|
+
startAngle?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Direction of the fill and animation. true = clockwise, false = counter-clockwise (anti-clockwise).
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
clockwise?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Gap between concentric rings, as percentage of chart radius (e.g. '4%') or number.
|
|
56
|
+
* @default '4%'
|
|
57
|
+
*/
|
|
58
|
+
ringGap?: string | number;
|
|
59
|
+
/**
|
|
60
|
+
* Whether to show the legend (ring labels).
|
|
61
|
+
* @default true
|
|
62
|
+
*/
|
|
63
|
+
showLegend?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Position of the legend.
|
|
66
|
+
* @default 'bottom'
|
|
67
|
+
*/
|
|
68
|
+
legendPosition?: 'left' | 'right' | 'top' | 'bottom';
|
|
69
|
+
/**
|
|
70
|
+
* Called when the user selects (taps/clicks) a ring’s value segment.
|
|
71
|
+
*/
|
|
72
|
+
onSelect?: (event: RadialChartSelectEvent) => void;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=radial-chart.props.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radial-chart.props.d.ts","sourceRoot":"","sources":["../../../../components/chart/radial/radial-chart.props.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,iEAAiE;AACjE,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD;;OAEG;IACH,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;IACrD;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACpD"}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/chart/scatter/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/scatter/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ScatterChart } from './scatter-chart';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ScatterChartProps } from './scatter-chart.props';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
/** common -> cartesian -> scatter */
|
|
4
|
+
export type { ScatterChartProps, ScatterSeriesData } from './scatter-chart.props';
|
|
5
|
+
export declare const ScatterChart: ((props: ScatterChartProps & {
|
|
6
|
+
theme?: Partial<import("..").ChartTheme>;
|
|
7
|
+
} & {
|
|
8
|
+
width?: number | string;
|
|
9
|
+
height?: number | string;
|
|
10
|
+
}) => React.JSX.Element) & {
|
|
11
|
+
displayName: string;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=scatter-chart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scatter-chart.d.ts","sourceRoot":"","sources":["../../../../components/chart/scatter/scatter-chart.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAS/D,OAAO,KAAqC,MAAM,OAAO,CAAC;AAI1D,qCAAqC;AACrC,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAyVlF,eAAO,MAAM,YAAY;;;;;;;CAEvB,CAAC"}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { withResponsiveContainer } from '../chart-container';
|
|
2
|
+
import { useChartTheme, withChartTheme } from '../chart-theme.context';
|
|
3
|
+
import { SkiaChart, SkiaRenderer } from '@wuba/react-native-echarts';
|
|
4
|
+
import { LineChart as EChartsLineChart, ScatterChart as EChartsScatterChart } from 'echarts/charts';
|
|
5
|
+
import { GridComponent, LegendComponent, TooltipComponent, } from 'echarts/components';
|
|
6
|
+
import * as echarts from 'echarts/core';
|
|
7
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
8
|
+
import { getAxis } from '../axis';
|
|
9
|
+
echarts.use([
|
|
10
|
+
TooltipComponent,
|
|
11
|
+
GridComponent,
|
|
12
|
+
LegendComponent,
|
|
13
|
+
SkiaRenderer,
|
|
14
|
+
EChartsScatterChart,
|
|
15
|
+
EChartsLineChart,
|
|
16
|
+
]);
|
|
17
|
+
/** Linear regression: returns [slope, intercept] for y = slope * x + intercept. */
|
|
18
|
+
function linearRegression(points) {
|
|
19
|
+
const n = points.length;
|
|
20
|
+
if (n === 0)
|
|
21
|
+
return { slope: 0, intercept: 0 };
|
|
22
|
+
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
|
23
|
+
for (const [x, y] of points) {
|
|
24
|
+
sumX += x;
|
|
25
|
+
sumY += y;
|
|
26
|
+
sumXY += x * y;
|
|
27
|
+
sumX2 += x * x;
|
|
28
|
+
}
|
|
29
|
+
const denom = n * sumX2 - sumX * sumX;
|
|
30
|
+
const slope = denom === 0 ? 0 : (n * sumXY - sumX * sumY) / denom;
|
|
31
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
32
|
+
return { slope, intercept };
|
|
33
|
+
}
|
|
34
|
+
const ChartComponent = ({ data, width = 220, height = 350, symbol = 'circle', symbolSize = 8, showXAxis = true, showXAxisTicks = true, showYAxis = true, showYAxisTicks = true, showXAxisSplitLines = true, showYAxisSplitLines = true, boundaryGap = false, grid, showLegend = false, showHighlighter = true, showRegressionLine = false, xAxisTickLabelFormatter, yAxisTickLabelFormatter, xAxisTicks, xAxisLabel, yAxisLabel, onSelect, ...props }) => {
|
|
35
|
+
const { theme } = useChartTheme(props.theme, props.colors);
|
|
36
|
+
const chartRef = useRef(null);
|
|
37
|
+
const onSelectRef = useRef(onSelect);
|
|
38
|
+
onSelectRef.current = onSelect;
|
|
39
|
+
const selectContextRef = useRef({ normalizedSeries: [] });
|
|
40
|
+
const normalizedSeries = useMemo(() => {
|
|
41
|
+
if (!Array.isArray(data) || data.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const first = data[0];
|
|
44
|
+
if (Array.isArray(first) && first.length >= 2 && typeof first[0] === 'number') {
|
|
45
|
+
return [{ data: data }];
|
|
46
|
+
}
|
|
47
|
+
if (typeof first === 'object' && first !== null && 'data' in first) {
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
return [];
|
|
51
|
+
}, [data]);
|
|
52
|
+
const hasNamedSeries = useMemo(() => normalizedSeries.some((s) => 'name' in s && s.name), [normalizedSeries]);
|
|
53
|
+
selectContextRef.current = { normalizedSeries };
|
|
54
|
+
const option = useMemo(() => {
|
|
55
|
+
const dataPoints = normalizedSeries.map(s => s.data.map(item => item[0])).flat();
|
|
56
|
+
const xAxisData = xAxisTicks != null && xAxisTicks.length > 0
|
|
57
|
+
? xAxisTicks
|
|
58
|
+
: getAxis(dataPoints).map(String);
|
|
59
|
+
const tooltipConfig = showHighlighter
|
|
60
|
+
? {
|
|
61
|
+
trigger: 'item',
|
|
62
|
+
axisPointer: {
|
|
63
|
+
type: 'cross',
|
|
64
|
+
lineStyle: {
|
|
65
|
+
type: 'line',
|
|
66
|
+
width: 1,
|
|
67
|
+
color: theme.series[0]?.color ?? '#999',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
: { trigger: 'item' };
|
|
72
|
+
// Scatter uses value axes for both x and y (data is [x, y] pairs)
|
|
73
|
+
const xAxisConfig = {
|
|
74
|
+
type: 'category',
|
|
75
|
+
boundaryGap,
|
|
76
|
+
data: xAxisData,
|
|
77
|
+
...(xAxisLabel != null && xAxisLabel !== '' && {
|
|
78
|
+
name: xAxisLabel,
|
|
79
|
+
nameLocation: 'middle',
|
|
80
|
+
nameGap: 25,
|
|
81
|
+
nameTextStyle: { color: theme.axis.x.tickLabelColor },
|
|
82
|
+
}),
|
|
83
|
+
axisLabel: {
|
|
84
|
+
show: showXAxis || xAxisTickLabelFormatter != null,
|
|
85
|
+
color: theme.axis.x.tickLabelColor,
|
|
86
|
+
...(xAxisTickLabelFormatter && { formatter: xAxisTickLabelFormatter }),
|
|
87
|
+
},
|
|
88
|
+
axisLine: showXAxis
|
|
89
|
+
? {
|
|
90
|
+
show: true,
|
|
91
|
+
lineStyle: {
|
|
92
|
+
color: theme.axis.x.lineColor,
|
|
93
|
+
width: theme.axis.x.lineWidth,
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
: { show: false },
|
|
97
|
+
axisTick: {
|
|
98
|
+
show: showXAxisTicks,
|
|
99
|
+
lineStyle: {
|
|
100
|
+
color: theme.axis.x.tickColor,
|
|
101
|
+
width: theme.axis.x.tickWidth,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
splitLine: {
|
|
105
|
+
show: showXAxisSplitLines,
|
|
106
|
+
lineStyle: {
|
|
107
|
+
color: theme.axis.x.splitLineColor,
|
|
108
|
+
width: theme.axis.x.splitLineWidth,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
const yAxisConfig = {
|
|
113
|
+
type: 'value',
|
|
114
|
+
...(yAxisLabel != null && yAxisLabel !== '' && {
|
|
115
|
+
name: yAxisLabel,
|
|
116
|
+
nameLocation: 'middle',
|
|
117
|
+
nameGap: 40,
|
|
118
|
+
nameTextStyle: { color: theme.axis.y.tickLabelColor },
|
|
119
|
+
}),
|
|
120
|
+
axisLabel: {
|
|
121
|
+
show: showYAxis || yAxisTickLabelFormatter != null,
|
|
122
|
+
color: theme.axis.y.tickLabelColor,
|
|
123
|
+
...(yAxisTickLabelFormatter && { formatter: yAxisTickLabelFormatter }),
|
|
124
|
+
},
|
|
125
|
+
axisLine: showYAxis
|
|
126
|
+
? {
|
|
127
|
+
show: true,
|
|
128
|
+
lineStyle: {
|
|
129
|
+
color: theme.axis.y.lineColor,
|
|
130
|
+
width: theme.axis.y.lineWidth,
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
: { show: false },
|
|
134
|
+
axisTick: {
|
|
135
|
+
show: showYAxisTicks,
|
|
136
|
+
lineStyle: {
|
|
137
|
+
color: theme.axis.y.tickColor,
|
|
138
|
+
width: theme.axis.y.tickWidth,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
splitLine: {
|
|
142
|
+
show: showYAxisSplitLines,
|
|
143
|
+
lineStyle: {
|
|
144
|
+
color: theme.axis.y.splitLineColor,
|
|
145
|
+
width: theme.axis.y.splitLineWidth,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const legendConfig = showLegend && hasNamedSeries
|
|
150
|
+
? {
|
|
151
|
+
data: normalizedSeries
|
|
152
|
+
.filter((s) => 'name' in s && s.name)
|
|
153
|
+
.map((s) => s.name),
|
|
154
|
+
textStyle: {
|
|
155
|
+
color: theme.legend.textColor,
|
|
156
|
+
fontSize: theme.legend.fontSize,
|
|
157
|
+
},
|
|
158
|
+
backgroundColor: theme.legend.backgroundColor,
|
|
159
|
+
}
|
|
160
|
+
: undefined;
|
|
161
|
+
const seriesConfig = [];
|
|
162
|
+
normalizedSeries.forEach((s, index) => {
|
|
163
|
+
const seriesColor = theme.series[index % theme.series.length].color;
|
|
164
|
+
const seriesData = 'data' in s ? s.data : [];
|
|
165
|
+
const seriesName = 'name' in s && s.name ? s.name : undefined;
|
|
166
|
+
const scatterSeries = {
|
|
167
|
+
type: 'scatter',
|
|
168
|
+
data: seriesData,
|
|
169
|
+
symbol: symbol === 'none' ? 'circle' : symbol,
|
|
170
|
+
symbolSize: symbol === 'none' ? 8 : symbolSize,
|
|
171
|
+
itemStyle: { color: seriesColor },
|
|
172
|
+
emphasis: showHighlighter
|
|
173
|
+
? {
|
|
174
|
+
focus: 'self',
|
|
175
|
+
scale: true,
|
|
176
|
+
symbol: 'circle',
|
|
177
|
+
symbolSize: 8,
|
|
178
|
+
itemStyle: {
|
|
179
|
+
color: seriesColor,
|
|
180
|
+
borderColor: '#FFFFFF',
|
|
181
|
+
borderWidth: 2,
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
: { focus: 'none', scale: false, symbolSize: 0 },
|
|
185
|
+
};
|
|
186
|
+
if (seriesName)
|
|
187
|
+
scatterSeries.name = seriesName;
|
|
188
|
+
seriesConfig.push(scatterSeries);
|
|
189
|
+
if (showRegressionLine && seriesData.length >= 2) {
|
|
190
|
+
const { slope, intercept } = linearRegression(seriesData);
|
|
191
|
+
const xs = seriesData.map((p) => p[0]);
|
|
192
|
+
const xMin = Math.min(...xs);
|
|
193
|
+
const xMax = Math.max(...xs);
|
|
194
|
+
const regressionData = [
|
|
195
|
+
[xMin, slope * xMin + intercept],
|
|
196
|
+
[xMax, slope * xMax + intercept],
|
|
197
|
+
];
|
|
198
|
+
const lineSeries = {
|
|
199
|
+
type: 'line',
|
|
200
|
+
data: regressionData,
|
|
201
|
+
symbol: 'none',
|
|
202
|
+
lineStyle: {
|
|
203
|
+
color: seriesColor,
|
|
204
|
+
width: 2,
|
|
205
|
+
type: 'solid',
|
|
206
|
+
},
|
|
207
|
+
showSymbol: false,
|
|
208
|
+
};
|
|
209
|
+
if (seriesName)
|
|
210
|
+
lineSeries.name = seriesName;
|
|
211
|
+
seriesConfig.push(lineSeries);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
const config = {
|
|
215
|
+
tooltip: tooltipConfig,
|
|
216
|
+
xAxis: xAxisConfig,
|
|
217
|
+
yAxis: yAxisConfig,
|
|
218
|
+
series: seriesConfig,
|
|
219
|
+
};
|
|
220
|
+
if (legendConfig)
|
|
221
|
+
config.legend = legendConfig;
|
|
222
|
+
if (grid)
|
|
223
|
+
config.grid = grid;
|
|
224
|
+
return config;
|
|
225
|
+
}, [
|
|
226
|
+
theme,
|
|
227
|
+
normalizedSeries,
|
|
228
|
+
symbol,
|
|
229
|
+
symbolSize,
|
|
230
|
+
showXAxis,
|
|
231
|
+
showXAxisTicks,
|
|
232
|
+
showYAxis,
|
|
233
|
+
showYAxisTicks,
|
|
234
|
+
showXAxisSplitLines,
|
|
235
|
+
showYAxisSplitLines,
|
|
236
|
+
boundaryGap,
|
|
237
|
+
grid,
|
|
238
|
+
showLegend,
|
|
239
|
+
hasNamedSeries,
|
|
240
|
+
showHighlighter,
|
|
241
|
+
showRegressionLine,
|
|
242
|
+
xAxisTickLabelFormatter,
|
|
243
|
+
yAxisTickLabelFormatter,
|
|
244
|
+
xAxisTicks,
|
|
245
|
+
xAxisLabel,
|
|
246
|
+
yAxisLabel,
|
|
247
|
+
]);
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
let chart;
|
|
250
|
+
if (chartRef.current) {
|
|
251
|
+
try {
|
|
252
|
+
chart = echarts.init(chartRef.current, 'light', { width, height });
|
|
253
|
+
chart.setOption(option);
|
|
254
|
+
const handleSeriesClick = (params) => {
|
|
255
|
+
const cb = onSelectRef.current;
|
|
256
|
+
if (typeof cb !== 'function')
|
|
257
|
+
return;
|
|
258
|
+
if (params.componentType !== 'series')
|
|
259
|
+
return;
|
|
260
|
+
if (params.seriesType !== 'scatter')
|
|
261
|
+
return;
|
|
262
|
+
const seriesIndex = params.seriesIndex;
|
|
263
|
+
const dataIndex = params.dataIndex;
|
|
264
|
+
if (typeof seriesIndex !== 'number' ||
|
|
265
|
+
typeof dataIndex !== 'number' ||
|
|
266
|
+
dataIndex < 0) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const { normalizedSeries: ns } = selectContextRef.current;
|
|
270
|
+
const s = ns[seriesIndex];
|
|
271
|
+
if (!s?.data || !Array.isArray(s.data))
|
|
272
|
+
return;
|
|
273
|
+
const point = s.data[dataIndex];
|
|
274
|
+
if (!point || point.length < 2)
|
|
275
|
+
return;
|
|
276
|
+
const seriesName = s.name != null && s.name !== ''
|
|
277
|
+
? String(s.name)
|
|
278
|
+
: `Series ${seriesIndex + 1}`;
|
|
279
|
+
const event = {
|
|
280
|
+
seriesIndex,
|
|
281
|
+
dataIndex,
|
|
282
|
+
seriesName,
|
|
283
|
+
x: point[0],
|
|
284
|
+
y: Number(point[1]),
|
|
285
|
+
};
|
|
286
|
+
cb(event);
|
|
287
|
+
};
|
|
288
|
+
chart.on('click', handleSeriesClick);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
console.warn('Chart initialization error:', error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return () => {
|
|
295
|
+
if (chart) {
|
|
296
|
+
try {
|
|
297
|
+
chart.dispose();
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.warn('Chart disposal error:', error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}, [option, width, height]);
|
|
305
|
+
return <SkiaChart ref={chartRef} useRNGH/>;
|
|
306
|
+
};
|
|
307
|
+
const ScatterChartComponent = withResponsiveContainer(withChartTheme(ChartComponent));
|
|
308
|
+
export const ScatterChart = Object.assign(ScatterChartComponent, {
|
|
309
|
+
displayName: 'ScatterChart',
|
|
310
|
+
});
|