Matplotlib Animation for custom artist classes - python

Goal
Hi,
I am trying to animate a complex figure with several subplots and have started testing with the artist animation and the function animation methods.
For now, my goal is to have the subplot on the left show a moving colored line (not the problem) and the subplot on the right show an updated representation of a brain scan (the problem). Static, this looks something like this.
# Imports
import nilearn as nil
from nilearn import plotting as nlp
from matplotlib import pyplot as plt
window = np.arange(0,200-50)
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
a = ax.axvspan(window[0], window[0]+50, color='blue', alpha=0.5)
ay = fig.add_subplot(122)
b = nlp.plot_stat_map(nil.image.index_img(s_img, 0), axes=ay, colorbar=False, display_mode='x', cut_coords=(0,))
Problem
As you can see, I am using nilearn for plotting the brain image. For some reason, the nilearn object from plot_stat_map does not have an attribute set_visible unlike the matplotlib object from axvspan.
So when I attempt a simple animation like so:
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
ay = fig.add_subplot(122)
iml = list()
for i in np.arange(50):
a = ax.axvspan(window[i], window[i]+50, color='blue', alpha=0.5)
b = nlp.plot_stat_map(nil.image.index_img(s_img, i), axes=ay)
iml.append((a,b))
ani = animation.ArtistAniTruemation(fig, iml, interval=50, blit=False,
repeat_delay=1000)
it crashes with the following error:
/home/surchs/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.pyc in _init_draw(self)
974 for f in self.new_frame_seq():
975 for artist in f:
--> 976 artist.set_visible(False)
977 # Assemble a list of unique axes that need flushing
978 if artist.axes not in axes:
AttributeError: 'OrthoSlicer' object has no attribute 'set_visible'
Makes sense, nilearn does maybe not conform to matplotlibs expectations. So I try the function animation method like so:
def show_things(i, window, ax, ay):
ax.axvspan(window[i], window[i]+50, color='blue', alpha=0.5)
nlp.plot_stat_map(nil.image.index_img(s_img, i), axes=ay, colorbar=False, display_mode='x', cut_coords=(0,))
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
ay = fig.add_subplot(122)
ani = animation.FuncAnimation(fig, show_things, interval=10, blit=False, fargs=(window, ax, ay))
Although I am not sure if I am using things correctly, this gives me an animated brain plot on the right. However, the plot on the left is now not updated but just drawn over. So instead of a sliding bar, I get an expanding color surface. Something like this:
Question
How do I
get the plot on the left to update (as opposed to overwrite) on each iteration when using the function animation method? I already tried the ax.cla() function in matplotlib but since this also clears all axis attributes (like xlim) this is not a solution for me. Are there altneratives?
get the plot on the right to work with the artist animation method even though the custom plotting class is obviously missing a crucial attribute.
Also, I am not sure if I am doing the whole implementation part right, so any advice on that front is also very appreciated.

I suspect you may need to clear the axvspan axis between plots with ax.cla() to get the correct left plot (N.B. probably should clear the right plot too). To get round the problem of missing attributes, I'd suggest extracting the data from the returned handle from nlp.plot_stat_map and plotting with matplotlib pcolormesh (or imshow). Another possibility is creating a child class and adding this method yourself. It may also be worth submitting a bug/feature request to nilearn if this should be present.
By the way, if you're only after a quick and easy plot, you can do a poor man's version of animation using interactive plots, as a minimal example,
import matplotlib.pyplot as plt
import numpy as np
import time
#Interactive plot
plt.ion()
#Setup figures
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ay = fig.add_subplot(122)
plt.show()
x = np.linspace(0,2*np.pi)
for i in range(10000):
print(i)
#Clear axes
ax.cla(); ay.cla()
#Update data
yx = np.sin(x+i*0.1)
yy = np.sin(2.*(x+i*0.1))
#Replot
ax.plot(x,yx)
ay.plot(x,yy)
#Pause to allow redraw
plt.draw()
plt.pause(0.01)

Related

Add plot to a given figure in matplotlib

