matplotlib / matplotlib

matplotlib: plotting with Python
https://matplotlib.org/stable/
20.22k stars 7.62k forks source link

Axes cannot be animated using animation.py with blit #2324

Closed hammer498 closed 11 months ago

hammer498 commented 11 years ago

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)

tacaswell commented 11 years ago

Also see http://stackoverflow.com/questions/17558096/animated-title-in-matplotlib/17562747#17562747 , http://stackoverflow.com/questions/6299943/animating-matplotlib-axes-ticks , http://stackoverflow.com/questions/14844223/python-matplotlib-blit-to-axes-or-sides-of-the-figure

mdboom commented 11 years ago

@dopplershift : Would you mind having a look at this, as the animation guy?

tacaswell commented 11 years ago

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.

dopplershift commented 11 years ago

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.

hammer498 commented 11 years ago

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.

tgarc commented 9 years ago

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.

tacaswell commented 9 years ago

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.

tgarc commented 9 years ago

@tacaswell Couple questions.

  1. What does the animated flag actually do in matplotlib? I haven't been able to find any even moderately detailed explanation in the docs or the code.
  2. Doesn't _blit_draw only get called after the first draw anyway? This seems true looking through the source but I may have missed something.
  3. What do you mean that the 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

tgarc commented 9 years ago

@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?

tacaswell commented 9 years ago

@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.

Silmathoron commented 7 years ago

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...

tacaswell commented 7 years ago

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).

Silmathoron commented 7 years ago

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.

bretcj7 commented 7 years ago

@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

tgarc commented 7 years ago

@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.

tacaswell commented 7 years ago

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.

bretcj7 commented 7 years ago

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.

github-actions[bot] commented 1 year ago

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!