matplotlib 和 PyQt:动态图形在多次加载后运行缓慢或看起来很乱 [英] matplotlib and PyQt: Dynamic figure runs slow after several loads or looks messy

查看:39
本文介绍了matplotlib 和 PyQt:动态图形在多次加载后运行缓慢或看起来很乱的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我决定重写这个以包含我的问题的工作示例.虽然这很长,但我希望它在未来证明对许多人有用.

I decided to rewrite this to include a working example of my problem. Although this is pretty long, I hope that it proves to be useful for many in the future.

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 图创建了一个画布,并将其设置为中心小部件.

I have A basic QMainWindow with a toolbar that has a "show" button. The main window also creates a canvas for a matplotlib figure and sets it to be the central widget.

当用户点击显示"按钮时,通过调用GraphCanvasdrawGraph()方法显示一些数据.在实际程序中,数据的变化取决于用户在单击按钮之前选择显示的内容.方法 resizeEvent() 基本上再次绘制图形以适应新的中央小部件大小.

When the user hits the "show" button, some data is shown by calling the drawGraph() method of GraphCanvas. In a real program that data changes depending on what the user has selected to be shown prior to clicking the button. The method resizeEvent() basically draws the figure again to accommodate the new central widget size.

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

The drawGraph() method creates four plots of which the first one has data, so it's visible. The last two lines draw the figure and saves the static background to the variable self.background.

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

When the user moves the mouse on the canvas, the background is first loaded. I wanted to save and load the static background to make the figure draw faster. After that, the last three plots get their data dynamically and are shown as 3 vertical lines that move with the mouse cursor.

问题

1) 当您继续单击显示"按钮时,图形会逐渐变慢.如果您尝试点击它 50 次并在图形上移动鼠标,您会看到垂直线更加滞后.当实际图形中的动态绘图和注释等多得多时,只需单击几下,程序就无法使用.

1) The figure becomes gradually slower as you keep clicking the "show" button. If you try hitting it like 50 times and move the mouse on the figure, you see that the vertical lines are much more laggy. When there's much more dynamic plots and annotations etc. in a real figure, the program becomes unusable after just a few clicks.

更聪明的人可能会知道为什么会发生这种放缓,但我猜之前加载的图形保存在内存中的某个地方,并且可能绘制在新创建的图形下方.而且堆栈会越来越大.

Someone wiser can probably tell why this slowdown is happening, but I guess that the previously loaded figures are kept somewhere in the memory and maybe drawn underneath the newly created figure. And the stack just keeps getting bigger and bigger.

2) 启动程序后如图所示.没什么大不了的,但我更喜欢那里只有一个空白区域,直到点击按钮.

2) The figure is shown right after starting the program. Not a huge deal, but I would prefer just a blank area there until the button is clicked.

尝试过的解决方案

我尝试的是将这两行从 class MainWindowdef __init__(self) 移动到 def showButtonClicked(self):

What I tried was that I moved these two lines from def __init__(self) of class MainWindow to def showButtonClicked(self):

self.graphLabel = GraphCanvas(self);
self.setCentralWidget(self.graphLabel)

现在看起来像这样:

def showButtonClicked(self):  
    self.graphLabel = GraphCanvas(self);
    self.setCentralWidget(self.graphLabel)
    self.graphLabel.drawGraph()

所以我只在按下按钮后创建了图形.这解决了减速问题,但带来了另一个问题.现在,当您点击显示"按钮并在画布上移动鼠标时,图形的已保存背景已加载,但原始大小为 5 x 4 英寸,因此我们一团糟.所以基本上背景不是以绘制图形的大小保存,而是以创建时的大小保存.

So I created the figure only after the button is pressed. That solves the slowdown problem but brings in another problem. Now when you hit the "show" button and move the mouse on the canvas, the saved background of the figure is loaded but in the original size of 5 by 4 inches and we have a mess. So basically the background was saved not in the size the figure was drawn, but in the size it was created in.

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

If I resize the window, however, everything works nicely. But the next time I click the "show" button, the problem reappears and I need to resize the window again.

我需要什么

我需要让这个东西流畅地工作,并且无论显示"按钮被点击多少次都应该看起来应该如此.另外,我希望图形直到第一次单击显示"按钮才显示,并且从那时起直到程序关闭才可见.

I need to make this thing work fluidly and to look as it should no matter how many times the "show" button is clicked. Also, I would prefer if the figure didn't show until the "show" button is clicked for the first time and from that point on be visible until the program is closed.

我想到了一些技巧,例如在单击显示"按钮时将窗口大小调整一个像素,但这不是正确的方法,是吗?

A few hacks come to mine like resizing the window one pixel when the "show" button is clicked, but that's not the right approach, is it?

非常欢迎任何想法和建议.谢谢.

Any ideas and suggestions are more than welcome. Thank you.

推荐答案

我已经为这个问题找到了一个不错的解决方案,直到找到更好的解决方案为止.

I have found a decent solution for the problem which will do until a better one is found.

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

The reason why the solution I tried caused a mess is that once an instance of GraphCanvas is created and set as a QCentralWidget, the QCentralWidget shrinks to the size of the GraphCanvas instance which is 500*400 in this case, and a Bbox of that size is saved. However, the figure itself uses the whole available space.

当你创建一个 GraphCanvas 并设置为 QCentralWidget 时,小部件使用 GraphCanvas 实例的大小,直到它的方法created in(及其所有父级)已完成执行.之后,他们都排队了.

When you create a GraphCanvas and set is as the QCentralWidget, the widget uses the size of the GraphCanvas instance until the method it was created in (and all its parents) has finished executing. After that they both line up.

如果我们在__init__方法中创建画布,不会造成混乱,因为在drawGraph方法中QCentralWidget的大小> 和 GraphCanvas 匹配并使用正确的 bbox 大小.但是,当我们在 showButtonClick 中创建它时,bbox 将处于错误"大小,直到方法完成.

If we create the canvas in the __init__ method, it doesn't cause a mess, because in the drawGraph method the size of QCentralWidget and GraphCanvas match and the right bbox size is used. However, when we create it in showButtonClick, the bbox will be in 'wrong' size until the method has finished.

另外,如果我们在QMainWindow__init__方法中创建一个QCentralWidget,它的大小会匹配整个的大小self.setGeometry() 设置的窗口.方法完成后,会计算QCentralWidget的大小以适应窗口,通常会变小.

In addition, if we create a QCentralWidget in the __init__ method of QMainWindow, the size of it will match the size of the whole window set by self.setGeometry(). After the method as finished, the size of QCentralWidget will be calculated to fit in the window and usually becomes smaller.

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

To solve this problem, I decided to create a dummy QWidget in the __init__ method and add that as QCentralWidget. Then in the showButtonClicked method I grab the width and height of the QCentralWidget and create a GraphCanvas using the saved width and height. That way the sizes match right from the beginning.

这里是相关代码:

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()

这篇关于matplotlib 和 PyQt:动态图形在多次加载后运行缓慢或看起来很乱的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