python/matplotlib - Specifying Parasite Axis Length - python

I am plotting data from several sources and need multiple x axes, preferably offset such as those seen in the link. I would very much like my x axes to have variable length, allowing me to put many plots on the same figure. What I have done so far is:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
host = host_subplot(111, axes_class=AA.Axes,yscale='log')
plt.subplots_adjust(bottom=0.25)
par1 = host.twiny()
offset = 60
new_fixed_axis = par1.get_grid_helper().new_fixed_axis
par1.axis['bottom'] = new_fixed_axis(loc='bottom',
axes=par1,
offset=(0, -60))
host.set_xlim(200, 350)
host.set_ylim(1050, 100)
par1.set_xlim(0, 1)
host.set_xlabel('Temperature (K)')
host.set_ylabel('Pressure (hPa)')
par1.set_xlabel('Relative Humidity (%)')
p1, = host.plot(T,P)
p2, = host.plot(pT,P)
p2, = par1.plot(RH,P)
So I get the axis to drop down, but cannot, for the life of me, figure out how to get the axis to actually compress horizontally (e.g. like the blue axes in the linked figure above).
My question is how can this be done (if at all)?
#Oz123
Here is what I have:
host = host_subplot(111, axes_class=AA.Axes,yscale='log')
plt.subplots_adjust(bottom=0.25)
par1 = host.twiny()
new_fixed_axis = par1.get_grid_helper().new_fixed_axis
cax1 = plt.axes(axisbg='none',frameon=False)
cax1 = plt.add_axes(plt.get_position(), frameon=False)
par1.axis['bottom'] = new_fixed_axis(loc='bottom',
axes=cax1,
offset=(0, -60))
When I get to:
cax1 = plt.add_axes(plt.get_position(), frameon=False)
My previous x/y axes disappear, and I am left with a grey screen with only cax1.
My apologies, I'm just picking up matplotlib so I'M afraid I'm still quite a novice here.

You are creating par1.axis['bottom'] with the major axobject, so you are quite limited in what you can actually do.
Instead you should create 2 or more axes instances. And put them on the figure instance.
adding new axes instance
cax1 = plt.axes(axisbg='none', frameon=False)
Like this you could you have fine grain control of the size of your humidity scale.
The following line:
par1.axis['bottom'] = new_fixed_axis(loc='bottom',
axes=par1,
offset=(0, -60))
should be for example:
par1.axis['bottom'] = new_fixed_axis(loc='bottom',
axes=cax1, # custom axis number 1
offset=(0, -60))
Note that using IPython, you can quickly find which methods are available to control
your newly created axes instance.
In [38]: cax1.set_ #tab pressed
cax1.set_adjustable cax1.set_axis_bgcolor cax1.set_frame_on cax1.set_subplotspec cax1.set_xticks
cax1.set_agg_filter cax1.set_axis_off cax1.set_gid cax1.set_title cax1.set_ybound
cax1.set_alpha cax1.set_axis_on
# many more options trimmed, but I think you might want to take a look in:
controlling the location of your newly created instance:
In [38]: cax1.set_position?
Type: instancemethod
String Form:<bound method AxesSubplot.set_position of <matplotlib.axes.AxesSubplot object at 0x2d7fb90>>
File: /usr/lib/pymodules/python2.7/matplotlib/axes.py
Definition: cax1.set_position(self, pos, which='both')
Docstring:
Set the axes position with::
pos = [left, bottom, width, height]

Related

How to add border or frame around individual subplots

