Search code examples
pythonmatplotlibevents

Matplotlib event handling within a class


Could someone please shed some light on this?

This code is supposed to print out the event details on a mouse button press. If I connect the button_press_event slot to on_button_press (outside the class), the code works as expected. If I instead use the class method self.on_button_press, nothing is printed out. Why is that?

import matplotlib.pyplot as plt

class Modifier:
    def __init__(self, initial_line):
        self.initial_line = initial_line
        self.ax = initial_line.axes
    
        canvas = self.ax.figure.canvas
        cid = canvas.mpl_connect('button_press_event', self.on_button_press)

    def on_button_press(self, event):
        print(event)

def on_button_press(event):
    print(event)


fig, ax = plt.subplots()
ax.set_aspect('equal')
initial = ax.plot([1,2,3], [4,5,6], color='b', lw=1, clip_on=False)

Modifier(initial[0])

plt.show()

Solution

  • Because you create the object but don't save it to a variable, Python automatically destroys it immediately after you create it. We can check this by adding a __del__ method with a print call to show it is being deleted. I also added a short sleep to prove that the destruction is happening before the program terminates.

    import matplotlib.pyplot as plt
    import time
    
    class Modifier:
        def __init__(self, initial_line):
            self.initial_line = initial_line
            self.ax = initial_line.axes
        
            canvas = self.ax.figure.canvas
            cid = canvas.mpl_connect("button_press_event", self.on_button_press)
    
        def on_button_press(self, event):
            print(event)
    
        def __del__(self):
            print("Destroyed.")
    
    fig, ax = plt.subplots()
    ax.set_aspect("equal")
    initial = ax.plot([1,2,3], [4,5,6], color='b', lw=1, clip_on=False)
    
    Modifier(initial[0])
    
    print("Starting to sleep...")
    time.sleep(5)
    print("Done sleeping.")
    
    plt.show()
    

    Output:

    Destroyed.
    Starting to sleep...
    Done sleeping.
    

    So, your class instance is destroyed before the plot is shown. In the case where you use the external button function, mpl_connect still knows about the function and can use it. In the case of the class method, the class is destroyed and so mpl_connect no longer has a function to call (and I'm not sure why this happens silently).

    The simple fix is to save the object to a variable so it isn't immediately destroyed, i.e.

    m = Modifier(initial[0])
    

    Once you do this, the code will work with the class method.

    As a side note, you should probably save cid as a class attribute to ensure that remains as well.