Animated title in matplotlib - python

I can't figure out how to get an animated title working on a FuncAnimation plot (that uses blit). Based on http://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/ and Python/Matplotlib - Quickly Updating Text on Axes, I've built an animation, but the text parts just won't animate. Simplified example:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
vls = np.linspace(0,2*2*np.pi,100)
fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.005, '', transform = ax.transAxes)
def init():
ttl.set_text('')
img.set_data([0],[0])
return img, ttl
def func(n):
ttl.set_text(str(n))
img.set_data(vls,np.sin(vls+.02*n*2*np.pi))
return img, ttl
ani = animation.FuncAnimation(fig,func,init_func=init,frames=50,interval=30,blit=True)
plt.show()
If blit=True is removed, the text shows up, but it slows way down. It seems to fail with plt.title, ax.set_title, and ax.text.
Edit: I found out why the second example in the first link worked; the text was inside the img part. If you make the above 1.005 a .99, you'll see what I mean. There probably is a way to do this with a bounding box, somehow...

See Animating matplotlib axes/ticks and python matplotlib blit to axes or sides of the figure?
So, the problem is that in the guts of animation where the blit backgrounds are actually saved (line 792 of animation.py), it grabs what is in the axes bounding box. This makes sense when you have multiple axes being independently animated. In your case you only have one axes to worry about and we want to animate stuff outside of the axes bounding box. With a bit of monkey patching, a level of tolerance for reaching into the guts of mpl and poking around a bit, and acceptance of the quickest and dirtyest solution we can solve your problem as such:
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def _blit_draw(self, artists, bg_cache):
# Handles blitted drawing, which renders only the artists given instead
# of the entire figure.
updated_ax = []
for a in artists:
# If we haven't cached the background for this axes object, do
# so now. This might not always be reliable, but it's an attempt
# to automate the process.
if a.axes not in bg_cache:
# bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
# change here
bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.figure.bbox)
a.axes.draw_artist(a)
updated_ax.append(a.axes)
# After rendering all the needed artists, blit each axes individually.
for ax in set(updated_ax):
# and here
# ax.figure.canvas.blit(ax.bbox)
ax.figure.canvas.blit(ax.figure.bbox)
# MONKEY PATCH!!
matplotlib.animation.Animation._blit_draw = _blit_draw
vls = np.linspace(0,2*2*np.pi,100)
fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.05, '', transform = ax.transAxes, va='center')
def init():
ttl.set_text('')
img.set_data([0],[0])
return img, ttl
def func(n):
ttl.set_text(str(n))
img.set_data(vls,np.sin(vls+.02*n*2*np.pi))
return img, ttl
ani = animation.FuncAnimation(fig,func,init_func=init,frames=50,interval=30,blit=True)
plt.show()
Note that this may not work as expected if you have more than one axes in your figure. A much better solution is to expand the axes.bbox just enough to capture your title + axis tick labels. I suspect there is code someplace in mpl to do that, but I don't know where it is off the top of my head.

To add to tcaswell's "monkey patching" solution, here is how you can add animation to the axis tick labels. Specifically, to animate the x-axis, set ax.xaxis.set_animated(True) and return ax.xaxis from the animation functions.
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def _blit_draw(self, artists, bg_cache):
# Handles blitted drawing, which renders only the artists given instead
# of the entire figure.
updated_ax = []
for a in artists:
# If we haven't cached the background for this axes object, do
# so now. This might not always be reliable, but it's an attempt
# to automate the process.
if a.axes not in bg_cache:
# bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
# change here
bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.figure.bbox)
a.axes.draw_artist(a)
updated_ax.append(a.axes)
# After rendering all the needed artists, blit each axes individually.
for ax in set(updated_ax):
# and here
# ax.figure.canvas.blit(ax.bbox)
ax.figure.canvas.blit(ax.figure.bbox)
# MONKEY PATCH!!
matplotlib.animation.Animation._blit_draw = _blit_draw
vls = np.linspace(0,2*2*np.pi,100)
fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.05, '', transform = ax.transAxes, va='center')
ax.xaxis.set_animated(True)
def init():
ttl.set_text('')
img.set_data([0],[0])
return img, ttl, ax.xaxis
def func(n):
ttl.set_text(str(n))
vls = np.linspace(0.2*n,0.2*n+2*2*np.pi,100)
img.set_data(vls,np.sin(vls))
ax.set_xlim(vls[0],vls[-1])
return img, ttl, ax.xaxis
ani = animation.FuncAnimation(fig,func,init_func=init,frames=60,interval=200,blit=True)
plt.show()

