Canvas matplotlib和PyQt:动态图形在多次加载后运行缓慢或看起来凌乱

Canvas matplotlib和PyQt:动态图形在多次加载后运行缓慢或看起来凌乱,canvas,matplotlib,pyqt,bounding-box,slowdown,Canvas,Matplotlib,Pyqt,Bounding Box,Slowdown,编辑: 我决定重写这篇文章,以包含我问题的一个工作示例。虽然这是相当长的时间,我希望它被证明是有用的许多在未来 import sys from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from PyQt4.QtGui import * from PyQt4.QtCore import * class Main

编辑:

我决定重写这篇文章,以包含我问题的一个工作示例。虽然这是相当长的时间,我希望它被证明是有用的许多在未来

import sys

from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt4.QtGui import *
from PyQt4.QtCore import *

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setGeometry(100, 100, 640, 480)
        showButton = QPushButton('Show')

        toolbarShowButton = self.addToolBar('Show button toolbar')

        toolbarShowButton.addWidget(showButton)

        self.connect(showButton, SIGNAL('clicked()'), self.showButtonClicked)
        self.graphLabel = GraphCanvas(self);
        self.setCentralWidget(self.graphLabel)

    def showButtonClicked(self):  
        self.graphLabel.drawGraph()

    def resizeEvent(self, event):
        try:
            self.graphLabel.setFig()
        except AttributeError:
            pass

class GraphCanvas(FigureCanvas):

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

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

        self.axes = self.fig.add_subplot(111)

        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                                   QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

        self.background = None

    def drawGraph(self):
        self.axes.cla()
        self.someplot = self.axes.plot(range(1,5), range(1,5))
        self.redVert, = self.axes.plot(None, None, 'r--')
        self.greenVert, = self.axes.plot(None, None, 'g--')
        self.yellowVert, = self.axes.plot(None, None, 'y--')

        self.verticalLines = (self.redVert, self.greenVert, self.yellowVert)

        self.fig.canvas.mpl_connect('motion_notify_event', self.onMove)

        self.draw()
        self.background = self.fig.canvas.copy_from_bbox(self.axes.bbox)

    def onMove(self, event):

        # cursor moves on the canvas
        if event.inaxes:

            # restore the clean background
            self.fig.canvas.restore_region(self.background)
            ymin, ymax = self.axes.get_ylim()
            x = event.xdata - 1

            # draw each vertical line
            for line in self.verticalLines:
                line.set_xdata((x,))
                line.set_ydata((ymin, ymax))
                self.axes.draw_artist(line)
                x += 1

            self.fig.canvas.blit(self.axes.bbox)

    def setFig(self):
        '''
        Draws the canvas again after the main window
        has been resized.
        '''

        try:
            # hide all vertical lines
            for line in self.verticalLines:
                line.set_visible(False)

        except AttributeError:
            pass

        else:
            # draw canvas again and capture the background
            self.draw()
            self.background = self.fig.canvas.copy_from_bbox(self.axes.bbox)

            # set all vertical lines visible again
            for line in self.verticalLines:
                line.set_visible(True)

def main():
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    sys.exit(app.exec_())

if __name__ == '__main__': main()
代码说明

我有一个基本的
QMainWindow
,工具栏上有一个“显示”按钮。主窗口还为
matplotlib
图形创建画布,并将其设置为中心小部件

当用户点击“显示”按钮时,通过调用
GraphCanvas的
drawGraph()
方法显示一些数据。在实际程序中,数据会根据用户在单击按钮之前选择要显示的内容而变化。方法
resizeEvent()
基本上再次绘制图形以适应新的中心小部件大小

drawGraph()
方法创建了四个图,其中第一个图有数据,因此它是可见的。最后两行绘制图形并将静态背景保存到变量
self.background

当用户在画布上移动鼠标时,首先加载背景。我想保存并加载静态背景,使图形绘制更快。之后,最后三个图动态获取数据,并显示为3条垂直线,随着鼠标光标移动

问题

1) 当您不断单击“显示”按钮时,图形逐渐变慢。如果你试着敲击它50次,然后在图形上移动鼠标,你会发现垂直线要长得多。当真实图形中有更多的动态绘图和注释等时,只需单击几下程序就无法使用

更聪明的人可能知道为什么会发生这种减速,但我猜以前加载的数字会保存在内存中的某个地方,可能会被画在新创建的数字下面。而堆栈只是越来越大

2) 该图在启动程序后立即显示。没什么大不了的,但我更喜欢在点击按钮之前在那里留一个空白区域

尝试的解决方案

我尝试的是将这两行从
类主窗口的
def\uuu init\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuo(self)
移动到
def showButtonClicked(self)

