@agions/taroviz 1.9.0 → 1.11.1

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.
@@ -3,6 +3,7 @@
3
3
  * 用于按需加载图表,减少首屏体积
4
4
  */
5
5
  import React, { Suspense, lazy, ComponentType } from 'react';
6
+ import type { BaseChartProps } from '../../charts/types';
6
7
 
7
8
  // 懒加载各个图表组件
8
9
  const LazyLineChart = lazy(() => import('../../charts/line'));
@@ -18,7 +19,7 @@ const LazySunburstChart = lazy(() => import('../../charts/sunburst'));
18
19
  const LazySankeyChart = lazy(() => import('../../charts/sankey'));
19
20
 
20
21
  // 统一的图表类型到懒加载组件映射
21
- const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: ComponentType<any> }>> = {
22
+ const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: ComponentType<BaseChartProps> }>> = {
22
23
  line: () => import('../../charts/line'),
23
24
  bar: () => import('../../charts/bar'),
24
25
  pie: () => import('../../charts/pie'),
@@ -35,10 +36,14 @@ const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: ComponentType<
35
36
  export const LAZY_CHART_TYPES = Object.keys(LAZY_CHART_MODULES);
36
37
 
37
38
  /**
38
- * 默认加载状态组件
39
+ * 默认加载状态组件(使用 CSS 变量,与 ThemeManager 对齐)
40
+ * 包含完整的无障碍支持
39
41
  */
40
42
  const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中...' }) => (
41
43
  <div
44
+ role="status"
45
+ aria-label={text}
46
+ aria-busy="true"
42
47
  style={{
43
48
  display: 'flex',
44
49
  alignItems: 'center',
@@ -46,8 +51,8 @@ const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中
46
51
  width: '100%',
47
52
  height: '100%',
48
53
  minHeight: '200px',
49
- backgroundColor: '#f5f5f5',
50
- borderRadius: '8px',
54
+ backgroundColor: 'var(--tv-bg-color-secondary, #f5f5f5)',
55
+ borderRadius: 'var(--tv-border-radius, 8px)',
51
56
  }}
52
57
  >
53
58
  <div style={{ textAlign: 'center' }}>
@@ -55,12 +60,13 @@ const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中
55
60
  style={{
56
61
  width: '40px',
57
62
  height: '40px',
58
- border: '3px solid #1890ff',
63
+ border: '3px solid var(--tv-primary-color, #1890ff)',
59
64
  borderTopColor: 'transparent',
60
65
  borderRadius: '50%',
61
66
  animation: 'taroviz-spin 1s linear infinite',
62
67
  margin: '0 auto 12px',
63
68
  }}
69
+ aria-hidden="true"
64
70
  />
65
71
  <style>
66
72
  {`
@@ -69,7 +75,28 @@ const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中
69
75
  }
70
76
  `}
71
77
  </style>
72
- <span style={{ color: '#666', fontSize: '14px' }}>{text}</span>
78
+ {/*
79
+ 视觉隐藏文本,屏幕阅读器可读取加载状态
80
+ 避免干扰视觉布局
81
+ */}
82
+ <span
83
+ style={{
84
+ position: 'absolute',
85
+ width: '1px',
86
+ height: '1px',
87
+ padding: 0,
88
+ margin: '-1px',
89
+ overflow: 'hidden',
90
+ clip: 'rect(0, 0, 0, 0)',
91
+ whiteSpace: 'nowrap',
92
+ border: 0,
93
+ }}
94
+ >
95
+ {text}
96
+ </span>
97
+ <span style={{ color: 'var(--tv-text-color-secondary, #666)', fontSize: 'var(--tv-font-size, 14px)' }}>
98
+ {text}
99
+ </span>
73
100
  </div>
74
101
  </div>
75
102
  );