I have created a figure in one part of the code as follows:
n = arange(51)
fig3 = plt.figure()
plt.semilogy(n,a1mag,'ro')
Now, i want to add another plot to this figure at a later part of the code. Is there some way to access fig3 while plotting?
It would be recommendable to either stay completely in the pyplot state-machine or comlpetely in the object oriented API; mixing the two causes just headaches.
pyplot
plt.figure(3)
plt.semilogy(x,y,'ro')
# .. do other stuff
# reactivate figure 3
plt.figure(3)
plt.plot(x,z)
object-oriented API
fig3, ax3 = plt.subplots()
ax3.semilogy(x,y)
# .. do other stuff
# plot to ax3
ax3.plot(x,z)

x axis label disappearing in matplotlib and basic plotting in python

I am new to matplotlib, and I am finding it very confusing. I have spent quite a lot of time on the matplotlib tutorial website, but I still cannot really understand how to build a figure from scratch. To me, this means doing everything manually... not using the plt.plot() function, but always setting figure, axis handles.
Can anyone explain how to set up a figure from the ground up?
Right now, I have this code to generate a double y-axis plot. But my xlabels are disappearing and I dont' know why
fig, ax1 = plt.subplots()
ax1.plot(yearsTotal,timeseries_data1,'r-')
ax1.set_ylabel('Windspeed [m/s]')
ax1.tick_params('y',colors='r')
ax2 = ax1.twinx()
ax2.plot(yearsTotal,timeseries_data2,'b-')
ax2.set_xticks(np.arange(min(yearsTotal),max(yearsTotal)+1))
ax2.set_xticklabels(ax1.xaxis.get_majorticklabels(), rotation=90)
ax2.set_ylabel('Open water duration [days]')
ax2.tick_params('y',colors='b')
plt.title('My title')
fig.tight_layout()
plt.savefig('plots/my_figure.png',bbox_inches='tight')
plt.show()
Because you are using a twinx, it makes sense to operate only on the original axes (ax1).
Further, the ticklabels are not defined at the point where you call ax1.xaxis.get_majorticklabels().
If you want to set the ticks and ticklabels manually, you can use your own data to do so (although I wouldn't know why you'd prefer this over using the automatic labeling) by specifying a list or array
ticks = np.arange(min(yearsTotal),max(yearsTotal)+1)
ax1.set_xticks(ticks)
ax1.set_xticklabels(ticks)
Since the ticklabels are the same as the tickpositions here, you may also just do
ax1.set_xticks(np.arange(min(yearsTotal),max(yearsTotal)+1))
plt.setp(ax1.get_xticklabels(), rotation=70)
Complete example:
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
yearsTotal = np.arange(1977, 1999)
timeseries_data1 = np.cumsum(np.random.normal(size=len(yearsTotal)))+5
timeseries_data2 = np.cumsum(np.random.normal(size=len(yearsTotal)))+20
fig, ax1 = plt.subplots()
ax1.plot(yearsTotal,timeseries_data1,'r-')
ax1.set_ylabel('Windspeed [m/s]')
ax1.tick_params('y',colors='r')
ax1.set_xticks(np.arange(min(yearsTotal),max(yearsTotal)+1))
plt.setp(ax1.get_xticklabels(), rotation=70)
ax2 = ax1.twinx()
ax2.plot(yearsTotal,timeseries_data2,'b-')
ax2.set_ylabel('Open water duration [days]')
ax2.tick_params('y',colors='b')
plt.title('My title')
fig.tight_layout()
plt.show()
Based on your code, it is not disappear, it is set (overwrite) by these two functions:
ax2.set_xticks(np.arange(min(yearsTotal),max(yearsTotal)+1))
ax2.set_xticklabels(ax1.xaxis.get_majorticklabels(), rotation=90)
set_xticks() on the axes will set the locations and set_xticklabels() will set the xtick labels with list of strings labels.

matplotlib: drawing simultaneously to different plots

