0

I need to animate something rather simple but I do need as well a slider to let the user interactively changes the parameters of the animation. I'd like it to happen online; i.e. if the user changes a parameter while the animation is playing, the animation should transit smoothly from its old dynamics to its new one.

So far I've written a function that takes arguments and makes an animation. But it's not interactive in the sense I mentioned before. There are no sliders or anything really interactive in my code. Nevertheless, the animation part is running smoothly at least.

Here is a simplified version of my code: A point revolves around the center with a specified distance r from the center and an angular velocity w. User can give them as arguments to see the animation. (If you saw something in the code that is never used don't bother yourself with it, it is probably because I forgot to trim it from the original code with is much longer.)

import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

def simplified_code(w,r):

    fps = 36
    M = int(.75*fps*2*np.pi/w) 
    T_final = 2*np.pi/w*16

    def positions(t):
        x = r*np.cos(w*t) 
        y = r*np.sin(w*t) 
        return x,y

    fig = plt.figure()
    ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, )
    ax.grid()

    # position
    x_e, y_e = [], []  

    # trajectory and center 
    traj_e, = plt.plot([],[],'-g',lw=1)
    e, = plt.plot([], [], 'ok')

    # time
    time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes)

    def init():
        ax.set_xlim(-(r+0.5), (r+0.5))
        ax.set_ylim(-(r+0.5), (r+0.5))    
        ax.plot([0], ms=7, c='k',marker='o')
        return d,e,traj_e


    def update(frame):

        x,y = positions(frame)

        x_e.append(x)
        y_e.append(y)

        traj_e.set_data(x_e[-M:], y_e[-M:])
        e.set_data(x, y)
        time_text.set_text('time = %.1f' % frame)

        return traj_d,traj_e,d,e, orb


    return FuncAnimation(fig, update, frames=np.linspace(0, T_final, T_final*fps),
                        init_func=init, blit=True, interval=1./36*1000)

Note that it's possible to stop the animation, change the parameters via a slider and rerun it. I want to avoid this pause in the animation. I'd appreciate any help.

arash
  • 161
  • 13
  • I don't see any slider in the code. What happens if you use one? – ImportanceOfBeingErnest Jun 28 '19 at 11:19
  • @ImportanceOfBeingErnest As I mentioned, my code is not interactive and it just takes arguments as regular functions and my goal is to make it interactive. I wanted to try [jupyter interactive widget](https://ipywidgets.readthedocs.io/en/stable/examples/Using%20Interact.html#Flickering-and-jumping-output) but I realized it's too slow for what I intend to do. I'm not sure how to it with matplotlib's slider without interupting the animation. – arash Jun 28 '19 at 15:57
  • Ok, so your first problem is to create sliders at all. Check existing questions like [this](https://stackoverflow.com/questions/44227474/animation-not-working-on-matplotlib). – ImportanceOfBeingErnest Jun 28 '19 at 16:11

1 Answers1

1

Thanks to @ImportanceOfBeingErnest I managed to combine update function of the animation and the one with the sliders:

import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation 
from matplotlib.widgets import Slider

fig = plt.figure(figsize=(6,7))
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, position=[.15,.15,.75,.75] )
ax.grid()

w = 2
r = 1
fps = 36
M= 1024#
T_final = 256

x_e, y_e = [], []  
orb_x, orb_y = [], [] 

# trajectories
traj_e, = ax.plot(x_e,y_e,'-g',lw=1)

# center 
e, = ax.plot([], [], 'ok')

# orbit
orb, = ax.plot([], [], '.r',ms=1)

# time
time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes)

def positions(t):
    x = r*np.cos(w*t) # epicycle
    y = r*np.sin(w*t) # epicycle
    return x,y

def orbit(r):
    phi = np.linspace(0, 2*np.pi, 360)
    orb_x =  r*np.cos(phi)
    orb_y =  r*np.sin(phi)
    return orb_x,orb_y

def init():
    ax.plot([0], ms=7, c='k',marker='o')
    return e,traj_e

def update(t):
    global r, w
    w = s_w.val
    r = s_r.val
    ax.set_xlim(-(r)*1.1, (r)*1.1)
    ax.set_ylim(-(r)*1.1, (r)*1.1)  

    x,y = positions(t)

    x_e.append(x)
    y_e.append(y)
    traj_e.set_data(x_e[-M:-1], y_e[-M:-1])
    orb.set_data(orbit(r))
    e.set_data(x, y)

    time_text.set_text('time = %.1f' % t)
    return traj_e,e, orb

ax_w = plt.axes([0.1, 0.05, 0.35, 0.03])#, facecolor=axcolor)
ax_r = plt.axes([0.55, 0.05, 0.35, 0.03])#, facecolor=axcolor)

s_w = Slider(ax_w, r'$\omega$', -20, 20, valinit=w, valstep=0.2)
s_r = Slider(ax_r, r'r', 0, 5, valinit=r, valstep=0.2)
s_w.on_changed(update)
s_r.on_changed(update)

def anim():
    fig.canvas.draw_idle()
    return FuncAnimation(fig, update, frames=np.linspace(0, T_final, T_final*fps),
                        init_func=init, blit=True, interval=30)

anim()

Using this piece of code I can change the values of r and w without pausing or restarting the animation from scratch. Another problem appears with this code though which is that the point jumps to some random position on the circle and then jumps back the expected trajectory. I'd address it in another question.

arash
  • 161
  • 13