Tommaso
Tommaso

Reputation: 89

Python - PyQt - matplotlib: drawRectangle method

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

Answers (1)

Isma
Isma

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

Related Questions