Due to the 2nd answer of this question I supposed the following code
import matplotlib.pyplot as plt
for i1 in range(2):
plt.figure(1)
f, ax = plt.subplots()
plt.plot((0,3), (2, 2), 'b')
for i2 in range(2):
plt.figure(2)
f, ax = plt.subplots()
plt.plot([1,2,3], [1,2,3], 'r')
plt.savefig('foo_{}_bar_{}.jpg'.format(i2, i1))
plt.close()
plt.figure(1)
plt.plot( [1,2,3],[1,2,3], 'r')
plt.savefig('bar_{}.jpg'.format(i1))
plt.close()
to create plots bar_0.jpg and bar_1.jpg showing a blue and a red line each.
However, figures look like
instead of
How can I achieve the desired behaviour?
Note that plots foo_*.jpg have to be closed and saved during handling of the bar plots.
You're already saving the Axes objects, so instead of calling the PyPlot plot function (which draws on the last created or activated Axes), use the objects' plot function:
ax.plot(...)
If you then give both a different name, say ax1 and ax2, you can draw on the one you like without interfering with the other. All plt. commands also exist as an Axes member function, but sometimes the name changes (plt.xticks becomes ax.set_xticks for example). See the documentation of Axes for details.
To save to figures, use the Figure objects in the same way:
f.savefig(...)
This API type is only just coming to Matlab, FYI, and will probably replace the old-fashioned "draw on the last active plot" behaviour in the future. The object-oriented approach here is more flexible with minimal overhead, so I strongly recommend you use it everywhere.
If unsure, better to make it explicit:
import matplotlib.pyplot as plt
for i1 in range(2):
fig1,ax1 = plt.subplots()
fig2,ax2 = plt.subplots()
ax1.plot([0,4],[2,2],'b')
for i2 in range(2):
ax2.plot([1,2,3],[1,2,3],'r')
fig2.savefig('abc{}.png'.format(2*i1+i2))
plt.figure(1)
ax1.plot([1,2,3],[1,2,3],'r')
fig1.savefig('cba{}.png'.format(i1))

matplotlib plotting in loop, removing colorbar but whitespace remains

My code is something (roughly) like this:
UPDATE: I've redone this with some actual mock-up code that reflects my general problem. Also, realized that the colorbar creation is in the actual loop as otherwise there's nothing to map it to. Sorry for the code before, typed it up in frantic desperation at the very end of the workday :).
import numpy
import matplotlib as mplot
import matplotlib.pyplot as plt
import os
#make some mock data
x = np.linspace(1,2, 100)
X, Y = np.meshgrid(x, x)
Z = plt.mlab.bivariate_normal(X,Y,1,1,0,0)
fig = plt.figure()
ax = plt.axes()
'''
Do some figure-related stuff that take up a lot of time,
I want to avoid having to do them in the loop over and over again.
They hinge on the presence of fig so I can't make
new figure to save each time or something, I'd have to do
them all over again.
'''
for i in range(1,1000):
plotted = plt.plot(X,Y,Z)
cbar = plt.colorbar(ax=ax, orientation = 'horizontal')
plt.savefig(os.path.expanduser(os.path.join('~/', str(i))))
plt.draw()
mplot.figure.Figure.delaxes(fig, fig.axes[1]) #deletes but whitespace remains
'''
Here I need something to remove the colorbar otherwise
I end up with +1 colorbar on my plot at every iteration.
I've tried various things to remove it BUT it keeps adding whitespace instead
so doesn't actually fix anything.
'''
Has anyone come across this problem before and managed to fix it? Hopefully this is enough
for an idea of the problem, I can post more code if needed but thought it'd be less of a clutter if I just give an overview example.
Thanks.
colorbar() allows you explicitly set which axis to render into - you can use this to ensure that they always appear in the same place, and not steal any space from another axis. Furthermore, you could reset the .mappable attribute of an existing colorbar, rather than redefine it each time.
Example with explicit axes:
x = np.linspace(1,2, 100)
X, Y = np.meshgrid(x, x)
Z = plt.mlab.bivariate_normal(X,Y,1,1,0,0)
fig = plt.figure()
ax1 = fig.add_axes([0.1,0.1,0.8,0.7])
ax2 = fig.add_axes([0.1,0.85,0.8,0.05])
...
for i in range(1,5):
plotted = ax1.pcolor(X,Y,Z)
cbar = plt.colorbar(mappable=plotted, cax=ax2, orientation = 'horizontal')
#note "cax" instead of "ax"
plt.savefig(os.path.expanduser(os.path.join('~/', str(i))))
plt.draw()
I had a very similar problem, which I finally managed to solve by defining a colorbar axes in a similar fashion to:
Multiple imshow-subplots, each with colorbar
The advantage compared to mdurant's answer is that it saves defining the axes location manually.
import matplotlib.pyplot as plt
import IPython.display as display
from mpl_toolkits.axes_grid1 import make_axes_locatable
from pylab import *
%matplotlib inline
def plot_res(ax,cax):
plotted=ax.imshow(rand(10, 10))
cbar=plt.colorbar(mappable=plotted,cax=cax)
fig, axarr = plt.subplots(2, 2)
cax1 = make_axes_locatable(axarr[0,0]).append_axes("right", size="10%", pad=0.05)
cax2 = make_axes_locatable(axarr[0,1]).append_axes("right", size="10%", pad=0.05)
cax3 = make_axes_locatable(axarr[1,0]).append_axes("right", size="10%", pad=0.05)
cax4 = make_axes_locatable(axarr[1,1]).append_axes("right", size="10%", pad=0.05)
# plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0.3, hspace=0.3)
N=10
for j in range(N):
plot_res(axarr[0,0],cax1)
plot_res(axarr[0,1],cax2)
plot_res(axarr[1,0],cax3)
plot_res(axarr[1,1],cax4)
display.clear_output(wait=True)
display.display(plt.gcf())
display.clear_output(wait=True)

