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.
pyplot
. See matplotlib.org/examples/user_interfaces/embedding_in_qt4.html You are mucking up the internals aspyplot
sets up its own gui, mainloop and canvas. – Remembrancer