You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Matplotlib中结合Blitting实现带滚动时间窗口的动态曲线图

如何在Matplotlib中结合Blitting实现带滚动时间窗口的动态曲线图

我完全懂你的需求——要做一个带10秒滚动时间窗口的动态曲线图,而且因为要同时跑多个图,必须靠blitting来提升性能,避免全量重绘拖慢动画节奏。你之前尝试让x轴跟着时间窗口滚动但没成功,问题主要出在blitting的流程没处理好x轴的更新逻辑,我来帮你修正代码并解释关键点。

问题分析

你之前的代码已经尝试把x轴设为animated=True,但有两个核心问题:

  • 没有把x轴纳入blitting的更新元素列表,导致它不会被重绘
  • 缺少blitting必备的背景恢复步骤,旧的x轴元素会一直留在画布上

修正后的完整代码

下面是调整后的代码,我标注了关键改动的地方:

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.animation as animation
from PyQt5 import QtWidgets, QtCore

class PlotsUI(object):
    def setupUi(self, PlotsUI):
        PlotsUI.setObjectName("PlotsUI")
        PlotsUI.setWindowModality(QtCore.Qt.NonModal)
        PlotsUI.resize(1041, 799)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(PlotsUI.sizePolicy().hasHeightForWidth())
        PlotsUI.setSizePolicy(sizePolicy)
        self.gridLayout_2 = QtWidgets.QGridLayout(PlotsUI)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.plotLayout = QtWidgets.QVBoxLayout()
        self.plotLayout.setObjectName("plotLayout")
        self.gridLayout_2.addLayout(self.plotLayout, 0, 0, 1, 1)
        self.retranslateUi(PlotsUI)
        QtCore.QMetaObject.connectSlotsByName(PlotsUI)

    def retranslateUi(self, PlotsUI):
        _translate = QtCore.QCoreApplication.translate
        PlotsUI.setWindowTitle(_translate("PlotsUI", "Plots"))

# 模拟你的数据管理类,每秒返回一组新数据
class DataSeriesMgr:
    def __init__(self):
        self.current_time = 0
        self.x_limits = (0, 10)
    
    @property
    def XLimits(self):
        return self.x_limits
    
    def NextXYDataSet(self):
        # 生成模拟数据:每秒增加一个点,x从current_time到current_time+1
        x_vals = list(range(self.current_time, self.current_time + 1))
        y_vals = [min(25, self.current_time * 2) for _ in x_vals]  # 模拟递增数据
        
        self.current_time += 1
        # 当时间超过初始窗口,滚动x轴限制
        if self.current_time > 10:
            self.x_limits = (self.current_time - 10, self.current_time)
        
        return x_vals, y_vals

class SimplePlotter(QtWidgets.QDialog):
    def __init__(self):
        super(SimplePlotter, self).__init__()
        self.ui = PlotsUI()
        self.ui.setupUi(self)
        self._xlimits = None
        self._line2D = None
        self._animation = None
        self._dataSeriesMgr = None
        self._background = None  # 用于保存blitting背景
        self._xaxis = None       # 保存x轴对象,避免重复获取

    def Plot(self):
        self._lines = []
        self._figure = Figure()
        self._canvas = FigureCanvas(self._figure)
        self.ui.plotLayout.addWidget(self._canvas)
        
        self._dataSeriesMgr = DataSeriesMgr()
        self._ax = self._figure.add_subplot(1,1,1)
        
        # 初始化曲线,设置为animated
        self._line2D, = self._ax.plot([], [], animated=True)
        # 获取x轴并设置为animated
        self._xaxis = self._ax.get_xaxis()
        self._xaxis.set_animated(True)
        
        # 初始化轴范围
        self._ax.set_ylim(0,30)
        self._ax.set_xlim(0,10)
        self._xlimits = (0,10)
        
        # 关键!先绘制一次画布,保存初始背景
        self._canvas.draw()
        self._background = self._canvas.copy_from_bbox(self._ax.bbox)
        
        self.show()
        
        # 启动动画,blit=True开启优化
        self._animation = animation.FuncAnimation(
            self._figure, self.Animate, None,
            interval=1000, blit=True, repeat=False
        )

    def Animate(self, index):
        allXValues, allYValues = self._dataSeriesMgr.NextXYDataSet()
        if not allXValues:
            return [self._line2D, self._xaxis]
        
        # 更新曲线数据
        self._line2D.set_data(allXValues, allYValues)
        
        # 检查是否需要滚动x轴
        if self._xlimits[1] != self._dataSeriesMgr.XLimits[1]:
            self._xlimits = self._dataSeriesMgr.XLimits
            self._ax.set_xlim(self._xlimits[0], self._xlimits[1])
            # 因为x轴范围变了,需要重新计算刻度,这里调用autoscale_view确保刻度更新
            self._ax.autoscale_view(scalex=True, scaley=False)
        
        # 关键步骤:先恢复背景,再绘制需要更新的元素
        self._canvas.restore_region(self._background)
        # 绘制曲线
        self._ax.draw_artist(self._line2D)
        # 绘制x轴(包括刻度和标签)
        self._ax.draw_artist(self._xaxis)
        
        # 更新画布的指定区域
        self._canvas.blit(self._ax.bbox)
        
        # 返回所有需要动画的元素,让FuncAnimation管理
        return [self._line2D, self._xaxis]

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    plotter = SimplePlotter()
    plotter.Plot()
    sys.exit(app.exec_())

关键改动说明

  1. 背景保存与恢复:在初始化时先绘制一次画布并保存背景,每次动画帧先恢复背景,这样只需要重绘变化的曲线和x轴,避免全量重绘。
  2. x轴纳入动画元素:把x轴对象保存下来,设置为animated=True,并且在动画函数里绘制它,同时作为返回值的一部分,让blitting系统处理它的更新。
  3. x轴刻度更新:当x轴范围滚动时,调用autoscale_view确保刻度和标签正确更新,否则x轴的刻度不会跟着变化。
  4. 明确的blit流程:手动调用restore_region恢复背景,draw_artist绘制变化元素,最后blit更新画布区域,这是blitting的标准流程。

这样修改后,你的x轴就会跟着10秒时间窗口滚动,同时保持blitting的高性能,适合同时运行多个曲线图的场景。

备注:内容来源于stack exchange,提问作者GAF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 11:29:49