statistics_panel.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. import os
  2. import random
  3. from datetime import datetime, timedelta
  4. from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSlider,
  5. QTabWidget, QGroupBox, QComboBox, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView)
  6. from PyQt5.QtCore import Qt, pyqtSlot, QTimer, QMargins
  7. from PyQt5.QtGui import QFont, QPainter, QPen, QColor
  8. from PyQt5.QtChart import QChart, QChartView, QLineSeries, QBarSet, QBarSeries, QPieSeries, QValueAxis, QDateTimeAxis, QBarCategoryAxis, QLegend
  9. class StatisticsPanel(QWidget):
  10. """统计面板组件,显示各类灾害检测统计信息和趋势图表"""
  11. def __init__(self, config, parent=None):
  12. super().__init__(parent)
  13. self.config = config
  14. # 初始化统计数据
  15. self.alert_stats = {
  16. 'fire': 0,
  17. 'animal': 0,
  18. 'landslide': 0,
  19. 'pest': 0
  20. }
  21. # 初始化区域统计数据为空字典
  22. self.region_stats = {}
  23. # 初始化区域名称映射字典
  24. self.region_mapping = {
  25. '北部山区': '东北区', # 假设北部山区属于东北区
  26. '南部林区': '东南区', # 假设南部林区属于东南区
  27. '东部山脊': '东北区', # 假设东部山脊属于东北区
  28. '西部谷地': '西北区', # 假设西部谷地属于西北区
  29. '西部林区': '西北区', # 假设西部林区属于西北区
  30. '中央林场': '中部区' # 假设中央林场属于中部区
  31. }
  32. # 模拟数据
  33. self.init_mock_data()
  34. # 初始化UI
  35. self.init_ui()
  36. # 创建并保存趋势图视图的引用
  37. self.trend_chart_view = self.create_trend_chart_view()
  38. # 设置自动更新定时器
  39. self.update_timer = QTimer(self)
  40. self.update_timer.timeout.connect(self.auto_update_statistics)
  41. self.update_timer.start(5000) # 每5秒更新一次统计数据
  42. def init_mock_data(self):
  43. """初始化模拟数据"""
  44. # 病虫害类型统计数据
  45. self.pest_type_stats = {
  46. '松材线虫': 0,
  47. '杨树食叶害虫': 0,
  48. '松毛虫': 0,
  49. '蚜虫': 0,
  50. '松墨天牛': 0,
  51. '落叶松针叶锈病': 0
  52. }
  53. # 初始化区域统计字典
  54. if not self.region_stats:
  55. self.region_stats = {
  56. '东北区': 0,
  57. '西北区': 0,
  58. '中部区': 0,
  59. '东南区': 0,
  60. '西南区': 0
  61. }
  62. # 模拟24小时数据
  63. self.hour_data = []
  64. now = datetime.now()
  65. for i in range(24):
  66. time_point = now - timedelta(hours=23-i)
  67. self.hour_data.append({
  68. 'time': time_point,
  69. 'fire': 0,
  70. 'animal': 0,
  71. 'landslide': 0,
  72. 'forest_degradation': 0,
  73. 'pest': 0
  74. })
  75. # 模拟7天数据
  76. self.day_data = []
  77. for i in range(7):
  78. time_point = now - timedelta(days=6-i)
  79. self.day_data.append({
  80. 'time': time_point,
  81. 'fire': 0,
  82. 'animal': 0,
  83. 'landslide': 0,
  84. 'forest_degradation': 0,
  85. 'pest': 0
  86. })
  87. # 模拟30天数据
  88. self.month_data = []
  89. for i in range(30):
  90. time_point = now - timedelta(days=29-i)
  91. self.month_data.append({
  92. 'time': time_point,
  93. 'fire': 0,
  94. 'animal': 0,
  95. 'landslide': 0,
  96. 'forest_degradation': 0,
  97. 'pest': 0
  98. })
  99. def init_ui(self):
  100. """初始化UI"""
  101. # 创建主布局
  102. layout = QVBoxLayout(self)
  103. layout.setContentsMargins(5, 5, 5, 5)
  104. # 创建标签页
  105. self.tab_widget = QTabWidget()
  106. # 概览标签页
  107. overview_tab = self.create_overview_tab()
  108. self.tab_widget.addTab(overview_tab, "概览")
  109. # 趋势图标签页
  110. trend_tab = self.create_trend_tab()
  111. self.tab_widget.addTab(trend_tab, "趋势图")
  112. # 区域统计标签页
  113. region_tab = self.create_region_tab()
  114. self.tab_widget.addTab(region_tab, "区域统计")
  115. # 添加标签页到布局
  116. layout.addWidget(self.tab_widget)
  117. # 底部工具栏
  118. toolbar = QHBoxLayout()
  119. # 时间范围下拉框
  120. self.time_range_combo = QComboBox()
  121. self.time_range_combo.addItems(["最近24小时", "最近7天", "最近30天", "本月", "本年"])
  122. self.time_range_combo.currentIndexChanged.connect(self.change_time_range)
  123. toolbar.addWidget(QLabel("时间范围:"))
  124. toolbar.addWidget(self.time_range_combo)
  125. # 添加刷新按钮
  126. refresh_btn = QPushButton("刷新")
  127. refresh_btn.clicked.connect(self.refresh_statistics)
  128. toolbar.addStretch(1)
  129. toolbar.addWidget(refresh_btn)
  130. # 添加工具栏到布局
  131. layout.addLayout(toolbar)
  132. # 设置最小高度
  133. self.setMinimumHeight(300)
  134. def create_overview_tab(self):
  135. """创建概览标签页"""
  136. tab = QWidget()
  137. tab.setObjectName("overview_tab") # 添加对象名称
  138. layout = QVBoxLayout(tab)
  139. # 告警统计
  140. stats_group = QGroupBox("告警统计")
  141. stats_group.setObjectName("stats_group") # 添加对象名称
  142. stats_layout = QHBoxLayout(stats_group)
  143. # 火灾告警
  144. fire_label = QLabel(f"火灾告警: {self.alert_stats['fire']}")
  145. fire_label.setObjectName("fire_label") # 添加对象名称
  146. fire_label.setStyleSheet("color: red;")
  147. stats_layout.addWidget(fire_label)
  148. # 动物告警
  149. animal_label = QLabel(f"动物告警: {self.alert_stats['animal']}")
  150. animal_label.setObjectName("animal_label") # 添加对象名称
  151. animal_label.setStyleSheet("color: green;")
  152. stats_layout.addWidget(animal_label)
  153. # 滑坡告警
  154. landslide_label = QLabel(f"滑坡告警: {self.alert_stats['landslide']}")
  155. landslide_label.setObjectName("landslide_label") # 添加对象名称
  156. landslide_label.setStyleSheet("color: blue;")
  157. stats_layout.addWidget(landslide_label)
  158. # 病虫害告警
  159. pest_label = QLabel(f"病虫害: {self.alert_stats['pest']}")
  160. pest_label.setObjectName("pest_label") # 添加对象名称
  161. pest_label.setStyleSheet("color: purple;")
  162. stats_layout.addWidget(pest_label)
  163. layout.addWidget(stats_group)
  164. # 饼图 - 使用create_pie_chart方法
  165. chart_view = self.create_pie_chart()
  166. chart_view.setObjectName("overview_pie_chart") # 添加对象名称
  167. layout.addWidget(chart_view)
  168. return tab
  169. def create_trend_tab(self):
  170. """创建趋势图标签页"""
  171. tab = QWidget()
  172. tab.setObjectName("trend_tab") # 添加对象名称
  173. layout = QVBoxLayout(tab)
  174. # 24小时趋势图
  175. trend_chart = QChart()
  176. trend_chart.setTitle("24小时告警趋势")
  177. trend_chart.setAnimationOptions(QChart.SeriesAnimations)
  178. trend_chart.setBackgroundBrush(QColor("#0a1a2a"))
  179. trend_chart.setTitleBrush(QColor("white"))
  180. trend_chart.setTitleFont(QFont("Microsoft YaHei", 10, QFont.Bold))
  181. # 创建折线系列 - 火灾
  182. fire_series = QLineSeries()
  183. fire_series.setName("火灾告警")
  184. fire_series.setColor(QColor(255, 100, 100))
  185. # 创建折线系列 - 动物
  186. animal_series = QLineSeries()
  187. animal_series.setName("动物告警")
  188. animal_series.setColor(QColor(100, 255, 100))
  189. # 创建折线系列 - 滑坡
  190. landslide_series = QLineSeries()
  191. landslide_series.setName("滑坡告警")
  192. landslide_series.setColor(QColor(100, 100, 255))
  193. # 创建折线系列 - 病虫害
  194. pest_series = QLineSeries()
  195. pest_series.setName("病虫害告警")
  196. pest_series.setColor(QColor(180, 100, 200))
  197. # 添加数据点
  198. for i, data in enumerate(self.hour_data):
  199. timestamp = data['time'].timestamp() * 1000 # 转换为毫秒
  200. fire_series.append(timestamp, data['fire'])
  201. animal_series.append(timestamp, data['animal'])
  202. landslide_series.append(timestamp, data['landslide'])
  203. pest_series.append(timestamp, data['pest'])
  204. # 添加系列到图表
  205. trend_chart.addSeries(fire_series)
  206. trend_chart.addSeries(animal_series)
  207. trend_chart.addSeries(landslide_series)
  208. trend_chart.addSeries(pest_series)
  209. # 创建X轴(时间轴)
  210. axis_x = QDateTimeAxis()
  211. axis_x.setFormat("HH:mm")
  212. axis_x.setTitleText("时间")
  213. axis_x.setTickCount(8) # 显示8个刻度
  214. axis_x.setRange(
  215. self.hour_data[0]['time'],
  216. self.hour_data[-1]['time']
  217. )
  218. axis_x.setTitleBrush(QColor("white"))
  219. axis_x.setLabelsColor(QColor("white"))
  220. # 创建Y轴
  221. axis_y = QValueAxis()
  222. axis_y.setLabelFormat("%d")
  223. axis_y.setTitleText("告警数量")
  224. axis_y.setRange(0, 15) # 增大Y轴范围,避免文字被裁剪
  225. axis_y.setTickCount(6) # 增加刻度数量以更好显示
  226. axis_y.setTitleBrush(QColor("white"))
  227. axis_y.setLabelsColor(QColor("white"))
  228. # 添加坐标轴到图表
  229. trend_chart.addAxis(axis_x, Qt.AlignBottom)
  230. trend_chart.addAxis(axis_y, Qt.AlignLeft)
  231. # 将所有系列依附到坐标轴
  232. fire_series.attachAxis(axis_x)
  233. fire_series.attachAxis(axis_y)
  234. animal_series.attachAxis(axis_x)
  235. animal_series.attachAxis(axis_y)
  236. landslide_series.attachAxis(axis_x)
  237. landslide_series.attachAxis(axis_y)
  238. pest_series.attachAxis(axis_x)
  239. pest_series.attachAxis(axis_y)
  240. # 设置图例位置和样式
  241. trend_chart.legend().setVisible(True)
  242. trend_chart.legend().setAlignment(Qt.AlignBottom)
  243. trend_chart.legend().setLabelColor(QColor("white"))
  244. trend_chart.legend().setMarkerShape(QLegend.MarkerShapeCircle)
  245. # 创建图表视图
  246. chart_view = QChartView(trend_chart)
  247. chart_view.setRenderHint(QPainter.Antialiasing)
  248. chart_view.setMinimumHeight(250) # 增加最小高度
  249. chart_view.setBackgroundBrush(QColor("#0a1a2a"))
  250. layout.addWidget(chart_view)
  251. return tab
  252. def create_region_tab(self):
  253. """创建区域统计标签页"""
  254. tab = QWidget()
  255. layout = QVBoxLayout(tab)
  256. # 添加区域告警统计图表
  257. chart_view = self.create_region_chart_view()
  258. layout.addWidget(chart_view)
  259. # 添加病虫害类型统计表格
  260. pest_table = self.create_pest_table()
  261. layout.addWidget(pest_table)
  262. return tab
  263. def create_pest_table(self):
  264. """创建病虫害类型统计表格"""
  265. # 创建表格
  266. table = QTableWidget()
  267. table.setObjectName("pest_table") # 添加对象名称便于检索
  268. table.setColumnCount(2)
  269. table.setRowCount(len(self.pest_type_stats))
  270. # 设置表头
  271. table.setHorizontalHeaderLabels(["病虫害类型", "检测数量"])
  272. # 添加数据
  273. row = 0
  274. for pest_type, count in self.pest_type_stats.items():
  275. # 添加病虫害类型
  276. type_item = QTableWidgetItem(pest_type)
  277. type_item.setTextAlignment(Qt.AlignCenter)
  278. table.setItem(row, 0, type_item)
  279. # 添加数量
  280. count_item = QTableWidgetItem(str(count))
  281. count_item.setTextAlignment(Qt.AlignCenter)
  282. table.setItem(row, 1, count_item)
  283. row += 1
  284. # 设置表格样式
  285. table.setStyleSheet("""
  286. QTableWidget {
  287. background-color: rgba(0, 20, 40, 0.8);
  288. color: white;
  289. gridline-color: rgba(80, 160, 220, 0.5);
  290. border: 1px solid rgba(80, 160, 220, 0.5);
  291. border-radius: 4px;
  292. }
  293. QHeaderView::section {
  294. background-color: rgba(0, 60, 120, 0.9);
  295. color: white;
  296. padding: 4px;
  297. border: 1px solid rgba(80, 160, 220, 0.5);
  298. }
  299. QTableWidget::item {
  300. border-bottom: 1px solid rgba(80, 160, 220, 0.3);
  301. }
  302. """)
  303. # 调整表格大小
  304. table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
  305. table.verticalHeader().setVisible(False)
  306. table.setFixedHeight(200)
  307. # 打印当前病虫害统计数据用于调试
  308. print("病虫害统计数据:")
  309. for pest_type, count in self.pest_type_stats.items():
  310. print(f" {pest_type}: {count}")
  311. return table
  312. @pyqtSlot(int)
  313. def change_time_range(self, index):
  314. """切换时间范围"""
  315. # 获取当前选择的时间范围
  316. time_range = self.time_range_combo.currentText()
  317. # 更新趋势图
  318. if time_range == "最近24小时":
  319. self.update_trend_chart(self.hour_data, "HH:mm", 8)
  320. elif time_range == "最近7天":
  321. self.update_trend_chart(self.day_data[:7], "MM-dd", 7)
  322. elif time_range == "最近30天":
  323. self.update_trend_chart(self.day_data, "MM-dd", 10)
  324. elif time_range == "本月":
  325. # 筛选本月数据
  326. now = datetime.now()
  327. month_data = [d for d in self.day_data if d['time'].month == now.month]
  328. self.update_trend_chart(month_data, "MM-dd", 10)
  329. elif time_range == "本年":
  330. # 筛选本年数据 (这里使用全部数据模拟)
  331. self.update_trend_chart(self.day_data, "MM-dd", 10)
  332. def update_trend_chart(self, data, date_format, tick_count):
  333. """更新趋势图"""
  334. if not data:
  335. return
  336. # 获取当前标签页中的图表
  337. chart = self.trend_chart_view.chart()
  338. # 清除所有系列
  339. chart.removeAllSeries()
  340. # 创建新系列
  341. fire_series = QLineSeries()
  342. fire_series.setName("火灾告警")
  343. fire_series.setColor(QColor(255, 100, 100))
  344. animal_series = QLineSeries()
  345. animal_series.setName("动物告警")
  346. animal_series.setColor(QColor(100, 255, 100))
  347. landslide_series = QLineSeries()
  348. landslide_series.setName("滑坡告警")
  349. landslide_series.setColor(QColor(100, 100, 255))
  350. pest_series = QLineSeries()
  351. pest_series.setName("病虫害告警")
  352. pest_series.setColor(QColor(180, 100, 200))
  353. # 添加数据点
  354. for i, d in enumerate(data):
  355. timestamp = d['time'].timestamp() * 1000 # 转换为毫秒
  356. fire_series.append(timestamp, d['fire'])
  357. animal_series.append(timestamp, d['animal'])
  358. landslide_series.append(timestamp, d['landslide'])
  359. pest_series.append(timestamp, d['pest'])
  360. # 添加系列到图表
  361. chart.addSeries(fire_series)
  362. chart.addSeries(animal_series)
  363. chart.addSeries(landslide_series)
  364. chart.addSeries(pest_series)
  365. # 更新图表标题
  366. chart.setTitle(f"{self.time_range_combo.currentText()}告警趋势")
  367. # 创建/更新坐标轴
  368. chart.createDefaultAxes()
  369. # 更新X轴
  370. x_axis = chart.axes(Qt.Horizontal)[0]
  371. if isinstance(x_axis, QDateTimeAxis):
  372. x_axis.setFormat(date_format)
  373. x_axis.setTickCount(tick_count)
  374. x_axis.setRange(data[0]['time'], data[-1]['time'])
  375. # 更新Y轴
  376. y_axis = chart.axes(Qt.Vertical)[0]
  377. if isinstance(y_axis, QValueAxis):
  378. # 寻找最大值
  379. max_value = 0
  380. for d in data:
  381. for t in ['fire', 'animal', 'landslide', 'forest_degradation', 'pest']:
  382. max_value = max(max_value, d[t])
  383. # 确保Y轴至少有一些高度,即使没有数据
  384. if max_value < 1:
  385. max_value = 1
  386. # 设置Y轴范围,上浮20%以便更好地查看
  387. y_axis.setRange(0, max_value * 1.2)
  388. # 根据数据范围确定合适的刻度数量
  389. if max_value <= 5:
  390. y_axis.setTickCount(max_value + 1) # 每个值一个刻度
  391. else:
  392. y_axis.setTickCount(6) # 较大范围使用5-6个刻度
  393. @pyqtSlot()
  394. def refresh_statistics(self):
  395. """刷新统计数据"""
  396. # 不重置数据,只刷新UI
  397. # 更新当前标签页
  398. current_tab = self.tab_widget.currentIndex()
  399. if current_tab == 0:
  400. # 更新概览标签页
  401. overview_tab = self.create_overview_tab()
  402. self.tab_widget.removeTab(0)
  403. self.tab_widget.insertTab(0, overview_tab, "概览")
  404. elif current_tab == 1:
  405. # 更新趋势图标签页
  406. self.change_time_range(self.time_range_combo.currentIndex())
  407. elif current_tab == 2:
  408. # 更新区域统计标签页
  409. region_tab = self.create_region_tab()
  410. self.tab_widget.removeTab(2)
  411. self.tab_widget.insertTab(2, region_tab, "区域统计")
  412. # 切换回当前标签页
  413. self.tab_widget.setCurrentIndex(current_tab)
  414. print("已刷新统计面板显示")
  415. @pyqtSlot()
  416. def auto_update_statistics(self):
  417. """自动更新统计数据(由定时器调用)"""
  418. # 更新数据
  419. self.update_statistics()
  420. # 动态更新UI
  421. self.update_current_tab()
  422. def update_current_tab(self):
  423. """更新所有标签页的UI,确保数据及时刷新"""
  424. # 更新概览标签页上的告警统计
  425. overview_tab = self.tab_widget.widget(0)
  426. if overview_tab:
  427. stats_group = overview_tab.findChild(QGroupBox, "stats_group")
  428. if stats_group:
  429. # 更新标签
  430. for i, (key, color) in enumerate([
  431. ('fire', 'red'),
  432. ('animal', 'green'),
  433. ('landslide', 'blue'),
  434. ('pest', 'purple')
  435. ]):
  436. label_name = f"{key}_label"
  437. label = stats_group.findChild(QLabel, label_name)
  438. if label:
  439. label.setText(f"{key.capitalize()}告警: {self.alert_stats[key]}")
  440. # 更新饼图
  441. chart_view = overview_tab.findChild(QChartView)
  442. if chart_view:
  443. # 创建新饼图
  444. new_chart_view = self.create_pie_chart()
  445. # 替换旧饼图
  446. layout = overview_tab.layout()
  447. layout.replaceWidget(chart_view, new_chart_view)
  448. chart_view.deleteLater()
  449. # 更新趋势图
  450. self.change_time_range(self.time_range_combo.currentIndex())
  451. # 保存当前区域图表视图的引用,便于后续更新
  452. if not hasattr(self, 'region_chart_view'):
  453. self.region_chart_view = self.create_region_chart_view()
  454. # 更新区域统计图
  455. region_tab = self.tab_widget.widget(2)
  456. if region_tab:
  457. chart_view = region_tab.findChild(QChartView)
  458. if chart_view:
  459. # 创建新区域图
  460. new_chart_view = self.create_region_chart_view()
  461. # 替换旧区域图
  462. layout = region_tab.layout()
  463. layout.replaceWidget(chart_view, new_chart_view)
  464. chart_view.deleteLater()
  465. # 更新病虫害类型表格
  466. table = region_tab.findChild(QTableWidget)
  467. if table:
  468. for row, (pest_type, count) in enumerate(self.pest_type_stats.items()):
  469. if row < table.rowCount() and count > 0:
  470. count_item = QTableWidgetItem(str(count))
  471. count_item.setTextAlignment(Qt.AlignCenter)
  472. table.setItem(row, 1, count_item)
  473. # 触发刷新
  474. self.update() # 强制刷新UI
  475. print("已刷新所有统计图表")
  476. def update_statistics(self):
  477. """更新统计数据(由外部定时器调用)"""
  478. # 获取当前时间
  479. now = datetime.now()
  480. # 对所有数据点进行处理,实现自然衰减的效果
  481. for data in self.hour_data:
  482. for key in ['fire', 'animal', 'landslide', 'forest_degradation', 'pest']:
  483. # 如果是当前小时的数据,保持不变
  484. if data['time'].hour == now.hour and data['time'].day == now.day:
  485. continue
  486. # 对于旧数据,每次更新减少20%,但不低于0
  487. # 这样可以实现数值的自然衰减,使图表能体现趋势变化
  488. if data[key] > 0:
  489. data[key] = max(0, data[key] - 0.2)
  490. # 对日数据也进行类似处理
  491. for data in self.day_data:
  492. # 如果不是当前日期的数据,进行衰减
  493. if data['time'].day != now.day or data['time'].month != now.month:
  494. for key in ['fire', 'animal', 'landslide', 'forest_degradation', 'pest']:
  495. if data[key] > 0:
  496. data[key] = max(0, data[key] - 0.1) # 日数据衰减更慢一些
  497. # 更新UI显示
  498. self.update_current_tab()
  499. def create_pie_chart(self):
  500. """创建新的饼图供外部使用"""
  501. pie_chart = QChart()
  502. pie_chart.setTitle("告警类型分布")
  503. pie_chart.setAnimationOptions(QChart.SeriesAnimations)
  504. pie_chart.setBackgroundBrush(QColor("#0a1a2a"))
  505. pie_chart.setTitleBrush(QColor("white"))
  506. pie_chart.setTitleFont(QFont("Microsoft YaHei", 10, QFont.Bold))
  507. # 创建饼图系列
  508. series = QPieSeries()
  509. # 检查是否有数据
  510. total_alerts = sum(self.alert_stats.values())
  511. if total_alerts > 0:
  512. series.append("火灾告警", self.alert_stats['fire'])
  513. series.append("动物告警", self.alert_stats['animal'])
  514. series.append("滑坡告警", self.alert_stats['landslide'])
  515. series.append("病虫害告警", self.alert_stats['pest'])
  516. # 设置切片颜色
  517. if len(series.slices()) >= 5:
  518. series.slices()[0].setBrush(QColor(255, 100, 100)) # 红色
  519. series.slices()[1].setBrush(QColor(100, 255, 100)) # 绿色
  520. series.slices()[2].setBrush(QColor(100, 100, 255)) # 蓝色
  521. series.slices()[3].setBrush(QColor(255, 200, 100)) # 橙色
  522. series.slices()[4].setBrush(QColor(180, 100, 200)) # 紫色
  523. # 设置标签颜色
  524. for slice in series.slices():
  525. slice.setLabelColor(QColor("white"))
  526. slice.setLabelFont(QFont("Microsoft YaHei", 9))
  527. # 突出显示第一个切片
  528. series.slices()[0].setExploded(True)
  529. series.slices()[0].setLabelVisible(True)
  530. else:
  531. # 如果没有数据,添加一个空的占位切片
  532. placeholder = series.append("无告警数据", 1)
  533. placeholder.setBrush(QColor(100, 100, 100)) # 灰色
  534. placeholder.setLabelColor(QColor("white"))
  535. placeholder.setLabelFont(QFont("Microsoft YaHei", 9))
  536. placeholder.setLabelVisible(True)
  537. pie_chart.addSeries(series)
  538. pie_chart.legend().setLabelColor(QColor("white"))
  539. # 创建图表视图
  540. chart_view = QChartView(pie_chart)
  541. chart_view.setRenderHint(QPainter.Antialiasing)
  542. chart_view.setBackgroundBrush(QColor("#0a1a2a"))
  543. return chart_view
  544. def create_trend_chart_view(self):
  545. """创建新的趋势图视图供外部使用"""
  546. trend_chart = QChart()
  547. trend_chart.setTitle("24小时告警趋势")
  548. trend_chart.setAnimationOptions(QChart.SeriesAnimations)
  549. trend_chart.setBackgroundBrush(QColor("#0a1a2a"))
  550. trend_chart.setTitleBrush(QColor("white"))
  551. trend_chart.setTitleFont(QFont("Microsoft YaHei", 10, QFont.Bold))
  552. # 设置图表边距,增加底部和左侧空间显示坐标文字
  553. trend_chart.setMargins(QMargins(10, 10, 10, 20))
  554. # 创建折线系列 - 火灾
  555. fire_series = QLineSeries()
  556. fire_series.setName("火灾告警")
  557. fire_series.setColor(QColor(255, 100, 100))
  558. # 创建折线系列 - 动物
  559. animal_series = QLineSeries()
  560. animal_series.setName("动物告警")
  561. animal_series.setColor(QColor(100, 255, 100))
  562. # 创建折线系列 - 滑坡
  563. landslide_series = QLineSeries()
  564. landslide_series.setName("滑坡告警")
  565. landslide_series.setColor(QColor(100, 100, 255))
  566. # 创建折线系列 - 病虫害
  567. pest_series = QLineSeries()
  568. pest_series.setName("病虫害告警")
  569. pest_series.setColor(QColor(180, 100, 200))
  570. # 添加数据点
  571. for i, data in enumerate(self.hour_data):
  572. timestamp = data['time'].timestamp() * 1000 # 转换为毫秒
  573. fire_series.append(timestamp, data['fire'])
  574. animal_series.append(timestamp, data['animal'])
  575. landslide_series.append(timestamp, data['landslide'])
  576. pest_series.append(timestamp, data['pest'])
  577. # 添加系列到图表
  578. trend_chart.addSeries(fire_series)
  579. trend_chart.addSeries(animal_series)
  580. trend_chart.addSeries(landslide_series)
  581. trend_chart.addSeries(pest_series)
  582. # 创建X轴(时间轴)
  583. axis_x = QDateTimeAxis()
  584. axis_x.setFormat("HH:mm")
  585. axis_x.setTitleText("时间")
  586. axis_x.setTickCount(8) # 显示8个刻度
  587. axis_x.setRange(
  588. self.hour_data[0]['time'],
  589. self.hour_data[-1]['time']
  590. )
  591. axis_x.setTitleBrush(QColor("white"))
  592. axis_x.setLabelsColor(QColor("white"))
  593. axis_x.setLabelsFont(QFont("Microsoft YaHei", 8))
  594. axis_x.setTitleFont(QFont("Microsoft YaHei", 9, QFont.Bold))
  595. # 创建Y轴
  596. axis_y = QValueAxis()
  597. axis_y.setLabelFormat("%d")
  598. axis_y.setTitleText("告警数量")
  599. axis_y.setRange(0, 15) # 增大Y轴范围,避免文字被裁剪
  600. axis_y.setTickCount(6) # 增加刻度数量以更好显示
  601. axis_y.setTitleBrush(QColor("white"))
  602. axis_y.setLabelsColor(QColor("white"))
  603. axis_y.setLabelsFont(QFont("Microsoft YaHei", 8))
  604. axis_y.setTitleFont(QFont("Microsoft YaHei", 9, QFont.Bold))
  605. # 添加坐标轴到图表
  606. trend_chart.addAxis(axis_x, Qt.AlignBottom)
  607. trend_chart.addAxis(axis_y, Qt.AlignLeft)
  608. # 将所有系列依附到坐标轴
  609. fire_series.attachAxis(axis_x)
  610. fire_series.attachAxis(axis_y)
  611. animal_series.attachAxis(axis_x)
  612. animal_series.attachAxis(axis_y)
  613. landslide_series.attachAxis(axis_x)
  614. landslide_series.attachAxis(axis_y)
  615. pest_series.attachAxis(axis_x)
  616. pest_series.attachAxis(axis_y)
  617. # 设置图例位置和样式
  618. trend_chart.legend().setVisible(True)
  619. trend_chart.legend().setAlignment(Qt.AlignBottom)
  620. trend_chart.legend().setLabelColor(QColor("white"))
  621. trend_chart.legend().setMarkerShape(QLegend.MarkerShapeCircle)
  622. trend_chart.legend().setFont(QFont("Microsoft YaHei", 8))
  623. # 创建图表视图
  624. chart_view = QChartView(trend_chart)
  625. chart_view.setRenderHint(QPainter.Antialiasing)
  626. chart_view.setMinimumHeight(280) # 增加最小高度
  627. chart_view.setBackgroundBrush(QColor("#0a1a2a"))
  628. return chart_view
  629. def create_region_chart_view(self):
  630. """创建区域统计图视图"""
  631. chart = QChart()
  632. chart.setTitle("区域统计")
  633. chart.setAnimationOptions(QChart.SeriesAnimations)
  634. chart.setBackgroundBrush(QColor("#0a1a2a"))
  635. chart.setTitleBrush(QColor("white"))
  636. chart.setTitleFont(QFont("Microsoft YaHei", 10, QFont.Bold))
  637. # 设置图表边距,增加底部和左侧空间显示坐标文字
  638. chart.setMargins(QMargins(10, 10, 10, 20))
  639. # 创建数据集
  640. barset = QBarSet("告警数量")
  641. barset.setColor(QColor(100, 200, 255)) # 设置柱状图颜色
  642. # 获取区域名称和数据
  643. regions = list(self.region_stats.keys())
  644. values = list(self.region_stats.values())
  645. # 如果区域统计为空,使用默认区域
  646. if not regions:
  647. regions = ['东北区', '西北区', '中部区', '东南区', '西南区']
  648. values = [0, 0, 0, 0, 0]
  649. print("警告: 区域统计数据为空,使用默认空值")
  650. # 打印当前区域统计数据用于调试
  651. print("区域统计数据:")
  652. for region, value in zip(regions, values):
  653. print(f" {region}: {value}")
  654. # 添加数据到集合
  655. for value in values:
  656. barset.append(value)
  657. # 创建条形系列
  658. series = QBarSeries()
  659. series.append(barset)
  660. series.setLabelsVisible(True)
  661. series.setLabelsPosition(QBarSeries.LabelsInsideEnd) # 标签位置在柱内端
  662. # 添加系列到图表
  663. chart.addSeries(series)
  664. # 创建X轴(区域)
  665. axis_x = QBarCategoryAxis()
  666. axis_x.append(regions)
  667. axis_x.setTitleText("区域")
  668. axis_x.setTitleBrush(QColor("white"))
  669. axis_x.setLabelsColor(QColor("white"))
  670. axis_x.setLabelsFont(QFont("Microsoft YaHei", 8))
  671. axis_x.setTitleFont(QFont("Microsoft YaHei", 9, QFont.Bold))
  672. # 创建Y轴
  673. axis_y = QValueAxis()
  674. axis_y.setLabelFormat("%d")
  675. axis_y.setTitleText("告警数量")
  676. # 设置Y轴范围,确保即使是小数值也能看到变化
  677. max_value = max(values) if values and max(values) > 0 else 1
  678. axis_y.setRange(0, max_value * 1.2 + 1) # 最大值上浮20%,并确保至少有高度
  679. axis_y.setTickCount(6)
  680. axis_y.setTitleBrush(QColor("white"))
  681. axis_y.setLabelsColor(QColor("white"))
  682. axis_y.setLabelsFont(QFont("Microsoft YaHei", 8))
  683. axis_y.setTitleFont(QFont("Microsoft YaHei", 9, QFont.Bold))
  684. # 添加坐标轴到图表
  685. chart.addAxis(axis_x, Qt.AlignBottom)
  686. chart.addAxis(axis_y, Qt.AlignLeft)
  687. # 将系列附加到坐标轴
  688. series.attachAxis(axis_x)
  689. series.attachAxis(axis_y)
  690. # 图例设置
  691. chart.legend().setVisible(False) # 隐藏图例
  692. # 创建图表视图
  693. chart_view = QChartView(chart)
  694. chart_view.setRenderHint(QPainter.Antialiasing)
  695. chart_view.setMinimumHeight(280) # 增加最小高度
  696. chart_view.setBackgroundBrush(QColor("#0a1a2a"))
  697. return chart_view
  698. def handle_new_alert(self, alert_type, region):
  699. """处理新的告警信息
  700. Args:
  701. alert_type (str): 告警类型 ('fire', 'animal', 'landslide', 'forest_degradation', 'pest')
  702. region (str): 告警区域
  703. """
  704. print(f"统计面板收到新告警: 类型={alert_type}, 区域={region}")
  705. # 更新告警统计总数
  706. if alert_type in self.alert_stats:
  707. self.alert_stats[alert_type] += 1
  708. print(f"更新告警统计: {alert_type} = {self.alert_stats[alert_type]}")
  709. # 更新区域统计
  710. mapped_region = self.region_mapping.get(region, '中部区') # 如果没有映射则默认为中部区
  711. if mapped_region not in self.region_stats:
  712. self.region_stats[mapped_region] = 0
  713. self.region_stats[mapped_region] += 1
  714. print(f"更新区域统计: {mapped_region} = {self.region_stats[mapped_region]}")
  715. # 更新趋势数据
  716. now = datetime.now()
  717. # 更新小时数据
  718. for data in self.hour_data:
  719. if data['time'].hour == now.hour and data['time'].day == now.day:
  720. if alert_type in data:
  721. data[alert_type] += 1
  722. print(f"更新小时趋势: {alert_type} = {data[alert_type]}")
  723. break
  724. # 更新日数据
  725. for data in self.day_data:
  726. if data['time'].day == now.day and data['time'].month == now.month:
  727. if alert_type in data:
  728. data[alert_type] += 1
  729. print(f"更新日趋势: {alert_type} = {data[alert_type]}")
  730. break
  731. # 更新月数据
  732. for data in self.month_data:
  733. if data['time'].day == now.day and data['time'].month == now.month:
  734. if alert_type in data:
  735. data[alert_type] += 1
  736. print(f"更新月趋势: {alert_type} = {data[alert_type]}")
  737. break
  738. # 更新UI显示
  739. self.update_current_tab()
  740. # 强制刷新UI
  741. from PyQt5.QtWidgets import QApplication
  742. QApplication.processEvents()
  743. def handle_alert_processed(self, alert_type, region):
  744. """处理告警已被处理的信号
  745. Args:
  746. alert_type (str): 告警类型 ('fire', 'animal', 'landslide', 'forest_degradation', 'pest')
  747. region (str): 告警区域
  748. """
  749. print(f"统计面板收到告警处理通知: 类型={alert_type}, 区域={region}")
  750. # 减少告警统计总数,但确保不会小于0
  751. if alert_type in self.alert_stats and self.alert_stats[alert_type] > 0:
  752. self.alert_stats[alert_type] -= 1
  753. print(f"减少告警统计: {alert_type} = {self.alert_stats[alert_type]}")
  754. # 区域名称映射(将告警区域名称映射到区域统计中的键)
  755. region_mapping = {
  756. '北部山区': '东北区',
  757. '南部林区': '东南区',
  758. '东部山脊': '东北区',
  759. '西部谷地': '西北区',
  760. '西部林区': '西北区',
  761. '中央林场': '中部区'
  762. }
  763. # 减少区域统计总数
  764. mapped_region = region_mapping.get(region, region)
  765. if mapped_region in self.region_stats and self.region_stats[mapped_region] > 0:
  766. self.region_stats[mapped_region] -= 1
  767. print(f"减少区域统计: {mapped_region} = {self.region_stats[mapped_region]}")
  768. # 如果是病虫害类型,减少一种随机病虫害
  769. if alert_type == 'pest':
  770. import random
  771. pest_types = [k for k, v in self.pest_type_stats.items() if v > 0]
  772. if pest_types:
  773. selected_pest = random.choice(pest_types)
  774. self.pest_type_stats[selected_pest] -= 1
  775. print(f"减少病虫害类型: {selected_pest} = {self.pest_type_stats[selected_pest]}")
  776. # 更新当前小时的趋势数据 - 减少告警量
  777. now = datetime.now()
  778. for data in self.hour_data:
  779. if data['time'].hour == now.hour and data['time'].day == now.day:
  780. if alert_type in data and data[alert_type] > 0:
  781. data[alert_type] -= 1
  782. print(f"减少小时趋势: {alert_type} = {data[alert_type]}")
  783. break
  784. # 更新当前日期的趋势数据 - 减少告警量
  785. for data in self.day_data:
  786. if data['time'].day == now.day and data['time'].month == now.month:
  787. if alert_type in data and data[alert_type] > 0:
  788. data[alert_type] -= 1
  789. print(f"减少日趋势: {alert_type} = {data[alert_type]}")
  790. break
  791. # 基于当前选择的时间范围更新趋势图
  792. current_index = self.time_range_combo.currentIndex() if hasattr(self, 'time_range_combo') else 0
  793. self.change_time_range(current_index)
  794. # 更新UI - 确保立即刷新所有标签页
  795. self.update_current_tab()
  796. # 强制刷新UI
  797. from PyQt5.QtWidgets import QApplication
  798. QApplication.processEvents()