1

I tried to implement the workaround mentioned here to the problem of the figure not updating properly when a draw_event is triggered once a user zooms into a plot. This was meant to improve this answer. In the solution, a Timer is added to the Canvas of the Figure, which delays the draw function for a short while.

In the linked answer this is done for a single figure and a reference to the timer is stored as self.timer to the class that contains the update function. I would like to have an a bit more general behaviour and allow this for many Figure instances. Therefore I tried not to save the Timer reference (I don't need it anymore). But when I do so, the script crashes with a Segmentation Fault (no traceback). Here is my code:

from matplotlib import pyplot as plt
import numpy as np

##plt.switch_backend('TkAgg')

class DrawEventHandler:

    def __init__(self):
        x = np.linspace(0,1,10)
        y = np.sin(x)

        self.fig, self.ax = plt.subplots()
        self.ax.plot(x,y)

        self.ax.figure.canvas.mpl_connect('draw_event', self.update)


    def update(self, event = None):
        self._redraw_later_ok()
        ##self._redraw_later_not_ok(self.fig)

    def _redraw_later_ok(self):
        self.timer = self.fig.canvas.new_timer(interval=10)
        self.timer.single_shot = True
        self.timer.add_callback(lambda : self.fig.canvas.draw_idle())
        self.timer.start()

    def _redraw_later_not_ok(self, fig):
        print('start')
        timer = fig.canvas.new_timer(interval=10)
        timer.single_shot = True
        timer.add_callback(lambda : fig.canvas.draw_idle())
        timer.start()
        print('stop')    

if __name__ == '__main__':
    my_handler = DrawEventHandler()
    plt.show()

The original solution is implemented as _redraw_later_ok(), which works fine for me. The problematic solution is called `_redraw_later_not_ok()', which produces this output:

start
stop
Segmentation fault: 11

I use a Mac with High Sierra, Python 3.6.4 or Python 2.7.14 and Matplotlib 2.1.1 with the MacOSX backend. When I switch to the TkAgg backend (which is terribly slow), the code works fine. Can anybody explain what is going on?

Thomas Kühn
  • 9,412
  • 3
  • 47
  • 63

1 Answers1

1

As usual with interactive features which use the event loop of a GUI you need to keep a reference to the object that handles the callback.

The problem with the approach of not storing the timer in this code

def _redraw_later_not_ok(self, fig):
    print('start')
    timer = fig.canvas.new_timer(interval=10)
    timer.single_shot = True
    timer.add_callback(lambda : fig.canvas.draw_idle())
    timer.start()
    print('stop')

is that timer gets garbage collected once the _redraw_later_not_ok function terminates (i.e. directly after stop is printed). At this point there would be a pointer to a place in memory, which may or may not store the callback (any more). An attempt by the python interpreter to call the callback would hence (most probably) fail.

The solution is indeed to always keep a reference to the object that steers the callback. This is the solution shown in _redraw_later_ok, where the timer is made a class attribute, self.timer. Such that it can later be used at the time the callback is called.

I do not understand in how far using this working approach would prevent the use of several figures, so it may make sense to create a MWE with two figures that shows the problem clearly.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712