I want to create an image like this, but I'm unable to put the individual plots inside a frame.
Figures and axes have a patch attribute, which is the rectangle that makes up the background. Setting a figure frame is hence pretty straightforward:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 1)
# add a bit more breathing room around the axes for the frames
fig.subplots_adjust(top=0.85, bottom=0.15, left=0.2, hspace=0.8)
fig.patch.set_linewidth(10)
fig.patch.set_edgecolor('cornflowerblue')
# When saving the figure, the figure patch parameters are overwritten (WTF?).
# Hence we need to specify them again in the save command.
fig.savefig('test.png', edgecolor=fig.get_edgecolor())
Now the axes are a much tougher nut to crack. We could use the same approach as for the figure (which #jody-klymak I think is suggesting), however, the patch only corresponds to the area that is inside the axis limits, i.e. it does not include the tick labels, axis labels, nor the title.
However, axes have a get_tightbbox method, which is what we are after. However, using that also has some gotchas, as explained in the code comments.
# We want to use axis.get_tightbbox to determine the axis dimensions including all
# decorators, i.e. tick labels, axis labels, etc.
# However, get_tightbox requires the figure renderer, which is not initialized
# until the figure is drawn.
plt.ion()
fig.canvas.draw()
for ii, ax in enumerate(axes):
ax.set_title(f'Title {ii+1}')
ax.set_ylabel(f'Y-Label {ii+1}')
ax.set_xlabel(f'X-Label {ii+1}')
bbox = ax.get_tightbbox(fig.canvas.get_renderer())
x0, y0, width, height = bbox.transformed(fig.transFigure.inverted()).bounds
# slightly increase the very tight bounds:
xpad = 0.05 * width
ypad = 0.05 * height
fig.add_artist(plt.Rectangle((x0-xpad, y0-ypad), width+2*xpad, height+2*ypad, edgecolor='red', linewidth=3, fill=False))
fig.savefig('test2.png', edgecolor=fig.get_edgecolor())
plt.show()
I found something very similar and somehow configured it out what its doing .
autoAxis1 = ax8i[1].axis() #ax8i[1] is the axis where we want the border
import matplotlib.patches as ptch
rec = ptch.Rectangle((autoAxis1[0]-12,autoAxis1[2]-30),(autoAxis1[1]-
autoAxis1[0])+18,(autoAxis1[3]-
autoAxis1[2])+35,fill=False,lw=2,edgecolor='cyan')
rec = ax8i[1].add_patch(rec)
rec.set_clip_on(False)
The code is a bit complex but once we get to know what part of the bracket inside the Rectangle() is doing what its quite easy to get the code .

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)

x-axis labels crops figure size [duplicate]

