@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.
Files changed (145) hide show
  1. package/.npmignore +4 -0
  2. package/LICENSE +21 -0
  3. package/README.md +39 -0
  4. package/area/area-chart.d.ts +12 -0
  5. package/area/area-chart.d.ts.map +1 -0
  6. package/area/area-chart.js +404 -0
  7. package/area/area-chart.props.d.ts +64 -0
  8. package/area/area-chart.props.d.ts.map +1 -0
  9. package/area/area-chart.props.js +0 -0
  10. package/area/index.d.ts +3 -0
  11. package/area/index.d.ts.map +1 -0
  12. package/area/index.js +1 -0
  13. package/axis.d.ts +3 -0
  14. package/axis.d.ts.map +1 -0
  15. package/axis.js +16 -0
  16. package/bar/bar-chart.d.ts +11 -0
  17. package/bar/bar-chart.d.ts.map +1 -0
  18. package/bar/bar-chart.js +6 -0
  19. package/bar/bar-chart.props.d.ts +7 -0
  20. package/bar/bar-chart.props.d.ts.map +1 -0
  21. package/bar/bar-chart.props.js +0 -0
  22. package/bar/index.d.ts +3 -0
  23. package/bar/index.d.ts.map +1 -0
  24. package/bar/index.js +1 -0
  25. package/bubble/bubble-chart.d.ts +13 -0
  26. package/bubble/bubble-chart.d.ts.map +1 -0
  27. package/bubble/bubble-chart.js +305 -0
  28. package/bubble/bubble-chart.props.d.ts +26 -0
  29. package/bubble/bubble-chart.props.d.ts.map +1 -0
  30. package/bubble/bubble-chart.props.js +0 -0
  31. package/bubble/index.d.ts +3 -0
  32. package/bubble/index.d.ts.map +1 -0
  33. package/bubble/index.js +1 -0
  34. package/candlestick/candlestick-chart.d.ts +12 -0
  35. package/candlestick/candlestick-chart.d.ts.map +1 -0
  36. package/candlestick/candlestick-chart.js +292 -0
  37. package/candlestick/candlestick-chart.props.d.ts +51 -0
  38. package/candlestick/candlestick-chart.props.d.ts.map +1 -0
  39. package/candlestick/candlestick-chart.props.js +0 -0
  40. package/candlestick/index.d.ts +3 -0
  41. package/candlestick/index.d.ts.map +1 -0
  42. package/candlestick/index.js +1 -0
  43. package/chart-container.d.ts +6 -0
  44. package/chart-container.d.ts.map +1 -0
  45. package/chart-container.js +63 -0
  46. package/chart-theme.context.d.ts +191 -0
  47. package/chart-theme.context.d.ts.map +1 -0
  48. package/chart-theme.context.js +276 -0
  49. package/column/column-chart.d.ts +13 -0
  50. package/column/column-chart.d.ts.map +1 -0
  51. package/column/column-chart.js +481 -0
  52. package/column/column-chart.props.d.ts +83 -0
  53. package/column/column-chart.props.d.ts.map +1 -0
  54. package/column/column-chart.props.js +0 -0
  55. package/column/index.d.ts +3 -0
  56. package/column/index.d.ts.map +1 -0
  57. package/column/index.js +1 -0
  58. package/gauge/digital/digital.gauge.d.ts +28 -0
  59. package/gauge/digital/digital.gauge.d.ts.map +1 -0
  60. package/gauge/digital/digital.gauge.js +213 -0
  61. package/gauge/gauge.types.d.ts +51 -0
  62. package/gauge/gauge.types.d.ts.map +1 -0
  63. package/gauge/gauge.types.js +0 -0
  64. package/gauge/index.d.ts +6 -0
  65. package/gauge/index.d.ts.map +1 -0
  66. package/gauge/index.js +4 -0
  67. package/gauge/radial/radial.gauge.d.ts +18 -0
  68. package/gauge/radial/radial.gauge.d.ts.map +1 -0
  69. package/gauge/radial/radial.gauge.js +284 -0
  70. package/gauge/simple/simple.gauge.d.ts +28 -0
  71. package/gauge/simple/simple.gauge.d.ts.map +1 -0
  72. package/gauge/simple/simple.gauge.js +102 -0
  73. package/gauge/speedometer/speedometer.gauge.d.ts +35 -0
  74. package/gauge/speedometer/speedometer.gauge.d.ts.map +1 -0
  75. package/gauge/speedometer/speedometer.gauge.js +241 -0
  76. package/geo/geo-chart.d.ts +15 -0
  77. package/geo/geo-chart.d.ts.map +1 -0
  78. package/geo/geo-chart.js +200 -0
  79. package/geo/geo-chart.props.d.ts +96 -0
  80. package/geo/geo-chart.props.d.ts.map +1 -0
  81. package/geo/geo-chart.props.js +0 -0
  82. package/geo/index.d.ts +7 -0
  83. package/geo/index.d.ts.map +1 -0
  84. package/geo/index.js +3 -0
  85. package/geo/us-chart.d.ts +15 -0
  86. package/geo/us-chart.d.ts.map +1 -0
  87. package/geo/us-chart.js +15 -0
  88. package/geo/world-chart.d.ts +15 -0
  89. package/geo/world-chart.d.ts.map +1 -0
  90. package/geo/world-chart.js +10 -0
  91. package/index.d.ts +17 -0
  92. package/index.d.ts.map +1 -0
  93. package/index.js +15 -0
  94. package/line/index.d.ts +3 -0
  95. package/line/index.d.ts.map +1 -0
  96. package/line/index.js +1 -0
  97. package/line/line-chart.d.ts +9 -0
  98. package/line/line-chart.d.ts.map +1 -0
  99. package/line/line-chart.js +8 -0
  100. package/line/line-chart.props.d.ts +12 -0
  101. package/line/line-chart.props.d.ts.map +1 -0
  102. package/line/line-chart.props.js +0 -0
  103. package/package.json +39 -0
  104. package/pie/index.d.ts +4 -0
  105. package/pie/index.d.ts.map +1 -0
  106. package/pie/index.js +2 -0
  107. package/pie/pie-chart.d.ts +13 -0
  108. package/pie/pie-chart.d.ts.map +1 -0
  109. package/pie/pie-chart.js +222 -0
  110. package/pie/pie-chart.props.d.ts +97 -0
  111. package/pie/pie-chart.props.d.ts.map +1 -0
  112. package/pie/pie-chart.props.js +12 -0
  113. package/props/cartesian.d.ts +120 -0
  114. package/props/cartesian.d.ts.map +1 -0
  115. package/props/cartesian.js +0 -0
  116. package/props/common.d.ts +28 -0
  117. package/props/common.d.ts.map +1 -0
  118. package/props/common.js +0 -0
  119. package/radar/index.d.ts +3 -0
  120. package/radar/index.d.ts.map +1 -0
  121. package/radar/index.js +1 -0
  122. package/radar/radar-chart.d.ts +12 -0
  123. package/radar/radar-chart.d.ts.map +1 -0
  124. package/radar/radar-chart.js +197 -0
  125. package/radar/radar-chart.props.d.ts +80 -0
  126. package/radar/radar-chart.props.d.ts.map +1 -0
  127. package/radar/radar-chart.props.js +0 -0
  128. package/radial/index.d.ts +3 -0
  129. package/radial/index.d.ts.map +1 -0
  130. package/radial/index.js +1 -0
  131. package/radial/radial-chart.d.ts +12 -0
  132. package/radial/radial-chart.d.ts.map +1 -0
  133. package/radial/radial-chart.js +235 -0
  134. package/radial/radial-chart.props.d.ts +74 -0
  135. package/radial/radial-chart.props.d.ts.map +1 -0
  136. package/radial/radial-chart.props.js +0 -0
  137. package/scatter/index.d.ts +3 -0
  138. package/scatter/index.d.ts.map +1 -0
  139. package/scatter/index.js +1 -0
  140. package/scatter/scatter-chart.d.ts +13 -0
  141. package/scatter/scatter-chart.d.ts.map +1 -0
  142. package/scatter/scatter-chart.js +310 -0
  143. package/scatter/scatter-chart.props.d.ts +36 -0
  144. package/scatter/scatter-chart.props.d.ts.map +1 -0
  145. 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,3 @@
1
+ export { ScatterChart } from './scatter-chart';
2
+ export type { ScatterChartProps, ScatterSeriesData } from './scatter-chart.props';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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
+ });