现在看起来是这样的:

def showButtonClicked(self):  
    self.graphLabel = GraphCanvas(self);
    self.setCentralWidget(self.graphLabel)
    self.graphLabel.drawGraph()
所以我只是在按下按钮后才创建了这个图形。这解决了减速问题,但也带来了另一个问题。现在,当您点击“显示”按钮并在画布上移动鼠标时,已保存的图形背景已加载,但原始大小为5×4英寸,我们有一团混乱。因此,基本上背景不是以绘制图形的大小保存的,而是以创建图形的大小保存的

但是,如果我调整窗口的大小,一切都很好。但下次我单击“显示”按钮时,问题再次出现,我需要再次调整窗口大小

我需要什么

我需要让这个东西流畅地工作,无论点击多少次“显示”按钮,都要让它看起来像应该的那样。此外,我更希望在第一次单击“显示”按钮之前,图形不显示,并且从该点开始,直到程序关闭,图形才可见

我遇到了一些黑客,比如在单击“显示”按钮时将窗口大小调整一个像素,但这不是正确的方法,不是吗


欢迎提出任何意见和建议。谢谢。

我已经为这个问题找到了一个像样的解决方案,在找到更好的解决方案之前,这个解决方案会一直有效

我尝试的解决方案造成混乱的原因是,一旦创建了
GraphCanvas
的实例并将其设置为
QCentralWidget
,那么
QCentralWidget
将缩小为
GraphCanvas
实例的大小,在本例中为500*400,并且保存了该大小的
Bbox
。但是,图形本身使用了整个可用空间

创建
GraphCanvas
并将其设置为
QCentralWidget
时,小部件使用
GraphCanvas
实例的大小,直到创建它的方法(及其所有父对象)完成执行。在那之后,他们都排好队

如果我们在
\uuuu init\uuuu
方法中创建画布,它不会造成混乱,因为在
drawGraph
方法中,
QCentralWidget
GraphCanvas
的大小匹配,并且使用了正确的
bbox
大小。但是,当我们在
showButtonClick
中创建它时,
bbox
的大小将是“错误的”,直到方法完成为止

此外,如果我们在
QMainWindow
方法中创建
QCentralWidget
,它的大小将匹配
self.setGeometry()
设置的整个窗口的大小。方法完成后,将计算
QCentralWidget
的大小以适合窗口,并且通常会变小

为了解决这个问题,我决定在
\uuuu init\uuuu
方法中创建一个虚拟
QWidget
,并将其添加为
QCentralWidget
。然后在
showButtonClicked
方法中,我获取
QCentralWidget
的宽度和高度,并使用保存的宽度和高度创建一个
GraphCanvas
。这样,尺寸从一开始就匹配

以下是相关代码:

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setGeometry(100, 100, 640, 480)
        showButton = QPushButton('Show')

        toolbarShowButton = self.addToolBar('Show button toolbar')

        toolbarShowButton.addWidget(showButton)
        self.connect(showButton, SIGNAL('clicked()'), self.showButtonClicked)

        # dummy QWidget
        tempWidget = QWidget()
        self.setCentralWidget(tempWidget)

    def showButtonClicked(self):

        width = self.centralWidget().width()
        height = self.centralWidget().height()

        # convert to float (python 2) to prevent
        # flooring in the following divisions
        dpi = float(100)

        # create the canvas and replace the central widget
        self.graphLabel = GraphCanvas(self, width=width/dpi, height=height/dpi, dpi=dpi);
        self.setCentralWidget(self.graphLabel)

        self.graphLabel.drawGraph()

如果要嵌入,请不要导入
pyplot
。当
pyplot
设置了自己的gui、mainloop和canvas时,你会发现你正在破坏内部结构。谢谢你的观点!然而,这并不能解决我所面临的问题,似乎也没有什么不同。我正在用一个有效的例子重写这个问题。
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setGeometry(100, 100, 640, 480)
        showButton = QPushButton('Show')

        toolbarShowButton = self.addToolBar('Show button toolbar')

        toolbarShowButton.addWidget(showButton)
        self.connect(showButton, SIGNAL('clicked()'), self.showButtonClicked)

        # dummy QWidget
        tempWidget = QWidget()
        self.setCentralWidget(tempWidget)

    def showButtonClicked(self):

        width = self.centralWidget().width()
        height = self.centralWidget().height()

        # convert to float (python 2) to prevent
        # flooring in the following divisions
        dpi = float(100)

        # create the canvas and replace the central widget
        self.graphLabel = GraphCanvas(self, width=width/dpi, height=height/dpi, dpi=dpi);
        self.setCentralWidget(self.graphLabel)

        self.graphLabel.drawGraph()