Moving matplotlib legend outside of the axis makes it cutoff by the figure box

I'm familiar with the following questions:
Matplotlib savefig with a legend outside the plot
How to put the legend out of the plot
It seems that the answers in these questions have the luxury of being able to fiddle with the exact shrinking of the axis so that the legend fits.
Shrinking the axes, however, is not an ideal solution because it makes the data smaller making it actually more difficult to interpret; particularly when its complex and there are lots of things going on ... hence needing a large legend
The example of a complex legend in the documentation demonstrates the need for this because the legend in their plot actually completely obscures multiple data points.
http://matplotlib.sourceforge.net/users/legend_guide.html#legend-of-complex-plots
What I would like to be able to do is dynamically expand the size of the figure box to accommodate the expanding figure legend.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
ax.grid('on')
Notice how the final label 'Inverse tan' is actually outside the figure box (and looks badly cutoff - not publication quality!)
Finally, I've been told that this is normal behaviour in R and LaTeX, so I'm a little confused why this is so difficult in python... Is there a historical reason? Is Matlab equally poor on this matter?
I have the (only slightly) longer version of this code on pastebin http://pastebin.com/grVjc007
Sorry EMS, but I actually just got another response from the matplotlib mailling list (Thanks goes out to Benjamin Root).
The code I am looking for is adjusting the savefig call to:
fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Note that the bbox_extra_artists must be an iterable
This is apparently similar to calling tight_layout, but instead you allow savefig to consider extra artists in the calculation. This did in fact resize the figure box as desired.
import matplotlib.pyplot as plt
import numpy as np
plt.gcf().clear()
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
handles, labels = ax.get_legend_handles_labels()
lgd = ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5,-0.1))
text = ax.text(-0.2,1.05, "Aribitrary text", transform=ax.transAxes)
ax.set_title("Trigonometry")
ax.grid('on')
fig.savefig('samplefigure', bbox_extra_artists=(lgd,text), bbox_inches='tight')
This produces:
[edit] The intent of this question was to completely avoid the use of arbitrary coordinate placements of arbitrary text as was the traditional solution to these problems. Despite this, numerous edits recently have insisted on putting these in, often in ways that led to the code raising an error. I have now fixed the issues and tidied the arbitrary text to show how these are also considered within the bbox_extra_artists algorithm.
Added: I found something that should do the trick right away, but the rest of the code below also offers an alternative.
Use the subplots_adjust() function to move the bottom of the subplot up:
fig.subplots_adjust(bottom=0.2) # <-- Change the 0.02 to work for your plot.
Then play with the offset in the legend bbox_to_anchor part of the legend command, to get the legend box where you want it. Some combination of setting the figsize and using the subplots_adjust(bottom=...) should produce a quality plot for you.
Alternative:
I simply changed the line:
fig = plt.figure(1)
to:
fig = plt.figure(num=1, figsize=(13, 13), dpi=80, facecolor='w', edgecolor='k')
and changed
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
to
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,-0.02))
and it shows up fine on my screen (a 24-inch CRT monitor).
Here figsize=(M,N) sets the figure window to be M inches by N inches. Just play with this until it looks right for you. Convert it to a more scalable image format and use GIMP to edit if necessary, or just crop with the LaTeX viewport option when including graphics.
Here is another, very manual solution. You can define the size of the axis and paddings are considered accordingly (including legend and tickmarks). Hope it is of use to somebody.
Example (axes size are the same!):
Code:
#==================================================
# Plot table
colmap = [(0,0,1) #blue
,(1,0,0) #red
,(0,1,0) #green
,(1,1,0) #yellow
,(1,0,1) #magenta
,(1,0.5,0.5) #pink
,(0.5,0.5,0.5) #gray
,(0.5,0,0) #brown
,(1,0.5,0) #orange
]
import matplotlib.pyplot as plt
import numpy as np
import collections
df = collections.OrderedDict()
df['labels'] = ['GWP100a\n[kgCO2eq]\n\nasedf\nasdf\nadfs','human\n[pts]','ressource\n[pts]']
df['all-petroleum long name'] = [3,5,2]
df['all-electric'] = [5.5, 1, 3]
df['HEV'] = [3.5, 2, 1]
df['PHEV'] = [3.5, 2, 1]
numLabels = len(df.values()[0])
numItems = len(df)-1
posX = np.arange(numLabels)+1
width = 1.0/(numItems+1)
fig = plt.figure(figsize=(2,2))
ax = fig.add_subplot(111)
for iiItem in range(1,numItems+1):
ax.bar(posX+(iiItem-1)*width, df.values()[iiItem], width, color=colmap[iiItem-1], label=df.keys()[iiItem])
ax.set(xticks=posX+width*(0.5*numItems), xticklabels=df['labels'])
#--------------------------------------------------
# Change padding and margins, insert legend
fig.tight_layout() #tight margins
leg = ax.legend(loc='upper left', bbox_to_anchor=(1.02, 1), borderaxespad=0)
plt.draw() #to know size of legend
padLeft = ax.get_position().x0 * fig.get_size_inches()[0]
padBottom = ax.get_position().y0 * fig.get_size_inches()[1]
padTop = ( 1 - ax.get_position().y0 - ax.get_position().height ) * fig.get_size_inches()[1]
padRight = ( 1 - ax.get_position().x0 - ax.get_position().width ) * fig.get_size_inches()[0]
dpi = fig.get_dpi()
padLegend = ax.get_legend().get_frame().get_width() / dpi
widthAx = 3 #inches
heightAx = 3 #inches
widthTot = widthAx+padLeft+padRight+padLegend
heightTot = heightAx+padTop+padBottom
# resize ipython window (optional)
posScreenX = 1366/2-10 #pixel
posScreenY = 0 #pixel
canvasPadding = 6 #pixel
canvasBottom = 40 #pixel
ipythonWindowSize = '{0}x{1}+{2}+{3}'.format(int(round(widthTot*dpi))+2*canvasPadding
,int(round(heightTot*dpi))+2*canvasPadding+canvasBottom
,posScreenX,posScreenY)
fig.canvas._tkcanvas.master.geometry(ipythonWindowSize)
plt.draw() #to resize ipython window. Has to be done BEFORE figure resizing!
# set figure size and ax position
fig.set_size_inches(widthTot,heightTot)
ax.set_position([padLeft/widthTot, padBottom/heightTot, widthAx/widthTot, heightAx/heightTot])
plt.draw()
plt.show()
#--------------------------------------------------
#==================================================
I tried a very simple way, just make the figure a bit wider:
fig, ax = plt.subplots(1, 1, figsize=(a, b))
adjust a and b to a proper value such that the legend is included in the figure

Categories

Resources