Based on the John Hunter's answer to a question regarding using patches in animations here, I am under the impression that:
- I can create a
patch
object, with its animated stated beingTrue
. - Add it to an existing
axis
(let's say theaxis
object is calledax
) using theadd_patch
method. - Then when I want to draw the
patch
, I do:ax.draw_artist(patch)
.
Doing this, I am faced with the following error:
File "environment.py", line 254, in animation_function
ax.draw_artist(patches[index])
File "A:\Anaconda\lib\site-packages\matplotlib\axes\_base.py", line 2121, in draw_artist
assert self._cachedRenderer is not None
AssertionError
The top level code is organized as follows:
a function creates patches from data, and then adds them to an
axis
object -- basically, I get a list of patch objects back,patches
, where each patch has been added toax
; I think the issue might be here, since the patch objects inpatches
are not really connected toax
..., they were added to it, but passed by copy, not reference?the animation function uses the number (let's say
n
) received fromFuncAnimation
to reference relevant patch objects, and then callsax.draw_artist(patches[n])
At first I was doing the following:
patches = []
...
patch = mpatches.PathPatch(...)
patch.set_animated(True)
ax.add_patch(patch)
patches.append(patch)
...
ax.draw_artist(patches[n])
Then, after reading the documentation, which suggests that a patch object (possibly now connected to an axes object?) is returned, I tried the following:
patches = []
...
patch = mpatches.PathPatch(...)
patch.set_animated(True)
ax_patch = ax.add_patch(patch)
patches.append(ax_patch)
...
ax.draw_artist(patches[n])
However, the issue is still the same.
Can you comment on what you think the issue might be, or where I might need to provide additional information in order to figure out the issue?
EDIT: the top-level function where the error arises from.
def create_animation_from_data(self, vertex_marker_radius=0.25, labels = ['a', 'a', 'b', 'b'], unit_line=0.5, colours=['red', 'blue', 'green', 'orange']):
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, xlim=(-2, 100), ylim=(-2, 100))
ax.grid()
print "Initializing patches..."
patches, patch_indices_per_timestep, num_polys, num_timesteps = self.make_patches_from_environment(ax, vertex_marker_radius, labels, unit_line, colours)
def animation_function(n):
relevant_patch_indices = patch_indices_per_timestep[n]
for polygon_based_index_group in relevant_patch_indices:
for index in polygon_based_index_group:
patches[index].draw(fig.canvas.get_renderer())
return patches,
print "Beginning animation..."
ani = animation.FuncAnimation(fig, animation_function, blit=True)
plt.show()
1 Answer 1
You're understanding everything correctly, but missing a couple of lower-level steps. The renderer
hasn't been initialized yet, and you're getting an error that reflects that.
In a nutshell, you can't use draw_artist
before the plot has been drawn the first time. You'll need to call fig.canvas.draw()
(in some cases you can get away with just fig.canvas.get_renderer()
) at least once before you can use draw_artist
.
If you're running into this problem, it's often because you're trying to go "against the grain" and do things that are better not to be handled directly.
What exactly are you trying to do? There's likely an easier way to handle this (e.g. if you're trying to grab a background, put this part of your code in a callback to the draw event).
Let me back up and explain what's happening. Matplotlib Artist
s draw on a FigureCanvas
(e.g. fig.canvas
) using an instance of Renderer
(e.g. fig.canvas.renderer
). The renderer
is backend-specific and low-level. You normally won't touch it directly.
ax.draw_artist
is a lower-level function than fig.canvas.draw
. More specifically, it's shorthand for artist.draw(renderer)
.
Initializing the renderer
is relatively slow, so it's cached and reused unless the figure's size or dpi changes. This is what the error you're getting is saying: The canvas
doesn't have a renderer yet.
You have a few different options. You could manually initialize the renderer (the easy way is to call fig.canvas.get_renderer()
). However, sometimes you're want to get something (such as the size of a text object) that isn't defined until after it's been drawn. In those cases, you'll need a "full" fig.canvas.draw()
.
Usually, though, running into things like this is a sign that there's an easier way to do it. Often it's best to put code that needs a draw to have happened into a callback to the draw event. (Especially if it's something that depends on the exact size of the figure -- e.g. a background for blitting).
Update based on code sample
If you're using the matplotlib.animation
framework, then you don't need to draw the artists inside of the update function. The animation framework will take care of that step for you.
It sounds like you're trying to display only a subset of the artists you've plotted at each timestep?
If so, you might consider toggling their visibility instead. As a quick example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
def main():
fig, ax = plt.subplots()
p = Plotter(ax)
# Note: We need to save a referce to the animation object, otherwise it
# will be garbage collected and never properly animate.
anim = matplotlib.animation.FuncAnimation(fig, p.update,
init_func=p.anim_init)
plt.show()
class Plotter(object):
def __init__(self, ax):
self.ax = ax
def anim_init(self):
self.triangles = [self.random_triangle() for _ in range(10)]
# Initially, show a blank plot...
for tri in self.triangles:
tri.set_visible(False)
return self.triangles
def update(self, i):
"""Animation step."""
# Hide all triangles
for tri in self.triangles:
tri.set(visible=False)
# pick 2 at random to show (e.g. your patch_indices_per_timestep)
for tri in np.random.choice(self.triangles, size=2):
tri.set(visible=True)
return self.triangles
def random_triangle(self):
x, y = np.random.random((2,3))
x += 10 * np.random.random(1)
y += 10 * np.random.random(1)
return self.ax.fill(x, y)[0]
main()
-
Thanks, again! So, what I am trying to do is something similar to what is described in this post: devolab.msu.edu/2015/01/20/… -- essentially, I am setting up all the patches first, and then in the animation function, I select which patches have "draw_artist()" called on them (as per your answer here: stackoverflow.com/questions/30135489/… . In the link, they use
set_visible
instead ofdraw_artist
. I don't think I need to grab backgrounds, but I do need to blit.bzm3r– bzm3r2015年05月10日 21:00:21 +00:00Commented May 10, 2015 at 21:00 -
In my post, I added a relevant function that makes the figure, patches, and then the
animation_function
forFuncAnimation
. Does that help to see what sort of usecase I have?bzm3r– bzm3r2015年05月10日 21:05:26 +00:00Commented May 10, 2015 at 21:05 -
Toggling their visibility is exactly what works. Thank very much!bzm3r– bzm3r2015年05月13日 15:09:10 +00:00Commented May 13, 2015 at 15:09