Home > Blockchain >  Python MATPLOTLIB ANIMATION without the use of Global Variables?
Python MATPLOTLIB ANIMATION without the use of Global Variables?

Time:05-04

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:

  1. initialize the figure and axes
  2. initialize empty lines
  3. 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()
  • Related