I am trying to animate annotations from a list of xy coordinates. The code below animates the annotation line but I cannot get the arrow function to animate using the same code.
The example dataset is a representation of the data I'm using. It is horizontally formatted. With this, I make a list from all the X-Coordinates and all the Y-Coordiantes from each subject. I then make a list pertaining to each time point, which is each row of data. From that I can plot a scatter and annotations.
However, when trying to plot an arrow between two separate coordinates I run into the error as stated by #ImportanceOfBeingErnest. The function should be a tuple of two elements but I'm having trouble with the arrow animation function as I think I need to provide 4 elements. The X and Y coordinate for the first point and X and Y coordinate for the second point.
Will I have to re-format that data or is there a way to animate the arrow function were 4 tuples are required?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x_data = np.random.randint(80, size=(400, 4))
y_data = np.random.randint(80, size=(400, 4))
lists = [[],[]]
lists[0] = x_data
lists[1] = y_data
fig, ax = plt.subplots(figsize = (8,6))
ax.set_xlim(0,80)
ax.set_ylim(0,80)
scatter = ax.scatter(lists[0][0], lists[1][0], zorder = 5) #Scatter plot
annotation = ax.annotate(' Subject 1', xy=(lists[0][0][2],lists[1][0][2]))
arrow = ax.annotate('', xy = (lists[0][0][0], lists[1][0][0]), xytext = (lists[0][0][1],lists[1][0][1]), arrowprops = {'arrowstyle': "<->"})
def animate(i) :
scatter.set_offsets([[[[[lists[0][0+i][0], lists[1][0+i][0]], [lists[0][0+i][1], lists[1][0+i][1]], [lists[0][0+i][2], lists[1][0+i][2]], [lists[0][0+i][3], lists[1][0+i][3]]]]]])
Subject_x = lists[0][0+i][2]
Subject_y = lists[1][0+i][2]
annotation.set_position((Subject_x, Subject_y))
annotation.xy = (Subject_x, Subject_y)
Arrow1 = (lists[0][0+i][0], lists[1][0+i][0]) #Code for arrow animation doesn't work. Produces a blank screen after the 1st frame
Arrow2 = (lists[0][0+i][1], lists[1][0+i][1])
arrow.set_position((Arrow1, Arrow2))
arrow.xy = (Arrow1, Arrow2)
ani = animation.FuncAnimation(fig, animate,
interval = 50, blit = False)
plt.show()
The solution to this is still given in this question:
Animate points with labels with mathplotlib
As said several times in the comments, each position is determined by a tuple of two values (x,y). Hence you cannot provide a tuple of tuples to those positions. Also the start and end of the arrow should of course be at different positions.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x_data = np.random.randint(80, size=(400, 4))
y_data = np.random.randint(80, size=(400, 4))
fig, ax = plt.subplots(figsize = (8,6))
ax.set_xlim(0,80)
ax.set_ylim(0,80)
scatter = ax.scatter(x_data[0], y_data[0], zorder = 5) #Scatter plot
annotation = ax.annotate(' Subject 1', xy=(x_data[0,2],y_data[0,2]))
arrow = ax.annotate('', xy = (x_data[0,0], y_data[0,0]),
xytext = (x_data[0,1],y_data[0,1]),
arrowprops = {'arrowstyle': "<->"})
def animate(i) :
scatter.set_offsets(np.c_[x_data[i,:], y_data[i,:]])
annotation.set_position((x_data[i,2], y_data[i,2]))
start = (x_data[i,0], y_data[i,0])
end = (x_data[i,1], y_data[i,1])
arrow.set_position(start)
arrow.xy = end
ani = animation.FuncAnimation(fig, animate, frames=len(x_data),
interval = 700, blit = False)
plt.show()
Related
I am trying to animate a histogram using matplotlib and I want to show the different bars using a colormap, e.g:
I have this working when I clear the complete figure every frame and then redraw everything. But this is very slow, so I am trying out the example by matplotlib itself.
This works and is very fast, but unfortunately I have no idea on how to specify a colormap because it is using the patches.PathPatch object to draw the histogram now. I can only get it to work with the same single color for every individual bar.
How can I specify a gradient or colormap to achieve the desired result shown above?
Here is an example of a working animation with a single color which I am currently using.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.animation as animation
# Fixing random state for reproducibility
np.random.seed(19680801)
# histogram our data with numpy
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
nrects = len(left)
nverts = nrects * (1 + 3 + 1)
verts = np.zeros((nverts, 2))
codes = np.ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5, 0] = left
verts[0::5, 1] = bottom
verts[1::5, 0] = left
verts[1::5, 1] = top
verts[2::5, 0] = right
verts[2::5, 1] = top
verts[3::5, 0] = right
verts[3::5, 1] = bottom
patch = None
def animate(i):
# simulate new data coming in
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
top = bottom + n
verts[1::5, 1] = top
verts[2::5, 1] = top
return [patch, ]
fig, ax = plt.subplots()
barpath = path.Path(verts, codes)
patch = patches.PathPatch(
barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
plt.show()
I recommend u using BarContainer, you can change bar color individually. In your example, the path is single object, matplotlib seems not to support gradient color for a single patch (not sure though).
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
# histogram our data with numpy
data = np.random.randn(1000)
colors = plt.cm.coolwarm(np.linspace(0, 1, 100))
def animate(i):
data = np.random.randn(1000)
bc = ax.hist(data, 100)[2]
for i, e in enumerate(bc):
e.set_color(colors[i])
return bc
fig, ax = plt.subplots(1, 1, figsize=(7.2, 7.2))
ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
I am trying to create an animation of a scatterplot as well as a 2d Histogram. I can get the scatter plot working. I can also create individual stills of the 2d Histogram but cannot get it to animate with the scatter plot.
I can create some mock data if that would help. Please find code below.
import numpy as np
import matplotlib.pyplot as plt
import csv
import matplotlib.animation as animation
#Create empty lists
visuals = [[],[],[]]
#This dataset contains XY coordinates from 21 different players derived from a match
with open('Heatmap_dataset.csv') as csvfile :
readCSV = csv.reader(csvfile, delimiter=',')
n=0
for row in readCSV :
if n == 0 :
n+=1
continue
#All I'm doing here is appending all the X-Coordinates and all the Y-Coordinates. As the data is read across the screen, not down.
visuals[0].append([float(row[3]),float(row[5]),float(row[7]),float(row[9]),float(row[11]),float(row[13]),float(row[15]),float(row[17]),float(row[19]),float(row[21]),float(row[23]),float(row[25]),float(row[27]),float(row[29]),float(row[31]),float(row[33]),float(row[35]),float(row[37]),float(row[39]),float(row[41]),float(row[43])])
visuals[1].append([float(row[2]),float(row[4]),float(row[6]),float(row[8]),float(row[10]),float(row[12]),float(row[14]),float(row[16]),float(row[18]),float(row[20]),float(row[22]),float(row[24]),float(row[26]),float(row[28]),float(row[30]),float(row[32]),float(row[34]),float(row[36]),float(row[38]),float(row[40]),float(row[42])])
visuals[2].append([1,2])
#Create a list that contains all the X-Coordinates and all the Y-Coordinates. The 2nd list indicates the row. So visuals[1][100] would be the 100th row.
Y = visuals[1][0]
X = visuals[0][0]
fig, ax = plt.subplots(figsize = (8,8))
plt.grid(False)
# Create scatter plot
scatter = ax.scatter(visuals[0][0], visuals[1][0], c=['white'], alpha = 0.7, s = 20, edgecolor = 'black', zorder = 2)
#Create 2d Histogram
data = (X, Y)
data,x,y,p = plt.hist2d(X,Y, bins = 15, range = np.array([(-90, 90), (0, 140)]))
#Smooth with filter
im = plt.imshow(data.T, interpolation = 'gaussian', origin = 'lower', extent = [-80,80,0,140])
ax.set_ylim(0,140)
ax.set_xlim(-85,85)
#Define animation.
def animate(i) :
scatter.set_offsets([[[[[[[[[[[[[[[[[[[[[visuals[0][0+i][0], visuals[1][0+i][0]], [visuals[0][0+i][1], visuals[1][0+i][1]], [visuals[0][0+i][2], visuals[1][0+i][2]], [visuals[0][0+i][3], visuals[1][0+i][3]], [visuals[0][0+i][4], visuals[1][0+i][4]],[visuals[0][0+i][5], visuals[1][0+i][5]], [visuals[0][0+i][6], visuals[1][0+i][6]], [visuals[0][0+i][7], visuals[1][0+i][7]], [visuals[0][0+i][8], visuals[1][0+i][8]], [visuals[0][0+i][9], visuals[1][0+i][9]], [visuals[0][0+i][10], visuals[1][0+i][10]], [visuals[0][0+i][11], visuals[1][0+i][11]], [visuals[0][0+i][12], visuals[1][0+i][12]], [visuals[0][0+i][13], visuals[1][0+i][13]], [visuals[0][0+i][14], visuals[1][0+i][14]], [visuals[0][0+i][15], visuals[1][0+i][15]], [visuals[0][0+i][16], visuals[1][0+i][16]], [visuals[0][0+i][17], visuals[1][0+i][17]], [visuals[0][0+i][18], visuals[1][0+i][18]], [visuals[0][0+i][19], visuals[1][0+i][19]], [visuals[0][0+i][20], visuals[1][0+i][20]]]]]]]]]]]]]]]]]]]]]])
# This is were I'm having trouble...How do I animate the image derived from the 2d histogram
im.set_array[i+1]
ani = animation.FuncAnimation(fig, animate, np.arange(0,1000),
interval = 100, blit = False)
The image can be updated with im.set_data(data), where you need to call hist2d to get the updated data to pass to im. As a minimal example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
X = np.random.randn(100000)
Y = np.random.randn(100000) + 5
fig, ax = plt.subplots(figsize = (8,8))
#Create 2d Histogram
data,x,y = np.histogram2d(X,Y, bins = 15)
#Smooth with filter
im = plt.imshow(data.T, interpolation = 'gaussian', origin = 'lower')
#Define animation.
def animate(i) :
X = np.random.randn(100000)
Y = np.random.randn(100000) + 5
data,x,y = np.histogram2d(X,Y, bins = 15)
im.set_data(data)
ani = animation.FuncAnimation(fig, animate, np.arange(0,1000),
interval = 100, blit = False)
plt.show()
Consider the following code which implements ArtistAnimation to animate two different subplots within the same figure object.
import numpy as np
import itertools
import matplotlib.pyplot as plt
import matplotlib.mlab as ml
import matplotlib.animation as animation
def f(x,y,a):
return ((x/a)**2+y**2)
avals = np.linspace(0.1,1,10)
xaxis = np.linspace(-2,2,9)
yaxis = np.linspace(-2,2,9)
xy = itertools.product(xaxis,yaxis)
xy = list(map(list,xy))
xy = np.array(xy)
x = xy[:,0]
y = xy[:,1]
fig, [ax1,ax2] = plt.subplots(2)
ims = []
for a in avals:
xi = np.linspace(min(x), max(x), len(x))
yi = np.linspace(min(y), max(y), len(y))
zi = ml.griddata(x, y, f(x, y, a), xi, yi, interp='linear') # turn it into grid data, this is what imshow takes
title = plt.text(35,-4,str(a), horizontalalignment = 'center')
im1 = ax1.imshow(zi, animated = True, vmin = 0, vmax = 400)
im2 = ax2.imshow(zi, animated=True, vmin=0, vmax=400)
ims.append([im1,im2, title])
ani = animation.ArtistAnimation(fig, ims, interval = 1000, blit = False)
plt.show()
In this case the number of items in im1 and im2 are the same, and the frame rate for each subplot is identical.
Now, imagine I have 2 lists with different numbers of items, and that I wish ArtistAnimate to go through the frames in the same total time. Initially I thought of manipulating the interval keyword in the ArtistAnimation call but this implies that you can set different intervals for different artists, which I don't think is possible.
Anyway, I think the basic idea is pretty clear len(im1) is not equal to len(im2), but the animation needs to go through them all in the same amount of time.
Is there any way to do this please? Thanks
EDIT
While I try out the answer provided below, I should add that I would rather use ArtistAnimation due to the structure of my data. If there are no alternatives I will revert to the solution below.
Yes that is possible, kinda, using Funcanimation and encapsulating your data inside func.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
arr1 = np.random.rand(300,3,4)
arr2 = np.random.rand(200,5,6)
fig, (ax1, ax2) = plt.subplots(1,2)
img1 = ax1.imshow(arr1[0])
img2 = ax2.imshow(arr2[0])
# set relative display rates
r1 = 2
r2 = 3
def animate(ii):
if ii % r1:
img1.set_data(arr1[ii/r1])
if ii % r2:
img2.set_data(arr2[ii/r2])
return img1, img2
ani = animation.FuncAnimation(fig, func=animate, frames=np.arange(0, 600))
plt.show()
I'm trying to animate a stem plot in matplotlib and I can't find the necessary documentation to help me. I have a series of data files which each look like this:
1 0.345346
2 0.124325
3 0.534585
and I want plot each file as a separate frame.
According to this and this other tutorial, I should create a function which updates the data contained in each plot object (artist? I'm not sure about the terminology)
From the second link, this is the update function
def update(frame):
global P, C, S
# Every ring is made more transparent
C[:,3] = np.maximum(0, C[:,3] - 1.0/n)
# Each ring is made larger
S += (size_max - size_min) / n
# Reset ring specific ring (relative to frame number)
i = frame % 50
P[i] = np.random.uniform(0,1,2)
S[i] = size_min
C[i,3] = 1
# Update scatter object
scat.set_edgecolors(C)
scat.set_sizes(S)
scat.set_offsets(P)
# Return the modified object
return scat,
How can I adapt this kind of update function for a stem plot? The documentation for stem is horribly brief (in fact this is a recurring issue as I'm learning matplotlib), but the example code shows that the output of stem is a tuple markerline, stemlines, baseline rather than an artist object like for plt.plot or plt.imshow.
So when I write my update function for the animation, how can I update the data inside the stem plot?
Here you go!
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0.1, 2*np.pi, 10)
markerline, stemlines, baseline = ax.stem(x, np.cos(x), '-.')
def update(i):
ax.cla()
markerline, stemlines, baseline = ax.stem(x, np.cos(x+i/10), '-.')
ax.set_ylim((-1, 1))
anim = FuncAnimation(fig, update, frames=range(10, 110, 10), interval=500)
anim.save('so.gif', dpi=80, writer='imagemagick')
I think there can be better ways of achieving this- not requiring to clear the plot each time. However, this works!
When using the keyword use_line_collection=True (default behavior since Matplotlib 3.3) one can update the three elements
markerline
stemlines
baseline
individualy. Here is the code for the sine wave example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
x = np.linspace(0.1, 2*np.pi, 10)
y = np.cos(x)
bottom = 0
h_stem = ax.stem(x, y, bottom=bottom, use_line_collection=True, linefmt='-.')
def update(i):
y = np.cos(x+i/10)
# markerline
h_stem[0].set_ydata(y)
h_stem[0].set_xdata(x) # not necessary for constant x
# stemlines
h_stem[1].set_paths([np.array([[xx, bottom],
[xx, yy]]) for (xx, yy) in zip(x, y)])
# baseline
h_stem[2].set_xdata([np.min(x), np.max(x)])
h_stem[2].set_ydata([bottom, bottom]) # not necessary for constant bottom
anim = FuncAnimation(fig, update, frames=range(10, 110, 10), interval=1)
anim.save('so.gif', dpi=80, writer='imagemagick')
Depending on what values (x, y, bottom) should be updated you can omit some parts of this update or reuse the current values. I wrote a more general function, where you can pass an arbitrary combination of these values:
def update_stem(h_stem, x=None, y=None, bottom=None):
if x is None:
x = h_stem[0].get_xdata()
else:
h_stem[0].set_xdata(x)
h_stem[2].set_xdata([np.min(x), np.max(x)])
if y is None:
y = h_stem[0].get_ydata()
else:
h_stem[0].set_ydata(y)
if bottom is None:
bottom = h_stem[2].get_ydata()[0]
else:
h_stem[2].set_ydata([bottom, bottom])
h_stem[1].set_paths([np.array([[xx, bottom],
[xx, yy]]) for (xx, yy) in zip(x, y)])
Here is a snippet of my code:
fig2 = plt.figure(figsize=(8,6))
ax1 = fig2.add_subplot(111)
ax1.scatter((logngal),(logm200),c='r',label='$0.0<z<1.0$')
ax1.plot((logngal),(curve_y_1),'y',linewidth=2,label='$slope=%s \pm %s$'%(slope1,slope1_err))
ax1.fill_between(x_pred, lower, upper, color='#888888', alpha=0.5)
p1 = mpatches.Rectangle((0, 0), 1, 1, fc="#888888",alpha=0.5)
ax1.legend([p1],['$1\sigma\/confidence\/limts$'])
fig2.show()
When I perform the above, I only see $1\sigma\/confidence\/limts$ mentioned in the legend.
Whereas as you can see that I also call label='$0.0<z<1.0$' and label='$slope=%s \pm %s$'%(slope1,slope1_err) in ax1.scatter and ax1.plot respectively.
This does not get plotted in the legend.
How do I add all the above three labels inside the legend?
you need to grab the scatter and plot artists as you plot them, and then feed the handles and labels from them to legend. For example, here's your code modified (with some sample data at the beginning just to get it to run):
plt.plot returns a list of Line2D objects, so if you read it as pplot, = plt.plot(...), you unpack that one-item list.
You can then use .get_label() on pplot and pscat to give the labels to the legend.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as mpatches
# Some things to make your script run when I don't have your data
slope1,slope1_err='this','that'
logngal = np.linspace(0,1,20)
logm200 = np.random.rand(20)
x_pred = np.linspace(0,1,20)
curve_y_1 = 0.5*(np.sin(logngal)/2.+np.cos(logngal))
upper = np.sin(x_pred)/2.
lower = np.cos(x_pred)
# end of sample data
fig2 = plt.figure(figsize=(8,6))
ax1 = fig2.add_subplot(111)
pscat = ax1.scatter((logngal),(logm200), c='r',label='$0.0<z<1.0$')
pplot, = ax1.plot((logngal),(curve_y_1),'y',linewidth=2,label='$slope=%s \pm %s$'%(slope1,slope1_err))
ax1.fill_between(x_pred, lower, upper,color='#888888', alpha=0.5)
p1 = mpatches.Rectangle((0, 0), 1, 1, fc="#888888",alpha=0.5)
handles = [p1,pplot,pscat]
labels = ['$1\sigma\/confidence\/limts$',pplot.get_label(),pscat.get_label()]
ax1.legend(handles,labels)
fig2.show()