Reputation: 89
I'm using the matplotlib backend_qt5agg
to handle drawing a matplotlib canvas into a qt window. The user can draw rectangles (of the type matplotlib.patches.Rectangle
) on the plot and I show the rectangle by calling the canvas.draw()
method. This method could be quite slow if the canvas contains a lot of data, and I would like to speed it up.
Looking for solutions I found in the backend_qt5agg
manual that a method called drawRectangle(rect)
exists. Hoping that this method could draw only the patch without redrawing the entire canvas, I tried to call this method using as input the Rectangle patch. So I call canvas.drawRectangle(my_rect)
instead of calling canvas.draw()
. This doesn't draw anything.
Unfortunately the backend_qt5agg
manual is purely documented. So my question is: how does the drawRectangle
method work and should it perform better than redrawing the entire canvas?
Minimal example (a rectangle is shown when the mouse is clicked and moved inside the canvas) (just change self.canvas.draw()
with self.canvas.drawRectangle(self.rect)
inside on_motion
to look to test the drawRectangle
method):
import sys
import matplotlib
from PyQt5 import QtCore
import PyQt5.QtWidgets as QtW
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.patches import Rectangle
class MainWindow(QtW.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MyWindow')
self._main = QtW.QWidget()
self.setCentralWidget(self._main)
# Set canvas properties
self.fig = matplotlib.figure.Figure(figsize=(5,5))
self.canvas = FigureCanvasQTAgg(self.fig)
self.ax = self.fig.add_subplot(1,1,1)
self.rect = Rectangle((0,0), 0.2, 0.2, color='k', fill=None, alpha=1)
self.ax.add_patch(self.rect); self.rect.set_visible(False)
self.canvas.draw()
# set Qlayout properties and show window
self.gridLayout = QtW.QGridLayout(self._main)
self.gridLayout.addWidget(self.canvas)
self.setLayout(self.gridLayout)
self.show()
# connect mouse events to canvas
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
self.fig.canvas.mpl_connect('motion_notify_event', self.on_motion)
def on_click(self, event):
if event.button == 1 or event.button == 3:
# left or right click: get the x and y coordinates
if event.inaxes is not None:
self.xclick = event.xdata
self.yclick = event.ydata
self.on_press = True
def on_motion(self, event):
# draw the square
if event.button == 1 or event.button == 3 and self.on_press == True:
if (self.xclick is not None and self.yclick is not None):
x0, y0 = self.xclick, self.yclick
x1, y1 = event.xdata, event.ydata
if (x1 is not None or y1 is not None):
self.rect.set_width(x1 - x0)
self.rect.set_height(y1 - y0)
self.rect.set_xy((x0, y0))
self.rect.set_visible(True)
self.canvas.draw() # self.canvas.drawRectangle(self.rect)
if __name__ == '__main__':
app = QtCore.QCoreApplication.instance()
if app is None: app = QtW.QApplication(sys.argv)
win = MainWindow()
app.aboutToQuit.connect(app.deleteLater)
app.exec_()
Upvotes: 0
Views: 1650
Reputation: 15209
Instead of adding the rectangle yourself you could use the RectangleSelector
widget that is already included as part of matplotlib.widgets.
Here is an example, you will need to polish it and adapt it to your needs but I hope it will get you going:
import sys
import matplotlib
from PyQt5 import QtCore
import PyQt5.QtWidgets as QtW
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.widgets import RectangleSelector
class MainWindow(QtW.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MyWindow')
self._main = QtW.QWidget()
self.setCentralWidget(self._main)
# Set canvas properties
self.fig = matplotlib.figure.Figure(figsize=(5,5))
self.canvas = FigureCanvasQTAgg(self.fig)
self.ax = self.fig.add_subplot(1,1,1)
self.canvas.draw()
self.rs = RectangleSelector(self.ax, self.line_select_callback,
drawtype='box', useblit=True,
button=[1, 3], # don't use middle button
minspanx=5, minspany=5,
spancoords='pixels',
interactive=True)
# set Qlayout properties and show window
self.gridLayout = QtW.QGridLayout(self._main)
self.gridLayout.addWidget(self.canvas)
self.setLayout(self.gridLayout)
self.show()
# connect mouse events to canvas
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
def on_click(self, event):
if event.button == 1 or event.button == 3 and not self.rs.active:
self.rs.set_active(True)
else:
self.rs.set_active(False)
def line_select_callback(self, eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
print("(%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))
print(" The button you used were: %s %s" % (eclick.button, erelease.button))
if __name__ == '__main__':
app = QtCore.QCoreApplication.instance()
if app is None: app = QtW.QApplication(sys.argv)
win = MainWindow()
app.aboutToQuit.connect(app.deleteLater)
app.exec_()
Reference: https://matplotlib.org/examples/widgets/rectangle_selector.html
Upvotes: 2