0

I am attempting to show a sort of plot refresh on a sequence of array. Each array is an instance of the sequence and for each of them I want to plot the relative array, plus its smoothed version, like that: these frames should be showed in sequence thanks to the animation function Anyway the following error " AttributeError: 'list' object has no attribute 'get_zorder' " arises when I return the relative lines from the animation function. I tried solutions from other questions and channels but nothing really worked. Here is the code:

import random

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
from scipy.signal import savgol_filter

n_instances = 1000  # the instances that will be generated
instances_duration = 100  # the duration of each of them

# starting the data matrix
process_instances = np.zeros((n_instances, instances_duration))
# starting the filtered data matrix
filtered_instances = np.zeros((n_instances, instances_duration))

np.random.seed(2)
for i in range(n_instances):
    # creating the instance as a random array
    current_instance = np.random.normal(0, 1, instances_duration)
    # assigning to the relative matrix
    process_instances[i, :] = current_instance
    # filtering and assigning to the relative matrix
    filtered_instances[i, :] = savgol_filter(current_instance, 11, 3)

# managing the plots
fig, axs = plt.subplots()
axs.set_ylim([-3, 3])
axs.set_xlim([0, instances_duration])
axs.grid(True)
lines = axs.plot(process_instances[0, :], alpha=0.3, label='original')  # starting the main lines
lines_filt = axs.plot(filtered_instances[0, :], label='filtered')  # starting the filtered lines
axs.legend()


def animate(frame):
    # updating lines
    for columns, line in enumerate(lines):
        line.set_ydata(process_instances[frame, :])
    # updating filtered lines
    for columns, line in enumerate(lines_filt):
        line.set_ydata(filtered_instances[frame, :])
    print("Showing frame number: " + str(frame))
    return [lines, lines_filt]


animation = FuncAnimation(fig, animate, interval=1000, blit=True, repeat=True)
animation.event_source.start()
plt.show()
Foolvio
  • 15
  • 3
  • I cannot reproduce this problem; my error message is `RuntimeError: The animation function must return a sequence of Artist objects.` Is this indeed the code that generates your error message? If so, what is your matplotlib version and environment (backend, OS, Python version)? Here, matplotlib 3.5.1 with Qt5Agg backend. – Mr. T Mar 21 '22 at 14:01
  • I'm using matplotlib 3.3.4 with Python 3.9.7 – Foolvio Mar 21 '22 at 14:07
  • With 3.3.4, I get the same error message as you. Seemingly, newer versions unpack lists now. Anyhow, how would the layout for three axis objects look like? You did not define this in your example (e.g., `fig, axs = plt.subplots(1, 3, sharex=True, sharey=True)`). Where are the data stored for each of the three axis objects? I don't assume they all will show the same data. – Mr. T Mar 21 '22 at 14:23
  • Sorry, didn't get what you asked, I'll try out: I only have one plot axis, it must contain both the arrays to show the filter action. So I don't really need to set the figure layout, do I? Data are stored in the matrixes: each row contains a frame, each matrix contains a version of the same frame (non filtered and filtered), and I would like to show both version kind of overlayed, one over the other (as the figure shows). Hope to answered :D – Foolvio Mar 21 '22 at 14:46
  • If you are wondering why do I use the subplots method, is because I'm planning to add other things but I won't until I will be able to do one thing at time – Foolvio Mar 21 '22 at 14:49
  • I assumed your sample image shows three axis objects but it seems it is three frames of the same. Is this correct? – Mr. T Mar 21 '22 at 14:50
  • ok maybe I didn't express that correctly, sorry. Correct, that picture shows three different frames of the same plot, I'm changing it to make it more clear – Foolvio Mar 21 '22 at 14:57

1 Answers1

0

Only minor changes are necessary. Mainly, you have to unpack the Line2D objects for the animation loop

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
from scipy.signal import savgol_filter

n_instances = 1000  # the instances that will be generated
instances_duration = 100  # the duration of each of them

# starting the data matrix
process_instances = np.zeros((n_instances, instances_duration))
# starting the filtered data matrix
filtered_instances = np.zeros((n_instances, instances_duration))

np.random.seed(2)
for i in range(n_instances):
    # creating the instance as a random array
    current_instance = np.random.normal(0, 1, instances_duration)
    # assigning to the relative matrix
    process_instances[i, :] = current_instance
    # filtering and assigning to the relative matrix
    filtered_instances[i, :] = savgol_filter(current_instance, 11, 3)

# managing the plots
fig, axs = plt.subplots()
axs.set_ylim([-3, 3])
axs.set_xlim([0, instances_duration])
axs.grid(True)
#unpack the Line2D artists
lines, = axs.plot(process_instances[0, :], alpha=0.3, label='original')  # starting the main lines
lines_filt, = axs.plot(filtered_instances[0, :], label='filtered')  # starting the filtered lines
axs.legend()


def animate(frame):
    # updating lines
    lines.set_ydata(process_instances[frame, :])
    # updating filtered lines
    lines_filt.set_ydata(filtered_instances[frame, :])
    print("Showing frame number: " + str(frame))
    
    #return the Line2D artists for blitting
    return lines, lines_filt,


animation = FuncAnimation(fig, animate, interval=1000, blit=True, repeat=True)
animation.event_source.start()
plt.show()
Mr. T
  • 11,960
  • 10
  • 32
  • 54