代码
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.animation import FuncAnimation
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QTextEdit, QLabel, QSplitter, QGroupBox,
QGridLayout, QDoubleSpinBox, QSpinBox, QCheckBox)
from PyQt5.QtCore import Qt
# 设置matplotlib支持中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
# 初始化日志列表
log_messages = []
class WaveformApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("动态波形图")
self.resize(1000, 700)
# 创建中心部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建主布局
main_layout = QVBoxLayout(central_widget)
# 创建水平分割器(参数面板和波形/日志)
main_splitter = QSplitter(Qt.Horizontal)
# 创建参数控制面板
self.create_parameter_panel()
main_splitter.addWidget(self.param_panel)
# 创建右侧垂直分割器(波形和日志)
right_splitter = QSplitter(Qt.Vertical)
# 创建图形区域
self.fig, self.ax = plt.subplots(figsize=(8, 5))
self.canvas = FigureCanvas(self.fig)
toolbar = NavigationToolbar(self.canvas, self)
# 创建公式显示区域
self.formula_label = QLabel()
self.formula_label.setStyleSheet("font-size: 14pt; font-weight: bold;")
# 波形参数
self.params = {
'amplitude': 1.0, # 振幅
'frequency': 1.0, # 频率(Hz)
'phase': 0.0, # 相位(弧度)
'noise': 0.5, # 噪声强度
'time_range': 10.0, # 时间范围(秒)
'num_points': 1000, # 采样点数
'speed': 0.1 # 动画速度因子
}
# 现在可以安全地更新公式显示
self.update_formula_display()
# 创建日志区域
log_widget = QWidget()
log_layout = QVBoxLayout(log_widget)
log_label = QLabel("操作日志:")
self.log_text = QTextEdit()
self.log_text.setReadOnly(True)
log_layout.addWidget(log_label)
log_layout.addWidget(self.log_text)
# 添加到右侧分割器
formula_frame = QWidget()
formula_layout = QVBoxLayout(formula_frame)
formula_layout.addWidget(self.formula_label)
right_splitter.addWidget(self.canvas)
right_splitter.addWidget(formula_frame)
right_splitter.addWidget(log_widget)
right_splitter.setSizes([400, 50, 200])
# 添加到主分割器
main_splitter.addWidget(right_splitter)
main_splitter.setSizes([250, 750])
# 添加到主布局
main_layout.addWidget(toolbar)
main_layout.addWidget(main_splitter)
# 初始化数据和图形
self.t, self.data = self.init_data()
self.line, = self.ax.plot(self.t, self.data)
self.ax.set_xlabel('时间 (秒)')
self.ax.set_ylabel('幅度')
self.ax.set_title('动态波形图')
self.fig.tight_layout()
# 创建动画
self.paused = False
self.ani = FuncAnimation(self.fig, self.update, frames=range(1000),
interval=100, repeat=True, blit=True)
def create_parameter_panel(self):
self.param_panel = QGroupBox("波形参数")
param_layout = QGridLayout(self.param_panel)
# 创建参数控件
row = 0
# 振幅
param_layout.addWidget(QLabel("振幅:"), row, 0)
self.amplitude_spin = QDoubleSpinBox()
self.amplitude_spin.setRange(0.1, 5.0)
self.amplitude_spin.setValue(1.0)
self.amplitude_spin.setSingleStep(0.1)
self.amplitude_spin.valueChanged.connect(self.on_param_changed)
param_layout.addWidget(self.amplitude_spin, row, 1)
row += 1
# 频率
param_layout.addWidget(QLabel("频率 (Hz):"), row, 0)
self.frequency_spin = QDoubleSpinBox()
self.frequency_spin.setRange(0.1, 5.0)
self.frequency_spin.setValue(1.0)
self.frequency_spin.setSingleStep(0.1)
self.frequency_spin.valueChanged.connect(self.on_param_changed)
param_layout.addWidget(self.frequency_spin, row, 1)
row += 1
# 相位
param_layout.addWidget(QLabel("相位 (弧度):"), row, 0)
self.phase_spin = QDoubleSpinBox()
self.phase_spin.setRange(0.0, 2*np.pi)
self.phase_spin.setValue(0.0)
self.phase_spin.setSingleStep(0.1)
self.phase_spin.valueChanged.connect(self.on_param_changed)
param_layout.addWidget(self.phase_spin, row, 1)
row += 1
# 噪声强度
param_layout.addWidget(QLabel("噪声强度:"), row, 0)
self.noise_spin = QDoubleSpinBox()
self.noise_spin.setRange(0.0, 2.0)
self.noise_spin.setValue(0.5)
self.noise_spin.setSingleStep(0.1)
self.noise_spin.valueChanged.connect(self.on_param_changed)
param_layout.addWidget(self.noise_spin, row, 1)
row += 1
# 时间范围
param_layout.addWidget(QLabel("时间范围 (秒):"), row, 0)
self.time_range_spin = QDoubleSpinBox()
self.time_range_spin.setRange(1.0, 30.0)
self.time_range_spin.setValue(10.0)
self.time_range_spin.setSingleStep(1.0)
self.time_range_spin.valueChanged.connect(self.on_param_changed)
param_layout.addWidget(self.time_range_spin, row, 1)
row += 1
# 采样点数
param_layout.addWidget(QLabel("采样点数:"), row, 0)
self.num_points_spin = QSpinBox()
self.num_points_spin.setRange(100, 5000)
self.num_points_spin.setValue(1000)
self.num_points_spin.setSingleStep(100)
self.num_points_spin.valueChanged.connect(self.on_param_changed)
param_layout.addWidget(self.num_points_spin, row, 1)
row += 1
# 动画速度
param_layout.addWidget(QLabel("动画速度:"), row, 0)
self.speed_spin = QDoubleSpinBox()
self.speed_spin.setRange(0.01, 1.0)
self.speed_spin.setValue(0.1)
self.speed_spin.setSingleStep(0.01)
self.speed_spin.valueChanged.connect(self.on_param_changed)
param_layout.addWidget(self.speed_spin, row, 1)
row += 1
# 显示网格
self.grid_check = QCheckBox("显示网格")
self.grid_check.setChecked(True)
self.grid_check.stateChanged.connect(self.on_param_changed)
param_layout.addWidget(self.grid_check, row, 0, 1, 2)
row += 1
# 暂停/继续按钮
self.pause_button = QCheckBox("暂停动画")
self.pause_button.stateChanged.connect(self.toggle_pause)
param_layout.addWidget(self.pause_button, row, 0, 1, 2)
def on_param_changed(self):
# 更新参数
self.params['amplitude'] = self.amplitude_spin.value()
self.params['frequency'] = self.frequency_spin.value()
self.params['phase'] = self.phase_spin.value()
self.params['noise'] = self.noise_spin.value()
self.params['time_range'] = self.time_range_spin.value()
self.params['num_points'] = self.num_points_spin.value()
self.params['speed'] = self.speed_spin.value()
# 更新网格显示
self.ax.grid(self.grid_check.isChecked())
# 重新初始化数据
self.t, self.data = self.init_data()
self.line.set_data(self.t, self.data)
self.ax.set_xlim(0, self.params['time_range'])
# 更新公式显示
self.update_formula_display()
# 重绘图形
self.canvas.draw()
log_msg = "参数已更新"
log_messages.append(log_msg)
self.update_log(log_msg)
def toggle_pause(self, state):
self.paused = state == Qt.Checked
log_msg = "动画已暂停" if self.paused else "动画已恢复"
log_messages.append(log_msg)
self.update_log(log_msg)
def update_formula_display(self):
# 显示当前使用的数学公式
formula = f"y(t) = {self.params['amplitude']:.1f} × sin(2π × {self.params['frequency']:.1f}t + {self.params['phase']:.1f}) + {self.params['noise']:.1f} × 随机噪声"
self.formula_label.setText(formula)
def init_data(self):
t = np.linspace(0, self.params['time_range'], self.params['num_points'])
data = self.params['amplitude'] * np.sin(2 * np.pi * self.params['frequency'] * t + self.params['phase'])
data += self.params['noise'] * np.random.randn(len(t))
log_msg = f"数据已重新生成: 时间范围={self.params['time_range']}秒, 点数={self.params['num_points']}"
log_messages.append(log_msg)
self.update_log(log_msg)
return t, data
def update(self, frame):
if self.paused:
return self.line,
# 计算新数据
new_data = self.params['amplitude'] * np.sin(
2 * np.pi * self.params['frequency'] * self.t +
self.params['phase'] +
frame * self.params['speed']
)
new_data += self.params['noise'] * np.random.randn(len(self.t))
self.line.set_ydata(new_data)
log_msg = f"第{frame}帧更新,生成新的波形数据"
log_messages.append(log_msg)
self.update_log(log_msg)
return self.line,
def update_log(self, message):
self.log_text.append(message)
# 滚动到底部
cursor = self.log_text.textCursor()
cursor.movePosition(cursor.End)
self.log_text.setTextCursor(cursor)
def main():
app = QApplication(sys.argv)
window = WaveformApp()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()