QUESTION: Whats the cleanest and simplest way to use Python's MATPLOTLIB animation function without the use of global array's or constantly appending a global "list of data points" to a plot?
Here is an example of a animated graph that plots the bid and ask sizes of a stock ticker. In this example the variables time[], ask[], and bid[] are used as global variables.
How do we modify the matplotlib animate() function to not use global variables?
so I'm trying to remove "all" global variables and just run one function call...
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
from random import randint
stock = {'ask': 12.82, 'askSize': 21900, 'bid': 12.81, 'bidSize': 17800}
def get_askSize():
return stock["askSize"] randint(1,9000) # grab a random integer to be the next y-value in the animation
def get_bidSize():
return stock["bidSize"] randint(1,9000) # grab a random integer to be the next y-value in the animation
def animate(i):
pt_ask = get_askSize()
pt_bid = get_bidSize()
time.append(i) #x
ask.append(pt_ask) #y
bid.append(pt_bid) #y
ax.clear()
ax.plot(time, ask)
ax.plot(time, bid)
ax.set_xlabel('Time')
ax.set_ylabel('Volume')
ax.set_title('ask and bid size')
ax.set_xlim([0,40])
#axis = axis_size(get_bidSize, get_askSize)
ylim_min = (get_askSize() get_bidSize())/6
ylim_max = (get_askSize() get_bidSize())
ax.set_ylim([ylim_min,ylim_max])
# create empty lists for the x and y data
time = []
ask = []
bid = []
# create the figure and axes objects
fig, ax = plt.subplots()
# run the animation
ani = FuncAnimation(fig, animate, frames=40, interval=500, repeat=False)
plt.show()
CodePudding user response:
You can use the fargs
parameter of FuncAnimation
to provide additional arguments to your animate
callback function. So animate
might start like
def animate(i, askSize, bidSize):
...
and in the call of FuncAnimation
, you would add the parameter fargs=(askSize, bidSize)
. Add whatever variables (in whatever form) that you need to make available within the animate
function.
I use this in my example of the use of FuncAnimation
with AnimatedPNGWriter
in the package numpngw
; see Example 8. In that example, my callback function is
def update_line(num, x, data, line):
"""
Animation "call back" function for each frame.
"""
line.set_data(x, data[num, :])
return line,
and FuncAnimation
is created with
ani = animation.FuncAnimation(fig, update_line, frames=len(t),
init_func=lambda : None,
fargs=(x, sol, lineplot))
CodePudding user response:
As @Warren mentioned, you can use the fargs parameter to pass in shared variables to be used in your animation function.
You should also precompute all of your points, and then use your frames to merely act as an expanding window on those frames. This will be a much more performant solution and prevents you from needing to convert between numpy arrays and lists on every tick of your animation in order to update the underlying data for your lines.
This also enables you to precompute your y-limits to prevent your resultant plot from jumping all over the place.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
rng = np.random.default_rng(0)
def animate(i, ask_line, bid_line, data):
i = 1
x = data['x'][:i]
ask_line.set_data(x, data['ask'][:i])
bid_line.set_data(x, data['bid'][:i])
stock = {'ask': 12.82, 'askSize': 21900, 'bid': 12.81, 'bidSize': 17800}
frames = 40
data = {
'x': np.arange(0, frames),
'ask': stock['askSize'] rng.integers(0, 9000, size=frames),
'bid': stock['bidSize'] rng.integers(0, 9000, size=frames),
}
fig, ax = plt.subplots()
ask_line, = ax.plot([], [])
bid_line, = ax.plot([], [])
ax.set(xlabel='Time', ylabel='Volume', title='ask and bid size', xlim=(0, 40))
ax.set_ylim(
min(data['ask'].min(), data['bid'].min()),
max(data['ask'].max(), data['bid'].max()),
)
# run the animation
ani = FuncAnimation(
fig, animate, fargs=(ask_line, bid_line, data),
frames=40, interval=500, repeat=False
)
plt.show()
CodePudding user response:
You are using animation wrong, as you are adding and removing lines at each iteration, which makes the animation a lot slower. For line plots, the best way to proceed is:
- initialize the figure and axes
- initialize empty lines
- inside the
animate
function, update the data of each line.
Something like this:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
from random import randint
stock = {'ask': 12.82, 'askSize': 21900, 'bid': 12.81, 'bidSize': 17800}
def get_askSize():
return stock["askSize"] randint(1,9000) # grab a random integer to be the next y-value in the animation
def get_bidSize():
return stock["bidSize"] randint(1,9000) # grab a random integer to be the next y-value in the animation
def add_point_to_line(x, y, line):
# retrieve the previous data in the line
xd, yd = [list(t) for t in line.get_data()]
# append the new point
xd.append(x)
yd.append(y)
# set the new data
line.set_data(xd, yd)
def animate(i):
pt_ask = get_askSize()
pt_bid = get_bidSize()
# append a new value to the lines
add_point_to_line(i, pt_ask, ax.lines[0])
add_point_to_line(i, pt_bid, ax.lines[1])
# update axis limits if necessary
ylim_min = (get_askSize() get_bidSize())/6
ylim_max = (get_askSize() get_bidSize())
ax.set_ylim([ylim_min,ylim_max])
# create the figure and axes objects
fig, ax = plt.subplots()
# create empty lines that will be populated on the animate function
ax.plot([], [])
ax.plot([], [])
ax.set_xlabel('Time')
ax.set_ylabel('Volume')
ax.set_title('ask and bid size')
ax.set_xlim([0,40])
# run the animation
ani = FuncAnimation(fig, animate, frames=40, interval=500, repeat=False)
plt.show()