ntmt
ntmt

Reputation: 173

matplotlib draw_artist and canvas update removing previous avxline

I'm having trouble with canvas.update() not removing the previous draw_artist except only on canvas.draw(). What I'm trying to do is draw a verticle line across 30+ subplots in a timely fashion (i.e no noticeable delay between mouse and verticle line position). If I use canvas.draw() instead of canvas.update(), there is a significant delay due to the number of subplots that are being redrawn. The code I've implemented for the mouse mouse which draws the verticle line is shown below. The hovering line which spans across all subplots is initialized already when the user double click. Is there any way to remove the delay if I use canvas.draw(), or remove the previous drawn vertical line if I use canvas.update().

def onMove(self, event):
    if (dblclicked):
       self.hoveringLine.set_xdata(event.xdata)
    self.canvas.axis1.draw_artist(self.hoveringLine)
    self.canvas.update()

Edit #2: Here is the complete code, I apologize for the incomplete comments.

from PyQt4 import QtGui, QtCore
import sys
import matplotlib

from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.figure                  import Figure


class Canvas(FigureCanvasQTAgg):

    def __init__(self, parent=None):
        self.figure = Figure()
        super(Canvas, self).__init__(self.figure)

        self.ax1 = self.figure.add_subplot(1,1,1)
        self.figure.subplots_adjust(left = 0.05, bottom = 0.02, right = 0.98, top = 0.99)
        self.setMinimumWidth(1000)
        self.ax1.plot([1,2,3])
        self.draw()

    def add_subplot(self, data=[]):
        rows = len(self.figure.axes) + 1
        for index, axes in enumerate(self.figure.axes, start=1):
            axes.change_geometry(rows, 1, index)

        ax = self.figure.add_subplot(rows, 1, index+1)
        ax.plot(data)
        ax.patch.set_facecolor('None')
        self.figure.set_figheight(self.figure.get_figheight()*1.1)



class Window(QtGui.QMainWindow):

    def __init__(self):
        super(Window, self).__init__()

        self.showMaximized()
        self.widget = QtGui.QWidget()
        self.setCentralWidget(self.widget)
        self.widget.setLayout(QtGui.QVBoxLayout())
        self.widget.layout().setContentsMargins(0,0,0,0)
        self.widget.layout().setSpacing(5)

        self.canvas = Canvas(self)
        self.scroll = QtGui.QScrollArea(self.widget)
        self.scroll.setWidget(self.canvas)
        self.scroll.setWidgetResizable(False)
        self.numSubplots = 15

        for x in range(self.numSubplots):
            self.canvas.add_subplot()
            self.canvas.adjustSize()
        self.canvas.draw()
        self.widget.layout().addWidget(self.scroll)
        self.showVline = False
        self.hoveringLine = None

        self.canvas.mpl_connect("button_press_event", self.onClick)
        self.canvas.mpl_connect("motion_notify_event", self.onMove)

    def onClick(self, event):
        if event.dblclick and self.showVline == False:
            self.showVline = True
            self.hoveringLine  = self.canvas.ax1.axvline(x=event.xdata, ymin=-1.2*self.numSubplots, ymax=1.2,
                                                     lw=2, zorder=0, clip_on=False)
        elif event.dblclick and self.showVline:
            self.showVline = False
            self.hoveringLine = None
            self.canvas.ax1.axvline(x=event.xdata, ymin=-1.2*self.numSubplots, ymax=1.2,
                                                     lw=2, zorder=0, clip_on=False)
            self.canvas.draw()
        else:
            pass

    def onMove(self, event):
        if (self.showVline):
            self.hoveringLine.set_xdata(event.xdata)
        self.canvas.ax1.draw_artist(self.hoveringLine)
        self.canvas.update()
        #self.canvas.draw() works as desired but there is a delay due to redrawing
        #self.canvas.draw()


def main():
    app = QtGui.QApplication(sys.argv)
    app.aboutToQuit.connect(app.deleteLater)
    GUI = Window()
    GUI.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Upvotes: 2

Views: 2090

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339590

A. draw_idle()

First, you would already gain a bit by using self.canvas.draw_idle() instead of self.canvas.draw(). draw_idle only draws the canvas if the program is not busy doing other stuff. It may seem counterintuitive that this should perform better, as it draws the canvas less often, but that is exactly the point: It will not try to redraw it in an instance where the canvas is already being redrawn and thus limit the number of redraw events, which would otherwise queue up and limit the performance.

B. blitting

If the above still does not give satisfactory results, you may use a technique called blitting. There is a very good answer about this here on SO: Efficient Matplotlib Redrawing

The idea is essentially to take a snapshot of the canvas and store it. On the event, this stored region may be put back into the canvas and only the updated artist needs to be redrawn.

In this case from the question, it would look as follows:

def onClick(self, event):
    
    if event.dblclick and self.showVline == False:
        self.background = self.canvas.copy_from_bbox(self.canvas.figure.bbox)
        self.showVline = True
        self.hoveringLine  = self.canvas.ax1.axvline(x=event.xdata, ymin=-1.2*self.numSubplots, ymax=1.2,
                                                 lw=2, zorder=0, clip_on=False)
    elif event.dblclick and self.showVline:
        self.showVline = False
        self.hoveringLine = None
        self.canvas.ax1.axvline(x=event.xdata, ymin=-1.2*self.numSubplots, ymax=1.2,
                                                 lw=2, zorder=0, clip_on=False)
        self.canvas.draw()
    else:
        pass

def onMove(self, event):
    if (self.showVline):
        self.canvas.restore_region(self.background)
        self.hoveringLine.set_xdata(event.xdata)
        self.canvas.ax1.draw_artist(self.hoveringLine)
        self.canvas.blit(self.canvas.figure.bbox)

Upvotes: 3

Related Questions