staran 1.0.9__py3-none-any.whl → 1.0.10__py3-none-any.whl

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.
@@ -0,0 +1,689 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Staran 数据可视化集成模块 v1.0.10
6
+ ==============================
7
+
8
+ 提供与主流图表库的集成支持,用于日期数据的可视化。
9
+
10
+ 主要功能:
11
+ - 日期时间轴生成
12
+ - 日期分布图数据
13
+ - 时间序列图表数据
14
+ - 日历热力图数据
15
+ - 多种图表库适配器
16
+ """
17
+
18
+ import datetime
19
+ import json
20
+ from typing import Dict, List, Tuple, Optional, Union, Any
21
+ from dataclasses import dataclass, asdict
22
+ from collections import defaultdict, Counter
23
+
24
+ @dataclass
25
+ class ChartData:
26
+ """图表数据类"""
27
+ chart_type: str
28
+ title: str
29
+ data: List[Dict[str, Any]]
30
+ config: Dict[str, Any]
31
+ library: str # 目标图表库
32
+
33
+ @dataclass
34
+ class TimeSeriesPoint:
35
+ """时间序列数据点"""
36
+ date: datetime.date
37
+ value: Union[int, float]
38
+ label: Optional[str] = None
39
+ category: Optional[str] = None
40
+
41
+ class DateVisualization:
42
+ """日期数据可视化类"""
43
+
44
+ def __init__(self):
45
+ self.supported_libraries = ['echarts', 'matplotlib', 'plotly', 'chartjs', 'highcharts']
46
+
47
+ def create_timeline_data(self,
48
+ dates: List[datetime.date],
49
+ events: List[str],
50
+ library: str = 'echarts') -> ChartData:
51
+ """创建时间轴数据"""
52
+ if library not in self.supported_libraries:
53
+ raise ValueError(f"不支持的图表库: {library}")
54
+
55
+ if library == 'echarts':
56
+ return self._create_echarts_timeline(dates, events)
57
+ elif library == 'matplotlib':
58
+ return self._create_matplotlib_timeline(dates, events)
59
+ elif library == 'plotly':
60
+ return self._create_plotly_timeline(dates, events)
61
+ elif library == 'chartjs':
62
+ return self._create_chartjs_timeline(dates, events)
63
+ elif library == 'highcharts':
64
+ return self._create_highcharts_timeline(dates, events)
65
+
66
+ def create_calendar_heatmap(self,
67
+ date_values: Dict[datetime.date, float],
68
+ year: int,
69
+ library: str = 'echarts') -> ChartData:
70
+ """创建日历热力图数据"""
71
+ if library == 'echarts':
72
+ return self._create_echarts_calendar_heatmap(date_values, year)
73
+ elif library == 'matplotlib':
74
+ return self._create_matplotlib_calendar_heatmap(date_values, year)
75
+ elif library == 'plotly':
76
+ return self._create_plotly_calendar_heatmap(date_values, year)
77
+ else:
78
+ raise ValueError(f"日历热力图暂不支持 {library}")
79
+
80
+ def create_time_series_chart(self,
81
+ time_series: List[TimeSeriesPoint],
82
+ library: str = 'echarts') -> ChartData:
83
+ """创建时间序列图表数据"""
84
+ if library == 'echarts':
85
+ return self._create_echarts_time_series(time_series)
86
+ elif library == 'matplotlib':
87
+ return self._create_matplotlib_time_series(time_series)
88
+ elif library == 'plotly':
89
+ return self._create_plotly_time_series(time_series)
90
+ elif library == 'chartjs':
91
+ return self._create_chartjs_time_series(time_series)
92
+ elif library == 'highcharts':
93
+ return self._create_highcharts_time_series(time_series)
94
+
95
+ def create_date_distribution_chart(self,
96
+ dates: List[datetime.date],
97
+ group_by: str = 'month',
98
+ library: str = 'echarts') -> ChartData:
99
+ """创建日期分布图表数据"""
100
+ distribution = self._calculate_date_distribution(dates, group_by)
101
+
102
+ if library == 'echarts':
103
+ return self._create_echarts_distribution(distribution, group_by)
104
+ elif library == 'matplotlib':
105
+ return self._create_matplotlib_distribution(distribution, group_by)
106
+ elif library == 'plotly':
107
+ return self._create_plotly_distribution(distribution, group_by)
108
+ elif library == 'chartjs':
109
+ return self._create_chartjs_distribution(distribution, group_by)
110
+
111
+ def create_lunar_calendar_view(self,
112
+ year: int,
113
+ library: str = 'echarts') -> ChartData:
114
+ """创建农历日历视图数据"""
115
+ from .lunar import LunarDate
116
+
117
+ lunar_data = []
118
+ start_date = datetime.date(year, 1, 1)
119
+ end_date = datetime.date(year, 12, 31)
120
+
121
+ current_date = start_date
122
+ while current_date <= end_date:
123
+ try:
124
+ # 这里需要实现公历转农历的逻辑
125
+ lunar_info = {
126
+ 'date': current_date.strftime('%Y-%m-%d'),
127
+ 'solar_date': current_date.strftime('%m-%d'),
128
+ 'lunar_date': f"农历{current_date.month}-{current_date.day}", # 简化
129
+ 'is_festival': False, # 需要实现节日判断
130
+ 'is_solar_term': False # 需要实现节气判断
131
+ }
132
+ lunar_data.append(lunar_info)
133
+ except:
134
+ pass
135
+ current_date += datetime.timedelta(days=1)
136
+
137
+ return ChartData(
138
+ chart_type='lunar_calendar',
139
+ title=f'{year}年农历日历',
140
+ data=lunar_data,
141
+ config={'year': year},
142
+ library=library
143
+ )
144
+
145
+ def create_solar_terms_chart(self,
146
+ year: int,
147
+ library: str = 'echarts') -> ChartData:
148
+ """创建二十四节气图表数据"""
149
+ from .solar_terms import SolarTerms
150
+
151
+ solar_terms = SolarTerms.get_all_solar_terms(year)
152
+
153
+ if library == 'echarts':
154
+ data = []
155
+ for term in solar_terms:
156
+ data.append({
157
+ 'name': term.name,
158
+ 'date': term.date.strftime('%Y-%m-%d'),
159
+ 'season': term.season,
160
+ 'description': term.description,
161
+ 'month': term.date.month,
162
+ 'day': term.date.day
163
+ })
164
+
165
+ return ChartData(
166
+ chart_type='solar_terms',
167
+ title=f'{year}年二十四节气',
168
+ data=data,
169
+ config={
170
+ 'type': 'timeline',
171
+ 'year': year,
172
+ 'seasons': ['春季', '夏季', '秋季', '冬季']
173
+ },
174
+ library=library
175
+ )
176
+
177
+ # ECharts 适配器方法
178
+ def _create_echarts_timeline(self, dates: List[datetime.date], events: List[str]) -> ChartData:
179
+ """创建 ECharts 时间轴数据"""
180
+ data = []
181
+ for date, event in zip(dates, events):
182
+ data.append({
183
+ 'name': event,
184
+ 'value': date.strftime('%Y-%m-%d'),
185
+ 'symbol': 'circle',
186
+ 'symbolSize': 10
187
+ })
188
+
189
+ config = {
190
+ 'type': 'timeline',
191
+ 'xAxis': {'type': 'time'},
192
+ 'yAxis': {'type': 'category', 'data': events},
193
+ 'series': [{'type': 'scatter', 'data': data}]
194
+ }
195
+
196
+ return ChartData(
197
+ chart_type='timeline',
198
+ title='时间轴图表',
199
+ data=data,
200
+ config=config,
201
+ library='echarts'
202
+ )
203
+
204
+ def _create_echarts_calendar_heatmap(self, date_values: Dict[datetime.date, float], year: int) -> ChartData:
205
+ """创建 ECharts 日历热力图数据"""
206
+ data = []
207
+ for date, value in date_values.items():
208
+ if date.year == year:
209
+ data.append([date.strftime('%Y-%m-%d'), value])
210
+
211
+ config = {
212
+ 'type': 'heatmap',
213
+ 'calendar': {
214
+ 'range': str(year),
215
+ 'cellSize': ['auto', 20]
216
+ },
217
+ 'series': [{
218
+ 'type': 'heatmap',
219
+ 'coordinateSystem': 'calendar',
220
+ 'data': data
221
+ }]
222
+ }
223
+
224
+ return ChartData(
225
+ chart_type='calendar_heatmap',
226
+ title=f'{year}年日历热力图',
227
+ data=data,
228
+ config=config,
229
+ library='echarts'
230
+ )
231
+
232
+ def _create_echarts_time_series(self, time_series: List[TimeSeriesPoint]) -> ChartData:
233
+ """创建 ECharts 时间序列图表数据"""
234
+ data = []
235
+ categories = set()
236
+
237
+ for point in time_series:
238
+ data.append([
239
+ point.date.strftime('%Y-%m-%d'),
240
+ point.value,
241
+ point.category or 'default'
242
+ ])
243
+ if point.category:
244
+ categories.add(point.category)
245
+
246
+ config = {
247
+ 'type': 'line',
248
+ 'xAxis': {'type': 'time'},
249
+ 'yAxis': {'type': 'value'},
250
+ 'series': [{'type': 'line', 'data': data}]
251
+ }
252
+
253
+ return ChartData(
254
+ chart_type='time_series',
255
+ title='时间序列图表',
256
+ data=data,
257
+ config=config,
258
+ library='echarts'
259
+ )
260
+
261
+ def _create_echarts_distribution(self, distribution: Dict[str, int], group_by: str) -> ChartData:
262
+ """创建 ECharts 分布图表数据"""
263
+ categories = list(distribution.keys())
264
+ values = list(distribution.values())
265
+
266
+ data = [{'name': cat, 'value': val} for cat, val in distribution.items()]
267
+
268
+ config = {
269
+ 'type': 'bar',
270
+ 'xAxis': {'type': 'category', 'data': categories},
271
+ 'yAxis': {'type': 'value'},
272
+ 'series': [{'type': 'bar', 'data': values}]
273
+ }
274
+
275
+ return ChartData(
276
+ chart_type='distribution',
277
+ title=f'日期{group_by}分布图',
278
+ data=data,
279
+ config=config,
280
+ library='echarts'
281
+ )
282
+
283
+ # Matplotlib 适配器方法
284
+ def _create_matplotlib_timeline(self, dates: List[datetime.date], events: List[str]) -> ChartData:
285
+ """创建 Matplotlib 时间轴数据"""
286
+ data = []
287
+ for i, (date, event) in enumerate(zip(dates, events)):
288
+ data.append({
289
+ 'x': date.strftime('%Y-%m-%d'),
290
+ 'y': i,
291
+ 'label': event
292
+ })
293
+
294
+ config = {
295
+ 'type': 'scatter',
296
+ 'figsize': (12, 8),
297
+ 'xlabel': 'Date',
298
+ 'ylabel': 'Events',
299
+ 'title': 'Timeline Chart'
300
+ }
301
+
302
+ return ChartData(
303
+ chart_type='timeline',
304
+ title='时间轴图表',
305
+ data=data,
306
+ config=config,
307
+ library='matplotlib'
308
+ )
309
+
310
+ def _create_matplotlib_calendar_heatmap(self, date_values: Dict[datetime.date, float], year: int) -> ChartData:
311
+ """创建 Matplotlib 日历热力图数据"""
312
+ # 创建日历网格数据
313
+ import calendar
314
+
315
+ data = []
316
+ for month in range(1, 13):
317
+ month_cal = calendar.monthcalendar(year, month)
318
+ for week_num, week in enumerate(month_cal):
319
+ for day_num, day in enumerate(week):
320
+ if day != 0:
321
+ date = datetime.date(year, month, day)
322
+ value = date_values.get(date, 0)
323
+ data.append({
324
+ 'month': month,
325
+ 'week': week_num,
326
+ 'day': day_num,
327
+ 'value': value,
328
+ 'date': date.strftime('%Y-%m-%d')
329
+ })
330
+
331
+ config = {
332
+ 'type': 'imshow',
333
+ 'figsize': (15, 10),
334
+ 'cmap': 'YlOrRd',
335
+ 'title': f'{year}年日历热力图'
336
+ }
337
+
338
+ return ChartData(
339
+ chart_type='calendar_heatmap',
340
+ title=f'{year}年日历热力图',
341
+ data=data,
342
+ config=config,
343
+ library='matplotlib'
344
+ )
345
+
346
+ def _create_matplotlib_time_series(self, time_series: List[TimeSeriesPoint]) -> ChartData:
347
+ """创建 Matplotlib 时间序列图表数据"""
348
+ data = []
349
+ for point in time_series:
350
+ data.append({
351
+ 'x': point.date.strftime('%Y-%m-%d'),
352
+ 'y': point.value,
353
+ 'label': point.label or '',
354
+ 'category': point.category or 'default'
355
+ })
356
+
357
+ config = {
358
+ 'type': 'plot',
359
+ 'figsize': (12, 6),
360
+ 'xlabel': 'Date',
361
+ 'ylabel': 'Value',
362
+ 'title': 'Time Series Chart'
363
+ }
364
+
365
+ return ChartData(
366
+ chart_type='time_series',
367
+ title='时间序列图表',
368
+ data=data,
369
+ config=config,
370
+ library='matplotlib'
371
+ )
372
+
373
+ def _create_matplotlib_distribution(self, distribution: Dict[str, int], group_by: str) -> ChartData:
374
+ """创建 Matplotlib 分布图表数据"""
375
+ categories = list(distribution.keys())
376
+ values = list(distribution.values())
377
+
378
+ data = [{'category': cat, 'value': val} for cat, val in distribution.items()]
379
+
380
+ config = {
381
+ 'type': 'bar',
382
+ 'figsize': (10, 6),
383
+ 'xlabel': group_by.title(),
384
+ 'ylabel': 'Count',
385
+ 'title': f'Date {group_by.title()} Distribution'
386
+ }
387
+
388
+ return ChartData(
389
+ chart_type='distribution',
390
+ title=f'日期{group_by}分布图',
391
+ data=data,
392
+ config=config,
393
+ library='matplotlib'
394
+ )
395
+
396
+ # Plotly 适配器方法
397
+ def _create_plotly_timeline(self, dates: List[datetime.date], events: List[str]) -> ChartData:
398
+ """创建 Plotly 时间轴数据"""
399
+ data = []
400
+ for date, event in zip(dates, events):
401
+ data.append({
402
+ 'x': date.strftime('%Y-%m-%d'),
403
+ 'y': event,
404
+ 'text': event,
405
+ 'mode': 'markers'
406
+ })
407
+
408
+ config = {
409
+ 'type': 'scatter',
410
+ 'title': 'Timeline Chart',
411
+ 'xaxis_title': 'Date',
412
+ 'yaxis_title': 'Events'
413
+ }
414
+
415
+ return ChartData(
416
+ chart_type='timeline',
417
+ title='时间轴图表',
418
+ data=data,
419
+ config=config,
420
+ library='plotly'
421
+ )
422
+
423
+ def _create_plotly_calendar_heatmap(self, date_values: Dict[datetime.date, float], year: int) -> ChartData:
424
+ """创建 Plotly 日历热力图数据"""
425
+ data = []
426
+ for date, value in date_values.items():
427
+ if date.year == year:
428
+ data.append({
429
+ 'date': date.strftime('%Y-%m-%d'),
430
+ 'value': value,
431
+ 'month': date.month,
432
+ 'day': date.day
433
+ })
434
+
435
+ config = {
436
+ 'type': 'heatmap',
437
+ 'title': f'{year}年日历热力图',
438
+ 'colorscale': 'Viridis'
439
+ }
440
+
441
+ return ChartData(
442
+ chart_type='calendar_heatmap',
443
+ title=f'{year}年日历热力图',
444
+ data=data,
445
+ config=config,
446
+ library='plotly'
447
+ )
448
+
449
+ def _create_plotly_time_series(self, time_series: List[TimeSeriesPoint]) -> ChartData:
450
+ """创建 Plotly 时间序列图表数据"""
451
+ data = []
452
+ for point in time_series:
453
+ data.append({
454
+ 'x': point.date.strftime('%Y-%m-%d'),
455
+ 'y': point.value,
456
+ 'text': point.label or '',
457
+ 'category': point.category or 'default'
458
+ })
459
+
460
+ config = {
461
+ 'type': 'scatter',
462
+ 'mode': 'lines+markers',
463
+ 'title': 'Time Series Chart',
464
+ 'xaxis_title': 'Date',
465
+ 'yaxis_title': 'Value'
466
+ }
467
+
468
+ return ChartData(
469
+ chart_type='time_series',
470
+ title='时间序列图表',
471
+ data=data,
472
+ config=config,
473
+ library='plotly'
474
+ )
475
+
476
+ def _create_plotly_distribution(self, distribution: Dict[str, int], group_by: str) -> ChartData:
477
+ """创建 Plotly 分布图表数据"""
478
+ categories = list(distribution.keys())
479
+ values = list(distribution.values())
480
+
481
+ data = [{'x': categories, 'y': values, 'type': 'bar'}]
482
+
483
+ config = {
484
+ 'type': 'bar',
485
+ 'title': f'Date {group_by.title()} Distribution',
486
+ 'xaxis_title': group_by.title(),
487
+ 'yaxis_title': 'Count'
488
+ }
489
+
490
+ return ChartData(
491
+ chart_type='distribution',
492
+ title=f'日期{group_by}分布图',
493
+ data=data,
494
+ config=config,
495
+ library='plotly'
496
+ )
497
+
498
+ # Chart.js 适配器方法
499
+ def _create_chartjs_timeline(self, dates: List[datetime.date], events: List[str]) -> ChartData:
500
+ """创建 Chart.js 时间轴数据"""
501
+ data = []
502
+ for date, event in zip(dates, events):
503
+ data.append({
504
+ 'x': date.strftime('%Y-%m-%d'),
505
+ 'y': event,
506
+ 'label': event
507
+ })
508
+
509
+ config = {
510
+ 'type': 'scatter',
511
+ 'options': {
512
+ 'scales': {
513
+ 'x': {'type': 'time'},
514
+ 'y': {'type': 'category'}
515
+ }
516
+ }
517
+ }
518
+
519
+ return ChartData(
520
+ chart_type='timeline',
521
+ title='时间轴图表',
522
+ data=data,
523
+ config=config,
524
+ library='chartjs'
525
+ )
526
+
527
+ def _create_chartjs_time_series(self, time_series: List[TimeSeriesPoint]) -> ChartData:
528
+ """创建 Chart.js 时间序列图表数据"""
529
+ data = []
530
+ for point in time_series:
531
+ data.append({
532
+ 'x': point.date.strftime('%Y-%m-%d'),
533
+ 'y': point.value
534
+ })
535
+
536
+ config = {
537
+ 'type': 'line',
538
+ 'options': {
539
+ 'scales': {
540
+ 'x': {'type': 'time'},
541
+ 'y': {'type': 'linear'}
542
+ }
543
+ }
544
+ }
545
+
546
+ return ChartData(
547
+ chart_type='time_series',
548
+ title='时间序列图表',
549
+ data=data,
550
+ config=config,
551
+ library='chartjs'
552
+ )
553
+
554
+ def _create_chartjs_distribution(self, distribution: Dict[str, int], group_by: str) -> ChartData:
555
+ """创建 Chart.js 分布图表数据"""
556
+ labels = list(distribution.keys())
557
+ values = list(distribution.values())
558
+
559
+ data = {
560
+ 'labels': labels,
561
+ 'datasets': [{
562
+ 'label': f'{group_by.title()} Distribution',
563
+ 'data': values,
564
+ 'backgroundColor': 'rgba(75, 192, 192, 0.6)'
565
+ }]
566
+ }
567
+
568
+ config = {
569
+ 'type': 'bar',
570
+ 'options': {
571
+ 'responsive': True,
572
+ 'plugins': {
573
+ 'title': {
574
+ 'display': True,
575
+ 'text': f'Date {group_by.title()} Distribution'
576
+ }
577
+ }
578
+ }
579
+ }
580
+
581
+ return ChartData(
582
+ chart_type='distribution',
583
+ title=f'日期{group_by}分布图',
584
+ data=data,
585
+ config=config,
586
+ library='chartjs'
587
+ )
588
+
589
+ # Highcharts 适配器方法
590
+ def _create_highcharts_timeline(self, dates: List[datetime.date], events: List[str]) -> ChartData:
591
+ """创建 Highcharts 时间轴数据"""
592
+ data = []
593
+ for date, event in zip(dates, events):
594
+ timestamp = int(date.strftime('%s')) * 1000 # Highcharts 使用毫秒时间戳
595
+ data.append([timestamp, event])
596
+
597
+ config = {
598
+ 'chart': {'type': 'timeline'},
599
+ 'title': {'text': 'Timeline Chart'},
600
+ 'series': [{'data': data}]
601
+ }
602
+
603
+ return ChartData(
604
+ chart_type='timeline',
605
+ title='时间轴图表',
606
+ data=data,
607
+ config=config,
608
+ library='highcharts'
609
+ )
610
+
611
+ def _create_highcharts_time_series(self, time_series: List[TimeSeriesPoint]) -> ChartData:
612
+ """创建 Highcharts 时间序列图表数据"""
613
+ data = []
614
+ for point in time_series:
615
+ timestamp = int(point.date.strftime('%s')) * 1000
616
+ data.append([timestamp, point.value])
617
+
618
+ config = {
619
+ 'chart': {'type': 'line'},
620
+ 'title': {'text': 'Time Series Chart'},
621
+ 'xAxis': {'type': 'datetime'},
622
+ 'series': [{'data': data}]
623
+ }
624
+
625
+ return ChartData(
626
+ chart_type='time_series',
627
+ title='时间序列图表',
628
+ data=data,
629
+ config=config,
630
+ library='highcharts'
631
+ )
632
+
633
+ # 辅助方法
634
+ def _calculate_date_distribution(self, dates: List[datetime.date], group_by: str) -> Dict[str, int]:
635
+ """计算日期分布"""
636
+ distribution = Counter()
637
+
638
+ for date in dates:
639
+ if group_by == 'month':
640
+ key = f"{date.month}月"
641
+ elif group_by == 'weekday':
642
+ weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
643
+ key = weekdays[date.weekday()]
644
+ elif group_by == 'year':
645
+ key = str(date.year)
646
+ elif group_by == 'quarter':
647
+ quarter = (date.month - 1) // 3 + 1
648
+ key = f"Q{quarter}"
649
+ else:
650
+ key = date.strftime('%Y-%m')
651
+
652
+ distribution[key] += 1
653
+
654
+ return dict(distribution)
655
+
656
+ def export_chart_data(self, chart_data: ChartData, format: str = 'json') -> str:
657
+ """导出图表数据"""
658
+ if format == 'json':
659
+ return json.dumps(asdict(chart_data), ensure_ascii=False, indent=2)
660
+ elif format == 'csv':
661
+ import csv
662
+ import io
663
+
664
+ output = io.StringIO()
665
+ if chart_data.data and isinstance(chart_data.data[0], dict):
666
+ fieldnames = chart_data.data[0].keys()
667
+ writer = csv.DictWriter(output, fieldnames=fieldnames)
668
+ writer.writeheader()
669
+ writer.writerows(chart_data.data)
670
+
671
+ return output.getvalue()
672
+ else:
673
+ raise ValueError("支持的格式: json, csv")
674
+
675
+ # 便捷函数
676
+ def create_timeline_chart(dates: List[datetime.date], events: List[str], library: str = 'echarts') -> ChartData:
677
+ """创建时间轴图表(便捷函数)"""
678
+ viz = DateVisualization()
679
+ return viz.create_timeline_data(dates, events, library)
680
+
681
+ def create_calendar_heatmap(date_values: Dict[datetime.date, float], year: int, library: str = 'echarts') -> ChartData:
682
+ """创建日历热力图(便捷函数)"""
683
+ viz = DateVisualization()
684
+ return viz.create_calendar_heatmap(date_values, year, library)
685
+
686
+ def create_time_series_chart(time_series: List[TimeSeriesPoint], library: str = 'echarts') -> ChartData:
687
+ """创建时间序列图表(便捷函数)"""
688
+ viz = DateVisualization()
689
+ return viz.create_time_series_chart(time_series, library)