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