I want to to create a figure using matplotlib where I can explicitly specify the size of the axes, i.e. I want to set the width and height of the axes bbox.
I have looked around all over and I cannot find a solution for this. What I typically find is how to adjust the size of the complete Figure (including ticks and labels), for example using fig, ax = plt.subplots(figsize=(w, h))
This is very important for me as I want to have a 1:1 scale of the axes, i.e. 1 unit in paper is equal to 1 unit in reality. For example, if xrange is 0 to 10 with major tick = 1 and x axis is 10cm, then 1 major tick = 1cm. I will save this figure as pdf to import it to a latex document.
This question brought up a similar topic but the answer does not solve my problem (using plt.gca().set_aspect('equal', adjustable='box') code)
From this other question I see that it is possible to get the axes size, but not how to modify them explicitly.
Any ideas how I can set the axes box size and not just the figure size. The figure size should adapt to the axes size.
Thanks!
For those familiar with pgfplots in latex, it will like to have something similar to the scale only axis option (see here for example).
The axes size is determined by the figure size and the figure spacings, which can be set using figure.subplots_adjust(). In reverse this means that you can set the axes size by setting the figure size taking into acount the figure spacings:
import matplotlib.pyplot as plt
def set_size(w,h, ax=None):
""" w, h: width, height in inches """
if not ax: ax=plt.gca()
l = ax.figure.subplotpars.left
r = ax.figure.subplotpars.right
t = ax.figure.subplotpars.top
b = ax.figure.subplotpars.bottom
figw = float(w)/(r-l)
figh = float(h)/(t-b)
ax.figure.set_size_inches(figw, figh)
fig, ax=plt.subplots()
ax.plot([1,3,2])
set_size(5,5)
plt.show()
It appears that Matplotlib has helper classes that allow you to define axes with a fixed size Demo fixed size axes
I have found that ImportanceofBeingErnests answer which modifies that figure size to adjust the axes size provides inconsistent results with the paticular matplotlib settings I use to produce publication ready plots. Slight errors were present in the final figure size, and I was unable to find a way to solve the issue with his approach. For most use cases I think this is not a problem, however the errors were noticeable when combining multiple pdf's for publication.
In lieu of developing a minimum working example to find the real issue I am having with the figure resizing approach I instead found a work around which uses the fixed axes size utilising the divider class.
from mpl_toolkits.axes_grid1 import Divider, Size
def fix_axes_size_incm(axew, axeh):
axew = axew/2.54
axeh = axeh/2.54
#lets use the tight layout function to get a good padding size for our axes labels.
fig = plt.gcf()
ax = plt.gca()
fig.tight_layout()
#obtain the current ratio values for padding and fix size
oldw, oldh = fig.get_size_inches()
l = ax.figure.subplotpars.left
r = ax.figure.subplotpars.right
t = ax.figure.subplotpars.top
b = ax.figure.subplotpars.bottom
#work out what the new ratio values for padding are, and the new fig size.
neww = axew+oldw*(1-r+l)
newh = axeh+oldh*(1-t+b)
newr = r*oldw/neww
newl = l*oldw/neww
newt = t*oldh/newh
newb = b*oldh/newh
#right(top) padding, fixed axes size, left(bottom) pading
hori = [Size.Scaled(newr), Size.Fixed(axew), Size.Scaled(newl)]
vert = [Size.Scaled(newt), Size.Fixed(axeh), Size.Scaled(newb)]
divider = Divider(fig, (0.0, 0.0, 1., 1.), hori, vert, aspect=False)
# the width and height of the rectangle is ignored.
ax.set_axes_locator(divider.new_locator(nx=1, ny=1))
#we need to resize the figure now, as we have may have made our axes bigger than in.
fig.set_size_inches(neww,newh)
Things worth noting:
Once you call set_axes_locator() on an axis instance you break the tight_layout() function.
The original figure size you choose will be irrelevent, and the final figure size is determined by the axes size you choose and the size of the labels/tick labels/outward ticks.
This approach doesn't work with colour scale bars.
This is my first ever stack overflow post.
another method using fig.add_axes was quite accurate. I have included 1 cm grid aswell
import matplotlib.pyplot as plt
import matplotlib as mpl
# This example fits a4 paper with 5mm margin printers
# figure settings
figure_width = 28.7 # cm
figure_height = 20 # cm
left_right_magrin = 1 # cm
top_bottom_margin = 1 # cm
# Don't change
left = left_right_magrin / figure_width # Percentage from height
bottom = top_bottom_margin / figure_height # Percentage from height
width = 1 - left*2
height = 1 - bottom*2
cm2inch = 1/2.54 # inch per cm
# specifying the width and the height of the box in inches
fig = plt.figure(figsize=(figure_width*cm2inch,figure_height*cm2inch))
ax = fig.add_axes((left, bottom, width, height))
# limits settings (important)
plt.xlim(0, figure_width * width)
plt.ylim(0, figure_height * height)
# Ticks settings
ax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(mpl.ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(5))
ax.yaxis.set_minor_locator(mpl.ticker.MultipleLocator(1))
# Grid settings
ax.grid(color="gray", which="both", linestyle=':', linewidth=0.5)
# your Plot (consider above limits)
ax.plot([1,2,3,5,6,7,8,9,10,12,13,14,15,17])
# save figure ( printing png file had better resolution, pdf was lighter and better on screen)
plt.show()
fig.savefig('A4_grid_cm.png', dpi=1000)
fig.savefig('tA4_grid_cm.pdf')
result:

How to check if colorbar exists on figure

