Reputation: 401
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.
Upvotes: 21
Views: 12154
Reputation: 99
You could draw the polygon and update the vertices directly as this question suggested: How to update PolyCollection in matplotlib
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
x = np.arange(0.0, 2, 0.01)
y = np.sin(2*np.pi*x)
fig, ax = plt.subplots()
polycolelction = ax.fill_between(x, y)
def vertices_between(x, y1, y2):
if isinstance(y2, float | int):
y2 = np.full(x.size, y2)
new_x = np.hstack((x, x[::-1]))
new_y = np.hstack((y1, y2[::-1]))
return np.vstack((new_x, new_y)).T
def update(i):
y = 1.2*np.sin(i*np.pi*x)
polycolelction.set_verts([vertices_between(x, y, 0)])
fig.canvas.draw()
fig.canvas.flush_events()
anim = FuncAnimation(fig, update, 10)
plt.show()
Upvotes: 0
Reputation: 558
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).
Upvotes: 3
Reputation: 1827
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.
Upvotes: 3
Reputation: 3277
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)
Upvotes: 0
Reputation: 3277
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:
Upvotes: 0
Reputation: 4830
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?).
Upvotes: 15
Reputation: 5933
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
Upvotes: 5