I have a bunch of data points each associated with a time, and I want to render them in an animation where I fade points in and out based on this time. The problem I'm running into is that the points are being rendered out of order. I've reduced the problem down to the minimal failing example below. Any help in figuring out the cause would be greatly appreciated.
import os
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
from matplotlib.animation import PillowWriter
from matplotlib.animation import FFMpegWriter
def plot_data_3d_video_time(
data: np.ndarray,
steps: np.ndarray,
directory: str = './',
filename: str = 'video.webm',
fps: int = 30):
""" Produce a video of a 3D scatter plot varying the opacity of the points based on their time step. """
max_alpha = 1.0
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
artists = []
for t in range(steps.max()):
artists.append([])
alpha = steps == t
points = data[alpha,:] # Point that should be rendered
alpha = alpha * max_alpha
print(f'{points}')
a = ax.scatter(data[:,0], data[:,1], data[:,2],
alpha=alpha,
c='b'
)
artists[-1].append(a)
# This is what I expect to see. Why is it showing a different point?
artists[-1].append(
ax.scatter(
[points[0,0]], [points[0,1]], [points[0,2]],
c='r', marker='x', s=100
)
)
# Code for saving the video. Can be ignored.
video_format = filename.split('.')[-1]
filename = os.path.join(directory, filename)
animation = ArtistAnimation(plt.gcf(), artists, interval=50, blit=True)
if video_format == 'gif':
writer = PillowWriter(fps=fps)
elif video_format == 'mp4':
writer = FFMpegWriter(fps=fps)
elif video_format == 'webm':
writer = FFMpegWriter(fps=fps, codec='libvpx-vp9')
else:
raise ValueError('Unknown video format: {}'.format(video_format))
animation.save(filename, writer=writer)
print(f'Saved video to {os.path.abspath(filename)}')
plt.close()
if __name__ == '__main__':
# Generate some data
n = 10
steps = np.arange(n)
data0 = np.arange(n)*0#[::-1]
data1 = np.arange(n)*0#[::-1]
data2 = np.arange(n)[::-1]
data = np.stack([data0, data1, data2], axis=1)
# Plot the data
plot_data_3d_video_time(data=data, steps=steps, filename='test.gif', fps=5)
In this code, I'm plotting the real data as a blue circle and added a debugging point rendered as a red X. This X is where I expect the blue circle to be. The resulting video from the code above is
If I set change the input data by changing the data2 = np.arange(n)[::-1]
line to data2 = np.arange(n)
, then both the blue circle and red X coincide.
I haven't been able to find a clear pattern on where it works and where it fails. Other setups where it's reversed:
data0 = np.arange(n)[::-1]
data1 = np.arange(n)[::-1]
data2 = np.arange(n)[::-1]
data0 = np.arange(n)*0
data1 = np.arange(n)
data2 = np.arange(n)*0