Question: Is there a way to check if a color bar already exists?
I am making many plots with a loop. The issue is that the color bar is drawn every iteration!
If I could determine if the color bar exists then I can put the color bar function in an if statement.
if cb_exists:
# do nothing
else:
plt.colorbar() #draw the colorbar
If I use multiprocessing to make the figures, is it possible to prevent multiple color bars from being added?
import numpy as np
import matplotlib.pyplot as plt
import multiprocessing
def plot(number):
a = np.random.random([5,5])*number
plt.pcolormesh(a)
plt.colorbar()
plt.savefig('this_'+str(number))
# I want to make a 50 plots
some_list = range(0,50)
num_proc = 5
p = multiprocessing.Pool(num_proc)
temps = p.map(plot, some_list)
I realize I can clear the figure with plt.clf() and plt.cla() before plotting the next iteration. But, I have data on my basemap layer I don't want to re-plot (that adds to the time it takes to create the plot). So, if I could remove the colorbar and add a new one I'd save some time.
Is is actually not easy to remove a colorbar from a plot and later draw a new one to it.
The best solution I can come up with at the moment is the following, which assumes that there is only one axes present in the plot. Now, if there was a second axis, it must be the colorbar beeing present. So by checking how many axes we find on the plot, we can judge upon whether or not there is a colorbar.
Here we also mind the user's wish not to reference any named objects from outside. (Which does not makes much sense, as we need to use plt anyways, but hey.. so was the question)
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
im = ax.pcolormesh(np.array(np.random.rand(2,2) ))
ax.plot(np.cos(np.linspace(0.2,1.8))+0.9, np.sin(np.linspace(0.2,1.8))+0.9, c="k", lw=6)
ax.set_title("Title")
cbar = plt.colorbar(im)
cbar.ax.set_ylabel("Label")
for i in range(10):
# inside this loop we should not access any variables defined outside
# why? no real reason, but questioner asked for it.
#draw new colormesh
im = plt.gcf().gca().pcolormesh(np.random.rand(2,2))
#check if there is more than one axes
if len(plt.gcf().axes) > 1:
# if so, then the last axes must be the colorbar.
# we get its extent
pts = plt.gcf().axes[-1].get_position().get_points()
# and its label
label = plt.gcf().axes[-1].get_ylabel()
# and then remove the axes
plt.gcf().axes[-1].remove()
# then we draw a new axes a the extents of the old one
cax= plt.gcf().add_axes([pts[0][0],pts[0][1],pts[1][0]-pts[0][0],pts[1][1]-pts[0][1] ])
# and add a colorbar to it
cbar = plt.colorbar(im, cax=cax)
cbar.ax.set_ylabel(label)
# unfortunately the aspect is different between the initial call to colorbar
# without cax argument. Try to reset it (but still it's somehow different)
cbar.ax.set_aspect(20)
else:
plt.colorbar(im)
plt.show()
In general a much better solution would be to operate on the objects already present in the plot and only update them with the new data. Thereby, we suppress the need to remove and add axes and find a much cleaner and faster solution.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
im = ax.pcolormesh(np.array(np.random.rand(2,2) ))
ax.plot(np.cos(np.linspace(0.2,1.8))+0.9, np.sin(np.linspace(0.2,1.8))+0.9, c="k", lw=6)
ax.set_title("Title")
cbar = plt.colorbar(im)
cbar.ax.set_ylabel("Label")
for i in range(10):
data = np.array(np.random.rand(2,2) )
im.set_array(data.flatten())
cbar.set_clim(vmin=data.min(),vmax=data.max())
cbar.draw_all()
plt.draw()
plt.show()
Update:
Actually, the latter approach of referencing objects from outside even works together with the multiprocess approach desired by the questioner.
So, here is a code that updates the figure, without the need to delete the colorbar.
import matplotlib.pyplot as plt
import numpy as np
import multiprocessing
import time
fig, ax = plt.subplots()
im = ax.pcolormesh(np.array(np.random.rand(2,2) ))
ax.plot(np.cos(np.linspace(0.2,1.8))+0.9, np.sin(np.linspace(0.2,1.8))+0.9, c="w", lw=6)
ax.set_title("Title")
cbar = plt.colorbar(im)
cbar.ax.set_ylabel("Label")
tx = ax.text(0.2,0.8, "", fontsize=30, color="w")
tx2 = ax.text(0.2,0.2, "", fontsize=30, color="w")
def do(number):
start = time.time()
tx.set_text(str(number))
data = np.array(np.random.rand(2,2)*(number+1) )
im.set_array(data.flatten())
cbar.set_clim(vmin=data.min(),vmax=data.max())
tx2.set_text("{m:.2f} < {ma:.2f}".format(m=data.min(), ma= data.max() ))
cbar.draw_all()
plt.draw()
plt.savefig("multiproc/{n}.png".format(n=number))
stop = time.time()
return np.array([number, start, stop])
if __name__ == "__main__":
multiprocessing.freeze_support()
some_list = range(0,50)
num_proc = 5
p = multiprocessing.Pool(num_proc)
nu = p.map(do, some_list)
nu = np.array(nu)
plt.close("all")
fig, ax = plt.subplots(figsize=(16,9))
ax.barh(nu[:,0], nu[:,2]-nu[:,1], height=np.ones(len(some_list)), left=nu[:,1], align="center")
plt.show()
(The code at the end shows a timetable which allows to see that multiprocessing has indeed taken place)
If you can access to axis and image information, colorbar can be retrieved
as a property of the image (or the mappable to which associate colorbar).
Following a previous answer (How to retrieve colorbar instance from figure in matplotlib), an example could be:
ax=plt.gca() #plt.gca() for current axis, otherwise set appropriately.
im=ax.images #this is a list of all images that have been plotted
if im[-1].colorbar is None: #in this case I assume to be interested to the last one plotted, otherwise use the appropriate index or loop over
plt.colorbar() #plot a new colorbar
Note that an image without colorbar returns None to im[-1].colorbar
One approach is:
initially (prior to having any color bar drawn), set a variable
colorBarPresent = False
in the method for drawing the color bar, check to see if it's already drawn. If not, draw it and set the colorBarPresent variable True:
def drawColorBar():
if colorBarPresent:
# leave the function and don't draw the bar again
else:
# draw the color bar
colorBarPresent = True
There is an indirect way of guessing (with reasonable accuracy for most applications, I think) whether an Axes instance is home to a color bar. Depending on whether it is a horizontal or vertical color bar, either the X axis or Y axis (but not both) will satisfy all of these conditions:
No ticks
No tick labels
No axis label
Axis range is (0, 1)
So here's a function for you:
def is_colorbar(ax):
"""
Guesses whether a set of Axes is home to a colorbar
:param ax: Axes instance
:return: bool
True if the x xor y axis satisfies all of the following and thus looks like it's probably a colorbar:
No ticks, no tick labels, no axis label, and range is (0, 1)
"""
xcb = (len(ax.get_xticks()) == 0) and (len(ax.get_xticklabels()) == 0) and (len(ax.get_xlabel()) == 0) and \
(ax.get_xlim() == (0, 1))
ycb = (len(ax.get_yticks()) == 0) and (len(ax.get_yticklabels()) == 0) and (len(ax.get_ylabel()) == 0) and \
(ax.get_ylim() == (0, 1))
return xcb != ycb # != is effectively xor in this case, since xcb and ycb are both bool
Thanks to this answer for the cool != xor trick: https://stackoverflow.com/a/433161/6605826
With this function, you can see if a colorbar exists by:
colorbar_exists = any([is_colorbar(ax) for ax in np.atleast_1d(gcf().axes).flatten()])
or if you're sure the colorbar will always be last, you can get off easy with:
colorbar_exists = is_colorbar(gcf().axes[-1])

