update server
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
# plot_window.py
|
||||
"""独立绘图窗口:嵌入 matplotlib (QtAgg 后端) 显示控制数据曲线。
|
||||
|
||||
注意:matplotlib backend 由 main.py 在最早期统一设置,此处不再重复调用。
|
||||
使用 Figure() 直接创建图形,避免 plt.subplots() 污染 pyplot 全局状态导致闪退。
|
||||
"""
|
||||
|
||||
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg, NavigationToolbar2QT
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QLineEdit, QPushButton, QWidget
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
|
||||
class PlotWindow(QDialog):
|
||||
"""压力控制数据曲线窗口"""
|
||||
|
||||
def __init__(self, time_data, pressure_data, target_data, valve_data, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("控制数据曲线图")
|
||||
self.resize(1100, 800)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
|
||||
self.time_data = list(time_data)
|
||||
self.pressure_data = list(pressure_data)
|
||||
self.target_data = list(target_data)
|
||||
self.valve_data = list(valve_data)
|
||||
|
||||
self._fig = None
|
||||
self._ax1 = None
|
||||
self._ax2 = None
|
||||
self._canvas = None
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# ---- 控制面板 ----
|
||||
ctrl_widget = QWidget()
|
||||
ctrl_layout = QHBoxLayout(ctrl_widget)
|
||||
ctrl_layout.setContentsMargins(0, 0, 0, 0)
|
||||
ctrl_layout.setSpacing(8)
|
||||
|
||||
ctrl_layout.addWidget(QLabel("时间轴范围 (秒):"))
|
||||
|
||||
self.x_min_entry = QLineEdit("0")
|
||||
self.x_min_entry.setMaximumWidth(80)
|
||||
ctrl_layout.addWidget(self.x_min_entry)
|
||||
|
||||
ctrl_layout.addWidget(QLabel("到"))
|
||||
|
||||
x_max_default = f"{max(self.time_data):.1f}" if self.time_data else "10"
|
||||
self.x_max_entry = QLineEdit(x_max_default)
|
||||
self.x_max_entry.setMaximumWidth(80)
|
||||
ctrl_layout.addWidget(self.x_max_entry)
|
||||
|
||||
apply_btn = QPushButton("应用")
|
||||
apply_btn.clicked.connect(self._apply_x_limits)
|
||||
ctrl_layout.addWidget(apply_btn)
|
||||
|
||||
reset_btn = QPushButton("重置")
|
||||
reset_btn.clicked.connect(self._reset_view)
|
||||
ctrl_layout.addWidget(reset_btn)
|
||||
|
||||
all_btn = QPushButton("全部")
|
||||
all_btn.clicked.connect(self._show_all)
|
||||
ctrl_layout.addWidget(all_btn)
|
||||
|
||||
last30_btn = QPushButton("最后30秒")
|
||||
last30_btn.clicked.connect(lambda: self._zoom_last_n(30))
|
||||
ctrl_layout.addWidget(last30_btn)
|
||||
|
||||
ctrl_layout.addStretch()
|
||||
layout.addWidget(ctrl_widget)
|
||||
|
||||
# ---- matplotlib 画布 ----
|
||||
if not self.time_data or len(self.time_data) < 2:
|
||||
layout.addWidget(QLabel("数据不足,无法绘制图表"))
|
||||
return
|
||||
|
||||
try:
|
||||
# 使用 Figure() 直接创建,避免 plt.subplots() 将图形注册到 pyplot 全局状态
|
||||
self._fig = Figure(figsize=(10, 7), dpi=100)
|
||||
self._ax1 = self._fig.add_subplot(2, 1, 1)
|
||||
self._ax2 = self._fig.add_subplot(2, 1, 2)
|
||||
|
||||
# 压力曲线
|
||||
self._ax1.plot(self.time_data, self.pressure_data, 'b-o',
|
||||
linewidth=1, markersize=1, alpha=0.8, label='实际压力')
|
||||
self._ax1.plot(self.time_data, self.target_data, 'r--',
|
||||
linewidth=1.5, alpha=0.8, label='目标压力')
|
||||
self._ax1.set_ylabel('压力 (kPa)', fontsize=12)
|
||||
self._ax1.set_title('压力控制性能', fontsize=14, fontweight='bold')
|
||||
self._ax1.legend(loc='upper right', fontsize=10)
|
||||
self._ax1.grid(True, alpha=0.3)
|
||||
|
||||
# 阀门开度曲线
|
||||
self._ax2.plot(self.time_data, self.valve_data, 'm-o',
|
||||
linewidth=1, markersize=1, alpha=0.8, label='实际阀门指令')
|
||||
self._ax2.set_xlabel('时间 (秒)', fontsize=12)
|
||||
self._ax2.set_ylabel('阀门开度 (%)', fontsize=12)
|
||||
self._ax2.legend(loc='upper right', fontsize=10)
|
||||
self._ax2.set_ylim([0, 105])
|
||||
self._ax2.grid(True, alpha=0.3)
|
||||
|
||||
self._fig.tight_layout()
|
||||
|
||||
# 创建 canvas
|
||||
self._canvas = FigureCanvasQTAgg(self._fig)
|
||||
layout.addWidget(self._canvas, stretch=1)
|
||||
|
||||
# 导航工具栏(macOS 上某些版本可能崩溃,加容错)
|
||||
try:
|
||||
toolbar = NavigationToolbar2QT(self._canvas, self)
|
||||
layout.addWidget(toolbar)
|
||||
except Exception as e:
|
||||
print(f"[PlotWindow] 工具栏创建失败: {e}")
|
||||
|
||||
# 提示标签
|
||||
hint = QLabel("提示: 使用工具栏缩放/平移 | 拖动矩形区域可局部放大")
|
||||
hint.setStyleSheet("color: gray; font-size: 12px;")
|
||||
layout.addWidget(hint)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
layout.addWidget(QLabel(f"绘图创建失败: {e}"))
|
||||
|
||||
def _apply_x_limits(self):
|
||||
try:
|
||||
x_min = float(self.x_min_entry.text())
|
||||
x_max = float(self.x_max_entry.text())
|
||||
if x_min >= x_max or self._ax1 is None:
|
||||
return
|
||||
self._ax1.set_xlim([x_min, x_max])
|
||||
self._ax2.set_xlim([x_min, x_max])
|
||||
self._canvas.draw()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def _reset_view(self):
|
||||
if not self._ax1 or not self.time_data:
|
||||
return
|
||||
x_min = min(self.time_data)
|
||||
x_max = max(self.time_data)
|
||||
self._ax1.set_xlim([x_min, x_max])
|
||||
self._ax2.set_xlim([x_min, x_max])
|
||||
self.x_min_entry.setText(f"{x_min:.1f}")
|
||||
self.x_max_entry.setText(f"{x_max:.1f}")
|
||||
self._canvas.draw()
|
||||
|
||||
def _show_all(self):
|
||||
if not self._ax1 or not self.time_data:
|
||||
return
|
||||
x_min = min(self.time_data)
|
||||
x_max = max(self.time_data)
|
||||
self._ax1.set_xlim([x_min, x_max])
|
||||
self._ax2.set_xlim([x_min, x_max])
|
||||
self.x_min_entry.setText(f"{x_min:.1f}")
|
||||
self.x_max_entry.setText(f"{x_max:.1f}")
|
||||
self._canvas.draw()
|
||||
|
||||
def _zoom_last_n(self, n_seconds):
|
||||
if not self._ax1 or not self.time_data:
|
||||
return
|
||||
x_max = max(self.time_data)
|
||||
x_min = max(0, x_max - n_seconds)
|
||||
self._ax1.set_xlim([x_min, x_max])
|
||||
self._ax2.set_xlim([x_min, x_max])
|
||||
self.x_min_entry.setText(f"{x_min:.1f}")
|
||||
self.x_max_entry.setText(f"{x_max:.1f}")
|
||||
self._canvas.draw()
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Qt 会按控件树父子关系自动销毁所有子控件(canvas + toolbar)。
|
||||
此处只需清空 Python 侧引用,让 Figure 能被 GC 正常回收。
|
||||
|
||||
严禁 plt.close(self._fig)!plt.close() 内部绕过 Qt 直接销毁 canvas
|
||||
widget,与 WA_DeleteOnClose 冲突导致 double-free → SIGSEGV 闪退。
|
||||
"""
|
||||
self._canvas = None
|
||||
self._ax1 = None
|
||||
self._ax2 = None
|
||||
self._fig = None
|
||||
super().closeEvent(event)
|
||||
Reference in New Issue
Block a user