I'm trying to make a plot using matplotlib that resembles the following:
However, I'm not quite sure which type of graph to use. My data has the following form, where start x position is a positive value greater or equal to 0:
<item 1><start x position><end x position>
<item 2><start x position><end x position>
Looking at the docs, I see that there is barh and errorbar, but I'm not sure if its possible to use barh with a start offset. What would be the best method to use, given my type of data? I'm not that familiar with the library, so I was hoping to get some insight.
Appetizer
Commented Code
As far as I know, the most direct way to do what you want requires that you directly draw your rectangles on the matplotlib canvas using the patches module of matplotlib
A simple implementation follows
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def plot_rect(data, delta=0.4):
"""data is a dictionary, {"Label":(low,hi), ... }
return a drawing that you can manipulate, show, save etc"""
yspan = len(data)
yplaces = [.5+i for i in range(yspan)]
ylabels = sorted(data.keys())
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_yticks(yplaces)
ax.set_yticklabels(ylabels)
ax.set_ylim((0,yspan))
# later we'll need the min and max in the union of intervals
low, hi = data[ylabels[0]]
for pos, label in zip(yplaces,ylabels):
start, end = data[label]
ax.add_patch(patches.Rectangle((start,pos-delta/2.0),end-start,delta))
if start<low : low=start
if end>hi : hi=end
# little small trick, draw an invisible line so that the x axis
# limits are automatically adjusted...
ax.plot((low,hi),(0,0))
# now get the limits as automatically computed
xmin, xmax = ax.get_xlim()
# and use them to draw the hlines in your example
ax.hlines(range(1,yspan),xmin,xmax)
# the vlines are simply the x grid lines
ax.grid(axis='x')
# eventually return what we have done
return ax
# this is the main script, note that we have imported pyplot as plt
# the data, inspired by your example,
data = {'A':(1901,1921),
'B':(1917,1935),
'C':(1929,1948),
'D':(1943,1963),
'E':(1957,1983),
'F':(1975,1991),
'G':(1989,2007)}
# call the function and give its result a name
ax = plot_rect(data)
# so that we can further manipulate it using the `axes` methods, e.g.
ax.set_xlabel('Whatever')
# finally save or show what we have
plt.show()
The result of our sufferings has been shown in the first paragraph of this post...
Addendum
Let's say that you feel that blue is a very dull color...
The patches you've placed in your drawing are accessible as a property (aptly named patches...) of the drawing and modifiable too, e.g.,
ax = plot_rect(data)
ax.set_xlabel('Whatever')
for rect in ax.patches:
rect.set(facecolor=(0.9,0.9,0.2,1.0), # a tuple, RGBA
edgecolor=(0.6,0.2,0.3,1.0),
linewidth=3.0)
plt.show()
In my VH opinion, a custom plotting function should do the least indispensable to characterize the plot, as this kind of post-production is usually very easy in matplotlib.
Related
This question already has an answer here:
Drawing a colorbar aside a line plot, using Matplotlib
(1 answer)
Closed 1 year ago.
Let's say I have one figure with a certain number of plots, which resembles like this one:
where the colors of the single plots are decided automatically by matplotlib. The code to obtain this is very simple:
for i in range(len(some_list)):
x, y = some_function(dataset, some_list[i])
plt.plot(x, y)
Now suppose that all these lines depend on a third variable z. I would like to include this information plotting the given lines with a color that gives information about the magnitude of z, possibly using a colormap and a colorbar on the right side of the figure. What would you suggest me to do? I exclude to use a legend since in my figures I have many more lines that the ones I am showing. All information I can find is about how to draw one single line with different colors, but this is not what I am looking for. I thank you in advance!
Here it is some code that, in my opinion, you can easily adapt to your problem
import numpy as np
import matplotlib.pyplot as plt
from random import randint
# generate some data
N, vmin, vmax = 12, 0, 20
rd = lambda: randint(vmin, vmax)
segments_z = [((rd(),rd()),(rd(),rd()),rd()) for _ in range(N)]
# prepare for the colorization of the lines,
# first the normalization function and the colomap we want to use
norm = plt.Normalize(vmin, vmax)
cm = plt.cm.rainbow
# most important, plt.plot doesn't prepare the ScalarMappable
# that's required to draw the colorbar, so we'll do it instead
sm = plt.cm.ScalarMappable(cmap=cm, norm=norm)
# plot the segments, the segment color depends on z
for p1, p2, z in segments_z:
x, y = zip(p1,p2)
plt.plot(x, y, color=cm(norm(z)))
# draw the colorbar, note that we pass explicitly the ScalarMappable
plt.colorbar(sm)
# I'm done, I'll show the results,
# you probably want to add labels to the axes and the colorbar.
plt.show()
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])
I am trying to set a space between the boxplots (between the green and orange boxes) created with Python Seaborn module's sns.boxplot(). Please see attached the graph, that the green and orange subplot boxes are stuck to each other, making it visually not the most appealing.
Can't find a way to do that, anyone could find a way (code attached)?
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset("tips")
sns.set(style="ticks", palette='Set2', font='Roboto Condensed')
sns.set_context("paper", font_scale=1.1, rc={"lines.linewidth": 1.1})
g=sns.factorplot(x="time", y="total_bill", hue="smoker",
col="day", data=tips, kind="box", size=4, aspect=0.5,
width=0.8,fliersize=2.5,linewidth=1.1, notch=False,orient="v")
sns.despine(trim=True)
g.savefig('test6.png', format='png', dpi=600)
The Seaborn boxplot documentation is here: http://stanford.edu/~mwaskom/software/seaborn/generated/seaborn.boxplot.html
Running the danger that this is not needed anymore, I found a solution to this problem. When drawing boxplots directly with matplotlib, the arrangement of the boxes can be controlled with the width and position keywords. However, when passing the positions keyword to sns.factorplot(kind='box',...), one gets a
TypeError: boxplot() got multiple values for keyword argument 'positions'
To get around this, one can set the widths of the boxes 'manually' after the boxplot has been created. This is a bit tedious, because the boxes are stored as PatchPatches within the individual Axes instances of the FacedGrid that is returned by sns.factorplot. Instead of the simple (x,y,width,height) syntax that Rects have, PathPatches use vertices to define the corners, which involves slightly more computation when one wants to adjust the boxes. On top of everything else, the PathPatches returned by matplotlib.boxplot contain an extra (ignored) vertex for the Path.CLOSEPOLY code, which is set to (0,0) and is best ignored. In addition to the box, the horizontal line that marks the median is now too wide and needs to be adjusted as well.
Below I define a function that adjusts widths of the boxes generated by the OP's example code(note the extra import):
from matplotlib.patches import PathPatch
def adjust_box_widths(g, fac):
"""
Adjust the withs of a seaborn-generated boxplot.
"""
##iterating through Axes instances
for ax in g.axes.flatten():
##iterating through axes artists:
for c in ax.get_children():
##searching for PathPatches
if isinstance(c, PathPatch):
##getting current width of box:
p = c.get_path()
verts = p.vertices
verts_sub = verts[:-1]
xmin = np.min(verts_sub[:,0])
xmax = np.max(verts_sub[:,0])
xmid = 0.5*(xmin+xmax)
xhalf = 0.5*(xmax - xmin)
##setting new width of box
xmin_new = xmid-fac*xhalf
xmax_new = xmid+fac*xhalf
verts_sub[verts_sub[:,0] == xmin,0] = xmin_new
verts_sub[verts_sub[:,0] == xmax,0] = xmax_new
##setting new width of median line
for l in ax.lines:
if np.all(l.get_xdata() == [xmin,xmax]):
l.set_xdata([xmin_new,xmax_new])
calling this function with
adjust_box_widths(g, 0.9)
gives the following output:
Changing the vertical distance between two subplot using tight_layout(h_pad=-1) changes the total figuresize. How can I define the figuresize using tight_layout?
Here is the code:
#define figure
pl.figure(figsize=(10, 6.25))
ax1=subplot(211)
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
subplot(212)
x=linspace(0,50)
pl.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
And here is the results:
If I write
pl.tight_layout(h_pad=-2)
in the last line, then I get this:
As you can see, the figure is bigger...
You can use a GridSpec object to control precisely width and height ratios, as answered on this thread and documented here.
Experimenting with your code, I could produce something like what you want, by using a height_ratio that assigns twice the space to the upper subplot, and increasing the h_pad parameter to the tight_layout call. This does not sound completely right, but maybe you can adjust this further ...
import numpy as np
from matplotlib.pyplot import *
import matplotlib.pyplot as pl
import matplotlib.gridspec as gridspec
#define figure
fig = pl.figure(figsize=(10, 6.25))
gs = gridspec.GridSpec(2, 1, height_ratios=[2,1])
ax1=subplot(gs[0])
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
ax2=subplot(gs[1])
x=np.linspace(0,50)
ax2.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
fig.tight_layout(h_pad=-5)
show()
There were other issues, like correcting the imports, adding numpy, and plotting to ax2 instead of directly with pl. The output I see is this:
This case is peculiar because of the fact that the default aspect ratios of images and plots are not the same. So it is worth noting for people looking to remove the spaces in a grid of subplots consisting of images only or of plots only that you may find an appropriate solution among the answers to this question (and those linked to it): How to remove the space between subplots in matplotlib.pyplot?.
The aspect ratios of the subplots in this particular example are as follows:
# Default aspect ratio of images:
ax1.get_aspect()
# 1.0
# Which is as it is expected based on the default settings in rcParams file:
matplotlib.rcParams['image.aspect']
# 'equal'
# Default aspect ratio of plots:
ax2.get_aspect()
# 'auto'
The size of ax1 and the space beneath it are adjusted automatically based on the number of pixels along the x-axis (i.e. width) so as to preserve the 'equal' aspect ratio while fitting both subplots within the figure. As you mentioned, using fig.tight_layout(h_pad=xxx) or the similar fig.set_constrained_layout_pads(hspace=xxx) is not a good option as this makes the figure larger.
To remove the gap while preserving the original figure size, you can use fig.subplots_adjust(hspace=xxx) or the equivalent plt.subplots(gridspec_kw=dict(hspace=xxx)), as shown in the following example:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
np.random.seed(1)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6.25),
gridspec_kw=dict(hspace=-0.206))
# For those not using plt.subplots, you can use this instead:
# fig.subplots_adjust(hspace=-0.206)
size = 50
ax1.imshow(np.random.random((10, size)))
ax1.xaxis.set_visible(False)
# Create plot of a line that is aligned with the image above
x = np.arange(0, size)
ax2.plot(x, x, 'k-')
ax2.set_xlim(ax1.get_xlim())
plt.show()
I am not aware of any way to define the appropriate hspace automatically so that the gap can be removed for any image width. As stated in the docstring for fig.subplots_adjust(), it corresponds to the height of the padding between subplots, as a fraction of the average axes height. So I attempted to compute hspace by dividing the gap between the subplots by the average height of both subplots like this:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Compute negative hspace to close the vertical gap between subplots
ax1_h = ax1_y1-ax1_y0
ax2_h = ax2_y1-ax2_y0
avg_h = (ax1_h+ax2_h)/2
gap = ax1_y0-ax2_y1
hspace=-(gap/avg_h) # this divided by 2 also does not work
fig.subplots_adjust(hspace=hspace)
Unfortunately, this does not work. Maybe someone else has a solution for this.
It is also worth mentioning that I tried removing the gap between subplots by editing the y positions like in this example:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Set new y positions: shift ax1 down over gap
gap = ax1_y0-ax2_y1
ax1.set_position([ax1_x0, ax1_y0-gap, ax1_x1, ax1_y1-gap])
ax2.set_position([ax2_x0, ax2_y0, ax2_x1, ax2_y1])
Unfortunately, this (and variations of this) produces seemingly unpredictable results, including a figure resizing similar to when using fig.tight_layout(). Maybe someone else has an explanation for what is happening here behind the scenes.
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).