You must call
plt.draw()
After
ttl.set_text(str(n))
Here there is a very simple example of a text-animation inside a figure "without FuncAnimation()". Try it, you will see if it is useful for you.
import matplotlib.pyplot as plt
import numpy as np
titles = np.arange(100)
plt.ion()
fig = plt.figure()
for text in titles:
plt.clf()
fig.text(0.5,0.5,str(text))
plt.draw()

Related

Add my own picture as marker in animation in Python [duplicate]

I would like to utilize customer markers in both scatter and line charts. How can I make custom marker out of a PNG file?
I don't believe matplotlib can customize markers like that. See here for the level of customization, which falls way short of what you need.
As an alternative, I've coded up this kludge which uses matplotlib.image to place images at the line point locations.
import matplotlib.pyplot as plt
from matplotlib import image
# constant
dpi = 72
path = 'smile.png'
# read in our png file
im = image.imread(path)
image_size = im.shape[1], im.shape[0]
fig = plt.figure(dpi=dpi)
ax = fig.add_subplot(111)
# plot our line with transparent markers, and markersize the size of our image
line, = ax.plot((1,2,3,4),(1,2,3,4),"bo",mfc="None",mec="None",markersize=image_size[0] * (dpi/ 96))
# we need to make the frame transparent so the image can be seen
# only in trunk can you put the image on top of the plot, see this link:
# http://www.mail-archive.com/matplotlib-users#lists.sourceforge.net/msg14534.html
ax.patch.set_alpha(0)
ax.set_xlim((0,5))
ax.set_ylim((0,5))
# translate point positions to pixel positions
# figimage needs pixels not points
line._transform_path()
path, affine = line._transformed_path.get_transformed_points_and_affine()
path = affine.transform_path(path)
for pixelPoint in path.vertices:
# place image at point, centering it
fig.figimage(im,pixelPoint[0]-image_size[0]/2,pixelPoint[1]-image_size[1]/2,origin="upper")
plt.show()
Produces:
Following on from Mark's answer. I just thought I would add to this a bit because I tried to run this and it does what I want with the exception of actually displaying the icons on the graph. Maybe something has changed with matplotlib. It has been 4 years.
The line of code that reads:
ax.get_frame().set_alpha(0)
does not seem to work, however
ax.patch.set_alpha(0)
does work.
The other answer may lead to problems when resizing the figure. Here is a different approach, positionning the images inside annotation boxes, which are anchored in data coordinates.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
path = "https://upload.wikimedia.org/wikipedia/commons/b/b5/Tango-example_icons.png"
image = plt.imread(path)[116:116+30, 236:236+30]
x = np.arange(10)
y = np.random.rand(10)
fig, ax = plt.subplots()
ax.plot(x,y)
def plot_images(x, y, image, ax=None):
ax = ax or plt.gca()
for xi, yi in zip(x,y):
im = OffsetImage(image, zoom=72/ax.figure.dpi)
im.image.axes = ax
ab = AnnotationBbox(im, (xi,yi), frameon=False, pad=0.0,)
ax.add_artist(ab)
plot_images(x, y, image, ax=ax)
plt.show()

Updating colorbar bounds without removing the previous one

