import pandas as pd import matplotlib.pyplot as plt from datetime import datetime import tkinter as tk from tkinter import filedialog import os import matplotlib.dates as mdates from jinja2 import Template from matplotlib import font_manager, rcParams class TemperatureDataAnalyzer: def __init__(self): self.data = None self.file_path = None self.timestamps = [] self.temperatures = [] self.statuses = [] self._configure_chinese_font() # 配置中文字体,修复中文字符缺失警告 def _configure_chinese_font(self): """ 配置 Matplotlib 中文字体,避免中文字符缺失的警告。 会尝试常见的中文字体并设置 axes.unicode_minus 为 False。 """ try: # 常见中文字体候选(跨平台) candidates = [ "Microsoft YaHei", "Microsoft YaHei UI", # Windows "SimHei", "SimSun", # Windows(黑体/宋体) "PingFang SC", "Heiti SC", # macOS "Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Micro Hei", # Linux "Arial Unicode MS" # 覆盖广的 Unicode 字体 ] available = {f.name for f in font_manager.fontManager.ttflist} for name in candidates: if name in available: rcParams["font.sans-serif"] = [name] rcParams["axes.unicode_minus"] = False # 可选:打印使用的字体名称 # print(f"使用中文字体: {name}") return # 如果没有找到常见中文字体,给出提示 rcParams["axes.unicode_minus"] = False print("未检测到常见中文字体,图中中文可能无法正常显示。建议安装 'Noto Sans CJK SC' 或 'Microsoft YaHei'。") except Exception as e: print(f"中文字体配置失败: {e}") def select_file(self): """手动选择CSV文件""" root = tk.Tk() root.withdraw() # 隐藏主窗口 file_types = [("CSV files", "*.csv"), ("All files", "*.*")] self.file_path = filedialog.askopenfilename(title="选择温度数据CSV文件", filetypes=file_types) if not self.file_path: print("未选择文件,程序退出") return False return True def load_and_process_data(self): """加载和处理数据""" try: # 读取CSV文件,无表头 self.data = pd.read_csv(self.file_path, header=None) # 重命名列以便于引用 self.data.columns = ['timestamp', 'temperature', 'status'] # 转换时间戳格式(文本例如:10/29/2025 2:20:41 PM) self.data['datetime'] = pd.to_datetime(self.data['timestamp'], format='%m/%d/%Y %I:%M:%S %p') # 提取处理后的数据 self.timestamps = self.data['datetime'] self.temperatures = self.data['temperature'] self.statuses = self.data['status'] print(f"成功加载 {len(self.data)} 条记录") return True except Exception as e: print(f"数据处理错误: {e}") return False def create_scatter_plots(self): """创建散点图""" fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10)) # 温度散点图 sc1 = ax1.scatter(self.timestamps, self.temperatures, c=self.temperatures, cmap='coolwarm', alpha=0.7, s=20) ax1.set_title('温度随时间变化趋势') ax1.set_ylabel('温度 (°C)') ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax1.grid(True, linestyle='--', alpha=0.7) ax1.tick_params(axis='x', rotation=45) plt.colorbar(sc1, ax=ax1, label="温度(°C)") # 状态散点图 sc2 = ax2.scatter(self.timestamps, self.statuses, c=self.statuses, cmap='viridis', alpha=0.7, s=20) ax2.set_title('状态随时间变化') ax2.set_xlabel('时间') ax2.set_ylabel('状态值') ax2.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax2.grid(True, linestyle='--', alpha=0.7) ax2.tick_params(axis='x', rotation=45) plt.colorbar(sc2, ax=ax2, label="状态值") plt.tight_layout() return fig def generate_statistics_report(self): """生成统计报告""" stats = { 'total_records': len(self.temperatures), 'avg_temperature': round(self.temperatures.mean(), 2), 'max_temperature': round(self.temperatures.max(), 2), 'min_temperature': round(self.temperatures.min(), 2), 'std_deviation': round(self.temperatures.std(), 2), 'temp_range': round(self.temperatures.max() - self.temperatures.min(), 2), 'start_time': self.timestamps.iloc[0].strftime('%Y-%m-%d %H:%M:%S'), 'end_time': self.timestamps.iloc[-1].strftime('%Y-%m-%d %H:%M:%S'), 'duration_hours': round((self.timestamps.iloc[-1] - self.timestamps.iloc[0]).total_seconds() / 3600, 2) } # 状态分布统计 status_counts = self.statuses.value_counts().to_dict() stats['status_distribution'] = status_counts return stats def save_fig_to_html(self, fig, output_path): """将图形保存为HTML""" import io import base64 # 将图形转换为base64编码 buf = io.BytesIO() fig.savefig(buf, format='png', dpi=150, bbox_inches='tight') buf.seek(0) img_str = base64.b64encode(buf.read()).decode('utf-8') buf.close() # HTML模板(修复了多余的 '}') html_template = """
数据文件: {{ file_name }}
生成时间: {{ generation_time }}
| 项目 | 数值 |
|---|---|
| {{ key.replace('_', ' ').title() }} | {{ value }} |
| 状态值 | 出现次数 |
|---|---|
| {{ status }} | {{ count }} |