Related
I am trying to animate a patch.Rectangle object using matplotlib. I want the said object to move along a path.Arc.
A roundabout way to do this would be (approximately) :
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.patches as mpat
fig, ax = plt.subplots()
ax.set(xlim=(0, 10), ylim=(0, 10))
# generate the patch
patch = mpat.Rectangle((5, 5), 1, 4)
patch.rotation_point = 'center'
# generate the path to follow
path_to_follow = mpat.Arc((5, 5), 2, 2)
ax.add_patch(path_to_follow)
def init():
patch.set(x=5, y=5)
ax.add_patch(patch)
return patch,
def animate(i, ax):
new_x = 5 + np.sin(np.radians(i)) - 0.5 # parametric form for the circle
new_y = 5 + np.cos(np.radians(i)) - 2
patch.set(x=new_x, y=new_y, angle=90-i)
return patch,
anim = animation.FuncAnimation(fig, animate,
init_func=init,
fargs=[ax],
frames=360,
interval=10,
blit=True)
plt.show()
The rectangle follows a circle, but a parametric one. Would it be possible to make it follow any path?
In other words, I would like to know if there are other simpler methods to do this (make my patch follow my path, here a circle), and if that could be generalized to other path.
Thanks in advance !
I searched into the matplotlib doc for a methods which gives the parametric form for a given path (but apparently there is not), or for a methods which directly move a patch along a path (obviously, there was not).
Here is one way to use matplotlib.path.Path to generate a path, whose vertices can be obtained using the method cleaned, to move a patch along it.
I have tried to showcase how blue and red colored Rectangles can be moved along a (blue) linear path and a (red) circular path, respectively:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation, path
import matplotlib.patches as mpat
fig, ax = plt.subplots()
ax.set(xlim=(0, 10), ylim=(0, 10))
# generate a linear path
path1 = np.column_stack((np.arange(500)/50, np.arange(500)/50))
# generate a circular path
circle = path.Path.circle(center=(5, 5), radius=1)
path2 = circle.cleaned().vertices[:-3]
# create patches
patch1 = mpat.Rectangle((0, 0), 1, 3)
patch2 = mpat.Rectangle((0, 0), 1, 3, color='red', fill=None)
# plot path vertices
plt.scatter(x=path1[:, 0], y=path1[:, 1], s=2)
plt.scatter(x=path2[:, 0], y=path2[:, 1], color='red', s=2)
def init():
patch1.set(x=0, y=0)
patch2.set(x=5, y=6)
ax.add_patch(patch1)
ax.add_patch(patch2)
return [patch1, patch2]
def animate(i, ax):
j = i % 500 # path1 has shape (500, 2)
k = (i % 16) # path2 has shape (16, 2)
patch1.set(x=path1[j][0], y=path1[j][1], angle=-j)
patch2.set(x=path2[k][0], y=path2[k][1], angle=-k)
return [patch1, patch2]
anim = animation.FuncAnimation(fig, animate,
init_func=init,
fargs=[ax],
frames=360,
interval=100,
blit=True)
plt.show()
If your path is some collection of coordinates, you can not only translate the rectangle, but also compute the vector from one point to the next and update the rectangle angle accordingly. In the next example (mix of your code with mine), we generate from the beginning the path, but it could be instead live read from some external source.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.patches as mpat
# create some path with pairs of X and Y coordinates
t = np.linspace(0, 360, 361)
X = 5. * np.sin(np.radians(t))
Y = t * (t-360*2) / 8000 + 7.5
# create x and y lists to store the position pairs as they are plotted
x = [X[0],]
y = [Y[0],]
# plotting
fig, ax = plt.subplots()
ax.set(xlim=(-10, 10), ylim=(-10, 10))
patch = mpat.Rectangle((x[0], y[0]), 1, 3)
def init():
patch.set(x=x[0], y=y[0])
ax.plot(X, Y)
ax.add_patch(patch)
return patch,
def animate(i, ax):
new_x = X[i] # we are getting from pre-generated data,
new_y = Y[i] # but it could be some function, or even live external source
vx = new_x - x[-1] # calculate the vectors, which are used for angle
vy = new_y - y[-1]
x.append(new_x) # store for next iteration, so that we can calculate the vectors
y.append(new_y)
new_alfa = np.degrees(np.arctan2(vy, vx))
patch.set(x=new_x, y=new_y, angle = new_alfa)
return patch,
anim = animation.FuncAnimation(fig, animate,
init_func=init,
fargs=[ax],
frames=360,
interval=20,
blit=True)
plt.show()
Thanks a lot for your answers, here is the code I made (mixing the two answers) and which does exactly what I wanted, if it helps anyone :
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.patches as mpat
fig, ax = plt.subplots()
ax.set(xlim=(-6, 6), ylim=(-6, 6))
# generate a circular path
circle = mpat.Arc((0, 0), 10, 10, theta1=20, theta2=220, color='green')
path = circle.get_transform().transform_path(circle.get_path()).cleaned().vertices[:-3] # get_path is not enough because of the transformation, so we apply to the path the same transformation as the circle has got from the identity circle
ax.add_patch(circle)
# create patch
patch = mpat.Rectangle((0, 0), 1, 4, color='red', fill=None)
patch.rotation_point = 'center'
# plot path vertices
plt.scatter(x=path[:, 0], y=path[:, 1], color='red', s=2)
shape = len(path)
def init():
patch.set(x=5-0.5, y=6-2) # we substract to get to the center
ax.add_patch(patch)
return [patch]
def animate(i, ax):
k = i % shape
new_x = path[k][0]
new_y = path[k][1]
vx = new_x - path[k-1][0]
vy = new_y - path[k-1][1]
patch.set(x=new_x-0.5, y=new_y-2, angle=np.degrees(np.arctan2(vy, vx) + 90))
return [patch]
anim = animation.FuncAnimation(fig, animate,
init_func=init,
fargs=[ax],
frames=360,
interval=200,
blit=True,
repeat=False)
plt.show()
To improve this, is anyone know how to increase the number of points given? In other words, increase the len of path to be more precise in moving the rectangle.
Thanks in advance !
What am I doing wrong? Can anyone help me? Or give me specific keywords for google search (I'm sure I'm not the first)? Have been dealing with this problem for over 8h now, cant find something on the internet.
Full Notebook Link (problem at the end): Kaggle Notebook
My code:
dict_data = data.copy()
dict_data.drop(["Date"], axis=1, inplace=True)
series_data = dict_data.to_dict()
bar_label = []
for key in dict_data:
bar_label.append(key)
bar_color = generate_color_series(28)
fig = plt.figure(figsize=(7, 5))
axes = fig.add_subplot(1, 1, 1)
axes.set_xlim(0, 35)
axes.set_xlabel("Popularity in %")
def animate(i):
i_value = []
for key in dict_data:
i_value.append(dict_data[key][i])
i_value = tuple(i_value)
plt.barh(bar_label, i_value, color=bar_color)
ani = FuncAnimation(fig, animate, interval=30)
%time ani.save('myAnimation1.gif', writer='imagemagick', fps=15)
plt.close()
Output:
[Picture]
The reason is that the new graph is being drawn with the previous drawing still intact, as described in the comments. So, the easiest way to deal with this is to put the action to clear the current graph in the loop process. Clearing the graph removes the x-axis limit and changes the height of the bar graph, so the x-axis limit is added again.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
# set global variable for color palette (plots) and grid style
PALETTE = "magma_r" # my favourite palettes: flare, CMRmap_r, magma_r
sns.set(style="darkgrid")
# function that generates n color values out of defined PALETTE
def generate_color_series(n):
segments = cm.get_cmap(PALETTE, n)
return segments(range(n))
data = pd.read_csv('./data/Most Popular Programming Languages from 2004 to 2022.csv', sep=',')
data["Date"] = pd.to_datetime(data["Date"])
dict_data = data.copy()
dict_data.drop(["Date"], axis=1, inplace=True)
series_data = dict_data.to_dict()
bar_label = []
for key in dict_data:
bar_label.append(key)
bar_color = generate_color_series(28)
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(1, 1, 1)
ax.set_xlim(0, 35)
ax.set_xlabel("Popularity in %")
def animate(i):
i_value = []
for key in dict_data:
i_value.append(dict_data[key][i])
i_value = tuple(i_value)
ax.cla()
ax.set_xlim(0, 35)
ax.barh(bar_label, i_value, color=bar_color)
ani = FuncAnimation(fig, animate, interval=30)
from IPython.display import HTML
plt.close()
HTML(ani.to_html5_video())
I'm trying to animate a 2d path, and I would like it to have a sort of "Disappearing Tail", where at any given time, it shows only the last 5 (for example) particles.
What I currently have is quite far from this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
sample_path = np.random.uniform(size=(100,2))
fig, ax = plt.subplots()
x = np.arange(-1, 1, 0.01) # x-array
line, = ax.plot(sample_path[0,0], sample_path[0,1])
def connect(i):
(x0,y0) = sample_path[i-1,:]
(x1,y1) = sample_path[i,:]
plt.plot([x0,x1],[y0,y1],'ro-')
return line,
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, connect, np.arange(1, 100), init_func=init,
interval=200, blit=True)
HTML(ani.to_html5_video())
This retains a 'full tail', i.e. after k steps, it shows all of the first k locations.
Is there a way to adapt what I've got so that the animation only shows the most recent history of the particle?
You would probably want to update the line instead of adding a lot of new points to the plot. Selecting the 5 most recent points can be done via indexing, e.g.
sample_path[i-5:i, 0]
Complete example, where we take care not to have a negative index and also don't use blit (which does not make sense if saving the animation).
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
r = np.sin(np.linspace(0,3.14,100))
t = np.linspace(0, 10, 100)
sample_path = np.c_[r*(np.sin(t)+np.cos(t)), r*(np.cos(t)-np.sin(t))]/1.5
fig, ax = plt.subplots()
line, = ax.plot(sample_path[0,0], sample_path[0,1], "ro-")
def connect(i):
start=max((i-5,0))
line.set_data(sample_path[start:i,0],sample_path[start:i,1])
return line,
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
ani = animation.FuncAnimation(fig, connect, np.arange(1, 100), interval=200)
plt.show()
Not as good as ImportanceOfBeingErnest's answer technically, but it still does the job and looks pretty cool, just plot the latest points and clear the old ones. I added a few more and sped it up because I thought it looked better with a longer trail.
def connect(i):
#clear current points
plt.clf()
#prevent axis auto-resizing
plt.plot(0,0)
plt.plot(1,1)
#generate points to plot
(x0,y0) = sample_path[i-8,:]
(x1,y1) = sample_path[i-7,:]
(x2,y2) = sample_path[i-6,:]
(x3,y3) = sample_path[i-5,:]
(x4,y4) = sample_path[i-4,:]
(x5,y5) = sample_path[i-3,:]
(x6,y6) = sample_path[i-2,:]
(x7,y7) = sample_path[i-1,:]
(x8,y8) = sample_path[i,:]
#plot old points
plt.plot([x0,x1,x2,x3,x4,x5,x6,x7],[y0,y1,y2,y3,y4,y5,y6,y7],'ro-')
#plot new point in blue
plt.plot([x7,x8],[y7,y8],'bo-')
return line,
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, connect, frames=np.arange(1, 100),
init_func=init,
interval=50, blit=True)
HTML(ani.to_html5_video())
I'm trying to draw the animation of several 3DScatter with matplotlib. I succeeded to draw all the points but I'm struggling with the colors. Even if I'm calling the function set_color(..) nothing is changed.
Here is what I'm currently doing, to_plot is an array of size total with (5120, 3) float elements and colors is an array of size total with (5120,) elements (equal to 'r' or 'b'):
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.patches as mpatches
total = 10
num_whatever = 100 # old = 5120
to_plot = [np.random.rand(num_whatever, 3) for i in range(total)]
colors = [['r' if i%2==0 else 'b' for i in range(num_whatever)] for i in range(total)]
red_patch = mpatches.Patch(color='red', label='Men')
blue_patch = mpatches.Patch(color='blue', label='Women')
fig = plt.figure()
ax3d = Axes3D(fig)
scat3D = ax3d.scatter([],[],[], s=10)
ttl = ax3d.text2D(0.05, 0.95, "", transform=ax3d.transAxes)
def update_plot(i):
print i, to_plot[i].shape
ttl.set_text('PCA on 3 components at step = {}'.format(i*20))
scat3D._offsets3d = np.transpose(to_plot[i])
scat3D.set_color(colors[i])
return scat3D,
def init():
scat3D.set_offsets([[],[],[]])
ax3d.set_xlim(-1.,2.)
ax3d.set_ylim(-0.5,0.7)
ax3d.set_zlim(-1.,0.75)
plt.style.use('ggplot')
plt.legend(handles=[red_patch, blue_patch])
ani = animation.FuncAnimation(fig, update_plot, init_func=init, blit=False, interval=100, frames=xrange(total))
# ani.save(os.path.join(config.workdir, 'gif', 'bins','anim.gif'), writer="imagemagick")
plt.plot()
The scatter plot is a Path3DCollection. It can have a colormap associated to it such that its points are colored according to the color array.
So you can provide a list of numeric values to the scatter via scat3D.set_array(colors[i]), where colors[i] = [0,1,0,...,1,0,1]. Those values are then mapped according to the colormap in use. For blue/red color this is simple, because there exists already a colormap "bwr" from blue to red.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.patches as mpatches
total = 10
num_whatever = 100
to_plot = [np.random.rand(num_whatever, 3) for i in range(total)]
colors = [np.tile([0,1],num_whatever//2) for i in range(total)]
red_patch = mpatches.Patch(color='red', label='Men')
blue_patch = mpatches.Patch(color='blue', label='Women')
fig = plt.figure()
ax3d = Axes3D(fig)
scat3D = ax3d.scatter([],[],[], s=10, cmap="bwr", vmin=0, vmax=1)
scat3D.set_cmap("bwr") # cmap argument above is ignored, so set it manually
ttl = ax3d.text2D(0.05, 0.95, "", transform=ax3d.transAxes)
def update_plot(i):
print i, to_plot[i].shape
ttl.set_text('PCA on 3 components at step = {}'.format(i*20))
scat3D._offsets3d = np.transpose(to_plot[i])
scat3D.set_array(colors[i])
return scat3D,
def init():
scat3D.set_offsets([[],[],[]])
plt.style.use('ggplot')
plt.legend(handles=[red_patch, blue_patch])
ani = animation.FuncAnimation(fig, update_plot, init_func=init,
blit=False, interval=100, frames=xrange(total))
ani.save("ani.gif", writer="imagemagick")
plt.show()
The reason why calling set_color failed is described here: https://github.com/matplotlib/matplotlib/issues/13035
... the bug is due to the set_facecolor not setting _facecolor3d because it is inherited from the base class (Collection) setting _facecolors. The same for edge colors.
And yes, this is a bug in matplotlib.
Therefore, if you want to change the face colors, direct assignment of _facecolor3d just work out fine. Note that you must assign it with an rgba_array, like
scat3D._facecolor3d[0] = [1., 0., 0., 1.]
or instead, if you want to use the presets (like 'r', '.5', etc.), you can do it this way
scat3D.set_color(...)
scat3D._facecolor3d = scat3D.get_facecolor()
scat3D._edgecolor3d = scat3D.get_edgecolor()
I have tested on both python 2.7 and 3.6, and no problem arose.
I wanted to visualize the path of a 2D-algorithm. So I wrote a short Python code, that produces such a animation. The problem is, that for each pixel (a plt.Rectangle) I add, the code gets slower and slower. The first 20 pixels get displayed in about 1 second, the last 20 pixels take already 3 seconds. And obviously it gets even worse for bigger grids and more pixels.
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
fig.set_dpi(100)
fig.set_size_inches(7, 7)
ax = plt.axes(xlim=(0, 20), ylim=(0, 20))
pixels = list()
def init():
return list()
def animate(i):
index = len(pixels)
index_x, index_y = index // 20, index % 20
pixel = plt.Rectangle((index_x, index_y), 1, 1, fc='r')
ax.add_patch(pixel)
pixels.append(pixel)
return pixels
anim = animation.FuncAnimation(fig, animate,
init_func=init,
frames=100,
interval=5,
blit=True)
plt.show()
It's quite clear to me, why it takes longer. Since the number of patches gets bigger and bigger each frame, matplotlibs rendering gets slower and slower. But not this slow! How can I get more speed? Is there a way that I can keep the old plot and only overwrite the current pixel?
I tried timing the animate function and for some reason there is no apparent slowdown in the function itself. Besides, blit should ensure only the latest rectangle is drawn. A possible way to prevent the redraw you suggest is to rasterize the plot with,
m=ax.add_patch(pixel)
m.set_rasterized(True)
Although this doesn't seem to improve speed for my timing. I think the increase must result somewhere in matplotlib when updating the plot. This is clear using interactive plotting, e.g.
from matplotlib import pyplot as plt
import time
fig = plt.figure()
fig.set_dpi(100)
fig.set_size_inches(7, 7)
ax = plt.axes(xlim=(0, 20), ylim=(0, 20))
npx = 20; npy = 20
Npix = npx*npy
plt.ion()
plt.show()
for i in range(Npix):
#ax.cla()
t0 = time.time()
index_x, index_y = i // 20, i % 20
pixel = plt.Rectangle((index_x, index_y), 1, 1, fc='r')
ax.add_patch(pixel)
fig.set_rasterized(True)
plt.pause(0.0001)
t1 = time.time()
print("Time=",i,t1-t0)
Which gives for the rasterised, non-rasterised and cleared axis (ax.cla) case,
I'm not sure why this happens, maybe someone with better insight into matplotlib would be able to help. One way to speed up the plot is to setup and put all rectangles in a patchcollection. Then the animation just changes the facecolor so only the rectangles which need be shown are displayed,
from matplotlib import pyplot as plt
from matplotlib import animation
import numpy as np
from matplotlib.collections import PatchCollection
fig = plt.figure()
fig.set_dpi(100)
fig.set_size_inches(7, 7)
ax = plt.axes(xlim=(0, 20), ylim=(0, 20))
npx = 20; npy = 20
Npix = npx*npy
displayed = np.zeros((npx, npy, 4))
pixels = []
def init():
for index in range(Npix):
index_x, index_y = index // npx, index % npy
pixel = plt.Rectangle((index_x, index_y), 1, 1, fc='r', ec='none')
pixels.append(pixel)
return pixels
pixels = init()
collection = PatchCollection(pixels, match_original=True, animated=True)
ax.add_collection(collection)
def animate(index):
index_x, index_y = index // npx, index % npy
displayed[index_x, index_y] = [1, 0, 0, 1]
collection.set_facecolors(displayed.reshape(-1, 4))
return (collection,)
anim = animation.FuncAnimation(fig, animate,
frames=400,
interval=1,
blit=True,
repeat=False)
plt.show()
This is much faster, although I couldn't work out how to turn edges on or off so just disable for all rectangles.