总结一下PyQT5 + matplotlib的使用方法

搜了一下网上的用法还挺多的,买的这本PyQt5快速开发和实战里也有介绍,尝试了一下还是可以做到的。

原理是使用:

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

获取matplotlib下backend-qt5agg 接口

(其实我也不懂这其中的细节,反正能实现自己想要的功能了)

 

首先是封装一个FigureCanvasQTAgg类,画图的时候调用这个类就行了

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
from PyQt5.QtWidgets import QSizePolicy


class Mydemo(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):

        plt.rcParams['font.family'] = ['SimHei']    # 更换字体使中文显示正常
        plt.rcParams['axes.unicode_minus'] = False    # 用来正常显示负号
        
        # 创建绘图对象,就像matlab里创建一个新的窗口那样

        self.fig = Figure(figsize=(width, height), dpi=dpi) 

   
        # 紧接着在这个新的窗口加上一个子图,也就是实际画图的地方
        # 看到subplot我想你
<think>我们已经在之前的讨论中详细介绍了在PyQt5使用Matplotlib的plt.subplots()创建多子图的方法。现在,用户特别关注如何实现动态图表的更新。下面我将结合引用[1]和[2]中的信息,以及之前的知识,详细解释在PyQt5中实现动态图表的方法。 ## 一、动态更新图表的核心思路 在PyQt5中实现Matplotlib图表的动态更新,需要解决两个关键问题: 1. **数据更新**:定时或事件触发数据变化 2. **界面刷新**:更新图表后重绘画布 ### 基本步骤: 1. 创建Matplotlib图形和坐标轴 2. 初始化图表(绘制初始数据) 3. 创建定时器(QTimer)定期触发更新函数 4. 在更新函数中: - 修改数据 - 更新图表(通过`set_data()`等方法) - 调整坐标轴范围(如果需要) - 调用`canvas.draw()`或`canvas.draw_idle()`重绘 ## 二、实现动态图表示例 以下示例展示一个实时更新的正弦波: ```python import sys import numpy as np import matplotlib matplotlib.use("Qt5Agg") # 设置使用Qt5后端[^1] from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget from PyQt5.QtCore import QTimer class DynamicPlotWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("动态正弦波") # 创建图形和坐标轴 self.fig = Figure(figsize=(10, 6)) self.ax = self.fig.add_subplot(111) # 初始化数据 self.x = np.linspace(0, 10, 500) self.y = np.sin(self.x) # 绘制初始曲线,并保存引用 self.line, = self.ax.plot(self.x, self.y, 'r-', linewidth=2) self.ax.set_xlim(0, 10) self.ax.set_ylim(-1.5, 1.5) self.ax.grid(True, linestyle='--', alpha=0.5) # 设置网格线透明度[^2] # 创建画布并嵌入Qt self.canvas = FigureCanvas(self.fig) layout = QVBoxLayout() layout.addWidget(self.canvas) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) # 创建定时器 self.timer = QTimer(self) self.timer.timeout.connect(self.update_plot) self.timer.start(100) # 每100毫秒更新一次 self.phase = 0 def update_plot(self): """更新图表数据""" # 更新数据:相位随时间变化 self.phase += 0.1 new_y = np.sin(self.x + self.phase) # 更新曲线数据(避免完全重绘) self.line.set_ydata(new_y) # 可选:动态调整坐标轴范围(如果需要) # self.ax.set_ylim(new_y.min()-0.1, new_y.max()+0.1) # 重绘画布 self.canvas.draw_idle() # 使用draw_idle优化性能 if __name__ == "__main__": app = QApplication(sys.argv) window = DynamicPlotWindow() window.show() sys.exit(app.exec_()) ``` ### 代码解析: 1. **数据初始化**:创建初始正弦波数据 2. **保存引用**:`self.line, = self.ax.plot(...)`中的逗号用于解包返回的列表,保存线条对象引用 3. **定时器更新**:QTimer每100ms触发一次`update_plot()` 4. **数据更新**:`set_ydata()`仅更新y轴数据,避免重绘整个图表 5. **优化重绘**:`draw_idle()`在Qt事件循环空闲时重绘,减少资源占用 ## 三、性能优化技巧 ### 1. 避免完全重绘 使用`set_data()`方法更新现有对象,而非清除重绘: ```python # 正确做法(高效) self.line.set_data(new_x, new_y) # 错误做法(低效) self.ax.clear() self.ax.plot(new_x, new_y) ``` ### 2. 使用`draw_idle()`代替`draw()` - `draw()`:强制立即重绘,可能造成性能瓶颈 - `draw_idle()`:请求在Qt空闲时重绘,更高效 ### 3. 局部重绘(blitting技术) 对于复杂图表,可使用blitting技术只重绘变化部分: ```python # 初始化时缓存背景 self.background = self.canvas.copy_from_bbox(self.ax.bbox) def update_plot(self): # 恢复背景 self.canvas.restore_region(self.background) # 更新数据 self.line.set_ydata(new_y) # 重绘线条 self.ax.draw_artist(self.line) # 局部刷新 self.canvas.blit(self.ax.bbox) ``` ### 4. 控制更新频率 根据实际需求调整定时器间隔: ```python self.timer.setInterval(50) # 20帧/秒 (1000ms/50ms=20) ``` ## 四、多子图动态更新 扩展至多子图动态更新(参考之前的多子图布局): ```python class MultiDynamicPlots(DynamicPlotWindow): def __init__(self): super().__init__() # 注意:这里我们继承DynamicPlotWindow,但需要调整初始化 # 修改为2x2子图 self.fig.clear() self.axs = self.fig.subplots(2, 2) # 初始化四个子图的线条 self.lines = [] titles = ['正弦波', '随机散点', '方波', '衰减振荡'] for i, ax in enumerate(self.axs.flat): # 每个子图初始化不同数据 if i == 0: # 正弦波 x = np.linspace(0, 10, 500) y = np.sin(x) line, = ax.plot(x, y, 'r-') elif i == 1: # 随机散点 x = np.random.rand(50) y = np.random.rand(50) line, = ax.plot(x, y, 'bo', markersize=3) elif i == 2: # 方波 x = np.linspace(0, 10, 500) y = np.sign(np.sin(x)) line, = ax.plot(x, y, 'g-') else: # 衰减振荡 x = np.linspace(0, 10, 500) y = np.exp(-0.2*x) * np.sin(2*x) line, = ax.plot(x, y, 'm-') ax.set_title(titles[i]) ax.grid(True, alpha=0.3) self.lines.append(line) self.fig.tight_layout() self.canvas.draw() # 重置定时器(如果之前有) if hasattr(self, 'timer'): self.timer.timeout.disconnect() self.timer.timeout.connect(self.update_multi_plots) self.phase = 0 def update_multi_plots(self): self.phase += 0.1 # 更新子图0: 正弦波 new_y0 = np.sin(self.lines[0].get_xdata() + self.phase) self.lines[0].set_ydata(new_y0) # 更新子图1: 随机散点 new_x1 = np.random.rand(50) new_y1 = np.random.rand(50) self.lines[1].set_data(new_x1, new_y1) # 更新子图2: 方波 new_y2 = np.sign(np.sin(self.lines[2].get_xdata() + self.phase)) self.lines[2].set_ydata(new_y2) # 更新子图3: 衰减振荡 x = self.lines[3].get_xdata() new_y3 = np.exp(-0.1*x) * np.sin(2*x + self.phase) self.lines[3].set_ydata(new_y3) self.canvas.draw_idle() ``` ## 五、常见问题解决 ### 1. 图表闪烁问题 - **原因**:每次更新都清除整个画布 - **解决**:使用`set_data()`代替清除重绘 ### 2. 更新后坐标轴不调整 - **现象**:数据超出当前坐标范围 - **解决**:添加自动调整逻辑 ```python # 方法1: 固定范围(适合已知范围的数据) self.ax.set_ylim(-1.1, 1.1) # 方法2: 动态调整 self.ax.relim() # 重新计算数据范围 self.ax.autoscale_view() # 自动调整坐标轴 ``` ### 3. 资源占用过高 - **降低更新频率**:增加定时器间隔(如从50ms改为200ms) - **使用blitting技术**:仅重绘变化区域 ## 六、高级应用:结合线程处理 当数据更新涉及耗时操作(如网络请求、复杂计算)时,应使用QThread避免界面冻结: ```python from PyQt5.QtCore import QThread, pyqtSignal class DataWorker(QThread): data_ready = pyqtSignal(np.ndarray) # 自定义信号 def run(self): while True: # 模拟耗时数据生成 data = np.random.rand(1000) * np.sin(time.time()) self.data_ready.emit(data) time.sleep(0.1) # 控制数据生成速率 class ThreadedPlotWindow(DynamicPlotWindow): def __init__(self): super().__init__() # 创建工作线程 self.worker = DataWorker() self.worker.data_ready.connect(self.update_with_data) self.worker.start() def update_with_data(self, data): self.line.set_ydata(data) self.canvas.draw_idle() ``` > **注意**:多线程中,所有界面操作必须在主线程完成,因此通过信号传递数据。 通过上述方法,可以在PyQt5中高效实现Matplotlib图表的动态更新,满足实时数据展示的需求。实际应用中,根据数据量和更新频率选择合适的优化策略。 §§相关问题§§ 1. 在动态更新图表时如何避免界面卡顿? 2. 如何实现多个动态图表之间的数据联动? 3. 使用Matplotlib绘制动态图表时有哪些性能瓶颈?</think># 在PyQt5中实现Matplotlib动态图表更新 在PyQt5中结合Matplotlib实现动态图表更新是数据可视化应用的常见需求。下面我将详细介绍实现方法和优化技巧,帮助您创建流畅的动态图表。 ## 一、动态图表实现的核心方法 ### 1. 使用QTimer定时更新 这是实现动态图表最常用的方法,通过定时器定期触发数据更新和图表重绘。 ```python import sys import numpy as np import matplotlib matplotlib.use("Qt5Agg") # 设置使用Qt5后端[^1] from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget from PyQt5.QtCore import QTimer class DynamicPlotWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("动态图表示例") # 创建图形和坐标轴 self.fig = Figure(figsize=(10, 6)) self.ax = self.fig.add_subplot(111) # 初始化数据 self.x = np.linspace(0, 10, 100) self.y = np.sin(self.x) # 绘制初始曲线,并保存引用 self.line, = self.ax.plot(self.x, self.y, 'r-', linewidth=2) self.ax.set_xlim(0, 10) self.ax.set_ylim(-1.5, 1.5) self.ax.grid(True, linestyle='--', alpha=0.5) # 设置网格线透明度[^2] # 创建画布并嵌入Qt self.canvas = FigureCanvas(self.fig) layout = QVBoxLayout() layout.addWidget(self.canvas) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) # 创建定时器 self.timer = QTimer(self) self.timer.timeout.connect(self.update_plot) self.timer.start(100) # 每100毫秒更新一次 self.phase = 0 def update_plot(self): """更新图表数据""" # 更新数据:相位随时间变化 self.phase += 0.1 new_y = np.sin(self.x + self.phase) # 更新曲线数据(避免完全重绘) self.line.set_ydata(new_y) # 重绘画布 self.canvas.draw_idle() # 使用draw_idle优化性能 if __name__ == "__main__": app = QApplication(sys.argv) window = DynamicPlotWindow() window.show() sys.exit(app.exec_()) ``` ### 2. 使用动画API(FuncAnimation) Matplotlib提供了内置的动画API,可以简化动画创建过程。 ```python from matplotlib.animation import FuncAnimation class AnimationPlotWindow(QMainWindow): def __init__(self): super().__init__() # ...(与前面相同的初始化代码)... # 使用FuncAnimation创建动画 self.animation = FuncAnimation( self.fig, self.update_frame, interval=100, # 更新间隔(毫秒) blit=True # 使用blitting优化 ) def update_frame(self, frame): """更新动画帧""" self.phase += 0.1 new_y = np.sin(self.x + self.phase) self.line.set_ydata(new_y) return self.line, # 返回需要更新的artist对象 ``` ## 二、性能优化技巧 ### 1. 避免完全重绘 使用`set_data()`方法更新现有对象,而非清除重绘: ```python # 正确做法(高效) self.line.set_data(new_x, new_y) # 错误做法(低效) self.ax.clear() self.ax.plot(new_x, new_y) ``` ### 2. 使用blitting技术 Blitting只重绘变化的部分,大幅提升性能: ```python def init_plot(self): """初始化并缓存背景""" self.line.set_data([], []) self.canvas.draw() # 初始绘制 self.background = self.canvas.copy_from_bbox(self.ax.bbox) def update_with_blit(self): """使用blitting更新""" # 恢复背景 self.canvas.restore_region(self.background) # 更新数据 new_y = np.sin(self.x + self.phase) self.line.set_ydata(new_y) # 重绘艺术家 self.ax.draw_artist(self.line) # 局部刷新 self.canvas.blit(self.ax.bbox) ``` ### 3. 控制更新频率 根据实际需求调整更新频率: ```python # 设置不同更新频率 self.timer.setInterval(50) # 20帧/秒 (1000ms/50ms=20) self.timer.setInterval(200) # 5帧/秒 ``` ## 三、动态图表类型实现 ### 1. 实时数据流图表 处理连续到达的数据流: ```python class RealTimePlot(DynamicPlotWindow): def __init__(self): super().__init__() # 初始化数据缓冲区 self.max_points = 200 self.data_buffer = np.zeros(self.max_points) self.x = np.arange(self.max_points) def update_plot(self): # 模拟新数据到达 new_value = np.random.randn() # 更新缓冲区 self.data_buffer = np.roll(self.data_buffer, -1) self.data_buffer[-1] = new_value # 更新图表 self.line.set_data(self.x, self.data_buffer) self.canvas.draw_idle() ``` ### 2. 动态散点图 实现随时间变化的散点图: ```python class DynamicScatterPlot(DynamicPlotWindow): def __init__(self): super().__init__() # 创建散点图 self.scatter = self.ax.scatter([], [], c=[], cmap='viridis', alpha=0.7) self.fig.colorbar(self.scatter) # 添加颜色条 def update_plot(self): # 生成新数据 n_points = 50 x = np.random.rand(n_points) y = np.random.rand(n_points) colors = np.random.rand(n_points) sizes = np.random.randint(10, 100, n_points) # 更新散点图 self.scatter.set_offsets(np.column_stack([x, y])) self.scatter.set_array(colors) self.scatter.set_sizes(sizes) self.canvas.draw_idle() ``` ## 四、高级应用:多图表联动 实现多个图表之间的联动交互: ```python class LinkedPlotsWindow(QMainWindow): def __init__(self): super().__init__() # 创建2x1子图布局 self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1, figsize=(10, 8)) # 主图表(正弦波) self.x = np.linspace(0, 10, 100) self.y = np.sin(self.x) self.line1, = self.ax1.plot(self.x, self.y, 'b-') # 细节图表(初始为空) self.line2, = self.ax2.plot([], [], 'r-') # 添加交互功能 self.canvas = FigureCanvas(self.fig) self.canvas.mpl_connect('button_press_event', self.on_click) self.canvas.mpl_connect('motion_notify_event', self.on_motion) # 定时器用于动态更新 self.timer = QTimer(self) self.timer.timeout.connect(self.update_wave) self.timer.start(100) self.phase = 0 def update_wave(self): """更新波形""" self.phase += 0.05 new_y = np.sin(self.x + self.phase) self.line1.set_ydata(new_y) self.canvas.draw_idle() def on_click(self, event): """点击事件处理""" if event.inaxes == self.ax1: # 在点击位置附近创建细节视图 x_center = event.xdata x_range = 1.0 # 显示范围 # 更新细节图表 mask = (self.x > x_center - x_range) & (self.x < x_center + x_range) self.line2.set_data(self.x[mask], np.sin(self.x[mask] + self.phase)) self.ax2.set_xlim(x_center - x_range, x_center + x_range) self.ax2.set_ylim(-1.1, 1.1) self.canvas.draw_idle() def on_motion(self, event): """鼠标移动事件处理""" if event.inaxes == self.ax1 and event.button == 1: # 左键拖动 self.on_click(event) ``` ## 五、常见问题解决方案 ### 1. 图表闪烁问题 **原因**:每次更新都清除整个画布 **解决**:使用`set_data()`代替清除重绘 ```python # 正确做法 self.line.set_data(new_x, new_y) # 错误做法(会导致闪烁) self.ax.clear() self.ax.plot(new_x, new_y) ``` ### 2. 更新后坐标轴不调整 **解决**:添加自动调整逻辑 ```python def update_plot(self): self.line.set_data(new_x, new_y) self.ax.relim() # 重新计算数据范围 self.ax.autoscale_view() # 自动调整坐标轴 self.canvas.draw_idle() ``` ### 3. 内存泄漏问题 **原因**:未正确释放资源 **解决**:重写关闭事件处理 ```python class DynamicPlotWindow(QMainWindow): # ...其他代码... def closeEvent(self, event): """窗口关闭时停止定时器""" self.timer.stop() super().closeEvent(event) ``` ## 六、完整动态图表示例 ```python import sys import numpy as np import matplotlib matplotlib.use("Qt5Agg") from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget, QHBoxLayout, QPushButton, QSlider) from PyQt5.QtCore import QTimer class AdvancedDynamicPlot(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("高级动态图表") self.setGeometry(100, 100, 1200, 800) # 创建主部件 main_widget = QWidget() main_layout = QVBoxLayout() # 创建图形和坐标轴 self.fig = Figure(figsize=(10, 6)) self.ax = self.fig.add_subplot(111) # 初始化数据 self.x = np.linspace(0, 10, 500) self.y = np.sin(self.x) # 绘制初始曲线 self.line, = self.ax.plot(self.x, self.y, 'b-', linewidth=1.5, label='正弦波') self.ax.set_xlim(0, 10) self.ax.set_ylim(-2, 2) self.ax.grid(True, linestyle='--', alpha=0.5) self.ax.legend() # 创建画布 self.canvas = FigureCanvas(self.fig) main_layout.addWidget(self.canvas) # 创建控制面板 control_layout = QHBoxLayout() # 开始/暂停按钮 self.start_button = QPushButton("开始") self.start_button.clicked.connect(self.toggle_animation) control_layout.addWidget(self.start_button) # 速度控制滑块 speed_label = QWidget() speed_label.setText("速度:") control_layout.addWidget(speed_label) self.speed_slider = QSlider() self.speed_slider.setOrientation(1) # 水平方向 self.speed_slider.setRange(10, 500) # 10ms到500ms self.speed_slider.setValue(100) self.speed_slider.valueChanged.connect(self.update_speed) control_layout.addWidget(self.speed_slider) # 振幅控制滑块 amp_label = QWidget() amp_label.setText("振幅:") control_layout.addWidget(amp_label) self.amp_slider = QSlider() self.amp_slider.setOrientation(1) self.amp_slider.setRange(1, 20) self.amp_slider.setValue(10) control_layout.addWidget(self.amp_slider) main_layout.addLayout(control_layout) main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) # 创建定时器 self.timer = QTimer(self) self.timer.timeout.connect(self.update_plot) self.timer.start(100) self.phase = 0 self.running = True self.amplitude = 1.0 def toggle_animation(self): """切换动画状态""" self.running = not self.running if self.running: self.timer.start() self.start_button.setText("暂停") else: self.timer.stop() self.start_button.setText("开始") def update_speed(self): """更新动画速度""" interval = self.speed_slider.value() self.timer.setInterval(interval) def update_plot(self): """更新图表数据""" # 获取当前振幅 self.amplitude = self.amp_slider.value() / 10.0 # 更新数据 self.phase += 0.05 new_y = self.amplitude * np.sin(self.x + self.phase) # 更新曲线 self.line.set_ydata(new_y) # 更新标题 self.ax.set_title(f"动态正弦波 (振幅={self.amplitude}, 相位={self.phase:.2f})") # 重绘画布 self.canvas.draw_idle() def closeEvent(self, event): """窗口关闭时停止定时器""" self.timer.stop() super().closeEvent(event) if __name__ == "__main__": app = QApplication(sys.argv) window = AdvancedDynamicPlot() window.show() sys.exit(app.exec_()) ``` 通过以上方法和示例,您可以在PyQt5中高效实现各种动态图表效果。关键点包括: 1. 使用`set_data()`而非完全重绘 2. 合理控制更新频率 3. 利用blitting技术优化性能 4. 添加交互控件增强用户体验
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值