I am plotting dynamic figures (with FuncAnimation) from matplotlib.animation but I struggle a lot for updating the boundaries of my colorbar. Of course I would like to update it without removing the current colorbar and drawing a new one, I just would like to update the boundaries of the current colorbar.
If the 1st boudnaries are from 0 to 1 for example, I would like to be able to change them from 0 to 100 or anything.
I am surprised that any information for this is so difficult to find, I've been searching the whole day and I feel a bit stupid and tired about this little thing consuming so much time :').
Any idea please ?
This is a basic figure with a colorbar... I would like to update the boundaries of the colorbar (imagine the figure shown has been updated and the colorbar bounds don't fit anymore with the figure).
import numpy as np
import matplotlib.pyplot as plt
# Initial figure
data_array = np.random.rand(50,50)
fig, ax = plt.subplots()
IMAGE = ax.imshow(data_array, animated = True)
# Color bar new axis
cbaxes = fig.add_axes()
colorbar_bounds = np.linspace(np.min(data_array), np.max(data_array), 50)
colorbar_ticks = np.linspace(np.min(data_array), np.max(data_array) , 10)
# Colorbar
clb = fig.colorbar(IMAGE, cbaxes, orientation='vertical', boundaries=colorbar_bounds, ticks = colorbar_ticks)
plt.show()
As I think there is no way to do this with the basic matplotlib colorbar options (at least I was not able to find any), I created my own colorbar.
Here is the solution.
Note :
The animated=True attribute is only necessary if you want a dynamic colorbar (auto updating in an animated figure canvas for example).
import numpy as np
import matplotlib.pyplot as plt
# INIT figure and axes
data_array = np.random.rand(50,50)
fig, [ax, cbax1, cbax2] = plt.subplots(1,3, sharex = False)
IMAGE = ax.imshow(data_array, animated = True)
# Colorbar boundaries and ticks
colorbar_bounds = np.linspace(np.min(data_array), np.max(data_array), 50)
colorbar_ticks = np.linspace(np.min(data_array), np.max(data_array) , 10)
# Classic Colorbar
classic_clb = fig.colorbar(IMAGE, cbax1, orientation='vertical', boundaries=colorbar_bounds, ticks = colorbar_ticks)
cbax1.set_title("Classic colorbar")
# Personalized (Dynamic or not as you want) Colorbar
myclb = colorbar_bounds[:, np.newaxis]
myclb = cbax2.imshow(myclb, animated = True, cmap = "nipy_spectral")
cbax2.set_title("My colorbar")
plt.show()
Then I can update my colorbar with : (forget this if you don't need your colorbar to change)
myclb.set_array(mycb)

manipulating and animating an image on a matplotlib plot

I'm using matplotlib to animate a planets movements around a star.
I draw a simple small circle that represents the planet then i use funcanimation with an animate() function that changes the circles center each time, as is done on this website : https://nickcharlton.net/posts/drawing-animating-shapes-matplotlib.html.
Now I'm trying to use an image file instead of a circle but I barely know how to draw the image on the plot and really don't see how i can make it move on it
Any ideas ?
Thanks
Something like this will work:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.image import BboxImage
from matplotlib.transforms import Bbox, TransformedBbox
# make figure + Axes
fig, ax = plt.subplots()
# make initial bounding box
bbox0 = Bbox.from_bounds(0, 0, 1, 1)
# use the `ax.transData` transform to tell the bounding box we have given
# it position + size in data. If you want to specify in Axes fraction
# use ax.transAxes
bbox = TransformedBbox(bbox0, ax.transData)
# make image Artist
bbox_image = BboxImage(bbox,
cmap=plt.get_cmap('winter'),
norm=None,
origin=None,
**kwargs
)
# shove in some data
a = np.arange(256).reshape(1, 256)/256.
bbox_image.set_data(a)
# add the Artist to the Axes
ax.add_artist(bbox_image)
# set limits
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
# loop over new positions
for j in range(50):
x = j % 10
y = j // 10
# make a new bounding box
bbox0 = Bbox.from_bounds(x, y, 1, 1)
bbox = TransformedBbox(bbox0, ax.transData)
bbox_image.bbox = bbox
# re-draw the plot
plt.draw()
# pause so the gui can catch up
plt.pause(.1)
It is probably a bit more complicated than it needs to be and you really should use the animation framework rather than pause.
I wanna give you a +1 but my reputation doesn't allow it yet.
Thank's alot for the code, I succeeded in putting an imported image in the artist you use by modifying this line :
bbox_image.set_data(mpimg.imread("C:\\image.png"))
note I added this too
Import matplotlib.image as mpimg
But something's still amiss when I try to use funcanimation to animate this I get an error, here's my code (your's modified) :
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.image import BboxImage
from matplotlib.transforms import Bbox, TransformedBbox
import matplotlib.image as mpimg
from matplotlib import animation
# make figure + Axes
fig, ax = plt.subplots()
# make initial bounding box
bbox0 = Bbox.from_bounds(0, 0, 1, 1)
# use the `ax.transData` transform to tell the bounding box we have given
# it position + size in data. If you want to specify in Axes fraction
# use ax.transAxes
bbox = TransformedBbox(bbox0, ax.transData)
# make image Artist
bbox_image = BboxImage(bbox,
cmap=plt.get_cmap('winter'),
norm=None,
origin=None
)
bbox_image.set_data(mpimg.imread("C:\\icon-consulting.png"))
# add the Artist to the Axes
ax.add_artist(bbox_image)
# set limits
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
def animate(i):
bbox0 = Bbox.from_bounds(i, i, 1, 1)
bbox = TransformedBbox(bbox0, ax.transData)
bbox_image.bbox = bbox
return bbox_image
anim = animation.FuncAnimation(fig, animate,
frames=100000,
interval=20,
blit=True)
plt.show()
It tells me Error : 'BboxImage' object is not iterable
I guess only the position part of this BboxImage should be returned
I was used to doing this with Line2D objets by adding a coma, example : return lineobject,
which means only the first element of the tuple will be returned, but I don't see how It can be done with BboxImage
In fact I can simply use the loop as you first did,but perhaps you know how to adapt this to funcanimation ?
Edit :
I modified your code again using a bbox method :
for j in range(5000):
x = 2*np.sin(np.radians(j))
y = 2*np.cos(np.radians(j))
# make a new bounding box
bbox0.set_points([[x,y],[x+1,y+1]])
# re-draw the plot
plt.draw()
# pause so the gui can catch up
plt.pause(0.1)
Then I can convert this to use funcanimation this way :
def animate(i):
x = 2*np.sin(np.radians(i))
y = 2*np.cos(np.radians(i))
# make a new bounding box
bbox0.set_points([[x,y],[x+1,y+1]])
return bbox0.get_points()
anim = animation.FuncAnimation(fig, animate,
frames=100000,
interval=20,
blit=True)
plt.show()
This gives me an error : 'list' object is has no attribute 'axes'
it's the return I'm doing in the animate function, the returned value should be converted somehow I guess ... Do you know how I can do that ? Thanks

How to rotate a simple matplotlib Axes

is it possible to rotate matplotlib.axes.Axes as it is for matplotlib.text.Text
# text and Axes instance
t = figure.text(0.5,0.5,"some text")
a = figure.add_axes([0.1,0.1,0.8,0.8])
# rotation
t.set_rotation(angle)
a.set_rotation()???
a simple set_rotation on a text instance will rotate the text by the angle value about its coordinates axes. Is there any way to do to same for the axes instance ?
Are you asking how to rotate the entire axes (and not just the text)?
If so, yes, it's possible, but you have to know the extents of the plot beforehand.
You'll have to use axisartist, which allows more complex relationships like this, but is a bit more complex and not meant for interactive visualization. If you try to zoom, etc, you'll run into problems.
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist.floating_axes as floating_axes
fig = plt.figure()
plot_extents = 0, 10, 0, 10
transform = Affine2D().rotate_deg(45)
helper = floating_axes.GridHelperCurveLinear(transform, plot_extents)
ax = floating_axes.FloatingSubplot(fig, 111, grid_helper=helper)
fig.add_subplot(ax)
plt.show()
Yes, it is possible. But you have to rotate each label separately. Therefore, you can try using an iteration:
from matplotlib import pyplot as plt
figure = plt.figure()
ax = figure.add_subplot(111)
t = figure.text(0.5,0.5,"some text")
t.set_rotation(90)
labels = ax.get_xticklabels()
for label in labels:
label.set_rotation(45)
plt.show()

Updating the x-axis values using matplotlib animation

I am trying to use matplotlib.ArtistAnimation to animate two subplots. I want the x-axis to increase in value as the animation progresses, such that the total length of the animation is 100 but at any time the subplot is only presenting me with the time values from 0-24 and then iterates up to 100.
A great example is given here. The link uses FuncAnimation and updates the x-axis labels in a rolling fashion using plot().axes.set_xlim() and incrementing the x-values. The code is available via the link below the YouTube video in the link provided.
I have appended code below that shows my attempts to replicate these results but the x-limits seem to take on their final values instead of incrementing with time. I have also tried incrementing the solution (as opposed to the axis) by only plotting the values in the window that will be seen in the subplot, but that does not increment the x-axis values. I also tried to implement autoscaling but the x-axis still does not update.
I also found this question which is virtually the same problem, but the question was never answered.
Here is my code:
import matplotlib.pylab as plt
import matplotlib.animation as anim
import numpy as np
#create image with format (time,x,y)
image = np.random.rand(100,10,10)
#setup figure
fig = plt.figure()
ax1=fig.add_subplot(1,2,1)
ax2=fig.add_subplot(1,2,2)
#set up viewing window (in this case the 25 most recent values)
repeat_length = (np.shape(image)[0]+1)/4
ax2.set_xlim([0,repeat_length])
#ax2.autoscale_view()
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
#set up list of images for animation
ims=[]
for time in xrange(np.shape(image)[0]):
im = ax1.imshow(image[time,:,:])
im2, = ax2.plot(image[0:time,5,5],color=(0,0,1))
if time>repeat_length:
lim = ax2.set_xlim(time-repeat_length,time)
ims.append([im, im2])
#run animation
ani = anim.ArtistAnimation(fig,ims, interval=50,blit=False)
plt.show()
I only want the second subplot (ax2) to update the x-axis values.
Any help would be much appreciated.
If you don't need blitting
import matplotlib.pylab as plt
import matplotlib.animation as animation
import numpy as np
#create image with format (time,x,y)
image = np.random.rand(100,10,10)
#setup figure
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
#set up viewing window (in this case the 25 most recent values)
repeat_length = (np.shape(image)[0]+1)/4
ax2.set_xlim([0,repeat_length])
#ax2.autoscale_view()
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
#set up list of images for animation
im = ax1.imshow(image[0,:,:])
im2, = ax2.plot([], [], color=(0,0,1))
def func(n):
im.set_data(image[n,:,:])
im2.set_xdata(np.arange(n))
im2.set_ydata(image[0:n, 5, 5])
if n>repeat_length:
lim = ax2.set_xlim(n-repeat_length, n)
else:
# makes it look ok when the animation loops
lim = ax2.set_xlim(0, repeat_length)
return im, im2
ani = animation.FuncAnimation(fig, func, frames=image.shape[0], interval=30, blit=False)
plt.show()
will work.
If you need to run faster, you will need to play games with the bounding box used for blitting so that the axes labels are updated.
If you are using blitting, you can call pyplot.draw() to redraw the entire figure, each time you change y/x axis.
This updates whole figure, so is relatively slow, but it's acceptable if you don't call it many items.
This moves your axis, but is very slow.
import matplotlib.pylab as plt
import matplotlib.animation as anim
import numpy as np
image = np.random.rand(100,10,10)
repeat_length = (np.shape(image)[0]+1)/4
fig = plt.figure()
ax1 = ax1=fig.add_subplot(1,2,1)
im = ax1.imshow(image[0,:,:])
ax2 = plt.subplot(122)
ax2.set_xlim([0,repeat_length])
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
im2, = ax2.plot(image[0:0,5,5],color=(0,0,1))
canvas = ax2.figure.canvas
def init():
im = ax1.imshow(image[0,:,:])
im2.set_data([], [])
return im,im2,
def animate(time):
time = time%len(image)
im = ax1.imshow(image[time,:,:])
im2, = ax2.plot(image[0:time,5,5],color=(0,0,1))
if time>repeat_length:
print time
im2.axes.set_xlim(time-repeat_length,time)
plt.draw()
return im,im2,
ax2.get_yaxis().set_animated(True)
# call the animator. blit=True means only re-draw the parts that have changed.
animate = anim.FuncAnimation(fig, animate, init_func=init,
interval=0, blit=True, repeat=True)
plt.show()

Categories