matplotlib和PyQt:动态图运行缓慢后几个负载或看起来凌乱 [英] matplotlib and PyQt: Dynamic figure runs slow after several loads or looks messy

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

问题描述

编辑:



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

  import sys 

from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as figureCanvas
从matplotlib.figure import图
从PyQt4.QtGui import *
从PyQt4.QtCore import *

class MainWindow(QMainWindow):
def __init __(self):
super(MainWindow,self).__ init __()

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

toolbarShowButton = self.addToolBar('显示按钮工具栏')

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事件):
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 = 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):

#光标在画布上移动
if event.inaxes:

#恢复干净的背景
self.fig.canvas.restore_region(self.background)
ymin,ymax = self.axes.get_ylim()
x = event.xdata - 1

#绘制每条垂直线
在self.verticalLines中的行:
line.set_xdata((x,))
.set_ydata((ymin,ymax))
self.axes.draw_artist(line)
x + = 1

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

def setFig(self):
'''
在主窗口
调整大小后再次绘制画布。
'''

try:
#隐藏所有垂直线
在self.verticalLines中的行:
line.set_visible(False)

except AttributeError:
pass

else:
#再次绘制画布并捕获背景
self.draw()
self。 background = self.fig.canvas.copy_from_bbox(self.axes.bbox)

#设置所有垂直线条可见
在self.verticalLines中的行:
line.set_visible True)

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

如果__name__ =='__main__':main()

代码说明



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



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



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



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



问题 / p>

1)随着您点击显示按钮,图形会逐渐变慢。如果你尝试点击它50次,将鼠标移动到图形上,你会发现垂直线条更加滞后。当实际数字中有更多动态图表和注释等时,只需点击几下即可使程序无法使用。



有人更聪明可能会知道为什么会出现这种减速,但我猜,以前加载的数字保存在内存中的某处,并可能绘制在新创建的数字下面。



2)该图在程序启动后立即显示。



尝试的解决方案



我试过的是,我从中移动这两行从_ code> def Main_Window code>到 def showButtonClicked(self)

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

现在看起来像这样: p>

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

所以我只有在按下按钮后才创建图形。这解决了减速问题,但带来了另一个问题。现在,当你点击显示按钮,并移动鼠标在画布上,保存的图形背景加载,但原始大小为5×4英寸,我们有一个混乱。所以基本上保存的背景不是在绘制的大小,而是在它的大小创建。



如果我调整窗口大小,但是,一切工作很好。但是下次我点击显示按钮,问题再次出现,我需要重新调整窗口大小。



我需要 / p>

我需要使这个东西流畅地工作,看起来应该无论多少次显示按钮被点击。此外,我宁愿如果数字没有显示,直到显示按钮第一次点击,从那一点是可见的,直到程序关闭。



一些黑客来到我的,像调整窗口一个像素,当显示按钮被点击,但这不是正确的方法,是吗?



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

解决方案

我找到一个合适的解决方案,直到找到更好的解决方案。 >

我尝试的解决方案造成混乱的原因是,一旦 GraphCanvas 的实例被创建并设置为 QCentralWidget QCentralWidget 缩小到 GraphCanvas 在这种情况下为500 * 400,并且保存该大小的 Bbox



当你创建一个 GraphCanvas 并设置为 QCentralWidget ,该小部件使用 GraphCanvas 实例的大小,直到在其中创建的方法(及其所有父项)完成执行。



如果我们在 __ init __ 方法中创建画布,因为在 drawGraph 方法中的大小 QCentralWidget GraphCanvas 匹配,使用右侧 bbox 大小。然而,当我们在 showButtonClick 中创建它时, bbox 将在错误大小中,直到方法完成。



此外,如果我们在 __ init __ QCentralWidget c> c> 方法,它的大小将匹配整个窗口的大小 self.setGeometry()。在方法完成后, QCentralWidget 的大小将被计算以适合窗口,通常变得更小。



为了解决这个问题,我决定在 __ init __ 方法中创建一个 QWidget $ c> QCentralWidget 。然后在 showButtonClicked 方法中,抓取 QCentralWidget 的宽度和高度,并创建一个 GraphCanvas 使用保存的宽度和高度。



这里是相关的代码:

 

code> class MainWindow(QMainWindow):
def __init __(self):
super(MainWindow,self).__ init __()

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

toolbarShowButton = self.addToolBar('显示按钮工具栏')

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

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

def showButtonClicked(self):

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

#转换为float(python 2)以防止
#在以下部分中分配
dpi = float(100)

#create画布和替换中心窗口小部件
self.graphLabel = GraphCanvas(self,width = width / dpi,height = height / dpi,dpi = dpi);
self.setCentralWidget(self.graphLabel)

self.graphLabel.drawGraph()


EDIT:

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

Description of the code

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.

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.

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.

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.

The problem

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

A solution tried

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)

So it looks now like this:

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

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.

What I need

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.

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.

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.

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.

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.

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.

So here's the relevant code:

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天全站免登陆