@agions/taroviz 1.3.1 → 1.6.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/README.md +4 -4
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +3814 -2724
- package/package.json +1 -1
- package/src/__tests__/integration.test.tsx +12 -10
- package/src/adapters/BaseAdapter.ts +116 -0
- package/src/adapters/__tests__/index.test.ts +10 -10
- package/src/adapters/index.ts +63 -74
- package/src/adapters/swan/index.ts +26 -223
- package/src/adapters/tt/index.ts +28 -225
- package/src/adapters/types.ts +36 -0
- package/src/adapters/weapp/index.ts +29 -189
- package/src/charts/bar/index.tsx +5 -9
- package/src/charts/boxplot/__tests__/index.test.tsx +130 -0
- package/src/charts/boxplot/index.tsx +18 -0
- package/src/charts/boxplot/types.ts +46 -0
- package/src/charts/candlestick/__tests__/index.test.tsx +37 -0
- package/src/charts/candlestick/index.tsx +13 -0
- package/src/charts/common/BaseChartWrapper.tsx +47 -38
- package/src/charts/funnel/index.tsx +5 -9
- package/src/charts/gauge/index.tsx +5 -9
- package/src/charts/graph/__tests__/index.test.tsx +41 -0
- package/src/charts/graph/index.tsx +13 -0
- package/src/charts/heatmap/index.tsx +5 -9
- package/src/charts/index.ts +10 -1
- package/src/charts/line/index.tsx +4 -7
- package/src/charts/parallel/__tests__/index.test.tsx +164 -0
- package/src/charts/parallel/index.tsx +18 -0
- package/src/charts/parallel/types.ts +73 -0
- package/src/charts/pie/index.tsx +5 -10
- package/src/charts/radar/index.tsx +5 -9
- package/src/charts/scatter/index.tsx +5 -9
- package/src/charts/types.ts +48 -4
- package/src/charts/wordcloud/__tests__/index.test.tsx +36 -0
- package/src/charts/wordcloud/index.tsx +13 -0
- package/src/core/animation/AnimationManager.ts +15 -0
- package/src/core/components/Annotation.tsx +26 -21
- package/src/core/components/BaseChart.tsx +280 -1105
- package/src/core/components/ErrorBoundary.tsx +4 -1
- package/src/core/components/LazyChart.tsx +42 -55
- package/src/core/components/hooks/index.ts +20 -0
- package/src/core/components/hooks/useChartEvents.ts +143 -0
- package/src/core/components/hooks/useChartInit.ts +80 -0
- package/src/core/components/hooks/usePerformance.ts +186 -0
- package/src/core/components/hooks/useVirtualScroll.ts +156 -0
- package/src/core/echarts.ts +1 -1
- package/src/core/themes/ThemeManager.ts +31 -15
- package/src/core/types/index.ts +2 -2
- package/src/core/utils/chartInstances.ts +10 -3
- package/src/core/utils/chartUtils.ts +46 -0
- package/src/core/utils/common.ts +14 -1
- package/src/core/utils/export/ExportUtils.ts +13 -22
- package/src/core/utils/performance/PerformanceAnalyzer.ts +32 -5
- package/src/core/utils/uuid.ts +1 -1
- package/src/editor/EnhancedThemeEditor.tsx +624 -0
- package/src/editor/ThemeEditor.tsx +1 -6
- package/src/hooks/__tests__/index.test.tsx +14 -11
- package/src/hooks/__tests__/useDataTransform.test.ts +159 -0
- package/src/hooks/index.ts +54 -19
- package/src/hooks/useDataTransform.ts +503 -0
- package/src/index.ts +27 -9
- package/src/main.tsx +4 -4
- package/src/themes/__tests__/index.test.ts +2 -2
- package/src/themes/index.ts +13 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 虚拟滚动 Hook
|
|
3
|
+
* 负责大数据集的分页渲染和数据筛选
|
|
4
|
+
*/
|
|
5
|
+
import { useRef, useCallback } from 'react';
|
|
6
|
+
import type { EChartsOption } from 'echarts';
|
|
7
|
+
|
|
8
|
+
export interface UseVirtualScrollOptions {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
pageSize?: number;
|
|
11
|
+
preloadSize?: number;
|
|
12
|
+
filters?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface VirtualScrollState {
|
|
16
|
+
currentPage: number;
|
|
17
|
+
totalPages: number;
|
|
18
|
+
totalDataCount: number;
|
|
19
|
+
startIndex: number;
|
|
20
|
+
endIndex: number;
|
|
21
|
+
isScrolling: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useVirtualScroll(options: UseVirtualScrollOptions = {}) {
|
|
25
|
+
const { enabled = false, pageSize = 100, preloadSize = 50 } = options;
|
|
26
|
+
|
|
27
|
+
const stateRef = useRef<VirtualScrollState>({
|
|
28
|
+
currentPage: 0,
|
|
29
|
+
totalPages: 1,
|
|
30
|
+
totalDataCount: 0,
|
|
31
|
+
startIndex: 0,
|
|
32
|
+
endIndex: 0,
|
|
33
|
+
isScrolling: false,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 筛选数据
|
|
38
|
+
*/
|
|
39
|
+
const filterData = useCallback(
|
|
40
|
+
(data: unknown[], filters: Record<string, unknown> = {}): unknown[] => {
|
|
41
|
+
if (!filters || Object.keys(filters).length === 0) {
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return data.filter((item) => {
|
|
46
|
+
const record = item as Record<string, unknown>;
|
|
47
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
48
|
+
if (record[key] !== value && !(record[key] as unknown[])?.includes?.(value)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
[]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 处理虚拟滚动
|
|
60
|
+
* 返回处理后的配置和当前页数据范围
|
|
61
|
+
*/
|
|
62
|
+
const processVirtualScroll = useCallback(
|
|
63
|
+
(
|
|
64
|
+
originalOption: EChartsOption,
|
|
65
|
+
filters: Record<string, unknown> = {}
|
|
66
|
+
): { processedOption: EChartsOption; state: VirtualScrollState } => {
|
|
67
|
+
const state = stateRef.current;
|
|
68
|
+
|
|
69
|
+
if (!enabled || !originalOption?.series) {
|
|
70
|
+
return { processedOption: originalOption, state };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 深拷贝避免修改原始数据
|
|
74
|
+
const processedOption = JSON.parse(JSON.stringify(originalOption)) as EChartsOption;
|
|
75
|
+
|
|
76
|
+
(processedOption.series as unknown[]).forEach((seriesItem: unknown, _index: number) => {
|
|
77
|
+
const s = seriesItem as { data?: unknown[] };
|
|
78
|
+
if (!s.data || !Array.isArray(s.data)) return;
|
|
79
|
+
|
|
80
|
+
// 应用筛选
|
|
81
|
+
const filteredData = filterData(s.data, filters);
|
|
82
|
+
|
|
83
|
+
// 计算分页
|
|
84
|
+
state.totalDataCount = filteredData.length;
|
|
85
|
+
state.totalPages = Math.ceil(filteredData.length / pageSize);
|
|
86
|
+
|
|
87
|
+
const startIndex = state.currentPage * pageSize;
|
|
88
|
+
const endIndex = Math.min(startIndex + pageSize + preloadSize, filteredData.length);
|
|
89
|
+
|
|
90
|
+
state.startIndex = startIndex;
|
|
91
|
+
state.endIndex = endIndex;
|
|
92
|
+
|
|
93
|
+
// 返回当前页数据
|
|
94
|
+
s.data = filteredData.slice(startIndex, endIndex);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return { processedOption, state };
|
|
98
|
+
},
|
|
99
|
+
[enabled, pageSize, preloadSize, filterData]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 跳转到指定页
|
|
104
|
+
*/
|
|
105
|
+
const goToPage = useCallback((page: number) => {
|
|
106
|
+
const state = stateRef.current;
|
|
107
|
+
if (page >= 0 && page < state.totalPages) {
|
|
108
|
+
state.currentPage = page;
|
|
109
|
+
}
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 下一页
|
|
114
|
+
*/
|
|
115
|
+
const nextPage = useCallback(() => {
|
|
116
|
+
goToPage(stateRef.current.currentPage + 1);
|
|
117
|
+
}, [goToPage]);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 上一页
|
|
121
|
+
*/
|
|
122
|
+
const prevPage = useCallback(() => {
|
|
123
|
+
goToPage(stateRef.current.currentPage - 1);
|
|
124
|
+
}, [goToPage]);
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 根据滚动位置更新页码
|
|
128
|
+
*/
|
|
129
|
+
const updatePageFromScroll = useCallback((scrollPercent: number) => {
|
|
130
|
+
const state = stateRef.current;
|
|
131
|
+
const newPage = Math.floor((scrollPercent / 100) * state.totalPages);
|
|
132
|
+
if (newPage !== state.currentPage && newPage >= 0 && newPage < state.totalPages) {
|
|
133
|
+
state.currentPage = newPage;
|
|
134
|
+
return true; // 页码变化
|
|
135
|
+
}
|
|
136
|
+
return false; // 页码未变化
|
|
137
|
+
}, []);
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 设置滚动状态
|
|
141
|
+
*/
|
|
142
|
+
const setScrolling = useCallback((scrolling: boolean) => {
|
|
143
|
+
stateRef.current.isScrolling = scrolling;
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
stateRef,
|
|
148
|
+
processVirtualScroll,
|
|
149
|
+
filterData,
|
|
150
|
+
goToPage,
|
|
151
|
+
nextPage,
|
|
152
|
+
prevPage,
|
|
153
|
+
updatePageFromScroll,
|
|
154
|
+
setScrolling,
|
|
155
|
+
};
|
|
156
|
+
}
|
package/src/core/echarts.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* TaroViz 主题系统
|
|
3
3
|
* 支持 CSS 变量、动态主题切换、自定义主题
|
|
4
4
|
*/
|
|
5
|
-
import type { EChartsOption } from 'echarts';
|
|
6
5
|
|
|
7
6
|
// ============================================================================
|
|
8
7
|
// 类型定义
|
|
@@ -79,7 +78,17 @@ export interface ThemeVariables {
|
|
|
79
78
|
/**
|
|
80
79
|
* 预设主题
|
|
81
80
|
*/
|
|
82
|
-
export type PresetThemeName =
|
|
81
|
+
export type PresetThemeName =
|
|
82
|
+
| 'default'
|
|
83
|
+
| 'dark'
|
|
84
|
+
| 'vintage'
|
|
85
|
+
| 'macarons'
|
|
86
|
+
| 'infographic'
|
|
87
|
+
| 'helianthus'
|
|
88
|
+
| 'blue'
|
|
89
|
+
| 'red'
|
|
90
|
+
| 'green'
|
|
91
|
+
| 'purple';
|
|
83
92
|
|
|
84
93
|
// ============================================================================
|
|
85
94
|
// 预设主题配置
|
|
@@ -112,7 +121,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
112
121
|
'--tv-chart-color-6': '#3ba272',
|
|
113
122
|
'--tv-chart-color-7': '#fc8452',
|
|
114
123
|
'--tv-chart-color-8': '#9a60b4',
|
|
115
|
-
'--tv-font-family':
|
|
124
|
+
'--tv-font-family':
|
|
125
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
116
126
|
'--tv-font-size': '14px',
|
|
117
127
|
'--tv-font-size-small': '12px',
|
|
118
128
|
'--tv-font-size-large': '16px',
|
|
@@ -147,7 +157,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
147
157
|
'--tv-chart-color-6': '#3ba272',
|
|
148
158
|
'--tv-chart-color-7': '#fc8452',
|
|
149
159
|
'--tv-chart-color-8': '#9a60b4',
|
|
150
|
-
'--tv-font-family':
|
|
160
|
+
'--tv-font-family':
|
|
161
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
151
162
|
'--tv-font-size': '14px',
|
|
152
163
|
'--tv-font-size-small': '12px',
|
|
153
164
|
'--tv-font-size-large': '16px',
|
|
@@ -182,7 +193,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
182
193
|
'--tv-chart-color-6': '#c9b8d4',
|
|
183
194
|
'--tv-chart-color-7': '#a8c4d4',
|
|
184
195
|
'--tv-chart-color-8': '#d4c49a',
|
|
185
|
-
'--tv-font-family':
|
|
196
|
+
'--tv-font-family':
|
|
197
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
186
198
|
'--tv-font-size': '14px',
|
|
187
199
|
'--tv-font-size-small': '12px',
|
|
188
200
|
'--tv-font-size-large': '16px',
|
|
@@ -217,7 +229,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
217
229
|
'--tv-chart-color-6': '#a8e6cf',
|
|
218
230
|
'--tv-chart-color-7': '#ffd3b6',
|
|
219
231
|
'--tv-chart-color-8': '#ffaaa5',
|
|
220
|
-
'--tv-font-family':
|
|
232
|
+
'--tv-font-family':
|
|
233
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
221
234
|
'--tv-font-size': '14px',
|
|
222
235
|
'--tv-font-size-small': '12px',
|
|
223
236
|
'--tv-font-size-large': '16px',
|
|
@@ -252,7 +265,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
252
265
|
'--tv-chart-color-6': '#e8352e',
|
|
253
266
|
'--tv-chart-color-7': '#b02ad3',
|
|
254
267
|
'--tv-chart-color-8': '#6475d4',
|
|
255
|
-
'--tv-font-family':
|
|
268
|
+
'--tv-font-family':
|
|
269
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
256
270
|
'--tv-font-size': '14px',
|
|
257
271
|
'--tv-font-size-small': '12px',
|
|
258
272
|
'--tv-font-size-large': '16px',
|
|
@@ -287,7 +301,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
287
301
|
'--tv-chart-color-6': '#f5a623',
|
|
288
302
|
'--tv-chart-color-7': '#6dd3ce',
|
|
289
303
|
'--tv-chart-color-8': '#d4778b',
|
|
290
|
-
'--tv-font-family':
|
|
304
|
+
'--tv-font-family':
|
|
305
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
291
306
|
'--tv-font-size': '14px',
|
|
292
307
|
'--tv-font-size-small': '12px',
|
|
293
308
|
'--tv-font-size-large': '16px',
|
|
@@ -322,7 +337,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
322
337
|
'--tv-chart-color-6': '#3ba272',
|
|
323
338
|
'--tv-chart-color-7': '#fc8452',
|
|
324
339
|
'--tv-chart-color-8': '#9a60b4',
|
|
325
|
-
'--tv-font-family':
|
|
340
|
+
'--tv-font-family':
|
|
341
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
326
342
|
'--tv-font-size': '14px',
|
|
327
343
|
'--tv-font-size-small': '12px',
|
|
328
344
|
'--tv-font-size-large': '16px',
|
|
@@ -357,7 +373,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
357
373
|
'--tv-chart-color-6': '#52c41a',
|
|
358
374
|
'--tv-chart-color-7': '#faad14',
|
|
359
375
|
'--tv-chart-color-8': '#8b5cf6',
|
|
360
|
-
'--tv-font-family':
|
|
376
|
+
'--tv-font-family':
|
|
377
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
361
378
|
'--tv-font-size': '14px',
|
|
362
379
|
'--tv-font-size-small': '12px',
|
|
363
380
|
'--tv-font-size-large': '16px',
|
|
@@ -392,7 +409,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
392
409
|
'--tv-chart-color-6': '#13c2c2',
|
|
393
410
|
'--tv-chart-color-7': '#fa8c16',
|
|
394
411
|
'--tv-chart-color-8': '#eb2f96',
|
|
395
|
-
'--tv-font-family':
|
|
412
|
+
'--tv-font-family':
|
|
413
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
396
414
|
'--tv-font-size': '14px',
|
|
397
415
|
'--tv-font-size-small': '12px',
|
|
398
416
|
'--tv-font-size-large': '16px',
|
|
@@ -427,7 +445,8 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
|
|
|
427
445
|
'--tv-chart-color-6': '#ff4d4f',
|
|
428
446
|
'--tv-chart-color-7': '#13c2c2',
|
|
429
447
|
'--tv-chart-color-8': '#fa8c16',
|
|
430
|
-
'--tv-font-family':
|
|
448
|
+
'--tv-font-family':
|
|
449
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
431
450
|
'--tv-font-size': '14px',
|
|
432
451
|
'--tv-font-size-small': '12px',
|
|
433
452
|
'--tv-font-size-large': '16px',
|
|
@@ -619,9 +638,6 @@ class ThemeManager {
|
|
|
619
638
|
// 导出单例实例
|
|
620
639
|
export const themeManager = ThemeManager.getInstance();
|
|
621
640
|
|
|
622
|
-
// 导出类型
|
|
623
|
-
export type { ThemeConfig, ThemeVariables, PresetThemeName };
|
|
624
|
-
|
|
625
641
|
// 导出预设主题
|
|
626
642
|
export { PRESET_THEMES };
|
|
627
643
|
|
package/src/core/types/index.ts
CHANGED
|
@@ -14,7 +14,9 @@ export function registerChart(id: string, instance: EChartsType): void {
|
|
|
14
14
|
// 如果已存在同名ID,先释放旧实例防止内存泄漏
|
|
15
15
|
if (CHART_INSTANCES[id]) {
|
|
16
16
|
try {
|
|
17
|
-
console.warn(
|
|
17
|
+
console.warn(
|
|
18
|
+
`[TaroViz] Chart instance '${id}' already exists, replacing and disposing old instance`
|
|
19
|
+
);
|
|
18
20
|
CHART_INSTANCES[id].dispose();
|
|
19
21
|
} catch (e) {
|
|
20
22
|
console.warn(`Failed to dispose old chart instance: ${id}`, e);
|
|
@@ -38,16 +40,21 @@ export function getChart(id: string): EChartsType | undefined {
|
|
|
38
40
|
*/
|
|
39
41
|
export function removeChart(id: string): void {
|
|
40
42
|
if (CHART_INSTANCES[id]) {
|
|
43
|
+
try {
|
|
44
|
+
CHART_INSTANCES[id].dispose();
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.warn(`Failed to dispose chart on removal: ${id}`, e);
|
|
47
|
+
}
|
|
41
48
|
delete CHART_INSTANCES[id];
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
53
|
* 获取所有图表实例
|
|
47
|
-
* @returns
|
|
54
|
+
* @returns 所有图表实例的浅拷贝
|
|
48
55
|
*/
|
|
49
56
|
export function getAllCharts(): Record<string, EChartsType> {
|
|
50
|
-
return CHART_INSTANCES;
|
|
57
|
+
return { ...CHART_INSTANCES };
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
/**
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart utilities shared between BaseChart and BaseChartWrapper
|
|
3
|
+
*/
|
|
4
|
+
import type { EChartsOption } from 'echarts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalize size value to CSS string
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeSize(value: number | string | undefined, fallback: string): string {
|
|
10
|
+
if (value === undefined) return fallback;
|
|
11
|
+
return typeof value === 'number' ? `${value}px` : value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Calculate total data points in an ECharts option for animation optimization
|
|
16
|
+
*/
|
|
17
|
+
export function calculateDataLength(option: { series?: unknown } | undefined): number {
|
|
18
|
+
if (!option) return 0;
|
|
19
|
+
let count = 0;
|
|
20
|
+
if (option.series) {
|
|
21
|
+
const series = Array.isArray(option.series) ? option.series : [option.series];
|
|
22
|
+
for (const seriesItem of series as any[]) {
|
|
23
|
+
if (seriesItem.data) {
|
|
24
|
+
if (Array.isArray(seriesItem.data)) {
|
|
25
|
+
count += seriesItem.data.length;
|
|
26
|
+
} else if (typeof seriesItem.data === 'object') {
|
|
27
|
+
count += Object.keys(seriesItem.data).length;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return count;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Filter data by filter conditions
|
|
37
|
+
*/
|
|
38
|
+
export function filterDataByKeys(data: any[], filters: Record<string, any>): any[] {
|
|
39
|
+
if (!filters || Object.keys(filters).length === 0) return data;
|
|
40
|
+
return data.filter((item) => {
|
|
41
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
42
|
+
if (item[key] !== value && !item[key]?.includes?.(value)) return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
}
|
package/src/core/utils/common.ts
CHANGED
|
@@ -20,7 +20,20 @@ export const isBrowser = typeof window !== 'undefined' && typeof document !== 'u
|
|
|
20
20
|
* 是否为NodeJS环境
|
|
21
21
|
* @returns 是否为NodeJS环境
|
|
22
22
|
*/
|
|
23
|
-
export const isNode =
|
|
23
|
+
export const isNode = (() => {
|
|
24
|
+
// 更可靠的环境检测:检查是否是真正的 Node.js 环境
|
|
25
|
+
// 而不是打包后的代码(如 webpack 定义的 process.env)
|
|
26
|
+
try {
|
|
27
|
+
return (
|
|
28
|
+
typeof process !== 'undefined' &&
|
|
29
|
+
process.versions &&
|
|
30
|
+
process.versions.node &&
|
|
31
|
+
Object.prototype.toString.call(globalThis.process) === '[object process]'
|
|
32
|
+
);
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
24
37
|
|
|
25
38
|
/**
|
|
26
39
|
* 是否为React Native环境
|
|
@@ -103,7 +103,7 @@ function dataURLToBlob(dataURL: string): Blob {
|
|
|
103
103
|
/**
|
|
104
104
|
* 下载文件
|
|
105
105
|
*/
|
|
106
|
-
function downloadFile(data: string | Blob, filename: string,
|
|
106
|
+
function downloadFile(data: string | Blob, filename: string, _mimeType: string): void {
|
|
107
107
|
const blob = typeof data === 'string' ? dataURLToBlob(data) : data;
|
|
108
108
|
const url = URL.createObjectURL(blob);
|
|
109
109
|
|
|
@@ -142,19 +142,12 @@ class ChartExporter {
|
|
|
142
142
|
/**
|
|
143
143
|
* 导出图表为图片
|
|
144
144
|
*/
|
|
145
|
-
static exportImage(
|
|
146
|
-
|
|
147
|
-
options: ExportImageOptions = {}
|
|
148
|
-
): ExportResult {
|
|
149
|
-
const {
|
|
150
|
-
type = 'png',
|
|
151
|
-
pixelRatio = 2,
|
|
152
|
-
backgroundColor = '#ffffff',
|
|
153
|
-
quality = 0.8,
|
|
154
|
-
} = options;
|
|
145
|
+
static exportImage(chart: ECharts, options: ExportImageOptions = {}): ExportResult {
|
|
146
|
+
const { type = 'png', pixelRatio = 2, backgroundColor = '#ffffff', quality = 0.8 } = options;
|
|
155
147
|
|
|
156
148
|
const mimeType = `image/${type}`;
|
|
157
|
-
|
|
149
|
+
// 使用 any 避免 ECharts 类型定义与实际支持类型不匹配
|
|
150
|
+
const data = (chart.getDataURL as any)({
|
|
158
151
|
type,
|
|
159
152
|
pixelRatio,
|
|
160
153
|
backgroundColor,
|
|
@@ -174,8 +167,9 @@ class ChartExporter {
|
|
|
174
167
|
static exportSVG(chart: ECharts, options: ExportSVGOptions = {}): ExportResult {
|
|
175
168
|
const { compress = false } = options;
|
|
176
169
|
|
|
177
|
-
|
|
178
|
-
|
|
170
|
+
// ECharts 5.x 使用 getDataURL 获取 SVG
|
|
171
|
+
const svgData = (chart.getDataURL as any)({ type: 'svg' });
|
|
172
|
+
if (!svgData || svgData === 'data:image/svg+xml;charset=utf8,') {
|
|
179
173
|
throw new Error('SVG export is not supported. Please use canvas renderer.');
|
|
180
174
|
}
|
|
181
175
|
|
|
@@ -198,10 +192,7 @@ class ChartExporter {
|
|
|
198
192
|
/**
|
|
199
193
|
* 导出图表为 PDF (需要 jspdf 库支持)
|
|
200
194
|
*/
|
|
201
|
-
static async exportPDF(
|
|
202
|
-
chart: ECharts,
|
|
203
|
-
options: ExportPDFOptions = {}
|
|
204
|
-
): Promise<ExportResult> {
|
|
195
|
+
static async exportPDF(chart: ECharts, options: ExportPDFOptions = {}): Promise<ExportResult> {
|
|
205
196
|
const {
|
|
206
197
|
orientation = 'portrait',
|
|
207
198
|
pageSize = 'a4',
|
|
@@ -222,7 +213,7 @@ class ChartExporter {
|
|
|
222
213
|
let jsPDF: any;
|
|
223
214
|
try {
|
|
224
215
|
// 尝试使用动态导入,使用 webpackIgnore 注释避免预解析
|
|
225
|
-
// @ts-
|
|
216
|
+
// @ts-expect-error - 动态导入
|
|
226
217
|
jsPDF = (await import(/* webpackIgnore: true */ 'jspdf')).default;
|
|
227
218
|
} catch {
|
|
228
219
|
// 如果没有 jspdf,提供备选方案
|
|
@@ -283,7 +274,7 @@ class ChartExporter {
|
|
|
283
274
|
doc.addImage(imageData, 'PNG', chartX, chartY, chartWidth, chartHeight);
|
|
284
275
|
|
|
285
276
|
// 添加页脚
|
|
286
|
-
const footerY = pageHeight - margin.bottom / 2;
|
|
277
|
+
const footerY = pageHeight - (margin.bottom ?? 40) / 2;
|
|
287
278
|
doc.setFontSize(10);
|
|
288
279
|
doc.setTextColor(153, 153, 153);
|
|
289
280
|
doc.text(`Generated by TaroViz on ${new Date().toLocaleDateString()}`, marginLeft, footerY);
|
|
@@ -306,7 +297,7 @@ class ChartExporter {
|
|
|
306
297
|
charts: Array<{ name: string; chart: ECharts }>,
|
|
307
298
|
options: BatchExportOptions
|
|
308
299
|
): Promise<ExportResult[]> {
|
|
309
|
-
const { format, filenamePrefix = 'chart', compress } = options;
|
|
300
|
+
const { format, filenamePrefix: _filenamePrefix = 'chart', compress } = options;
|
|
310
301
|
|
|
311
302
|
const results: ExportResult[] = [];
|
|
312
303
|
|
|
@@ -347,7 +338,7 @@ class ChartExporter {
|
|
|
347
338
|
static async copyToClipboard(chart: ECharts, options: ExportImageOptions = {}): Promise<boolean> {
|
|
348
339
|
try {
|
|
349
340
|
const result = this.exportImage(chart, { ...options, type: 'png' });
|
|
350
|
-
const blob = dataURLToBlob(result.data);
|
|
341
|
+
const blob = dataURLToBlob(result.data as string);
|
|
351
342
|
|
|
352
343
|
await navigator.clipboard.write([
|
|
353
344
|
new ClipboardItem({
|
|
@@ -156,6 +156,27 @@ export class PerformanceAnalyzer {
|
|
|
156
156
|
this.emit(PerformanceEventType.MONITORING_END);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* 释放资源
|
|
161
|
+
* 完全清理性能分析器,包括单例实例
|
|
162
|
+
*/
|
|
163
|
+
public dispose(): void {
|
|
164
|
+
this.stop();
|
|
165
|
+
this.metrics.clear();
|
|
166
|
+
this.eventHandlers.clear();
|
|
167
|
+
this.frameRateHistory = [];
|
|
168
|
+
|
|
169
|
+
// 如果是当前单例,清除单例引用
|
|
170
|
+
if (PerformanceAnalyzer.instance === this) {
|
|
171
|
+
PerformanceAnalyzer.instance = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* RAF 动画帧 ID,用于取消
|
|
177
|
+
*/
|
|
178
|
+
private rafId: number | null = null;
|
|
179
|
+
|
|
159
180
|
/**
|
|
160
181
|
* 开始帧率监控
|
|
161
182
|
*/
|
|
@@ -169,23 +190,29 @@ export class PerformanceAnalyzer {
|
|
|
169
190
|
const deltaTime = currentTime - this.lastFrameTime;
|
|
170
191
|
const frameRate = deltaTime > 0 ? Math.round(1000 / deltaTime) : 0;
|
|
171
192
|
|
|
172
|
-
|
|
173
|
-
|
|
193
|
+
// 使用 config.maxSamples 限制历史记录长度
|
|
194
|
+
const maxSamples = this.config.maxSamples ?? 100;
|
|
195
|
+
if (this.frameRateHistory.length >= maxSamples) {
|
|
174
196
|
this.frameRateHistory.shift();
|
|
175
197
|
}
|
|
198
|
+
this.frameRateHistory.push(frameRate);
|
|
176
199
|
|
|
177
200
|
this.lastFrameTime = currentTime;
|
|
178
|
-
requestAnimationFrame(updateFrameRate);
|
|
201
|
+
this.rafId = requestAnimationFrame(updateFrameRate);
|
|
179
202
|
};
|
|
180
203
|
|
|
181
|
-
requestAnimationFrame(updateFrameRate);
|
|
204
|
+
this.rafId = requestAnimationFrame(updateFrameRate);
|
|
182
205
|
}
|
|
183
206
|
|
|
184
207
|
/**
|
|
185
208
|
* 停止帧率监控
|
|
186
209
|
*/
|
|
187
210
|
private stopFrameRateMonitoring(): void {
|
|
188
|
-
//
|
|
211
|
+
// 取消 RAF 循环,防止继续运行
|
|
212
|
+
if (this.rafId !== null) {
|
|
213
|
+
cancelAnimationFrame(this.rafId);
|
|
214
|
+
this.rafId = null;
|
|
215
|
+
}
|
|
189
216
|
this.frameRateHistory = [];
|
|
190
217
|
}
|
|
191
218
|
|
package/src/core/utils/uuid.ts
CHANGED
|
@@ -8,7 +8,7 @@ export function uuid(): string {
|
|
|
8
8
|
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
9
9
|
return globalThis.crypto.randomUUID();
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
// 回退:使用 Math.random() + 时间戳混合
|
|
13
13
|
const timestamp = Date.now().toString(36);
|
|
14
14
|
const randomPart = Math.random().toString(36).substring(2, 15);
|