@@ -136,8 +163,8 @@ export function preloadAllCharts(): Promise<void[]> {
136
163
  * 创建懒加载图表映射
137
164
  * 用于动态导入图表
138
165
  */
139
- export function createLazyChart(chartType: string): ComponentType<any> | null {
140
- const lazyCharts: Record<string, ComponentType<any>> = {
166
+ export function createLazyChart(chartType: string): ComponentType<BaseChartProps> | null {
167
+ const lazyCharts: Record<string, ComponentType<BaseChartProps>> = {
141
168
  line: LazyLineChart,
142
169
  bar: LazyBarChart,
143
170
  pie: LazyPieChart,
@@ -159,7 +186,7 @@ export function createLazyChart(chartType: string): ComponentType<any> | null {
159
186
  * 用于按名称动态获取懒加载图表组件
160
187
  */
161
188
  export const LazyChartRegistry = {
162
- get(chartType: string): ComponentType<any> | null {
189
+ get(chartType: string): ComponentType<BaseChartProps> | null {
163
190
  return createLazyChart(chartType);
164
191
  },
165
192
 
@@ -578,6 +578,39 @@ class ThemeManager {
578
578
  return this.currentTheme.isDark || this.currentTheme.type === 'dark';
579
579
  }
580
580
 
581
+ /**
582
+ * 检测系统 prefers-color-scheme 是否为暗色
583
+ */
584
+ public getSystemPrefersDark(): boolean {
585
+ if (typeof window === 'undefined') return false;
586
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
587
+ }
588
+
589
+ /**
590
+ * 订阅系统主题变化,自动切换匹配的主题
591
+ * @returns 取消订阅的函数
592
+ */
593
+ public watchSystemTheme(): () => void {
594
+ if (typeof window === 'undefined') return () => {};
595
+
596
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
597
+ const handler = (e: MediaQueryListEvent) => {
598
+ // 当系统主题切换时,自动应用对应主题
599
+ this.setTheme(e.matches ? 'dark' : 'default');
600
+ };
601
+ mq.addEventListener('change', handler);
602
+ return () => mq.removeEventListener('change', handler);
603
+ }
604
+
605
+ /**
606
+ * 应用初始主题(根据系统偏好自动选择 dark/default)
607
+ * 必须在 DOM 加载后调用
608
+ */
609
+ public applyInitialTheme(): void {
610
+ const prefersDark = this.getSystemPrefersDark();
611
+ this.setTheme(prefersDark ? 'dark' : 'default');
612
+ }
613
+
581
614
  /**
582
615
  * 注册主题变更监听器
583
616
  */
@@ -4,24 +4,119 @@
4
4
  export type { EChartsOption } from 'echarts';
5
5
 
6
6
  /**
7
- * 图表事件参数类型
8
- * 使用 ECharts 内的事件参数接口
7
+ * ECharts 鼠标事件参数类型
8
+ * 基于 ECharts CallbackDataParams,用于 click、mousemove 等鼠标事件
9
9
  */
10
- export interface ChartEventParams {
11
- componentType?: string;
10
+ export interface EChartsMouseEventParams {
11
+ componentType: string;
12
+ componentSubType?: string;
13
+ componentIndex?: number;
12
14
  seriesType?: string;
13
15
  seriesIndex?: number;
16
+ seriesId?: string;
14
17
  seriesName?: string;
15
18
  name?: string;
16
19
  dataIndex?: number;
17
20
  data?: unknown;
21
+ dataType?: string;
18
22
  value?: unknown;
19
23
  color?: string;
20
24
  borderColor?: string;
21
25
  borderWidth?: number;
22
- target?: unknown;
26
+ dimensionNames?: string[];
27
+ encode?: Record<string, number[]>;
28
+ marker?: string;
29
+ status?: string;
30
+ dimensionIndex?: number;
31
+ percent?: number;
23
32
  }
24
33
 
34
+ /**
35
+ * ECharts DataZoom 事件参数类型
36
+ */
37
+ export interface EChartsDataZoomEventParams {
38
+ type: 'datazoom';
39
+ start?: number;
40
+ end?: number;
41
+ startValue?: number;
42
+ endValue?: number;
43
+ dataZoomIndex?: number;
44
+ batch?: Array<{
45
+ start?: number;
46
+ end?: number;
47
+ startValue?: number;
48
+ endValue?: number;
49
+ dataZoomIndex?: number;
50
+ }>;
51
+ }
52
+
53
+ /**
54
+ * ECharts Legend 事件参数类型
55
+ */
56
+ export interface EChartsLegendEventParams {
57
+ type: 'legendselectchanged' | 'legendselected' | 'legendunselected';
58
+ name: string;
59
+ selected: Record<string, boolean>;
60
+ }
61
+
62
+ /**
63
+ * ECharts Tooltip 事件参数类型
64
+ */
65
+ export interface EChartsTooltipEventParams {
66
+ type: 'tooltipshow' | 'tooltiphide';
67
+ data?: unknown;
68
+ dataIndex?: number;
69
+ dataType?: string;
70
+ name?: string;
71
+ value?: unknown;
72
+ }
73
+
74
+ /**
75
+ * 图表事件参数类型(向后兼容别名)
76
+ */
77
+ export type ChartEventParams = EChartsMouseEventParams;
78
+
79
+ /**
80
+ * 图表导出选项
81
+ */
82
+ export interface ChartExportOptions {
83
+ type?: 'png' | 'jpeg' | 'svg';
84
+ filename?: string;
85
+ pixelRatio?: number;
86
+ backgroundColor?: string;
87
+ }
88
+
89
+ /**
90
+ * 图表联动配置
91
+ */
92
+ export interface ChartLinkageConfig {
93
+ linkedChartIds?: string[];
94
+ enableClickLinkage?: boolean;
95
+ enableZoomLinkage?: boolean;
96
+ enableLegendLinkage?: boolean;
97
+ enableFilterLinkage?: boolean;
98
+ }
99
+
100
+ /**
101
+ * ECharts 系列数据基本类型
102
+ */
103
+ export interface EChartsSeriesData {
104
+ name?: string;
105
+ type?: string;
106
+ data?: unknown[];
107
+ [key: string]: unknown;
108
+ }
109
+
110
+ /**
111
+ * ECharts 事件参数联合类型
112
+ */
113
+ export type EChartsEventParams =
114
+ | EChartsMouseEventParams
115
+ | EChartsDataZoomEventParams
116
+ | EChartsLegendEventParams
117
+ | EChartsTooltipEventParams
118
+ | Record<string, unknown>;
119
+
25
120
  /**
26
121
  * 图表事件监听器
27
122
  */
@@ -3,6 +3,10 @@
3
3
  */
4
4
  import type { EChartsOption } from 'echarts';
5
5
 
6
+ interface SeriesItem {
7
+ data?: unknown[] | Record<string, unknown>;
8
+ }
9
+
6
10
  /**
7
11
  * Normalize size value to CSS string
8
12
  */
@@ -19,7 +23,7 @@ export function calculateDataLength(option: { series?: unknown } | undefined): n
19
23
  let count = 0;
20
24
  if (option.series) {
21
25
  const series = Array.isArray(option.series) ? option.series : [option.series];
22
- for (const seriesItem of series as any[]) {
26
+ for (const seriesItem of series as SeriesItem[]) {
23
27
  if (seriesItem.data) {
24
28
  if (Array.isArray(seriesItem.data)) {
25
29
  count += seriesItem.data.length;
@@ -35,11 +39,12 @@ export function calculateDataLength(option: { series?: unknown } | undefined): n
35
39
  /**
36
40
  * Filter data by filter conditions
37
41
  */
38
- export function filterDataByKeys(data: any[], filters: Record<string, any>): any[] {
42
+ export function filterDataByKeys<T>(data: T[], filters: Record<string, unknown>): T[] {
39
43
  if (!filters || Object.keys(filters).length === 0) return data;
40
44
  return data.filter((item) => {
41
45
  for (const [key, value] of Object.entries(filters)) {
42
- if (item[key] !== value && !item[key]?.includes?.(value)) return false;
46
+ const itemVal = (item as Record<string, unknown>)[key];
47
+ if (itemVal !== value && !(Array.isArray(itemVal) && itemVal.includes(value))) return false;
43
48
  }
44
49
  return true;
45
50
  });
@@ -8,12 +8,41 @@ import type { ECharts } from 'echarts';
8
8
  // 类型定义
9
9
  // ============================================================================
10
10
 
11
+ /**
12
+ * ECharts 支持的图片导出类型
13
+ * 注意: webp/gif 类型为扩展支持,不在官方类型中
14
+ */
15
+ type ExportImageType = 'png' | 'jpeg' | 'svg' | 'webp' | 'gif';
16
+
17
+ /**
18
+ * getDataURL 方法参数(基于 ECharts 官方类型,扩展 webp/gif 支持)
19
+ */
20
+ interface EChartsDataURLOptions {
21
+ type?: ExportImageType;
22
+ pixelRatio?: number;
23
+ backgroundColor?: string;
24
+ quality?: number;
25
+ excludeComponents?: string[];
26
+ }
27
+
28
+ /**
29
+ * jsPDF 库类型(简化版)
30
+ */
31
+ interface JSPDFInstance {
32
+ setProperties(props: Record<string, string>): void;
33
+ setFontSize(size: number): void;
34
+ setTextColor(r: number, g: number, b: number): void;
35
+ text(text: string, x: number, y: number): void;
36
+ addImage(imageData: string, format: string, x: number, y: number, width: number, height: number): void;
37
+ output(type: 'blob'): Blob;
38
+ }
39
+
11
40
  /**
12
41
  * 图片导出选项
13
42
  */
14
43
  export interface ExportImageOptions {
15
44
  /** 图片类型 */
16
- type?: 'png' | 'jpeg' | 'webp' | 'gif';
45
+ type?: ExportImageType;
17
46
  /** 设备像素比 */
18
47
  pixelRatio?: number;
19
48
  /** 背景色 */
@@ -155,13 +184,13 @@ class ChartExporter {
155
184
  const { type = 'png', pixelRatio = 2, backgroundColor = '#ffffff', quality = 0.8 } = options;
156
185
 
157
186
  const mimeType = `image/${type}`;
158
- // 使用 any 避免 ECharts 类型定义与实际支持类型不匹配
159
- const data = (chart.getDataURL as any)({
187
+ // 使用类型断言:ECharts 实际支持 webp/gif,但官方类型未声明
188
+ const data = chart.getDataURL({
160
189
  type,
161
190
  pixelRatio,
162
191
  backgroundColor,
163
192
  quality,
164
- });
193
+ } as unknown as Parameters<typeof chart.getDataURL>[0]);
165
194
 
166
195
  return {
167
196
  data,
@@ -177,7 +206,7 @@ class ChartExporter {
177
206
  const { compress = false } = options;
178
207
 
179
208
  // ECharts 5.x 使用 getDataURL 获取 SVG
180
- const svgData = (chart.getDataURL as any)({ type: 'svg' });
209
+ const svgData = chart.getDataURL({ type: 'svg' });
181
210
  if (!svgData || svgData === 'data:image/svg+xml;charset=utf8,') {
182
211
  throw new Error('SVG export is not supported. Please use canvas renderer.');
183
212
  }
@@ -219,11 +248,12 @@ class ChartExporter {
219
248
  });
220
249
 
221
250
  // 动态导入 jspdf
222
- let jsPDF: any;
251
+ let JSPDFClass: new (options: Record<string, string>) => JSPDFInstance;
223
252
  try {
224
253
  // 尝试使用动态导入,使用 webpackIgnore 注释避免预解析
225
254
  // @ts-expect-error - 动态导入
226
- jsPDF = (await import(/* webpackIgnore: true */ 'jspdf')).default;
255
+ const module = await import(/* webpackIgnore: true */ 'jspdf');
256
+ JSPDFClass = module.default as new (options: Record<string, string>) => JSPDFInstance;
227
257
  } catch {
228
258
  // 如果没有 jspdf,提供备选方案
229
259
  console.warn('[TaroViz] jspdf not found, falling back to image download');
@@ -246,7 +276,7 @@ class ChartExporter {
246
276
  const isLandscape = orientation === 'landscape';
247
277
 
248
278
  // 创建 PDF
249
- const doc = new jsPDF({
279
+ const doc = new JSPDFClass({
250
280
  orientation,
251
281
  unit: 'mm',
252
282
  format: pageSize,
@@ -289,7 +319,7 @@ class ChartExporter {
289
319
  doc.text(`Generated by TaroViz on ${new Date().toLocaleDateString()}`, marginLeft, footerY);
290
320
 
291
321
  // 导出为 Blob
292
- const pdfBlob = doc.output('blob');
322
+ const pdfBlob = doc.output('blob') as Blob;
293
323
 
294
324
  return {
295
325
  data: pdfBlob,
@@ -9,6 +9,7 @@ import {
9
9
  PerformanceAnalysisResult,
10
10
  PerformanceEventType,
11
11
  PerformanceEventHandler,
12
+ PerformanceEventData,
12
13
  PerformanceReportConfig,
13
14
  } from './types';
14
15
 
@@ -118,11 +119,20 @@ export class PerformanceAnalyzer {
118
119
  /**
119
120
  * 触发事件
120
121
  */
121
- private emit(eventType: PerformanceEventType, data?: any): void {
122
+ private emit(eventType: PerformanceEventType.MONITORING_START): void;
123
+ private emit(eventType: PerformanceEventType.MONITORING_END): void;
124
+ private emit(eventType: PerformanceEventType.METRIC_UPDATE, data: PerformanceMetric): void;
125
+ private emit(eventType: PerformanceEventType.ANALYSIS_COMPLETE, data: PerformanceAnalysisResult): void;
126
+ private emit(eventType: PerformanceEventType, data?: PerformanceMetric | PerformanceAnalysisResult): void {
122
127
  const handlers = this.eventHandlers.get(eventType);
128
+ const eventData: PerformanceEventData = data === undefined
129
+ ? { type: eventType as PerformanceEventType.MONITORING_START | PerformanceEventType.MONITORING_END }
130
+ : eventType === PerformanceEventType.METRIC_UPDATE
131
+ ? { type: eventType, data: data as PerformanceMetric }
132
+ : { type: eventType, data: data as PerformanceAnalysisResult };
123
133
  handlers?.forEach((handler) => {
124
134
  try {
125
- handler({ type: eventType, data });
135
+ handler(eventData);
126
136
  } catch (error) {
127
137
  console.error('Error in performance event handler:', error);
128
138
  }
@@ -324,7 +334,7 @@ export class PerformanceAnalyzer {
324
334
  /**
325
335
  * 记录数据大小
326
336
  */
327
- public recordDataSize(data: any): void {
337
+ public recordDataSize(data: unknown): void {
328
338
  try {
329
339
  const dataSize = new Blob([JSON.stringify(data)]).size / 1024;
330
340
  this.recordMetric('dataSize', dataSize, 'KB', '图表数据大小');
@@ -450,8 +460,8 @@ export class PerformanceAnalyzer {
450
460
  private generateJsonReport(
451
461
  result: PerformanceAnalysisResult,
452
462
  _config: PerformanceReportConfig
453
- ): object {
454
- const report: any = {
463
+ ): Record<string, unknown> {
464
+ const report: Record<string, unknown> = {
455
465
  timestamp: new Date().toISOString(),
456
466
  duration: result.duration,
457
467
  score: result.score,
@@ -154,10 +154,19 @@ export enum PerformanceEventType {
154
154
  ANALYSIS_COMPLETE = 'performanceAnalysisComplete',
155
155
  }
156
156
 
157
+ /**
158
+ * 性能事件数据
159
+ */
160
+ export type PerformanceEventData =
161
+ | { type: PerformanceEventType.MONITORING_START }
162
+ | { type: PerformanceEventType.MONITORING_END }
163
+ | { type: PerformanceEventType.METRIC_UPDATE; data: PerformanceMetric }
164
+ | { type: PerformanceEventType.ANALYSIS_COMPLETE; data: PerformanceAnalysisResult };
165
+
157
166
  /**
158
167
  * 性能事件回调类型
159
168
  */
160
- export type PerformanceEventHandler = (event: { type: PerformanceEventType; data?: any }) => void;
169
+ export type PerformanceEventHandler = (event: PerformanceEventData) => void;
161
170
 
162
171
  /**
163
172
  * 性能分析报告配置
@@ -9,6 +9,8 @@ import type { EChartsOption } from 'echarts';
9
9
  import { useDataZoom } from './useDataZoom';
10
10
  import { useChartConnect } from './useChartConnect';
11
11
  import { useChartDownload } from './useChartDownload';
12
+ import { useChartHistory } from './useChartHistory';
13
+ import { useChartSelection } from './useChartSelection';
12
14
 
13
15
  // ============================================================================
14
16
  // 类型定义
@@ -651,6 +653,55 @@ export {
651
653
  type DownloadDataOptions,
652
654
  } from './useChartDownload';
653
655
 
656
+ // 图表下载工具函数
657
+ export {
658
+ generateFilename,
659
+ downloadBlob,
660
+ downloadDataUrl,
661
+ csvToBlob,
662
+ jsonToBlob,
663
+ convertToCSV,
664
+ convertToJSON,
665
+ createPdfFromImage,
666
+ } from './utils/chartDownloadUtils';
667
+
668
+ // 数据转换工具函数(类型导出)
669
+ export type {
670
+ DataItem,
671
+ DataSource,
672
+ AggregationType,
673
+ TimePeriod,
674
+ TransformMapping,
675
+ } from './utils/dataTransformUtils';
676
+
677
+ // 数据转换工具函数(值导出)
678
+ export {
679
+ transformLineOrBar,
680
+ transformPie,
681
+ transformScatter,
682
+ transformRadar,
683
+ transformHeatmap,
684
+ groupByTime,
685
+ aggregateValues,
686
+ } from './utils/dataTransformUtils';
687
+
688
+ // 图表历史记录 Hook (Undo/Redo)
689
+ export {
690
+ useChartHistory,
691
+ type UseChartHistoryOptions,
692
+ type UseChartHistoryReturn,
693
+ } from './useChartHistory';
694
+
695
+ // 图表选择 Hook
696
+ export {
697
+ useChartSelection,
698
+ type UseChartSelectionOptions,
699
+ type UseChartSelectionReturn,
700
+ type DataPointKey,
701
+ type SelectionMode,
702
+ type SelectionEvent,
703
+ } from './useChartSelection';
704
+
654
705
  // ============================================================================
655
706
  // 导出
656
707
  // ============================================================================
@@ -683,4 +734,6 @@ export default {
683
734
  useDataZoom,
684
735
  useChartConnect,
685
736
  useChartDownload,
737
+ useChartHistory,
738
+ useChartSelection,
686
739
  };