You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
5.8 KiB
151 lines
5.8 KiB
"""
|
|
数据可视化组件
|
|
"""
|
|
from PyQt5.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSpinBox,
|
|
QPushButton, QComboBox
|
|
)
|
|
from PyQt5.QtCore import Qt
|
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
from matplotlib.figure import Figure
|
|
import matplotlib.pyplot as plt
|
|
from frontend.utils.api_client import APIClient
|
|
|
|
# 设置中文字体
|
|
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
|
|
plt.rcParams['axes.unicode_minus'] = False
|
|
|
|
class VisualizationWidget(QWidget):
|
|
"""数据可视化界面"""
|
|
|
|
def __init__(self, api_client: APIClient, parent=None):
|
|
super().__init__(parent)
|
|
self.api_client = api_client
|
|
self.init_ui()
|
|
self.load_ranking()
|
|
|
|
def init_ui(self):
|
|
"""初始化界面"""
|
|
layout = QVBoxLayout()
|
|
|
|
# 标题
|
|
title = QLabel("积分排名可视化")
|
|
title.setStyleSheet("font-size: 18px; font-weight: bold; margin: 10px;")
|
|
layout.addWidget(title)
|
|
|
|
# 控制栏
|
|
control_layout = QHBoxLayout()
|
|
|
|
control_layout.addWidget(QLabel("显示前"))
|
|
self.top_n_spin = QSpinBox()
|
|
self.top_n_spin.setRange(5, 50)
|
|
self.top_n_spin.setValue(10)
|
|
control_layout.addWidget(self.top_n_spin)
|
|
|
|
control_layout.addWidget(QLabel("名"))
|
|
|
|
self.chart_type_combo = QComboBox()
|
|
self.chart_type_combo.addItems(["柱形图", "折线图"])
|
|
control_layout.addWidget(self.chart_type_combo)
|
|
|
|
self.refresh_btn = QPushButton("刷新")
|
|
self.refresh_btn.clicked.connect(self.load_ranking)
|
|
control_layout.addWidget(self.refresh_btn)
|
|
|
|
control_layout.addStretch()
|
|
layout.addLayout(control_layout)
|
|
|
|
# 图表区域
|
|
self.figure = Figure(figsize=(10, 6))
|
|
self.canvas = FigureCanvas(self.figure)
|
|
layout.addWidget(self.canvas)
|
|
|
|
self.setLayout(layout)
|
|
|
|
def load_ranking(self):
|
|
"""加载排名数据并绘制图表"""
|
|
try:
|
|
top_n = self.top_n_spin.value()
|
|
data = self.api_client.get_ranking(top_n=top_n)
|
|
|
|
if not data['rankings']:
|
|
self.figure.clear()
|
|
ax = self.figure.add_subplot(111)
|
|
ax.text(0.5, 0.5, '暂无数据', ha='center', va='center', fontsize=16)
|
|
self.canvas.draw()
|
|
return
|
|
|
|
rankings = data['rankings']
|
|
names = [f"{r['name']}\n({r['student_id']})" for r in rankings]
|
|
scores = [r['total_score'] for r in rankings]
|
|
rollcall_counts = [r['random_rollcall_count'] for r in rankings]
|
|
|
|
self.figure.clear()
|
|
|
|
chart_type = self.chart_type_combo.currentText()
|
|
|
|
if chart_type == "柱形图":
|
|
# 创建双Y轴
|
|
ax1 = self.figure.add_subplot(111)
|
|
ax2 = ax1.twinx()
|
|
|
|
# 绘制积分柱形图
|
|
bars = ax1.bar(range(len(names)), scores, color='skyblue', alpha=0.7, label='总积分')
|
|
ax1.set_xlabel('学生')
|
|
ax1.set_ylabel('总积分', color='blue')
|
|
ax1.set_xticks(range(len(names)))
|
|
ax1.set_xticklabels(names, rotation=45, ha='right')
|
|
ax1.tick_params(axis='y', labelcolor='blue')
|
|
ax1.grid(True, alpha=0.3)
|
|
|
|
# 绘制点名次数折线图
|
|
line = ax2.plot(range(len(names)), rollcall_counts,
|
|
color='red', marker='o', linewidth=2, label='随机点名次数')
|
|
ax2.set_ylabel('随机点名次数', color='red')
|
|
ax2.tick_params(axis='y', labelcolor='red')
|
|
|
|
# 添加图例
|
|
ax1.legend(loc='upper left')
|
|
ax2.legend(loc='upper right')
|
|
|
|
self.figure.suptitle(f'积分排名前{top_n}名(柱形图:积分,折线图:点名次数)', fontsize=14)
|
|
|
|
else: # 折线图
|
|
ax = self.figure.add_subplot(111)
|
|
|
|
# 绘制积分折线
|
|
line1 = ax.plot(range(len(names)), scores,
|
|
color='blue', marker='o', linewidth=2, label='总积分')
|
|
ax.set_xlabel('学生')
|
|
ax.set_ylabel('总积分', color='blue')
|
|
ax.tick_params(axis='y', labelcolor='blue')
|
|
ax.set_xticks(range(len(names)))
|
|
ax.set_xticklabels(names, rotation=45, ha='right')
|
|
|
|
# 创建第二个Y轴用于点名次数
|
|
ax2 = ax.twinx()
|
|
line2 = ax2.plot(range(len(names)), rollcall_counts,
|
|
color='red', marker='s', linewidth=2, label='随机点名次数')
|
|
ax2.set_ylabel('随机点名次数', color='red')
|
|
ax2.tick_params(axis='y', labelcolor='red')
|
|
|
|
# 添加图例
|
|
ax.legend(loc='upper left')
|
|
ax2.legend(loc='upper right')
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
self.figure.suptitle(f'积分排名前{top_n}名(折线图)', fontsize=14)
|
|
|
|
self.figure.tight_layout()
|
|
self.canvas.draw()
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
error_msg = f"加载排名数据失败: {str(e)}\n{traceback.format_exc()}"
|
|
print(error_msg)
|
|
self.figure.clear()
|
|
ax = self.figure.add_subplot(111)
|
|
ax.text(0.5, 0.5, f'加载失败\n{str(e)}', ha='center', va='center', fontsize=12)
|
|
self.canvas.draw()
|
|
|