@agions/taroviz 1.11.1 → 2.0.3
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/CHANGELOG.md +245 -0
- package/README.md +104 -302
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/vendors.js +1 -0
- package/dist/cjs/vendors~echarts.js +1 -0
- package/dist/esm/index.js +1 -58151
- package/dist/esm/vendors.js +1 -0
- package/dist/esm/vendors~echarts.js +1 -0
- package/package.json +19 -25
- package/src/adapters/MiniAppAdapter.ts +136 -0
- package/src/adapters/__tests__/index.test.ts +1 -1
- package/src/adapters/h5/__tests__/index.test.ts +4 -2
- package/src/adapters/h5/index.ts +63 -64
- package/src/adapters/harmony/index.ts +23 -245
- package/src/adapters/index.ts +49 -45
- package/src/adapters/swan/index.ts +6 -69
- package/src/adapters/tt/index.ts +7 -70
- package/src/adapters/types.ts +25 -58
- package/src/adapters/weapp/index.ts +6 -69
- package/src/charts/__tests__/testUtils.tsx +87 -0
- package/src/charts/boxplot/__tests__/index.test.tsx +49 -103
- package/src/charts/boxplot/index.tsx +2 -1
- package/src/charts/boxplot/types.ts +17 -16
- package/src/charts/common/BaseChartWrapper.tsx +90 -82
- package/src/charts/common/__mocks__/BaseChartWrapper.tsx +17 -0
- package/src/charts/createChartComponent.tsx +36 -0
- package/src/charts/createOptionChartComponent.tsx +32 -0
- package/src/charts/funnel/__tests__/index.test.tsx +99 -0
- package/src/charts/funnel/index.tsx +60 -10
- package/src/charts/funnel/types.ts +6 -0
- package/src/charts/graph/__tests__/index.test.tsx +102 -33
- package/src/charts/graph/index.tsx +66 -9
- package/src/charts/graph/types.ts +6 -0
- package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
- package/src/charts/heatmap/index.tsx +103 -10
- package/src/charts/heatmap/types.ts +6 -0
- package/src/charts/index.ts +74 -26
- package/src/charts/liquid/__tests__/index.test.tsx +52 -0
- package/src/charts/liquid/index.tsx +239 -182
- package/src/charts/liquid/types.ts +11 -11
- package/src/charts/parallel/__tests__/index.test.tsx +40 -67
- package/src/charts/parallel/index.tsx +2 -1
- package/src/charts/parallel/types.ts +19 -18
- package/src/charts/radar/__tests__/index.test.tsx +210 -0
- package/src/charts/radar/index.tsx +143 -10
- package/src/charts/radar/types.ts +13 -0
- package/src/charts/sankey/__tests__/index.test.tsx +124 -0
- package/src/charts/sankey/index.tsx +62 -10
- package/src/charts/sankey/types.ts +6 -0
- package/src/charts/tree/__tests__/index.test.tsx +71 -0
- package/src/charts/tree/index.tsx +5 -2
- package/src/charts/tree/types.ts +9 -9
- package/src/charts/types.ts +208 -106
- package/src/charts/utils.ts +9 -7
- package/src/charts/wordcloud/__tests__/index.test.tsx +98 -31
- package/src/charts/wordcloud/index.tsx +75 -9
- package/src/charts/wordcloud/types.ts +6 -0
- package/src/components/DataFilter/index.tsx +32 -10
- package/src/core/animation/types.ts +6 -6
- package/src/core/components/Annotation.tsx +6 -7
- package/src/core/components/BaseChart.tsx +110 -168
- package/src/core/components/ErrorBoundary.tsx +17 -4
- package/src/core/components/LazyChart.tsx +54 -55
- package/src/core/components/hooks/index.ts +6 -2
- package/src/core/components/hooks/useChartInit.ts +6 -3
- package/src/core/components/hooks/usePerformance.ts +8 -2
- package/src/core/components/hooks/useVirtualScroll.ts +2 -1
- package/src/core/index.ts +1 -1
- package/src/core/themes/ThemeManager.ts +1 -1
- package/src/core/types/common.ts +2 -1
- package/src/core/types/index.ts +0 -12
- package/src/core/types/platform.ts +3 -5
- package/src/core/utils/__tests__/deepClone.test.ts +317 -0
- package/src/core/utils/__tests__/index.test.ts +2 -1
- package/src/core/utils/chartInstances.ts +13 -0
- package/src/core/utils/common.ts +20 -29
- package/src/core/utils/deepClone.ts +114 -0
- package/src/core/utils/download.ts +128 -0
- package/src/core/utils/drillDown.ts +34 -353
- package/src/core/utils/drillDownHelpers.ts +426 -0
- package/src/core/utils/events.ts +12 -0
- package/src/core/utils/export/ExportUtils.ts +36 -67
- package/src/core/utils/format.ts +44 -0
- package/src/core/utils/index.ts +21 -154
- package/src/core/utils/merge.ts +25 -0
- package/src/core/utils/performance/PerformanceAnalyzer.ts +38 -21
- package/src/core/utils/performance/hooks.ts +7 -0
- package/src/core/utils/performance/index.ts +2 -0
- package/src/{hooks → core/utils/performance}/useAnimation.ts +45 -41
- package/src/core/utils/performance/useDataZoom.ts +324 -0
- package/src/{hooks → core/utils/performance}/usePerformance.ts +49 -41
- package/src/core/utils/performance/usePerformanceHooks.ts +278 -0
- package/src/core/utils/performanceUtils.ts +310 -0
- package/src/core/utils/runtime.ts +190 -0
- package/src/core/utils/setOptionUtils.ts +59 -0
- package/src/core/version.ts +14 -0
- package/src/editor/EnhancedThemeEditor.tsx +362 -540
- package/src/editor/ThemeEditor.tsx +55 -321
- package/src/editor/components/ThemeBasicSettings.tsx +113 -0
- package/src/editor/components/ThemeColorEditor.tsx +105 -0
- package/src/editor/components/ThemeSelector.tsx +70 -0
- package/src/editor/hooks/useThemeEditorState.ts +201 -0
- package/src/editor/index.ts +10 -2
- package/src/hooks/__tests__/index.test.tsx +3 -1
- package/src/hooks/chartConnectHelpers.ts +341 -0
- package/src/hooks/index.ts +55 -660
- package/src/hooks/types.ts +189 -0
- package/src/hooks/useChartAutoResize.ts +73 -0
- package/src/hooks/useChartConnect.ts +92 -238
- package/src/hooks/useChartDownload.ts +25 -27
- package/src/hooks/useChartHistory.ts +34 -49
- package/src/hooks/useChartInit.ts +59 -0
- package/src/hooks/useChartOptions.ts +259 -0
- package/src/hooks/useChartPerformance.ts +109 -0
- package/src/hooks/useChartSelection.ts +52 -49
- package/src/hooks/useChartTheme.ts +51 -0
- package/src/hooks/useDataTransform.ts +19 -4
- package/src/hooks/utils/chartDownloadUtils.ts +40 -53
- package/src/hooks/utils/dataTransformUtils.ts +22 -0
- package/src/index.ts +48 -34
- package/src/main.tsx +4 -9
- package/src/react-dom.d.ts +3 -3
- package/src/themes/index.ts +30 -855
- package/src/themes/palettes/blue-green.ts +13 -0
- package/src/themes/palettes/chalk.ts +13 -0
- package/src/themes/palettes/cyber.ts +44 -0
- package/src/themes/palettes/dark.ts +52 -0
- package/src/themes/palettes/default.ts +52 -0
- package/src/themes/palettes/elegant.ts +34 -0
- package/src/themes/palettes/forest.ts +13 -0
- package/src/themes/palettes/glass.ts +49 -0
- package/src/themes/palettes/golden.ts +13 -0
- package/src/themes/palettes/neon.ts +43 -0
- package/src/themes/palettes/ocean.ts +39 -0
- package/src/themes/palettes/pastel.ts +37 -0
- package/src/themes/palettes/purple-passion.ts +13 -0
- package/src/themes/palettes/retro.ts +33 -0
- package/src/themes/palettes/sunset.ts +40 -0
- package/src/themes/palettes/walden.ts +13 -0
- package/src/themes/registry.ts +184 -0
- package/src/themes/types.ts +213 -0
- package/src/charts/bar/__tests__/index.test.tsx +0 -113
- package/src/charts/bar/index.tsx +0 -14
- package/src/charts/candlestick/__tests__/index.test.tsx +0 -40
- package/src/charts/candlestick/index.tsx +0 -13
- package/src/charts/gauge/index.tsx +0 -14
- package/src/charts/line/__tests__/index.test.tsx +0 -107
- package/src/charts/line/index.tsx +0 -15
- package/src/charts/pie/__tests__/index.test.tsx +0 -112
- package/src/charts/pie/index.tsx +0 -14
- package/src/charts/scatter/index.tsx +0 -14
- package/src/charts/sunburst/index.tsx +0 -18
- package/src/charts/treemap/index.tsx +0 -18
- package/src/core/utils/codeGenerator/CodeGenerator.ts +0 -669
- package/src/core/utils/codeGenerator/index.ts +0 -13
- package/src/core/utils/codeGenerator/types.ts +0 -198
- package/src/core/utils/configGenerator/ConfigGenerator.ts +0 -583
- package/src/core/utils/configGenerator/index.ts +0 -13
- package/src/core/utils/configGenerator/types.ts +0 -445
- package/src/core/utils/debug/DebugPanel.tsx +0 -637
- package/src/core/utils/debug/debugger.ts +0 -322
- package/src/core/utils/debug/index.ts +0 -21
- package/src/core/utils/debug/types.ts +0 -142
- package/src/hooks/useDataZoom.ts +0 -323
|
@@ -84,11 +84,22 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
84
84
|
fontFamily: 'var(--tv-font-family, sans-serif)',
|
|
85
85
|
}}
|
|
86
86
|
>
|
|
87
|
-
<div style={{ fontSize: '48px', marginBottom: '16px' }} aria-hidden="true"
|
|
88
|
-
|
|
87
|
+
<div style={{ fontSize: '48px', marginBottom: '16px' }} aria-hidden="true">
|
|
88
|
+
⚠️
|
|
89
|
+
</div>
|
|
90
|
+
<h3
|
|
91
|
+
style={{ margin: '0 0 12px', color: 'var(--tv-error-color, #ff4d4f)', fontWeight: 700 }}
|
|
92
|
+
>
|
|
89
93
|
图表渲染失败
|
|
90
94
|
</h3>
|
|
91
|
-
<p
|
|
95
|
+
<p
|
|
96
|
+
style={{
|
|
97
|
+
margin: '0 0 16px',
|
|
98
|
+
color: 'var(--tv-text-color-secondary, #666)',
|
|
99
|
+
textAlign: 'center',
|
|
100
|
+
maxWidth: '320px',
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
92
103
|
图表在渲染过程中遇到错误,请检查数据配置是否正确
|
|
93
104
|
</p>
|
|
94
105
|
{showDetails && (
|
|
@@ -104,7 +115,9 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
104
115
|
maxHeight: '150px',
|
|
105
116
|
}}
|
|
106
117
|
>
|
|
107
|
-
<summary style={{ cursor: 'pointer', marginBottom: '8px', fontWeight: 600 }}
|
|
118
|
+
<summary style={{ cursor: 'pointer', marginBottom: '8px', fontWeight: 600 }}>
|
|
119
|
+
错误详情
|
|
120
|
+
</summary>
|
|
108
121
|
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
|
|
109
122
|
{error.message}
|
|
110
123
|
{'\n\n'}
|
|
@@ -5,32 +5,53 @@
|
|
|
5
5
|
import React, { Suspense, lazy, ComponentType } from 'react';
|
|
6
6
|
import type { BaseChartProps } from '../../charts/types';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 图表类型到懒加载组件的映射
|
|
10
|
+
* 统一定义,避免重复
|
|
11
|
+
*/
|
|
12
|
+
const LAZY_CHART_REGISTRY: Record<string, ComponentType<BaseChartProps>> = {};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 创建懒加载组件
|
|
16
|
+
*/
|
|
17
|
+
function createLazyComponent(name: string): ComponentType<BaseChartProps> {
|
|
18
|
+
if (!LAZY_CHART_REGISTRY[name]) {
|
|
19
|
+
LAZY_CHART_REGISTRY[name] = lazy(() =>
|
|
20
|
+
import('../../charts').then((m) => ({
|
|
21
|
+
default: m[name as keyof typeof m] as ComponentType<BaseChartProps>,
|
|
22
|
+
}))
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return LAZY_CHART_REGISTRY[name];
|
|
26
|
+
}
|
|
20
27
|
|
|
21
|
-
//
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
// 预创建常用图表的懒加载组件
|
|
29
|
+
export const LazyLineChart = createLazyComponent('LineChart');
|
|
30
|
+
export const LazyBarChart = createLazyComponent('BarChart');
|
|
31
|
+
export const LazyPieChart = createLazyComponent('PieChart');
|
|
32
|
+
export const LazyScatterChart = createLazyComponent('ScatterChart');
|
|
33
|
+
export const LazyRadarChart = createLazyComponent('RadarChartCustom');
|
|
34
|
+
export const LazyHeatmapChart = createLazyComponent('HeatmapChart');
|
|
35
|
+
export const LazyFunnelChart = createLazyComponent('FunnelChart');
|
|
36
|
+
export const LazyTreeMapChart = createLazyComponent('TreeMapChart');
|
|
37
|
+
export const LazySunburstChart = createLazyComponent('SunburstChart');
|
|
38
|
+
export const LazySankeyChart = createLazyComponent('SankeyChart');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 图表类型到懒加载模块的映射
|
|
42
|
+
* 用于预加载功能
|
|
43
|
+
*/
|
|
44
|
+
const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: React.ComponentType<any> }>> = {
|
|
45
|
+
line: () => import('../../charts').then((m) => ({ default: m.LineChart })),
|
|
46
|
+
bar: () => import('../../charts').then((m) => ({ default: m.BarChart })),
|
|
47
|
+
pie: () => import('../../charts').then((m) => ({ default: m.PieChart })),
|
|
48
|
+
scatter: () => import('../../charts').then((m) => ({ default: m.ScatterChart })),
|
|
49
|
+
radar: () => import('../../charts').then((m) => ({ default: m.RadarChartCustom })),
|
|
50
|
+
heatmap: () => import('../../charts').then((m) => ({ default: m.HeatmapChart })),
|
|
51
|
+
funnel: () => import('../../charts').then((m) => ({ default: m.FunnelChart })),
|
|
52
|
+
treemap: () => import('../../charts').then((m) => ({ default: m.TreeMapChart })),
|
|
53
|
+
sunburst: () => import('../../charts').then((m) => ({ default: m.SunburstChart })),
|
|
54
|
+
sankey: () => import('../../charts').then((m) => ({ default: m.SankeyChart })),
|
|
34
55
|
};
|
|
35
56
|
|
|
36
57
|
export const LAZY_CHART_TYPES = Object.keys(LAZY_CHART_MODULES);
|
|
@@ -94,7 +115,12 @@ const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中
|
|
|
94
115
|
>
|
|
95
116
|
{text}
|
|
96
117
|
</span>
|
|
97
|
-
<span
|
|
118
|
+
<span
|
|
119
|
+
style={{
|
|
120
|
+
color: 'var(--tv-text-color-secondary, #666)',
|
|
121
|
+
fontSize: 'var(--tv-font-size, 14px)',
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
98
124
|
{text}
|
|
99
125
|
</span>
|
|
100
126
|
</div>
|
|
@@ -164,21 +190,8 @@ export function preloadAllCharts(): Promise<void[]> {
|
|
|
164
190
|
* 用于动态导入图表
|
|
165
191
|
*/
|
|
166
192
|
export function createLazyChart(chartType: string): ComponentType<BaseChartProps> | null {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
bar: LazyBarChart,
|
|
170
|
-
pie: LazyPieChart,
|
|
171
|
-
scatter: LazyScatterChart,
|
|
172
|
-
radar: LazyRadarChart,
|
|
173
|
-
heatmap: LazyHeatmapChart,
|
|
174
|
-
gauge: LazyGaugeChart,
|
|
175
|
-
funnel: LazyFunnelChart,
|
|
176
|
-
treemap: LazyTreeMapChart,
|
|
177
|
-
sunburst: LazySunburstChart,
|
|
178
|
-
sankey: LazySankeyChart,
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
return lazyCharts[chartType] || null;
|
|
193
|
+
const chartName = `${chartType.charAt(0).toUpperCase() + chartType.slice(1)}Chart`;
|
|
194
|
+
return LAZY_CHART_REGISTRY[chartName] || null;
|
|
182
195
|
}
|
|
183
196
|
|
|
184
197
|
/**
|
|
@@ -198,17 +211,3 @@ export const LazyChartRegistry = {
|
|
|
198
211
|
return preloadAllCharts();
|
|
199
212
|
},
|
|
200
213
|
};
|
|
201
|
-
|
|
202
|
-
export {
|
|
203
|
-
LazyLineChart,
|
|
204
|
-
LazyBarChart,
|
|
205
|
-
LazyPieChart,
|
|
206
|
-
LazyScatterChart,
|
|
207
|
-
LazyRadarChart,
|
|
208
|
-
LazyHeatmapChart,
|
|
209
|
-
LazyGaugeChart,
|
|
210
|
-
LazyFunnelChart,
|
|
211
|
-
LazyTreeMapChart,
|
|
212
|
-
LazySunburstChart,
|
|
213
|
-
LazySankeyChart,
|
|
214
|
-
};
|
|
@@ -16,5 +16,9 @@ export type {
|
|
|
16
16
|
export { useVirtualScroll } from './useVirtualScroll';
|
|
17
17
|
export type { UseVirtualScrollOptions, VirtualScrollState } from './useVirtualScroll';
|
|
18
18
|
|
|
19
|
-
export { usePerformance } from './usePerformance';
|
|
20
|
-
export type {
|
|
19
|
+
export { useChartPerformanceMetrics, usePerformance } from './usePerformance';
|
|
20
|
+
export type {
|
|
21
|
+
PerformanceData,
|
|
22
|
+
UseChartPerformanceMetricsOptions,
|
|
23
|
+
UsePerformanceOptions,
|
|
24
|
+
} from './usePerformance';
|
|
@@ -36,6 +36,8 @@ export function useChartInit(
|
|
|
36
36
|
let adapter: Adapter | null = null;
|
|
37
37
|
|
|
38
38
|
const initChart = async () => {
|
|
39
|
+
if (!mounted) return;
|
|
40
|
+
|
|
39
41
|
try {
|
|
40
42
|
adapter = await getAdapter({
|
|
41
43
|
width: options.width,
|
|
@@ -47,14 +49,15 @@ export function useChartInit(
|
|
|
47
49
|
direction: options.direction,
|
|
48
50
|
});
|
|
49
51
|
|
|
50
|
-
if (!mounted) return;
|
|
51
|
-
if (!adapter) return;
|
|
52
|
+
if (!mounted || !adapter) return;
|
|
52
53
|
|
|
53
54
|
adapterRef.current = adapter;
|
|
54
55
|
isReadyRef.current = true;
|
|
55
56
|
adapter.init();
|
|
56
57
|
} catch (error) {
|
|
57
|
-
|
|
58
|
+
if (mounted) {
|
|
59
|
+
console.error('[TaroViz] Failed to initialize chart:', error);
|
|
60
|
+
}
|
|
58
61
|
}
|
|
59
62
|
};
|
|
60
63
|
|
|
@@ -13,12 +13,15 @@ export interface PerformanceData {
|
|
|
13
13
|
dataSize: number;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export interface
|
|
16
|
+
export interface UseChartPerformanceMetricsOptions {
|
|
17
17
|
enabled?: boolean;
|
|
18
18
|
onPerformance?: (data: PerformanceData) => void;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/** @deprecated Use `UseChartPerformanceMetricsOptions` instead */
|
|
22
|
+
export type UsePerformanceOptions = UseChartPerformanceMetricsOptions;
|
|
23
|
+
|
|
24
|
+
export function useChartPerformanceMetrics(options: UseChartPerformanceMetricsOptions = {}) {
|
|
22
25
|
const { enabled = false, onPerformance } = options;
|
|
23
26
|
|
|
24
27
|
const analyzerRef = useRef<PerformanceAnalyzer | null>(null);
|
|
@@ -184,3 +187,6 @@ export function usePerformance(options: UsePerformanceOptions = {}) {
|
|
|
184
187
|
dispose,
|
|
185
188
|
};
|
|
186
189
|
}
|
|
190
|
+
|
|
191
|
+
/** @deprecated Use `useChartPerformanceMetrics` instead */
|
|
192
|
+
export const usePerformance = useChartPerformanceMetrics;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { useRef, useCallback } from 'react';
|
|
6
6
|
import type { EChartsOption } from 'echarts';
|
|
7
|
+
import { deepClone } from '../../../core/utils/deepClone';
|
|
7
8
|
|
|
8
9
|
export interface UseVirtualScrollOptions {
|
|
9
10
|
enabled?: boolean;
|
|
@@ -71,7 +72,7 @@ export function useVirtualScroll(options: UseVirtualScrollOptions = {}) {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
// 深拷贝避免修改原始数据
|
|
74
|
-
const processedOption =
|
|
75
|
+
const processedOption = deepClone(originalOption) as EChartsOption;
|
|
75
76
|
|
|
76
77
|
(processedOption.series as unknown[]).forEach((seriesItem: unknown, _index: number) => {
|
|
77
78
|
const s = seriesItem as { data?: unknown[] };
|
package/src/core/index.ts
CHANGED
|
@@ -248,7 +248,7 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
248
248
|
'--tv-bg-color-secondary': '#f5f7fa',
|
|
249
249
|
'--tv-text-color': '#1a1a1a',
|
|
250
250
|
'--tv-text-color-secondary': '#666666',
|
|
251
|
-
'--tv-primary-color': '#
|
|
251
|
+
'--tv-primary-color': '#277ace',
|
|
252
252
|
'--tv-primary-color-hover': '#3a8ee6',
|
|
253
253
|
'--tv-primary-color-active': '#146bb3',
|
|
254
254
|
'--tv-success-color': '#2fc25b',
|
package/src/core/types/common.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1
2
|
/**
|
|
2
3
|
* ECharts 类型定义
|
|
3
4
|
*/
|
|
@@ -120,7 +121,7 @@ export type EChartsEventParams =
|
|
|
120
121
|
/**
|
|
121
122
|
* 图表事件监听器
|
|
122
123
|
*/
|
|
123
|
-
export type
|
|
124
|
+
export type _ChartEventListener = Record<string, (_params: ChartEventParams) => void>;
|
|
124
125
|
|
|
125
126
|
/**
|
|
126
127
|
* 图表渲染器类型
|
package/src/core/types/index.ts
CHANGED
|
@@ -123,18 +123,6 @@ export interface ThemeType {
|
|
|
123
123
|
[key: string]: unknown;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
/**
|
|
127
|
-
* 渲染性能优化配置
|
|
128
|
-
*/
|
|
129
|
-
export interface RenderOptimizationConfig {
|
|
130
|
-
progressive?: boolean;
|
|
131
|
-
progressiveThreshold?: number;
|
|
132
|
-
lazyUpdate?: boolean;
|
|
133
|
-
animation?: boolean;
|
|
134
|
-
hardwareAcceleration?: boolean;
|
|
135
|
-
frameRate?: number;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
126
|
/**
|
|
139
127
|
* 适配器配置选项
|
|
140
128
|
*/
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1
2
|
/**
|
|
2
3
|
* 支持的平台类型
|
|
3
4
|
*/
|
|
4
|
-
import type {
|
|
5
|
-
EChartsType,
|
|
6
|
-
EChartsOption,
|
|
7
|
-
} from 'echarts';
|
|
5
|
+
import type { EChartsType, EChartsOption } from 'echarts';
|
|
8
6
|
import type { CSSProperties } from 'react';
|
|
9
7
|
import type { ChartEventParams } from './common';
|
|
10
8
|
|
|
@@ -115,7 +113,7 @@ export interface HarmonyAdapterOptions extends AdapterOptions {
|
|
|
115
113
|
export interface Adapter {
|
|
116
114
|
init(options?: object): EChartsType;
|
|
117
115
|
getInstance(): EChartsType | null;
|
|
118
|
-
setOption(option: EChartsOption,
|
|
116
|
+
setOption(option: EChartsOption, notMerge?: boolean, lazyUpdate?: boolean): void;
|
|
119
117
|
getWidth(): number;
|
|
120
118
|
getHeight(): number;
|
|
121
119
|
getDom(): HTMLElement | null;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { deepClone, deepMerge } from '../deepClone';
|
|
2
|
+
|
|
3
|
+
describe('deepClone', () => {
|
|
4
|
+
// Primitives
|
|
5
|
+
it('clones primitives', () => {
|
|
6
|
+
expect(deepClone(42)).toBe(42);
|
|
7
|
+
expect(deepClone('hello')).toBe('hello');
|
|
8
|
+
expect(deepClone(true)).toBe(true);
|
|
9
|
+
expect(deepClone(false)).toBe(false);
|
|
10
|
+
expect(deepClone(0)).toBe(0);
|
|
11
|
+
expect(deepClone(-1)).toBe(-1);
|
|
12
|
+
expect(deepClone(3.14)).toBe(3.14);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('handles null and undefined', () => {
|
|
16
|
+
expect(deepClone(null)).toBe(null);
|
|
17
|
+
expect(deepClone(undefined)).toBe(undefined);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Objects
|
|
21
|
+
it('clones plain objects', () => {
|
|
22
|
+
const obj = { a: 1, b: 'two', c: true };
|
|
23
|
+
const clone = deepClone(obj);
|
|
24
|
+
expect(clone).toEqual(obj);
|
|
25
|
+
expect(clone).not.toBe(obj);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('clones nested objects', () => {
|
|
29
|
+
const obj = { a: { b: { c: { d: 42 } } } };
|
|
30
|
+
const clone = deepClone(obj);
|
|
31
|
+
expect(clone).toEqual(obj);
|
|
32
|
+
expect(clone).not.toBe(obj);
|
|
33
|
+
expect(clone.a).not.toBe(obj.a);
|
|
34
|
+
expect(clone.a.b).not.toBe(obj.a.b);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Arrays
|
|
38
|
+
it('clones arrays', () => {
|
|
39
|
+
const arr = [1, 2, 3, [4, 5]];
|
|
40
|
+
const clone = deepClone(arr);
|
|
41
|
+
expect(clone).toEqual(arr);
|
|
42
|
+
expect(clone).not.toBe(arr);
|
|
43
|
+
expect(clone[3]).not.toBe(arr[3]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('clones arrays of objects', () => {
|
|
47
|
+
const arr = [{ a: 1 }, { b: 2 }];
|
|
48
|
+
const clone = deepClone(arr);
|
|
49
|
+
expect(clone).toEqual(arr);
|
|
50
|
+
expect(clone[0]).not.toBe(arr[0]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Date
|
|
54
|
+
it('clones Date objects', () => {
|
|
55
|
+
const date = new Date('2024-01-15');
|
|
56
|
+
const clone = deepClone(date);
|
|
57
|
+
expect(clone).toEqual(date);
|
|
58
|
+
expect(clone).not.toBe(date);
|
|
59
|
+
expect(clone.getTime()).toBe(date.getTime());
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// RegExp
|
|
63
|
+
it('clones RegExp objects', () => {
|
|
64
|
+
const re = /foo/gi;
|
|
65
|
+
const clone = deepClone(re);
|
|
66
|
+
expect(clone.source).toBe('foo');
|
|
67
|
+
expect(clone.flags).toBe('gi');
|
|
68
|
+
expect(clone).not.toBe(re);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Functions
|
|
72
|
+
it('preserves function references', () => {
|
|
73
|
+
const fn = (x: number) => x * 2;
|
|
74
|
+
expect(deepClone(fn)).toBe(fn);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('preserves nested function references', () => {
|
|
78
|
+
const fn = () => 'hello';
|
|
79
|
+
const obj = { formatter: fn, nested: { callback: fn } };
|
|
80
|
+
const clone = deepClone(obj);
|
|
81
|
+
expect(clone.formatter).toBe(fn);
|
|
82
|
+
expect(clone.nested.callback).toBe(fn);
|
|
83
|
+
expect(clone).not.toBe(obj);
|
|
84
|
+
expect(clone.nested).not.toBe(obj.nested);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Map
|
|
88
|
+
it('clones Map objects', () => {
|
|
89
|
+
const map = new Map<string, number>([
|
|
90
|
+
['a', 1],
|
|
91
|
+
['b', 2],
|
|
92
|
+
]);
|
|
93
|
+
const clone = deepClone(map);
|
|
94
|
+
expect(clone).not.toBe(map);
|
|
95
|
+
expect(clone.get('a')).toBe(1);
|
|
96
|
+
expect(clone.get('b')).toBe(2);
|
|
97
|
+
expect(clone.size).toBe(2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('clones Map with object keys and values', () => {
|
|
101
|
+
const key = { id: 1 };
|
|
102
|
+
const val = { name: 'test' };
|
|
103
|
+
const map = new Map([[key, val]]);
|
|
104
|
+
const clone = deepClone(map);
|
|
105
|
+
expect(clone).not.toBe(map);
|
|
106
|
+
// Keys are cloned, so we can't look up with the original key
|
|
107
|
+
const clonedEntries = Array.from(clone.entries());
|
|
108
|
+
expect(clonedEntries).toHaveLength(1);
|
|
109
|
+
expect(clonedEntries[0][0]).toEqual(key);
|
|
110
|
+
expect(clonedEntries[0][0]).not.toBe(key);
|
|
111
|
+
expect(clonedEntries[0][1]).toEqual(val);
|
|
112
|
+
expect(clonedEntries[0][1]).not.toBe(val);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Set
|
|
116
|
+
it('clones Set objects', () => {
|
|
117
|
+
const set = new Set([1, 2, 3]);
|
|
118
|
+
const clone = deepClone(set);
|
|
119
|
+
expect(clone).not.toBe(set);
|
|
120
|
+
expect(clone.size).toBe(3);
|
|
121
|
+
expect(clone.has(1)).toBe(true);
|
|
122
|
+
expect(clone.has(2)).toBe(true);
|
|
123
|
+
expect(clone.has(3)).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('clones Set with object values', () => {
|
|
127
|
+
const obj = { a: 1 };
|
|
128
|
+
const set = new Set([obj]);
|
|
129
|
+
const clone = deepClone(set);
|
|
130
|
+
const clonedValues = Array.from(clone.values());
|
|
131
|
+
expect(clonedValues[0]).toEqual(obj);
|
|
132
|
+
expect(clonedValues[0]).not.toBe(obj);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Circular references
|
|
136
|
+
it('handles circular references in objects', () => {
|
|
137
|
+
const obj: Record<string, unknown> = { a: 1 };
|
|
138
|
+
obj.self = obj;
|
|
139
|
+
const clone = deepClone(obj);
|
|
140
|
+
expect(clone.a).toBe(1);
|
|
141
|
+
expect(clone.self).toBe(clone);
|
|
142
|
+
expect(clone).not.toBe(obj);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('handles circular references in arrays', () => {
|
|
146
|
+
const arr: unknown[] = [1, 2];
|
|
147
|
+
arr.push(arr);
|
|
148
|
+
const clone = deepClone(arr);
|
|
149
|
+
expect(clone[0]).toBe(1);
|
|
150
|
+
expect(clone[1]).toBe(2);
|
|
151
|
+
expect(clone[2]).toBe(clone);
|
|
152
|
+
expect(clone).not.toBe(arr);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('handles cross-reference between objects', () => {
|
|
156
|
+
const shared = { value: 42 };
|
|
157
|
+
const obj = { a: shared, b: shared };
|
|
158
|
+
const clone = deepClone(obj);
|
|
159
|
+
expect(clone.a).toEqual(shared);
|
|
160
|
+
expect(clone.b).toEqual(shared);
|
|
161
|
+
// Both references should point to the same clone
|
|
162
|
+
expect(clone.a).toBe(clone.b);
|
|
163
|
+
expect(clone.a).not.toBe(shared);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ECharts-like option objects
|
|
167
|
+
it('clones ECharts-like option objects', () => {
|
|
168
|
+
const formatter = (params: { value: number }) => `${params.value}%`;
|
|
169
|
+
const options = {
|
|
170
|
+
title: {
|
|
171
|
+
text: 'Sales',
|
|
172
|
+
subtext: '2024',
|
|
173
|
+
},
|
|
174
|
+
tooltip: {
|
|
175
|
+
trigger: 'axis' as const,
|
|
176
|
+
formatter,
|
|
177
|
+
},
|
|
178
|
+
xAxis: {
|
|
179
|
+
type: 'category' as const,
|
|
180
|
+
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
|
|
181
|
+
},
|
|
182
|
+
yAxis: {
|
|
183
|
+
type: 'value' as const,
|
|
184
|
+
},
|
|
185
|
+
series: [
|
|
186
|
+
{
|
|
187
|
+
name: 'Sales',
|
|
188
|
+
type: 'line' as const,
|
|
189
|
+
data: [150, 230, 224, 218, 135],
|
|
190
|
+
label: {
|
|
191
|
+
show: true,
|
|
192
|
+
formatter: (params: { value: number }) => `${params.value}`,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const clone = deepClone(options);
|
|
199
|
+
|
|
200
|
+
// Structure equality
|
|
201
|
+
expect(clone).toEqual(options);
|
|
202
|
+
// Deep copy
|
|
203
|
+
expect(clone).not.toBe(options);
|
|
204
|
+
expect(clone.title).not.toBe(options.title);
|
|
205
|
+
expect(clone.series).not.toBe(options.series);
|
|
206
|
+
expect(clone.series[0]).not.toBe(options.series[0]);
|
|
207
|
+
// Function preserved
|
|
208
|
+
expect(clone.tooltip.formatter).toBe(formatter);
|
|
209
|
+
expect(clone.series[0].label.formatter).toBe(options.series[0].label.formatter);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Mixed types
|
|
213
|
+
it('clones complex mixed structures', () => {
|
|
214
|
+
const obj = {
|
|
215
|
+
str: 'hello',
|
|
216
|
+
num: 42,
|
|
217
|
+
bool: true,
|
|
218
|
+
nil: null,
|
|
219
|
+
undef: undefined,
|
|
220
|
+
date: new Date('2024-06-01'),
|
|
221
|
+
regex: /test/gi,
|
|
222
|
+
arr: [1, [2, 3], { a: 4 }],
|
|
223
|
+
fn: () => 'test',
|
|
224
|
+
map: new Map([['key', { nested: true }]]),
|
|
225
|
+
set: new Set([{ x: 1 }, { x: 2 }]),
|
|
226
|
+
nested: {
|
|
227
|
+
deep: {
|
|
228
|
+
deeper: {
|
|
229
|
+
value: 'bottom',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const clone = deepClone(obj);
|
|
236
|
+
|
|
237
|
+
expect(clone.str).toBe('hello');
|
|
238
|
+
expect(clone.num).toBe(42);
|
|
239
|
+
expect(clone.bool).toBe(true);
|
|
240
|
+
expect(clone.nil).toBe(null);
|
|
241
|
+
expect(clone.undef).toBe(undefined);
|
|
242
|
+
expect(clone.date).toEqual(obj.date);
|
|
243
|
+
expect(clone.date).not.toBe(obj.date);
|
|
244
|
+
expect(clone.regex.source).toBe('test');
|
|
245
|
+
expect(clone.regex).not.toBe(obj.regex);
|
|
246
|
+
expect(clone.arr).toEqual([1, [2, 3], { a: 4 }]);
|
|
247
|
+
expect(clone.arr).not.toBe(obj.arr);
|
|
248
|
+
expect(clone.fn).toBe(obj.fn);
|
|
249
|
+
expect(clone.map).not.toBe(obj.map);
|
|
250
|
+
expect(clone.set).not.toBe(obj.set);
|
|
251
|
+
expect(clone.nested.deep.deeper.value).toBe('bottom');
|
|
252
|
+
expect(clone.nested).not.toBe(obj.nested);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('deepMerge', () => {
|
|
257
|
+
it('merges flat objects', () => {
|
|
258
|
+
const target = { a: 1, b: 2 };
|
|
259
|
+
const source = { b: 3, c: 4 };
|
|
260
|
+
const result = deepMerge(target, source);
|
|
261
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('merges nested objects recursively', () => {
|
|
265
|
+
const target = { a: { x: 1, y: 2 }, b: 1 };
|
|
266
|
+
const source = { a: { y: 3, z: 4 } };
|
|
267
|
+
const result = deepMerge(target, source);
|
|
268
|
+
expect(result).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 1 });
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('replaces arrays instead of merging them', () => {
|
|
272
|
+
const target = { arr: [1, 2, 3] };
|
|
273
|
+
const source = { arr: [4, 5] };
|
|
274
|
+
const result = deepMerge(target, source);
|
|
275
|
+
expect(result.arr).toEqual([4, 5]);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('replaces primitives with source values', () => {
|
|
279
|
+
const target = { a: 1, b: 'old' };
|
|
280
|
+
const source = { a: 2, b: 'new' };
|
|
281
|
+
const result = deepMerge(target, source);
|
|
282
|
+
expect(result).toEqual({ a: 2, b: 'new' });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('preserves function references from source', () => {
|
|
286
|
+
const fn = () => 'hello';
|
|
287
|
+
const target = { a: 1 };
|
|
288
|
+
const source = { formatter: fn };
|
|
289
|
+
const result = deepMerge(target, source);
|
|
290
|
+
expect(result.formatter).toBe(fn);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('does not mutate target or source', () => {
|
|
294
|
+
const target = { a: { x: 1 } };
|
|
295
|
+
const source = { a: { y: 2 } };
|
|
296
|
+
deepMerge(target, source);
|
|
297
|
+
expect(target).toEqual({ a: { x: 1 } });
|
|
298
|
+
expect(source).toEqual({ a: { y: 2 } });
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('merges ECharts-like option objects', () => {
|
|
302
|
+
const target = {
|
|
303
|
+
title: { text: 'Chart' },
|
|
304
|
+
tooltip: { trigger: 'axis' as const },
|
|
305
|
+
series: [{ type: 'line' as const, data: [1, 2, 3] }],
|
|
306
|
+
};
|
|
307
|
+
const source = {
|
|
308
|
+
title: { subtext: 'Sub' },
|
|
309
|
+
tooltip: { formatter: (p: { value: number }) => `${p.value}%` },
|
|
310
|
+
};
|
|
311
|
+
const result = deepMerge(target, source);
|
|
312
|
+
expect(result.title).toEqual({ text: 'Chart', subtext: 'Sub' });
|
|
313
|
+
expect(result.tooltip.trigger).toBe('axis');
|
|
314
|
+
expect(typeof result.tooltip.formatter).toBe('function');
|
|
315
|
+
expect(result.series).toEqual([{ type: 'line', data: [1, 2, 3] }]);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
@@ -92,7 +92,8 @@ describe('Data Processing Utilities', () => {
|
|
|
92
92
|
|
|
93
93
|
it('should limit execution to once per interval', () => {
|
|
94
94
|
const mockFn = jest.fn();
|
|
95
|
-
|
|
95
|
+
// 使用 leading=true, trailing=false 来匹配简单 throttle 行为
|
|
96
|
+
const throttledFn = throttle(mockFn, 100, { leading: true, trailing: false });
|
|
96
97
|
|
|
97
98
|
throttledFn();
|
|
98
99
|
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
@@ -83,3 +83,16 @@ export function resizeAllCharts(): void {
|
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Dispose and remove stale chart instances (those whose DOM container is gone)
|
|
89
|
+
* Call periodically or from ErrorBoundary componentDidCatch
|
|
90
|
+
*/
|
|
91
|
+
export function disposeStaleCharts(): void {
|
|
92
|
+
Object.keys(CHART_INSTANCES).forEach((id) => {
|
|
93
|
+
const instance = CHART_INSTANCES[id];
|
|
94
|
+
if (!instance || instance.isDisposed()) {
|
|
95
|
+
delete CHART_INSTANCES[id];
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|