Search code examples
pythonmatplotlibrecursioneventspause

Exit recursion for matplotlib events


I have a little matplotlib figure with a button_press_event. Inside the listener I use plt.pause to make a short animation for every click. This works fine and as expected. However if I click again before the animation is over, I enter a recursion and the remaining animations are played at the end. If you click fast enough you can even reach the RecursionError.

What do I need to change, so a new click discards all remaining steps in the on_click method?

import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.subplots()

ax.set_xlim(0, 10)
ax.set_ylim(0, 10)

xy = np.random.random(2)*10
h1 = ax.plot(xy[0], xy[1], marker='x', color='k')[0]
h2 = ax.plot(xy[0], xy[1], marker='o', color='r')[0]

def on_click(event):
    h1.set_xdata(event.xdata)
    h1.set_ydata(event.ydata)
    for i in range(10):
        h2.set_xdata(event.xdata+np.random.random()-0.5)
        h2.set_ydata(event.ydata+np.random.random()-0.5)
        plt.pause(0.1)

cid_click = fig.canvas.mpl_connect('button_press_event', on_click)


Solution

  • You could use a FuncAnimation. Then make sure to stop and delete previous animations before a new animation starts.

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    
    fig, ax = plt.subplots()
    
    ax.set_xlim(0, 10)
    ax.set_ylim(0, 10)
    
    xy = np.random.random(2)*10
    h1 = ax.plot(xy[0], xy[1], marker='x', color='k')[0]
    h2 = ax.plot(xy[0], xy[1], marker='o', color='r')[0]
    
    anis = []
    def on_click(event):
        h1.set_xdata(event.xdata)
        h1.set_ydata(event.ydata)
        def animate(i):
            h2.set_xdata(event.xdata+np.random.random()-0.5)
            h2.set_ydata(event.ydata+np.random.random()-0.5)
        for ani in anis:
            ani.event_source.stop()
            anis.remove(ani)
            del ani
        anis.append(FuncAnimation(fig, animate, frames=10, repeat=False))
        fig.canvas.draw_idle()
    
    cid_click = fig.canvas.mpl_connect('button_press_event', on_click)
    
    plt.show()