Closed hammer498 closed 11 months ago
@dopplershift : Would you mind having a look at this, as the animation guy?
I have started to work on this/think about this. https://github.com/tacaswell/matplotlib/tree/blit_bbox
I am not sure the best way to deal with changing the axis limits as to make it work with blitting you need to make the xaxis
artist not-visible for the the first frame, which is a little fiddly on the user end.
A cursory look seems like either the figure bbox or clipbox may be the way to go. I can honestly say that I didn't give it too much thought when choosing the bbox for blitting.
As far as limits changing and blitting, that's kind of icky. The whole point of blitting is to avoid redrawing the axes, and all the ticks and, more importantly, avoid redrawing the text, which is relatively expensive. If you're changing the limits, I'm not sure you're gaining anything by blitting.
The use case I was trying to use it for originally was to blit axes of one of several subplots in a figure without having to re draw all of my axis. Perhaps there is a better way to update specific subplots with draw and blit others? If there is I didn't find it.
I came upon this post as I was trying to animate an axis label instance and found another solution to choosing a bbox which at least works better than what is currently implemented. Here's my modified _blit_draw
and _blit_clear
for the Animation
class.
def _blit_draw(self, artists, bg_cache):
# Handles blitted drawing, which renders only the artists given instead
# of the entire figure.
updated_artists = []
for a in artists:
# If we haven't cached the background for this axes object, do
# so now. This might not always be reliable, but it's an attempt
# to automate the process.
if a.axes is not None:
a = a.axes
if a not in bg_cache:
bg_cache[a] = a.figure.canvas.copy_from_bbox(a.get_window_extent(a.figure.canvas.renderer))
a.figure.draw_artist(a)
updated_artists.append(a)
# After rendering all the needed artists, blit each axes individually.
for a in set(updated_artists):
a.figure.canvas.blit(a.get_window_extent())
def _blit_clear(self, artists, bg_cache):
# Get a list of the axes that need clearing from the artists that
# have been drawn. Grab the appropriate saved background from the
# cache and restore.
axes = set(a if a.axes is None else a.axes for a in artists)
for a in axes:
a.figure.canvas.restore_region(bg_cache[a])
So my thought was just that an artist
's parent is either an Axes
or a Figure
. If the artist belongs to an axes, we can just cache/redraw the entire Axes
. Otherwise, we rely on artist.get_window_extent
to get a bounding box of the artist. I'm not really concerned about whether artists' bounding boxes may overlap or similar technical problems, this is just an iteration on the current implementation.
Obviously the draw back is that this relies on get_window_extent
whose support is questionable from the comments and other issues I've read. But, like I said, this works better than the current implementation so that at the very least Animation
is able to blit artists that implement get_window_extent
which of course includes Text
artists.
This relies on the artists having been drawn at least once. artist that are going to be blitted need to have the animated
flag set to true on master now (which is ensured as part of the animation code) and are skipped in the normal drawing call.
@tacaswell Couple questions.
_blit_draw
only get called after the first draw anyway? This seems true looking through the source but I may have missed something.animated
flag is set to true is ensured as part of the animation code? Are you talking about how the FuncAnimation
and ArtistAnimation
classes do this?EDIT: Nvm on question 1, I found the answer here
@tacaswell
After getting a better understanding of what the 'animated' flag does I think I've answered my own question. But can you tell me why an 'Axes' or Figure
instance always has a bounding box attribute set (even before being drawn) while this is not true for other artists?
@tgarc Sorry for not responding sooner, I have been absolutely clobbered the past couple days!
Joe's answer is good (as always). There is also a saving'
flag that can be passed around which disables the filtering animation flag.
For 2, maybe, but if the animated flag is False
and the animated artists are drawn as part of the first draw then they will be on the canvas when the blit background is grabbed so you will have a shadow of frame 0 on all of your future frames. There is some 'fun' issues with draw_idle
and deferred rendering as well.
For 3, yes, on master branch there is logic in the animation module that makes sure all of the artists used in blitting have the animation flag set in the _init_draw
methods.
Hi there! I was wondering what the status of this issue was... Answer from @tgarc is not working for me and on my version of matplotlib (1.5.3), this problem still exists. Moreover, I've seen no animation-related updates on the release notes of 2.0...
If you are re-drawing any text, you are going to get little benefit from blitting (as the biggest win of blitting is to not redraw any text which is expensive).
There has been no motion on this.
If you only need to re-draw every-so-often, I think throwing in an explicit fig.canvas.draw
(or draw_idle
?) into the callback might work (have not tested, but I expect it to work).
Ok, thank you for your answer, I guess this means that my implementation of a continuously moving window should be modified.
I'll try using directly draw
with much fewer (and larger) step motions of a half time window.
@tgarc does your implementation of _blit_draw work for more than one axis? I have ax1 and ax2 from plt.subplots(2,1) and need to update a right centered text on my animation while using blit. Can't seem to get anything to render the text as my text is dynamic and set in the data_gen method. Thanks
@bretcj7 I don't think I ever tried that. That code was a result of a project where I was displaying a video and drawing things on top of it; a much more constrained application than the general case of drawing arbitrary artists but from what I can see there's no reason why it wouldn't work with multiple axes. I'm afraid you'll have to try it and see.
I tried in the past to make that code more general and possibly even integrate some changes into matplotlib but I found the matplotlib api was just too inconsistent with regards to drawing different artists. IOW, don't expect this code to work in the general case, it's just a jumping off point. I'll try to answer any questions you might have about it though.
The api for drawing is pretty consistent (the signature of the draw
method is consistent), but how to update the data in the artist is not.
As suggested I did find what works best for me is to keep track of when I actually need to force a render for text on the axis and use the fig.canvas.draw
method. I actually set the clip_on=False
so i can render the text off the axis and only update the entire figure when I need to change the text. I see a small screen glitch when I do this but it still works while the animation is still going.
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!
When using animation.py with blit updates to axes are not drawn correctly. Tick marks update but labels do not as explained by this SO answer.
http://stackoverflow.com/a/10704274/852487
According to the answer, to fix this problem it seems line 792 in animation.py
bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
needs to be changed to
bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.get_figure().bbox) or perhaps bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.clipbox)?
and line 798
ax.figure.canvas.blit(ax.bbox)
changed to
ax.figure.canvas.blit(ax.clipbox)