Matplotlib animate fill_between shape

I am trying to animate a fill_between shape inside matplotlib and I don't know how to update the data of the PolyCollection. Take this simple example: I have two lines and I am always filling between them. Of course, the lines change and are animated.
Here is a dummy example:
import matplotlib.pyplot as plt
# Init plot:
f_dummy = plt.figure(num=None, figsize=(6, 6));
axes_dummy = f_dummy.add_subplot(111);
# Plotting:
line1, = axes_dummy.plot(X, line1_data, color = 'k', linestyle = '--', linewidth=2.0, animated=True);
line2, = axes_dummy.plot(X, line2_data, color = 'Grey', linestyle = '--', linewidth=2.0, animated=True);
fill_lines = axes_dummy.fill_between(X, line1_data, line2_data, color = '0.2', alpha = 0.5, animated=True);
f_dummy.show();
f_dummy.canvas.draw();
dummy_background = f_dummy.canvas.copy_from_bbox(axes_dummy.bbox);
# [...]
# Update plot data:
def update_data():
line1_data = # Do something with data
line2_data = # Do something with data
f_dummy.canvas.restore_region( dummy_background );
line1.set_ydata(line1_data);
line2.set_ydata(line2_data);
# Update fill data too
axes_dummy.draw_artist(line1);
axes_dummy.draw_artist(line2);
# Draw fill too
f_dummy.canvas.blit( axes_dummy.bbox );
The question is how to update the fill_between Poly data based on line1_data and line2_data each time update_data() is called and draw them before blit ("# Update fill data too" & "# Draw fill too"). I tried fill_lines.set_verts() without success and could not find an example.
Ok, as someone pointed out, we are dealing with a collection here, so we will have to delete and redraw. So somewhere in the update_data function, delete all collections associated with it:
axes_dummy.collections.clear()
and draw the new "fill_between" PolyCollection:
axes_dummy.fill_between(x, y-sigma, y+sigma, facecolor='yellow', alpha=0.5)
A similar trick is required to overlay an unfilled contour plot on top of a filled one, since an unfilled contour plot is a Collection as well (of lines I suppose?).
this is not my answer, but I found it most useful:
http://matplotlib.1069221.n5.nabble.com/animation-of-a-fill-between-region-td42814.html
Hi Mauricio,
Patch objects are a bit more difficult to work with than line objects, because unlike line objects are a step removed from the input data supplied by the user. There is an example similar to what you want to do here: http://matplotlib.org/examples/animation/histogram.html
Basically, you need to modify the vertices of the path at each frame. It might look something like this:
from matplotlib import animation
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_xlim([0,10000])
x = np.linspace(6000.,7000., 5)
y = np.ones_like(x)
collection = plt.fill_between(x, y)
def animate(i):
path = collection.get_paths()[0]
path.vertices[:, 1] *= 0.9
animation.FuncAnimation(fig, animate,
frames=25, interval=30)
Take a look at path.vertices to see how they're laid out.
Hope that helps,
Jake
If you don't want to use anitmation, or to remove everything from your figure to update only filling, you could use this way :
call fill_lines.remove() and then call again axes_dummy.fill_between() to draw new ones. It worked in my case.
initialize pyplot interactive mode
import matplotlib.pyplot as plt
plt.ion()
use the optional label argument when plotting the fill:
plt.fill_between(
x,
y1,
y2,
color="yellow",
label="cone"
)
plt.pause(0.001) # refresh the animation
later in our script we can select by label to delete that specific fill or a list of fills, thus animating on a object by object basis.
axis = plt.gca()
fills = ["cone", "sideways", "market"]
for collection in axis.collections:
if str(collection.get_label()) in fills:
collection.remove()
del collection
plt.pause(0.001)
you can use the same label for groups of objects you would like to delete; or otherwise encode the labels with tags as needed to suit needs
for example if we had fills labelled:
"cone1" "cone2" "sideways1"
if "cone" in str(collection.get_label()):
would sort to delete both those prefixed with "cone".
You can also animate lines in the same manner
for line in axis.lines:
another idiom which will work is too keep a list of your plotted objects; this method seems to work with any type of plotted object.
# plot interactive mode on
plt.ion()
# create a dict to store "fills"
# perhaps some other subclass of plots
# "yellow lines" etc.
plots = {"fills":[]}
# begin the animation
while 1:
# cycle through previously plotted objects
# attempt to kill them; else remember they exist
fills = []
for fill in plots["fills"]:
try:
# remove and destroy reference
fill.remove()
del fill
except:
# and if not try again next time
fills.append(fill)
pass
plots["fills"] = fills
# transformation of data for next frame
x, y1, y2 = your_function(x, y1, y2)
# fill between plot is appended to stored fills list
plots["fills"].append(
plt.fill_between(
x,
y1,
y2,
color="red",
)
)
# frame rate
plt.pause(1)
In contrast to what most answers here stated, it is not necessary to remove and redraw a PolyCollection returned by fill_between each time you want to update its data. Instead, you can modify the vertices and codes attribute of the underlying Path object. Let's assume you've created a PolyCollection via
import numpy as np
import matplotlib.pyplot as plt
#dummy data
x = np.arange(10)
y0 = x-1
y1 = x+1
fig = plt.figure()
ax = fig.add_subplot()
p = ax.fill_between(x,y0,y1)
and now you want to update p with new data xnew, y0new and y1new. Then what you could do is
v_x = np.hstack([xnew[0],xnew,xnew[-1],xnew[::-1],xnew[0]])
v_y = np.hstack([y1new[0],y0new,y0new[-1],y1new[::-1],y1new[0]])
vertices = np.vstack([v_x,v_y]).T
codes = np.array([1]+(2*len(xnew)+1)*[2]+[79]).astype('uint8')
path = p.get_paths()[0]
path.vertices = vertices
path.codes = codes
Explanation: path.vertices contains the vertices of the patch drawn by fill_between including additional start and end positions, path.codes contains instructions on how to use them (1=MOVE POINTER TO, 2=DRAW LINE TO, 79=CLOSE POLY).

Categories