@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,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDataTransform - 数据转换 Hook
|
|
3
|
+
* 提供便捷的数据转换功能,将原始数据转换为 ECharts 配置
|
|
4
|
+
*/
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import type { EChartsOption } from 'echarts';
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// 类型定义
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/** 原始数据条目 */
|
|
13
|
+
export interface DataItem {
|
|
14
|
+
name?: string;
|
|
15
|
+
value?: number | number[];
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 数据源 */
|
|
20
|
+
export interface DataSource {
|
|
21
|
+
categories?: (string | number)[];
|
|
22
|
+
series?: DataItem[];
|
|
23
|
+
rows?: Record<string, unknown>[];
|
|
24
|
+
columns?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 聚合方式 */
|
|
28
|
+
export type AggregationType = 'sum' | 'average' | 'max' | 'min' | 'count' | 'first' | 'last';
|
|
29
|
+
|
|
30
|
+
/** 时间周期 */
|
|
31
|
+
export type TimePeriod = 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
32
|
+
|
|
33
|
+
/** 转换选项 */
|
|
34
|
+
export interface TransformOptions {
|
|
35
|
+
/** 数据源 */
|
|
36
|
+
data: DataSource;
|
|
37
|
+
/** 图表类型 */
|
|
38
|
+
chartType: 'line' | 'bar' | 'pie' | 'scatter' | 'radar' | 'heatmap';
|
|
39
|
+
/** 映射配置 */
|
|
40
|
+
mapping?: {
|
|
41
|
+
/** 类别字段 (X轴) */
|
|
42
|
+
xField?: string;
|
|
43
|
+
/** 值字段 (Y轴) */
|
|
44
|
+
yField?: string;
|
|
45
|
+
/** 系列字段 */
|
|
46
|
+
seriesField?: string;
|
|
47
|
+
/** 大小字段 (用于散点图) */
|
|
48
|
+
sizeField?: string;
|
|
49
|
+
/** 颜色字段 */
|
|
50
|
+
colorField?: string;
|
|
51
|
+
/** 名称字段 (用于饼图等) */
|
|
52
|
+
nameField?: string;
|
|
53
|
+
/** 值字段 (用于饼图等) */
|
|
54
|
+
valueField?: string;
|
|
55
|
+
};
|
|
56
|
+
/** 聚合配置 */
|
|
57
|
+
aggregation?: {
|
|
58
|
+
field: string;
|
|
59
|
+
method: AggregationType;
|
|
60
|
+
};
|
|
61
|
+
/** 排序配置 */
|
|
62
|
+
sort?: {
|
|
63
|
+
field: string;
|
|
64
|
+
order: 'asc' | 'desc';
|
|
65
|
+
};
|
|
66
|
+
/** 过滤配置 */
|
|
67
|
+
filter?: (item: Record<string, unknown>) => boolean;
|
|
68
|
+
/** 转换后的额外配置 */
|
|
69
|
+
extraConfig?: Partial<EChartsOption>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 表格数据转换选项 */
|
|
73
|
+
export interface TableTransformOptions {
|
|
74
|
+
/** 表格数据 */
|
|
75
|
+
data: Record<string, unknown>[];
|
|
76
|
+
/** 列配置 */
|
|
77
|
+
columns?: Array<{
|
|
78
|
+
field: string;
|
|
79
|
+
label?: string;
|
|
80
|
+
aggregation?: AggregationType;
|
|
81
|
+
color?: string;
|
|
82
|
+
}>;
|
|
83
|
+
/** 是否转置 */
|
|
84
|
+
transpose?: boolean;
|
|
85
|
+
/** 额外配置 */
|
|
86
|
+
extraConfig?: Partial<EChartsOption>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 时间序列转换选项 */
|
|
90
|
+
export interface TimeSeriesTransformOptions {
|
|
91
|
+
/** 数据 */
|
|
92
|
+
data: DataItem[];
|
|
93
|
+
/** 日期字段 */
|
|
94
|
+
dateField: string;
|
|
95
|
+
/** 值字段 */
|
|
96
|
+
valueField: string;
|
|
97
|
+
/** 分组字段 (可选) */
|
|
98
|
+
groupField?: string;
|
|
99
|
+
/** 周期 */
|
|
100
|
+
period?: TimePeriod;
|
|
101
|
+
/** 聚合方式 */
|
|
102
|
+
aggregation?: AggregationType;
|
|
103
|
+
/** 填充方式 */
|
|
104
|
+
fillMissing?: 'zero' | 'forward' | 'interpolate';
|
|
105
|
+
/** 转换后的额外配置 */
|
|
106
|
+
extraConfig?: Partial<EChartsOption>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// 数据转换 Hook
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 使用数据转换
|
|
115
|
+
* @param options 转换选项
|
|
116
|
+
* @returns 转换后的 ECharts 配置
|
|
117
|
+
*/
|
|
118
|
+
export function useDataTransform(options: TransformOptions): EChartsOption {
|
|
119
|
+
const { data, chartType, mapping = {}, extraConfig = {} } = options;
|
|
120
|
+
|
|
121
|
+
return useMemo(() => {
|
|
122
|
+
switch (chartType) {
|
|
123
|
+
case 'line':
|
|
124
|
+
case 'bar':
|
|
125
|
+
return transformLineOrBar(data, chartType, mapping, extraConfig);
|
|
126
|
+
|
|
127
|
+
case 'pie':
|
|
128
|
+
return transformPie(data, mapping, extraConfig);
|
|
129
|
+
|
|
130
|
+
case 'scatter':
|
|
131
|
+
return transformScatter(data, mapping, extraConfig);
|
|
132
|
+
|
|
133
|
+
case 'radar':
|
|
134
|
+
return transformRadar(data, mapping, extraConfig);
|
|
135
|
+
|
|
136
|
+
case 'heatmap':
|
|
137
|
+
return transformHeatmap(data, mapping, extraConfig);
|
|
138
|
+
|
|
139
|
+
default:
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
}, [data, chartType, mapping, extraConfig]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 使用表格数据转换
|
|
147
|
+
* 将表格数据转换为 ECharts 配置
|
|
148
|
+
*/
|
|
149
|
+
export function useTableTransform(options: TableTransformOptions): EChartsOption {
|
|
150
|
+
const { data, columns = [], transpose = false, extraConfig = {} } = options;
|
|
151
|
+
|
|
152
|
+
return useMemo(() => {
|
|
153
|
+
if (!data || data.length === 0) {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const firstRow = data[0];
|
|
158
|
+
const fields = columns.length > 0 ? columns.map((c) => c.field) : Object.keys(firstRow);
|
|
159
|
+
|
|
160
|
+
// Build columns map for O(1) lookup instead of O(n) find
|
|
161
|
+
const columnsMap = new Map(columns.map((c) => [c.field, c]));
|
|
162
|
+
|
|
163
|
+
const categories = transpose ? fields : data.map((row) => String(row[fields[0]] || ''));
|
|
164
|
+
|
|
165
|
+
// For non-transpose, we skip the first field (it's used for categories)
|
|
166
|
+
const seriesFields = transpose ? fields : fields.slice(1);
|
|
167
|
+
|
|
168
|
+
const series = seriesFields.map((fieldName: string | Record<string, unknown>) => {
|
|
169
|
+
const key = typeof fieldName === 'string' ? fieldName : '';
|
|
170
|
+
const colConfig = columnsMap.get(key);
|
|
171
|
+
// values calculation is the same regardless of transpose
|
|
172
|
+
const values = data.map((row) => Number(row[key as keyof typeof row]) || 0);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
name: colConfig?.label || key,
|
|
176
|
+
type: 'bar' as const,
|
|
177
|
+
data: values,
|
|
178
|
+
itemStyle: colConfig?.color ? { color: colConfig.color } : undefined,
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
xAxis: { type: 'category', data: categories },
|
|
184
|
+
yAxis: { type: 'value' },
|
|
185
|
+
series,
|
|
186
|
+
...extraConfig,
|
|
187
|
+
};
|
|
188
|
+
}, [data, columns, transpose, extraConfig]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 使用时间序列转换
|
|
193
|
+
* 将时间序列数据转换为 ECharts 配置
|
|
194
|
+
*/
|
|
195
|
+
export function useTimeSeriesTransform(options: TimeSeriesTransformOptions): EChartsOption {
|
|
196
|
+
const {
|
|
197
|
+
data,
|
|
198
|
+
dateField,
|
|
199
|
+
valueField,
|
|
200
|
+
groupField,
|
|
201
|
+
period = 'day',
|
|
202
|
+
aggregation = 'sum',
|
|
203
|
+
fillMissing = 'forward',
|
|
204
|
+
extraConfig = {},
|
|
205
|
+
} = options;
|
|
206
|
+
|
|
207
|
+
return useMemo(() => {
|
|
208
|
+
if (!data || data.length === 0) {
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 按时间分组
|
|
213
|
+
const groupedData = groupByTime(data, dateField, period);
|
|
214
|
+
const categories = Object.keys(groupedData).sort();
|
|
215
|
+
|
|
216
|
+
if (groupField) {
|
|
217
|
+
// 多系列
|
|
218
|
+
const groups = new Set(data.map((d) => String(d[groupField])));
|
|
219
|
+
const series = Array.from(groups).map((group) => {
|
|
220
|
+
const groupValues = categories.map((date) => {
|
|
221
|
+
const items = groupedData[date]?.filter((d) => String(d[groupField]) === group) || [];
|
|
222
|
+
return aggregateValues(items, valueField, aggregation, fillMissing);
|
|
223
|
+
});
|
|
224
|
+
return { name: group, type: 'line', data: groupValues, smooth: true };
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
xAxis: { type: 'category', data: categories },
|
|
228
|
+
yAxis: { type: 'value' },
|
|
229
|
+
series,
|
|
230
|
+
...extraConfig,
|
|
231
|
+
};
|
|
232
|
+
} else {
|
|
233
|
+
// 单系列
|
|
234
|
+
const values = categories.map((date) =>
|
|
235
|
+
aggregateValues(groupedData[date] || [], valueField, aggregation, fillMissing)
|
|
236
|
+
);
|
|
237
|
+
return {
|
|
238
|
+
xAxis: { type: 'category', data: categories },
|
|
239
|
+
yAxis: { type: 'value' },
|
|
240
|
+
series: [{ type: 'line', data: values, smooth: true }],
|
|
241
|
+
...extraConfig,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}, [data, dateField, valueField, groupField, period, aggregation, fillMissing, extraConfig]);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// 辅助函数
|
|
249
|
+
// ============================================================================
|
|
250
|
+
|
|
251
|
+
function transformLineOrBar(
|
|
252
|
+
data: DataSource,
|
|
253
|
+
chartType: 'line' | 'bar',
|
|
254
|
+
mapping: TransformOptions['mapping'],
|
|
255
|
+
extraConfig: Partial<EChartsOption>
|
|
256
|
+
): EChartsOption {
|
|
257
|
+
const { xField = 'name', yField = 'value', seriesField } = mapping || {};
|
|
258
|
+
|
|
259
|
+
const categories = data.categories || data.rows?.map((r) => String(r[xField])) || [];
|
|
260
|
+
const seriesData = data.series || data.rows || [];
|
|
261
|
+
|
|
262
|
+
if (seriesField) {
|
|
263
|
+
const groups = new Map<string, DataItem[]>();
|
|
264
|
+
seriesData.forEach((item) => {
|
|
265
|
+
const key = String((item as Record<string, unknown>)[seriesField] || 'default');
|
|
266
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
267
|
+
groups.get(key)!.push(item);
|
|
268
|
+
});
|
|
269
|
+
const series = Array.from(groups.entries()).map(([name, items]) => ({
|
|
270
|
+
name,
|
|
271
|
+
type: chartType,
|
|
272
|
+
data: items.map((item) => (item as Record<string, unknown>)[yField] ?? 0),
|
|
273
|
+
}));
|
|
274
|
+
return {
|
|
275
|
+
xAxis: { type: 'category', data: categories },
|
|
276
|
+
yAxis: { type: 'value' },
|
|
277
|
+
series,
|
|
278
|
+
...extraConfig,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const series = [
|
|
283
|
+
{
|
|
284
|
+
type: chartType as 'line' | 'bar',
|
|
285
|
+
data: seriesData.map((item) => (item as Record<string, unknown>)[yField] ?? 0),
|
|
286
|
+
},
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
xAxis: { type: 'category', data: categories },
|
|
291
|
+
yAxis: { type: 'value' },
|
|
292
|
+
series,
|
|
293
|
+
...extraConfig,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function transformPie(
|
|
298
|
+
data: DataSource,
|
|
299
|
+
mapping: TransformOptions['mapping'],
|
|
300
|
+
extraConfig: Partial<EChartsOption>
|
|
301
|
+
): EChartsOption {
|
|
302
|
+
const { nameField = 'name', valueField = 'value' } = mapping || {};
|
|
303
|
+
|
|
304
|
+
const seriesData: Array<{ name: string; value: number }> = (data.series || data.rows || []).map(
|
|
305
|
+
(item) => ({
|
|
306
|
+
name: String((item as Record<string, unknown>)[nameField] || ''),
|
|
307
|
+
value: Number((item as Record<string, unknown>)[valueField]) || 0,
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
series: [{ type: 'pie', radius: '60%', data: seriesData }],
|
|
313
|
+
...extraConfig,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function transformScatter(
|
|
318
|
+
data: DataSource,
|
|
319
|
+
mapping: TransformOptions['mapping'],
|
|
320
|
+
extraConfig: Partial<EChartsOption>
|
|
321
|
+
): EChartsOption {
|
|
322
|
+
const { xField = 'x', yField = 'y', sizeField } = mapping || {};
|
|
323
|
+
|
|
324
|
+
const seriesData = (data.series || data.rows || []).map((item) => {
|
|
325
|
+
const record = item as Record<string, unknown>;
|
|
326
|
+
const point: (number | string)[] = [Number(record[xField]) || 0, Number(record[yField]) || 0];
|
|
327
|
+
if (sizeField) point.push(Number(record[sizeField]) || 1);
|
|
328
|
+
return point;
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
xAxis: { type: 'value', scale: true },
|
|
333
|
+
yAxis: { type: 'value', scale: true },
|
|
334
|
+
series: [{ type: 'scatter', data: seriesData }],
|
|
335
|
+
...extraConfig,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function transformRadar(
|
|
340
|
+
data: DataSource,
|
|
341
|
+
mapping: TransformOptions['mapping'],
|
|
342
|
+
extraConfig: Partial<EChartsOption>
|
|
343
|
+
): EChartsOption {
|
|
344
|
+
const { nameField = 'name', valueField = 'value' } = mapping || {};
|
|
345
|
+
|
|
346
|
+
const indicators = (data.series || data.rows || []).map((item) => {
|
|
347
|
+
const record = item as Record<string, unknown>;
|
|
348
|
+
return {
|
|
349
|
+
name: String(record[nameField] || ''),
|
|
350
|
+
max: Math.max(Number(record[valueField]) || 100, 100),
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const values = (data.series || data.rows || []).map(
|
|
355
|
+
(item) => Number((item as Record<string, unknown>)[valueField]) || 0
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
radar: { indicator: indicators },
|
|
360
|
+
series: [{ type: 'radar', data: [{ value: values }] }],
|
|
361
|
+
...extraConfig,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function transformHeatmap(
|
|
366
|
+
data: DataSource,
|
|
367
|
+
mapping: TransformOptions['mapping'],
|
|
368
|
+
extraConfig: Partial<EChartsOption>
|
|
369
|
+
): EChartsOption {
|
|
370
|
+
const { xField = 'x', yField = 'y', valueField = 'value' } = mapping || {};
|
|
371
|
+
|
|
372
|
+
const xCategories = [
|
|
373
|
+
...new Set(
|
|
374
|
+
(data.series || data.rows || []).map((d) => String((d as Record<string, unknown>)[xField]))
|
|
375
|
+
),
|
|
376
|
+
];
|
|
377
|
+
const yCategories = [
|
|
378
|
+
...new Set(
|
|
379
|
+
(data.series || data.rows || []).map((d) => String((d as Record<string, unknown>)[yField]))
|
|
380
|
+
),
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
const seriesData = (data.series || data.rows || []).map((item) => {
|
|
384
|
+
const record = item as Record<string, unknown>;
|
|
385
|
+
const xIndex = xCategories.indexOf(String(record[xField]));
|
|
386
|
+
const yIndex = yCategories.indexOf(String(record[yField]));
|
|
387
|
+
return [xIndex, yIndex, Number(record[valueField]) || 0];
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
xAxis: { type: 'category', data: xCategories },
|
|
392
|
+
yAxis: { type: 'category', data: yCategories },
|
|
393
|
+
visualMap: { min: 0, calculable: true },
|
|
394
|
+
series: [{ type: 'heatmap', data: seriesData }],
|
|
395
|
+
...extraConfig,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function groupByTime(
|
|
400
|
+
data: DataItem[],
|
|
401
|
+
dateField: string,
|
|
402
|
+
period: TimePeriod
|
|
403
|
+
): Record<string, DataItem[]> {
|
|
404
|
+
return data.reduce(
|
|
405
|
+
(acc, item) => {
|
|
406
|
+
const date = new Date(String((item as Record<string, unknown>)[dateField]));
|
|
407
|
+
let key: string;
|
|
408
|
+
|
|
409
|
+
switch (period) {
|
|
410
|
+
case 'day':
|
|
411
|
+
key = date.toISOString().split('T')[0];
|
|
412
|
+
break;
|
|
413
|
+
case 'week': {
|
|
414
|
+
const week = getWeekNumber(date);
|
|
415
|
+
key = `${date.getFullYear()}-W${week}`;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
case 'month':
|
|
419
|
+
key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
|
420
|
+
break;
|
|
421
|
+
case 'quarter':
|
|
422
|
+
key = `${date.getFullYear()}-Q${Math.ceil((date.getMonth() + 1) / 3)}`;
|
|
423
|
+
break;
|
|
424
|
+
case 'year':
|
|
425
|
+
key = String(date.getFullYear());
|
|
426
|
+
break;
|
|
427
|
+
default:
|
|
428
|
+
key = date.toISOString().split('T')[0];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (!acc[key]) acc[key] = [];
|
|
432
|
+
acc[key].push(item);
|
|
433
|
+
return acc;
|
|
434
|
+
},
|
|
435
|
+
{} as Record<string, DataItem[]>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function getWeekNumber(date: Date): number {
|
|
440
|
+
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
441
|
+
const dayNum = d.getUTCDay() || 7;
|
|
442
|
+
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
|
443
|
+
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
444
|
+
return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function aggregateValues(
|
|
448
|
+
items: DataItem[],
|
|
449
|
+
field: string,
|
|
450
|
+
method: AggregationType,
|
|
451
|
+
fillMissing?: 'zero' | 'forward' | 'interpolate'
|
|
452
|
+
): number {
|
|
453
|
+
if (items.length === 0) {
|
|
454
|
+
if (fillMissing === 'zero') return 0;
|
|
455
|
+
return NaN;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const values = items.map((item) => Number((item as Record<string, unknown>)[field]) || 0);
|
|
459
|
+
|
|
460
|
+
switch (method) {
|
|
461
|
+
case 'sum': {
|
|
462
|
+
let sum = 0;
|
|
463
|
+
for (let i = 0; i < values.length; i++) sum += values[i];
|
|
464
|
+
return sum;
|
|
465
|
+
}
|
|
466
|
+
case 'average': {
|
|
467
|
+
if (values.length === 0) return 0;
|
|
468
|
+
let sum = 0;
|
|
469
|
+
for (let i = 0; i < values.length; i++) sum += values[i];
|
|
470
|
+
return sum / values.length;
|
|
471
|
+
}
|
|
472
|
+
case 'max': {
|
|
473
|
+
let max = values[0];
|
|
474
|
+
for (let i = 1; i < values.length; i++) if (values[i] > max) max = values[i];
|
|
475
|
+
return max;
|
|
476
|
+
}
|
|
477
|
+
case 'min': {
|
|
478
|
+
let min = values[0];
|
|
479
|
+
for (let i = 1; i < values.length; i++) if (values[i] < min) min = values[i];
|
|
480
|
+
return min;
|
|
481
|
+
}
|
|
482
|
+
case 'count':
|
|
483
|
+
return values.length;
|
|
484
|
+
case 'first':
|
|
485
|
+
return values[0];
|
|
486
|
+
case 'last':
|
|
487
|
+
return values[values.length - 1];
|
|
488
|
+
default:
|
|
489
|
+
return values[0];
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ============================================================================
|
|
494
|
+
// 导出
|
|
495
|
+
// ============================================================================
|
|
496
|
+
|
|
497
|
+
export const useTransform = useDataTransform;
|
|
498
|
+
|
|
499
|
+
export default {
|
|
500
|
+
useDataTransform,
|
|
501
|
+
useTableTransform,
|
|
502
|
+
useTimeSeriesTransform,
|
|
503
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TaroViz - 基于 Taro 和 ECharts 的多端图表组件库
|
|
3
|
-
* @version 1.
|
|
3
|
+
* @version 1.6.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// 核心组件
|
|
@@ -45,11 +45,20 @@ export { default as HeatmapChart } from './charts/heatmap';
|
|
|
45
45
|
export { default as GaugeChart } from './charts/gauge';
|
|
46
46
|
export { default as FunnelChart } from './charts/funnel';
|
|
47
47
|
|
|
48
|
-
// 扩展图表组件
|
|
48
|
+
// 扩展图表组件
|
|
49
49
|
export { default as TreeMapChart } from './charts/treemap';
|
|
50
50
|
export { default as SunburstChart } from './charts/sunburst';
|
|
51
51
|
export { default as SankeyChart } from './charts/sankey';
|
|
52
52
|
|
|
53
|
+
// 新增图表组件
|
|
54
|
+
export { default as GraphChart } from './charts/graph';
|
|
55
|
+
export { default as CandlestickChart } from './charts/candlestick';
|
|
56
|
+
export { default as WordCloudChart } from './charts/wordcloud';
|
|
57
|
+
|
|
58
|
+
// v1.6.0 新增图表组件
|
|
59
|
+
export { default as BoxplotChart } from './charts/boxplot';
|
|
60
|
+
export { default as ParallelChart } from './charts/parallel';
|
|
61
|
+
|
|
53
62
|
// 适配器
|
|
54
63
|
export { getAdapter, detectPlatform, getEnv } from './adapters';
|
|
55
64
|
export { default as H5Adapter } from './adapters/h5';
|
|
@@ -70,7 +79,7 @@ export {
|
|
|
70
79
|
getThemesByTag,
|
|
71
80
|
} from './themes';
|
|
72
81
|
|
|
73
|
-
// 主题管理器
|
|
82
|
+
// 主题管理器
|
|
74
83
|
export {
|
|
75
84
|
themeManager,
|
|
76
85
|
PRESET_THEMES,
|
|
@@ -81,11 +90,17 @@ export {
|
|
|
81
90
|
|
|
82
91
|
// 编辑器
|
|
83
92
|
export { ThemeEditor } from './editor';
|
|
93
|
+
export { default as EnhancedThemeEditor } from './editor/EnhancedThemeEditor';
|
|
94
|
+
export type { EnhancedThemeEditorProps, ThemeExportOptions } from './editor/EnhancedThemeEditor';
|
|
84
95
|
|
|
85
|
-
// 错误边界组件
|
|
86
|
-
export {
|
|
96
|
+
// 错误边界组件
|
|
97
|
+
export {
|
|
98
|
+
ErrorBoundary,
|
|
99
|
+
withErrorBoundary,
|
|
100
|
+
type ErrorBoundaryProps,
|
|
101
|
+
} from './core/components/ErrorBoundary';
|
|
87
102
|
|
|
88
|
-
// 懒加载组件
|
|
103
|
+
// 懒加载组件
|
|
89
104
|
export {
|
|
90
105
|
withLazyLoad,
|
|
91
106
|
preloadChart,
|
|
@@ -94,7 +109,7 @@ export {
|
|
|
94
109
|
LazyChartRegistry,
|
|
95
110
|
} from './core/components/LazyChart';
|
|
96
111
|
|
|
97
|
-
// 标注系统
|
|
112
|
+
// 标注系统
|
|
98
113
|
export {
|
|
99
114
|
useAnnotation,
|
|
100
115
|
convertAnnotationToMarkLine,
|
|
@@ -109,7 +124,7 @@ export {
|
|
|
109
124
|
type ScatterAnnotationConfig,
|
|
110
125
|
} from './core/components/Annotation';
|
|
111
126
|
|
|
112
|
-
// 导出工具
|
|
127
|
+
// 导出工具
|
|
113
128
|
export {
|
|
114
129
|
exportChart,
|
|
115
130
|
type ExportImageOptions,
|
|
@@ -134,10 +149,13 @@ export {
|
|
|
134
149
|
useFullscreen,
|
|
135
150
|
useExport,
|
|
136
151
|
useChartTools,
|
|
152
|
+
useDataTransform,
|
|
153
|
+
useTableTransform,
|
|
154
|
+
useTimeSeriesTransform,
|
|
137
155
|
} from './hooks';
|
|
138
156
|
|
|
139
157
|
/**
|
|
140
158
|
* 库信息
|
|
141
159
|
*/
|
|
142
160
|
export const name = 'taroviz';
|
|
143
|
-
export const version = '1.
|
|
161
|
+
export const version = '1.6.0';
|
package/src/main.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { EChartsType } from './core/types';
|
|
5
5
|
|
|
6
6
|
import { BaseChart, LineChart, BarChart, PieChart } from './index';
|
|
7
7
|
|
|
8
|
-
// Type assertion helper
|
|
9
|
-
const asChartOptions = <T extends Record<string, any>>(options: T):
|
|
10
|
-
return options
|
|
8
|
+
// Type assertion helper - use any to bypass complex type mismatches
|
|
9
|
+
const asChartOptions = <T extends Record<string, any>>(options: T): any => {
|
|
10
|
+
return options;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// Example data for various chart types
|
|
@@ -83,8 +83,8 @@ describe('Theme System', () => {
|
|
|
83
83
|
expect(darkTheme).toHaveProperty('colors');
|
|
84
84
|
expect(Array.isArray(darkTheme.colors)).toBe(true);
|
|
85
85
|
expect(darkTheme.colors).toHaveLength(9);
|
|
86
|
-
expect(darkTheme).toHaveProperty('backgroundColor', '#
|
|
87
|
-
expect(darkTheme).toHaveProperty('textColor', '#
|
|
86
|
+
expect(darkTheme).toHaveProperty('backgroundColor', '#1a1a2e');
|
|
87
|
+
expect(darkTheme).toHaveProperty('textColor', '#e8e8e8');
|
|
88
88
|
expect(darkTheme).toHaveProperty('fontFamily');
|
|
89
89
|
});
|
|
90
90
|
});
|
package/src/themes/index.ts
CHANGED
|
@@ -703,6 +703,18 @@ export function unregisterTheme(name: string): void {
|
|
|
703
703
|
themeRegistry.delete(name);
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
/**
|
|
707
|
+
* 重置主题注册表(清除所有已注册的主题,恢复内置主题)
|
|
708
|
+
* 主要用于测试环境
|
|
709
|
+
*/
|
|
710
|
+
export function resetThemeRegistry(): void {
|
|
711
|
+
themeRegistry.clear();
|
|
712
|
+
// 重新注册所有内置主题
|
|
713
|
+
Object.entries(builtinThemes).forEach(([name, theme]) => {
|
|
714
|
+
themeRegistry.set(name, theme as ThemeOptions);
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
706
718
|
/**
|
|
707
719
|
* 动态切换主题
|
|
708
720
|
* @param theme 主题名称或主题配置
|
|
@@ -853,6 +865,7 @@ export default {
|
|
|
853
865
|
getRegisteredThemes,
|
|
854
866
|
getThemeByName,
|
|
855
867
|
unregisterTheme,
|
|
868
|
+
resetThemeRegistry,
|
|
856
869
|
switchTheme,
|
|
857
870
|
getThemesByTag,
|
|
858
871
|
getLightThemes,
|