I'm simulating real-time data by plotting values against system time every interval using FuncAnimation. I want to plot the y-axis (positional data, essentially a saw-tooth) against the current time of the animation interval over a range from the system time to 12 hours from then (i.e. 5pm- 5am). When I set this limit, no line is being drawn on the graph. What am I doing wrong?
fig, ax = plt.subplots(figsize=(10, 6))
xs = []
ys = []
random.seed(None, 2)
getcontext().prec = 3
gateStart = random.randint(0, 100)
waiting = False
returning = False
paused = False
currentTime = dt.datetime.today()
# This function is called periodically from FuncAnimation
def animate(i, xs, ys):
global gateStart, waiting, returning, paused
print(gateStart)
if gateStart == 100:
returning = True
elif gateStart == 0:
returning = False
if returning:
gateStart = round(gateStart - 0.1, 1)
else:
gateStart = round(gateStart + 0.1, 1)
# Add x and y to lists
xs.append(dt.datetime.now())
ys.append(gateStart)
# Draw x and y lists
ax.clear()
ax.plot(xs, ys)
# Format plot
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
endTime = currentTime + datetime.timedelta(hours=12)
plt.xlim([currentTime, endTime])
fig.autofmt_xdate()
plt.subplots_adjust(bottom=0.30)
plt.title('Longwall Data')
plt.ylabel('Shearer Position')
plt.ylim(0, 100)
ani = animation.FuncAnimation(fig, animate, fargs=(xs, ys), interval=1)
plt.show()
The reason for this is that the range of time series data handled by matplotlib is large, see this for matplotlib dates that appear to be too small to draw the data you are dealing with. So I draw the numbers on the x-axis as variable i, and the current time in string format as a list. For the sake of sample creation, my code is set to milliseconds. In your case it will be ts.append(dt.datetime.now().strftime('%H')).
Is this answer what you intend?
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import matplotlib.dates as mdates
import random
import datetime as dt
from IPython.display import HTML
import decimal
import numpy as np
from matplotlib.animation import PillowWriter
xs = []
ys = []
ts = []
random.seed(2021, 2)
decimal.getcontext().prec = 3
gateStart = random.randint(0, 100)
waiting = False
returning = False
paused = False
currentTime = dt.datetime.today()
fig = plt.figure(figsize=(14, 6))
ax = plt.axes(xlim=(0,100),ylim=(0, 100))
line, = ax.plot([], [], 'r-', lw=3)
ax.set_ylabel('Shearer Position')
ax.set_title('Longwall Data')
# This function is called periodically from FuncAnimation
def animate(i, xs, ys):
global gateStart, waiting, returning, paused
if gateStart == 100:
returning = True
elif gateStart == 0:
returning = False
if returning:
gateStart = round(gateStart - 1, 1)
else:
gateStart = round(gateStart + 1, 1)
# print(dt.datetime.now())
# Add x and y to lists
xs.append(i)
ys.append(gateStart)
ts.append(dt.datetime.now().strftime('%f')) # Microsecond(6)
# Draw x and y lists
line.set_data(xs, ys)
# Format plot
ax.set_xticks(np.arange(len(ts)))
ax.set_xticklabels(ts, rotation=90)
ani = FuncAnimation(fig, animate, fargs=(xs, ys), interval=200, repeat=False)
# ani.save('realtime_plot_anim.gif', writer='pillow')
plt.show()
# jupyter lab
# plt.close()
# HTML(ani.to_html5_video())
Related
I'm trying to add to an animated plot of a random variable the dates in the x axes.
I've tried different things but the code is working just with a static x-axes array..
I made a small function to update the dates array T and random var array y the I called shift().
To see how the basic code (no dates) is behaving you need to uncomment every line that is followed by "# uncomment 1". Viceversa uncomment every line that has "# uncomment 0".
I don't know why I can't plot the dates in the x-axes.
This below is the code:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import pandas as pd
import time
import datetime
plt.rcParams.update({'axes.facecolor':'lightgray'})
plt.rcParams.update({'figure.facecolor':'gray'})
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
# ax = plt.axes()
ax = plt.axes(xlim=(0, 2), ylim=(-8, 8))
line, = ax.plot([], [], lw=2)
def shift(y_arr,y_i,cont_cascata):
print("cont_cascata:",cont_cascata)
if type(y_arr)==list:
y_arr.pop(0)
y_arr = y_arr+[y_i]
if type(y_arr) is np.ndarray:
print("np.array..")
y_arr = np.delete(y_arr, 0) # togliamo primo
y_arr = np.append(y_arr, y_i) # aggiungiamo ultimo
return y_arr
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
# animation function. This is called sequentially
# pd._libs.tslibs.timestamps.Timestamp
def ts_array(n):
t0 = pd.Timestamp(2018,1,1,12,30)
T = []
for i in range(n):
t_i = t0+pd.Timedelta(minutes=i)
T = T+[t_i]
return T
# tarr = ts_array(n=100)
def animate(i):
global y,x,T
n = 100
if i==0:
y = np.round(np.random.normal(loc=0, scale=2, size=n), decimals=2)
x = np.linspace(0, 2, n) # uncomment 0
T = ts_array(n) # uncomment 1
y_i = np.round(np.random.normal(loc=0,scale=2),decimals=2)
t_i = T[-1]+pd.Timedelta(minutes=1) # uncomment1
y = shift(y_arr=y,y_i=y_i, cont_cascata=i)
T = shift(y_arr=T,y_i=t_i,cont_cascata=i) # uncomment 1
T = pd.DatetimeIndex(T) # uncomment 1
T = T.to_pydatetime() # uncomment 1
# line.set_data(x, y) # uncomment 0
line.set_data(T,y) # uncomment 1
time.sleep(0.5)
return line,
print("animate")
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
plt.show()
What should I do to make this code work properly? Thanks
You need to adjust the xlim of the axes to account for date time. Inside animate, just after line.set_data(T,y), try adding this:
ax.set_xlim(T.min(), T.max())
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.dates as mdates
import pandas as pd
import time
import datetime
plt.rcParams.update({'axes.facecolor': 'lightgray'})
plt.rcParams.update({'figure.facecolor': 'gray'})
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure() # include
ax = plt.axes() # include
# ax = plt.axes(xlim=(0, 2), ylim=(-8, 8))
line, = ax.plot([], [], lw=2)
## tilt dates
plt.setp(ax.xaxis.get_majorticklabels(), rotation=35)
def shift(y_arr, y_i, cont_cascata):
# print("cont_cascata:",cont_cascata)
if type(y_arr) == list:
y_arr.pop(0)
y_arr = y_arr + [y_i]
if type(y_arr) is np.ndarray:
# print("np.array..")
y_arr = np.delete(y_arr, 0) # togliamo primo
y_arr = np.append(y_arr, y_i) # aggiungiamo ultimo
return y_arr
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
line.axes.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M")) # "%Y-%m-%d %H:%M:%S"
return line,
# animation function. This is called sequentially
# pd._libs.tslibs.timestamps.Timestamp
def ts_array(n):
t0 = pd.Timestamp(2018, 1, 1, 12, 00)
T = []
for i in range(n):
t_i = t0 + pd.Timedelta(minutes=i)
T = T + [t_i]
return T
def animate(i):
global y, x, T
print("i:", i)
n = 10
if i == 0:
y = np.round(np.random.normal(loc=0, scale=2, size=n), decimals=2)
x = np.linspace(6, 2, n) # uncomment 0
T = ts_array(n) # uncomment 1
y_i = np.round(np.random.normal(loc=6, scale=2), decimals=2)
t_i = T[-1] + pd.Timedelta(minutes=1) # uncomment1
y = shift(y_arr=y, y_i=y_i, cont_cascata=i)
T = shift(y_arr=T, y_i=t_i, cont_cascata=i) # uncomment 1
T = pd.DatetimeIndex(T) # uncomment 1
T = T.to_pydatetime() # uncomment 1
# line.set_data(x, y) # uncomment 0
line.set_data(T, y) # uncomment 1
ax.relim(visible_only=True)
ax.autoscale()
# ax.autoscale_view(True,True,True)
# time.sleep(0.5)
return line,
print("animate")
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=500) # blit = True
plt.show()
I have a running times dataset that I have broken down into six months (Jan - Jun). I want to plot an animation of a scatter plot showing distance on the x-axis and time on the y-axis.
Without any animations I have:
plt.figure(figsize = (8,8))
plt.scatter(data = strava_df, x = 'Distance', y = 'Elapsed Time', c = col_list, alpha = 0.7)
plt.xlabel('Distance (km)')
plt.ylabel('Elapsed Time (min)')
plt.title('Running Distance vs. Time')
plt.show()
Which gives me:
What I'd like is an animation that plots the data for the first month, then after a delay the second month, and so on.
from matplotlib.animation import FuncAnimation
fig = plt.figure(figsize=(10,10))
ax = plt.axes(xlim=(2,15), ylim=(10, 80))
x = []
y = []
scat = plt.scatter(x, y)
def animate(i):
for m in range(0,6):
x.append(strava_df.loc[strava_df['Month'] == m,strava_df['Distance']])
y.append(strava_df.loc[strava_df['Month'] == m,strava_df['Elapsed Time']])
FuncAnimation(fig, animate, frames=12, interval=6, repeat=False)
plt.show()
This is what I've come up with, but it isn't working. Any advice?
The animate function should update the matplotlib object created by a call to scat = ax.scatter(...) and also return that object as a tuple. The positions can be updated calling scat.set_offsets() with an nx2 array of xy values. The color can be updated with scat.set_color() with a list or array of colors.
Supposing col_list is a list of color names or rgb-values, the code could look like:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pandas as pd
import numpy as np
strava_df = pd.DataFrame({'Month': np.random.randint(0, 6, 120),
'Distance': np.random.uniform(2, 13, 120),
'Color': np.random.choice(['blue', 'red', 'orange', 'cyan'], 120)
})
strava_df['Elapsed Time'] = strava_df['Distance'] * 5 + np.random.uniform(0, 5, 120)
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(xlim=(2, 15), ylim=(10, 80))
scat = ax.scatter([], [], s=20)
def animate(i):
x = np.array([])
y = np.array([])
c = np.array([])
for m in range(0, i + 1):
x = np.concatenate([x, strava_df.loc[strava_df['Month'] == m, 'Distance']])
y = np.concatenate([y, strava_df.loc[strava_df['Month'] == m, 'Elapsed Time']])
c = np.concatenate([c, strava_df.loc[strava_df['Month'] == m, 'Color']])
scat.set_offsets(np.array([x, y]).T)
scat.set_color(c)
return scat,
anim = FuncAnimation(fig, animate, frames=12, interval=6, repeat=False)
plt.show()
I am trying to plot an animation of a physics system. I've worked out the equations and it plots, but there's a second plot I don't want that keeps showing up and I can't get it to stop.
import numpy as np
from scipy import integrate
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
# Input constants
m = 10 # mass (kg)
L = 4 # length (m)
g = 9.81 # gravity (m/s^2)
dt = 0.1 # time step size (seconds)
t_max = 40 # max sim time (seconds)
num_steps = 1 + int(t_max/dt)
theta_0 = np.pi/2 # initial angle (radians)
theta_dot_0 = 0 # initial angular velocity (rad/s)
state0 = [theta_0,theta_dot_0]
# Get timesteps
time_index = np.arange(0, t_max + dt, dt)
def derivatives(state, time_index, L=L, m=m, g=g):
theta_dot = state[1]
theta_ddot = -g*np.sin(state[0])/L
return theta_dot,theta_ddot
output = integrate.odeint(derivatives, state0, time_index)
theta = output[:,0]
theta_dot = output[:,1]
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=True, xlim=(-2*L, 2*L), ylim=(-2*L, 2*L))
ax.set_aspect('equal')
ax.grid()
line, = ax.plot([], [], 'o-', lw=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
x = L*np.sin(theta)
y = -L*np.cos(theta)
def init():
# create empty object to put the points in
line.set_data([], [])
time_text.set_text('')
return line, time_text
def animate(t):
x_t = [0, x[t]]
y_t = [0, y[t]]
# add the point to the line
line.set_data(x_t, y_t)
# add the time text to plot
# time_template will be the text next to thetime
time_text.set_text(time_template % (t*dt))
return line, time_text
# we don't want the range of the steps to start at 0
# start it at one
ani = animation.FuncAnimation(fig, animate, range(1, num_steps),
interval=dt*1000, blit=True, init_func=init)
rc('animation', html='jshtml')
#ani.save('pendulum.mp4', fps=15)
ani
Here's the output:
The plot I want to get rid of is the one I circled with red. This is the entirety of my code, so it should be completely reproducible.
I tried several variations of trimming the plotting code but I wasn't able to debug why it's happening.
How can I get rid of this second plot?
A simple plt.close() before your call to ani will do the job.
Last few lines:
ani = animation.FuncAnimation(fig, animate, range(1, num_steps),
interval=dt*1000, blit=True, init_func=init)
rc('animation', html='jshtml')
#ani.save('pendulum.mp4', fps=15)
plt.close()
ani
Demo:
More info at this link.
I have been at it a day and a half and I guess it's time to call for some help. The following code gives the error:
TypeError: float() argument must be a string or a number, not
'datetime.datetime'
I try to put the datetime variable generated in function frames1, on the x-axis through the animation function.
Code:
import random
import time
from matplotlib import pyplot as plt
from matplotlib import animation
import datetime
# Plot parameters
fig, ax = plt.subplots()
line, = ax.plot([], [], 'k-', label = 'ABNA: Price', color = 'blue')
legend = ax.legend(loc='upper right',frameon=False)
plt.setp(legend.get_texts(), color='grey')
ax.margins(0.05)
ax.grid(True, which='both', color = 'grey')
# Creating data variables
x = []
y = []
x.append(1)
y.append(1)
def init():
line.set_data(x[:1],y[:1])
return line,
def animate(args):
# Args are the incoming value that are animated
animate.counter += 1
i = animate.counter
win = 60
imin = min(max(0, i - win), len(x) - win)
x.append(args[0])
y.append(args[1])
xdata = x[imin:i]
ydata = y[imin:i]
line.set_data(xdata, ydata)
line.set_color("red")
plt.title('ABNA CALCULATIONS', color = 'grey')
plt.ylabel("Price", color ='grey')
plt.xlabel("Time", color = 'grey')
ax.set_facecolor('black')
ax.xaxis.label.set_color('grey')
ax.tick_params(axis='x', colors='grey')
ax.yaxis.label.set_color('grey')
ax.tick_params(axis='y', colors='grey')
ax.relim()
ax.autoscale()
return line, #line2
animate.counter = 0
def frames1():
# Generating time variable
x = 10
target_time = datetime.datetime.now().strftime("%d %B %Y %H:%M:%000")
# Extracting time
FMT = "%d %B %Y %H:%M:%S"
target_time = datetime.datetime.strptime(target_time, FMT)
target_time = target_time.time().isoformat()
# Converting to time object
target_time = datetime.datetime.strptime(target_time,'%H:%M:%S')
while True:
# Add new time + 60 seconds
target_time = target_time + datetime.timedelta(seconds=60)
x = target_time
y = random.randint(250,450)/10
yield (x,y)
time.sleep(random.randint(2,5))
anim = animation.FuncAnimation(fig, animate,init_func=init,frames=frames1)
plt.show()
I have tried the following solutions:
Plotting dates on the x-axis with Python's matplotlib
Changing the formatting of a datetime axis in matplotlib
With no positive outcome so far.
I very much thank you in advance for looking at this problem.
Not sure why you append 1 to your array in the first place. I guess you mean
# Creating data variables
x = []
y = []
x.append(datetime.datetime.now())
y.append(1)
Then inside the generator function, there is a lot I don't understand. To me it seems you can leave out most of the back and forth conversion and just use now() as it is.
def frames1():
# Generating time variable
target_time = datetime.datetime.now()
while True:
# Add new time + 60 seconds
target_time = target_time + datetime.timedelta(seconds=60)
x = target_time
y = random.randint(250,450)/10
yield (x,y)
time.sleep(random.randint(2,5))
You may however format the axis to show times instead of numbers. Inside the init function you may add
line.axes.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
where you have imported matplotlib.dates as mdates.
The line imin = min(max(0, i - win), len(x) - win) does not seem to make much sense, why not use max(0, i - win) alone?
So in total a working version could look like this:
import random
import time
from matplotlib import pyplot as plt
import matplotlib.dates as mdates
from matplotlib import animation
import datetime
# Plot parameters
fig, ax = plt.subplots()
line, = ax.plot([], [], 'k-', label = 'ABNA: Price', color = 'blue')
legend = ax.legend(loc='upper right',frameon=False)
plt.setp(legend.get_texts(), color='grey')
ax.margins(0.05)
ax.grid(True, which='both', color = 'grey')
# Creating data variables
x = [datetime.datetime.now()]
y = [1]
def init():
line.set_data(x[:1],y[:1])
line.axes.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
return line,
def animate(args):
# Args are the incoming value that are animated
animate.counter += 1
i = animate.counter
win = 60
imin = max(0, i - win)
x.append(args[0])
y.append(args[1])
xdata = x[imin:i]
ydata = y[imin:i]
line.set_data(xdata, ydata)
line.set_color("red")
plt.title('ABNA CALCULATIONS', color = 'grey')
plt.ylabel("Price", color ='grey')
plt.xlabel("Time", color = 'grey')
ax.set_facecolor('black')
ax.xaxis.label.set_color('grey')
ax.tick_params(axis='x', colors='grey')
ax.yaxis.label.set_color('grey')
ax.tick_params(axis='y', colors='grey')
ax.relim()
ax.autoscale()
return line,
animate.counter = 0
def frames1():
# Generating time variable
target_time = datetime.datetime.now()
while True:
# Add new time + 60 seconds
target_time = target_time + datetime.timedelta(seconds=60)
x = target_time
y = random.randint(250,450)/10
yield (x,y)
time.sleep(random.randint(2,5))
anim = animation.FuncAnimation(fig, animate,init_func=init,frames=frames1)
plt.show()
I have a static plot which calculates in one step and a dynamically updating plot (animated). My codes displays it correctly but in different windows, how can I combine it in one plot window.
I am getting solutions for animating two plots simultaneously but not anything with one static and another dynamic
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import math
E50 = 5000
c = 5
phi = math.radians(30)
sig3 = 100
a = c/math.tan(phi)
# deviatoric load
qa = (sig3+a)*(2*math.sin(phi))/(1-math.sin(phi))
print(qa)
ultimateLoad = 200
def hyperbola():
stress = []
strain = []
q = 0
while q < ultimateLoad:
stress.append(q)
eps1 = (qa/(2*E50)) * (q/(qa-q))
strain.append(eps1)
q +=10
return strain, stress
def plotHyperbola():
strain, stress = hyperbola()
plt.plot(strain, stress ,'bo', linewidth=5, label='Existing Kernel' )
def data_gen():
load = 0
while load < ultimateLoad:
load += 10
# finally this yield function should give x any that needs to be plotted
yield load/5000, load
def init():
ax.set_ylim(-1.1, 300)
ax.set_xlim(0, 0.1)
del xdata[:]
del ydata[:]
line.set_data(xdata, ydata)
return line,
fig, ax = plt.subplots()
line, = ax.plot([], [], 'ro', lw=2)
ax.grid()
xdata, ydata = [], []
def run(data):
# update the data
t, y = data
xdata.append(t)
ydata.append(y)
xmin, xmax = ax.get_xlim()
plotHyperbola()
if t >= xmax:
ax.set_xlim(xmin, 2*xmax)
ax.figure.canvas.draw()
line.set_data(xdata, ydata)
return line,
# interval control the time in between each iteration
# repeat whether the whole process needs to be repeated
ani = animation.FuncAnimation(fig, run, data_gen, interval=1,
repeat=False, init_func=init)
plt.show()