0

I've been playing with the animation module from matplotlib and I realized I couldn't efficiently make a sine wave loop between two limits (in this case between -180° and 180°).

Like this...

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))
# initialize moving plots
line1, = ax.plot([], [], linewidth=2, label='sine')
line2, = ax.plot([], [], label='cosine')
ax.legend()
ax.grid()

def animate(i):
    step = np.pi/30
    # loop by hand...
    if i < 30:
        phase = i*step
    elif 30 <= i < 90:
        phase = -i*step
    elif 90 <= i < 150:
        phase = i*step
    elif 150 <= i < 210:
        phase = -i*step
    else:
        phase = i*step

    x = np.linspace(0, 0.04, 1000)
    y1 = np.sin( 2*np.pi*50*x - phase )
    y2 = 0.5*np.cos( 2*np.pi*50*x + phase )
    line1.set_data(x, y1)
    line2.set_data(x, y2)
    print('i:',i) # debug i
    return line1, line2

anim = animation.FuncAnimation(fig, animate, interval=250, blit=True)

plt.show()

The reason is because I'm using the i variable, that is used for the frames count and only increases with time. Is there a way to loop indefinitely without writing if conditions until the end of time?

From this answer I found that is posible to refresh the data from the plot, and I've manage to make it loop almost like I wanted.

Adapted example... (workaround not complete)

import matplotlib.pyplot as plt
import numpy as np

def Yvalue(t, phase):
    """Function to plot"""
    w = 2*np.pi*50
    return np.sin(w*t + phase)

plt.ion() # You probably won't need this if you're embedding things in a tkinter plot...

step = np.pi/30 # steps for phase shifting
t = np.linspace(0, 0.04) # x values
y1 = Yvalue(t, 0)   # y values

# starts figure
fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))
# Returns a tuple of line objects, thus the comma
line1, = ax.plot(t, y1, linewidth=2, label='sine')
# static plot (cosine)
ax.plot(t, np.cos(2*np.pi*50*t), label='cosine static')
ax.legend()
ax.grid()

# initial values
phase = 0
direction = 1 # 1: shifting plot to left; 0: shifting plot to right
UpperLimit = np.pi
LowerLimit = -np.pi

# magic begins...
for something in range(210):
# while 1:
    if direction and phase < UpperLimit:
        phase += step
        direction = 1
    else:
        phase -= step
        direction = 0
        # condition that helps to return to left shifting
        if phase < LowerLimit:
            direction = 1

    line1.set_ydata( Yvalue(t, phase) )
    fig.canvas.draw()

The problem with is that it doesn't allow me to close the window like it would be with the animation module. Therefore the program must be killed manually when changing the for loop by the while loop.

Traxidus Wolf
  • 808
  • 1
  • 9
  • 18
  • instead of `while 1` (or `while True`) use variable with `True` and later you may change valut to `False` to stop loop. `running = True ; while running: ... ; if close_window: running = False` – furas Dec 14 '17 at 01:40

2 Answers2

1

You would usually not use the animating function itself to calculate its animating parameter. Instead you would provide that parameter as argument to it using the frames argument.

In this case you would want the animating function to take the phase as argument. To create the phase, which is a kind of sawtooth function you can use numpy like

a = np.linspace(0,1, 30, endpoint=False)
phase = np.concatenate((a, 1-a, -a, a-1))*np.pi

Complete example:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))
line1, = ax.plot([], [], linewidth=2, label='sine')
line2, = ax.plot([], [], label='cosine')
ax.legend()
ax.grid()

x = np.linspace(0, 0.04, 1000)

a = np.linspace(0,1, 30, endpoint=False)
phase = np.concatenate((a, 1-a, -a, a-1))*np.pi

def animate(phase):
    y1 = np.sin( 2*np.pi*50*x - phase )
    y2 = 0.5*np.cos( 2*np.pi*50*x + phase )
    line1.set_data(x, y1)
    line2.set_data(x, y2)
    return line1, line2

anim = animation.FuncAnimation(fig, animate, frames=phase, interval=50, blit=True)

plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • This answer is quite simpler, following the KISS principle. Just for future reference, incrementing the time interval between repetitions (interval keyword) will make it move slower and you may also want to increment the number of points from the sawtooth (frames). – Traxidus Wolf Jan 05 '18 at 05:18
0

I don't know if I understand your problem because I don't see problem to use second method (used in for loop) inside animate

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))

# initialize moving plots
line1, = ax.plot([], [], linewidth=2, label='sine')
line2, = ax.plot([], [], label='cosine')

ax.legend()
ax.grid()

# -------------------------------------------------

def func1(t, phase):
    """Function to plot"""
    w = 2*np.pi*50
    return np.sin( w*t + phase)

def func2(t, phase):
    """Function to plot"""
    w = 2*np.pi*50
    return np.sin( w*t - phase)

# -------------------------------------------------

t = np.linspace(0, 0.04)
step = np.pi/30

UpperLimit = np.pi
LowerLimit = -np.pi

direction = 1
phase = 0

def animate(i):
    global direction 
    global phase

    if direction:
        phase += step
        if phase >= UpperLimit:
            direction = 0
    else:
        phase -= step
        if phase < LowerLimit:
            direction = 1

    line1.set_data(t, func1(t, phase))
    line2.set_data(t, func2(t, phase))

    return line1, line2

anim = animation.FuncAnimation(fig, animate, interval=250, blit=True)

plt.show()

Or even without variable direction

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))

# initialize moving plots
line1, = ax.plot([], [], linewidth=2, label='sine')
line2, = ax.plot([], [], label='cosine')

ax.legend()
ax.grid()

# -------------------------------------------------

def func1(t, phase):
    """Function to plot"""
    w = 2*np.pi*50
    return np.sin( w*t + phase)

def func2(t, phase):
    """Function to plot"""
    w = 2*np.pi*50
    return np.sin( w*t - phase)

# -------------------------------------------------

t = np.linspace(0, 0.04)
step = np.pi/30

UpperLimit = np.pi
LowerLimit = -np.pi

phase = 0

def animate(i):
    global phase
    global step

    phase += step

    if phase >= UpperLimit or phase <= LowerLimit:
       step = -step

    line1.set_data(t, func1(t, phase))
    line2.set_data(t, func2(t, phase))

    return line1, line2

anim = animation.FuncAnimation(fig, animate, interval=250, blit=True)

plt.show()
furas
  • 134,197
  • 12
  • 106
  • 148
  • What a beautiful combination of both codes and improvement at same time. It solves the problem when, for example, `while True` is needed (not having a limited amount of time like with a _for loop_). Thank you. – Traxidus Wolf Dec 14 '17 at 03:15