Skip to content

animation error #11602

Closed
Closed

Description

@charliboy

Bug report

the animation doesn't work
Bug summary

Code for reproduction

import numpy as np
#import matplotlib
#matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import host_subplot

import mpl_toolkits.axisartist as AA

host = host_subplot(111, axes_class=AA.Axes)
fig=host.get_figure()
plt.subplots_adjust(right=0.75)
par1 = host.twinx()
par2 = host.twinx()
#fig, ax = plt.subplots()
offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",
                                    axes=par2,
                                    offset=(offset, 0))
par1.axis["right"].toggle(all=True)
par2.axis["right"].toggle(all=True)
host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")

x = np.arange(0, 2*np.pi, 0.01)
p1, = host.plot(x,np.sin(x), label="Density")
p2, = par1.plot(x, np.cos(x), label="Temperature")
p3, = par2.plot(x, np.tan(x), label="Velocity")
#line, = ax.plot(x, np.sin(x))


def init():  # only required for blitting to give a clean slate.
    p1.set_ydata([np.nan] * len(x))
    p2.set_ydata([np.nan] * len(x))
    p3.set_ydata([np.nan] * len(x))
    return p1,p2,p3


def animate(i):
    #line.set_ydata(np.sin(x + i / 100))  # update the data.
    p1.set_ydata(np.sin(x + i / 100))
    p2.set_ydata(np.cos(x + i / 100))
    p3.set_ydata(np.tan(x + i / 100))
    return p1,p2,p3


ani = animation.FuncAnimation(
    fig, animate, init_func=init, interval=2, blit=True, save_count=50)

# To save the animation, use e.g.
#
# ani.save("movie.mp4")
#
# or
#
# from matplotlib.animation import FFMpegWriter
# writer = FFMpegWriter(fps=15, metadata=dict(artist='Me'), bitrate=1800)
# ani.save("movie.mp4", writer=writer)

plt.show()

Actual outcome
not work
Process finished with exit code 1

# If applicable, paste the console output here
#
#

Expected outcome

Matplotlib version

  • Operating system:
  • Matplotlib version:
  • Matplotlib backend (print(matplotlib.get_backend())):
  • Python version:
  • Jupyter version (if applicable):
  • Other libraries:

Activity

  1. charliboy commented on Jul 9, 2018

    @charliboy
    Author

    the animation does't work .Can anyone give me some advice ?.thanks.

  2. jklymak commented on Jul 9, 2018

    @jklymak
    Member

    plt.show() indeed doesn't work for me

    AttributeError: draw_artist can only be used after an initial draw which caches the renderer
    

    But saving the animation works fine.

  3. added this to the v3.1 milestone on Jul 9, 2018
  4. tacaswell commented on Jul 9, 2018

    @tacaswell
    Member

    Before you call plt.show try adding a fig.canvas.draw(). I suspect there is a race condition here (likely in AxisArtist) where we try to blit something before it has been drawn once.

  5. charliboy commented on Jul 9, 2018

    @charliboy
    Author

    @tacaswell thanks, it doesn't seem to be effective. The prompt as follows:
    "AttributeError: draw_artist can only be used after an initial draw which caches the render"

  6. tacaswell commented on Jul 9, 2018

    @tacaswell
    Member

    What backend? Can you sort out which artist is failing?

  7. charliboy commented on Jul 10, 2018

    @charliboy
    Author

    @tacaswell , I have tested tkagg and qt5agg,the problem is the same. I traced functionanimation, the artlist list and cache is empty.I can't find out where the problem is.

  8. ImportanceOfBeingErnest commented on Jul 10, 2018

    @ImportanceOfBeingErnest
    Member

    I can run the code above on windows 8, python 3.6 on master as well as 2.2.2, Qt5Agg without any error. However there is no animation happening.

    Running the code on python 2.7 with matplotlib 2.2.2 produces the reported error (AttributeError: draw_artist can only be used after an initial draw which caches the renderer) for me.

  9. tacaswell commented on Jul 10, 2018

    @tacaswell
    Member

    I can reproduce with py3.8 and matplotlib master.

    If you pass blit=False it displays correctly.

    The reason saving works is that we re-draw every frame when saving (and ignore blitting).

    The issue is that only the host axes really ends up in the draw
    tree of the figure and in the draw method it pulls out some artists from parsite axes to draw but does not actually draw the parasite axes.

    def draw(self, renderer):
    orig_artists = list(self.artists)
    orig_images = list(self.images)
    if hasattr(self, "get_axes_locator"):
    locator = self.get_axes_locator()
    if locator:
    pos = locator(self, renderer)
    self.set_position(pos, which="active")
    self.apply_aspect(pos)
    else:
    self.apply_aspect()
    else:
    self.apply_aspect()
    rect = self.get_position()
    for ax in self.parasites:
    ax.apply_aspect(rect)
    images, artists = ax.get_images_artists()
    self.images.extend(images)
    self.artists.extend(artists)
    self._get_base_axes_attr("draw")(self, renderer)
    self.artists = orig_artists
    self.images = orig_images

    When we go to blit we rely on the on the renderer to be stashed on the axes object, but due to the above code the parasite axes have never been drawn (at the axes level) so they don't have that stashed and hence the exception we see.

    Applying the following

    diff --git a/lib/mpl_toolkits/axes_grid1/parasite_axes.py b/lib/mpl_toolkits/axes_grid1/parasite_axes.py
    index bd8dd58a4..4dd541efe 100644
    --- a/lib/mpl_toolkits/axes_grid1/parasite_axes.py
    +++ b/lib/mpl_toolkits/axes_grid1/parasite_axes.py
    @@ -234,10 +234,6 @@ class HostAxesBase(object):
             return all_handles
     
         def draw(self, renderer):
    -
    -        orig_artists = list(self.artists)
    -        orig_images = list(self.images)
    -
             if hasattr(self, "get_axes_locator"):
                 locator = self.get_axes_locator()
                 if locator:
    @@ -253,13 +249,9 @@ class HostAxesBase(object):
     
             for ax in self.parasites:
                 ax.apply_aspect(rect)
    -            images, artists = ax.get_images_artists()
    -            self.images.extend(images)
    -            self.artists.extend(artists)
    +            ax.draw(renderer)
     
    -        self._get_base_axes_attr("draw")(self, renderer)
    -        self.artists = orig_artists
    -        self.images = orig_images
    +        super().draw(renderer)
     
         def cla(self):
             for ax in self.parasites:
    

    fixes blitting, but I think the axes ticks are being draw multiple times.

  10. jklymak commented on Jul 10, 2018

    @jklymak
    Member

    We do have twinx in the root library, so maybe it’s not worth fixing this for what is an axes grid problem. OTOH I’m not sure the example above is 100% reproducible with the main library...

  11. ImportanceOfBeingErnest commented on Jul 10, 2018

    @ImportanceOfBeingErnest
    Member

    Any idea on why the code would not produce the error on windows for me?

    It seems indeed be limited to axesgrid. Using the usual twinx mechanism from the main library, the code could look like the following

    Multiple axis animation code.
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    
    
    def make_patch_spines_invisible(ax):
        ax.set_frame_on(True)
        ax.patch.set_visible(False)
        for sp in ax.spines.values():
            sp.set_visible(False)
    
    
    fig, host = plt.subplots()
    fig.subplots_adjust(right=0.75)
    
    par1 = host.twinx()
    par2 = host.twinx()
    
    # Offset the right spine of par2.  The ticks and label have already been
    # placed on the right by twinx above.
    par2.spines["right"].set_position(("axes", 1.2))
    # Having been created by twinx, par2 has its frame off, so the line of its
    # detached spine is invisible.  First, activate the frame but make the patch
    # and spines invisible.
    make_patch_spines_invisible(par2)
    # Second, show the right spine.
    par2.spines["right"].set_visible(True)
    
    host.set_xlabel("Distance")
    host.set_ylabel("Density")
    par1.set_ylabel("Temperature")
    par2.set_ylabel("Velocity")
    
    x = np.arange(0, 2*np.pi, 0.01)
    p1, = host.plot(x, np.sin(x), label="Density")
    p2, = par1.plot(x, np.cos(x), label="Temperature")
    p3, = par2.plot(x, np.tan(x), label="Velocity")
    
    
    
    def init():  # only required for blitting to give a clean slate.
        p1.set_ydata([np.nan] * len(x))
        p2.set_ydata([np.nan] * len(x))
        p3.set_ydata([np.nan] * len(x))
        return p1,p2,p3
    
    init()
    fig.canvas.draw()
    
    def animate(i):
        #line.set_ydata(np.sin(x + i / 100))  # update the data.
        p1.set_ydata(np.sin(x + i / 100.))
        p2.set_ydata(np.cos(x + i / 100.))
        p3.set_ydata(np.tan(x + i / 100.))
        return p1,p2,p3
    
    
    ani = animation.FuncAnimation(
        fig, animate, init_func=init, interval=2, blit=True, save_count=50)
    
    plt.show()
    

    It works, but seems to have a different problem of not initializing blitting correctly.

    but I think the axes ticks are being draw multiple times.

    Even without animation, the axesgrid solution looks like it draws the axis decorators twice.

  12. tacaswell commented on Jul 10, 2018

    @tacaswell
    Member

    @ImportanceOfBeingErnest do other animations with blitting work for you on 3.6 on windows? Bit worried you have uncovered a more fundamental bug...

  13. ImportanceOfBeingErnest commented on Jul 11, 2018

    @ImportanceOfBeingErnest
    Member

    Sorry, I can now reproduce the reported error here. If I run the code shown outside of Spyder, the error is raised as seen above.

    I do encounter various other strange things with animations and blitting, but I currently have problems reproducing them, or distilling them into a minimal example. In any case they are unrelated to this problem of animating in parasite_axes, so I would then open a new issue about it.

  14. charliboy commented on Jul 13, 2018

    @charliboy
    Author

    @tacaswell ,thank you very much. I'll switch to axes.twinx() for the time being. I hope you can upgrade and fix bug. If you stop talking about , please help me close issue.

  15. anntzer commented on Jul 13, 2018

    @anntzer
    Contributor

    It seems like the easy fix is just to stash the cached renderer on the figure rather than on the axes. In fact that's already done, we just need to stop fetching it from the axes:

    diff --git i/examples/event_handling/pong_sgskip.py w/examples/event_handling/pong_sgskip.py
    index c7ddb8abe..e25153e82 100644
    --- i/examples/event_handling/pong_sgskip.py
    +++ w/examples/event_handling/pong_sgskip.py
    @@ -36,7 +36,7 @@ def start_anim(event):
         canvas.mpl_disconnect(start_anim.cid)
     
         def local_draw():
    -        if animation.ax._cachedRenderer:
    +        if animation.ax.get_renderer_cache():
                 animation.draw(None)
         start_anim.timer.add_callback(local_draw)
         start_anim.timer.start()
    diff --git i/lib/matplotlib/axes/_base.py w/lib/matplotlib/axes/_base.py
    index e03ead417..34ada8378 100644
    --- i/lib/matplotlib/axes/_base.py
    +++ w/lib/matplotlib/axes/_base.py
    @@ -486,7 +486,6 @@ class _AxesBase(martist.Artist):
             self.fmt_xdata = None
             self.fmt_ydata = None
     
    -        self._cachedRenderer = None
             self.set_navigate(True)
             self.set_navigate_mode(None)
     
    @@ -542,7 +541,7 @@ class _AxesBase(martist.Artist):
             # The renderer should be re-created by the figure, and then cached at
             # that point.
             state = super().__getstate__()
    -        for key in ['_cachedRenderer', '_layoutbox', '_poslayoutbox']:
    +        for key in ['_layoutbox', '_poslayoutbox']:
                 state[key] = None
             # Prune the sharing & twinning info to only contain the current group.
             for grouper_name in [
    @@ -2510,7 +2509,7 @@ class _AxesBase(martist.Artist):
         def draw(self, renderer=None, inframe=False):
             """Draw everything (plot lines, axes, labels)"""
             if renderer is None:
    -            renderer = self._cachedRenderer
    +            renderer = self.figure._cachedRenderer
     
             if renderer is None:
                 raise RuntimeError('No renderer defined')
    @@ -2581,7 +2580,6 @@ class _AxesBase(martist.Artist):
             mimage._draw_list_compositing_images(renderer, self, artists)
     
             renderer.close_group('axes')
    -        self._cachedRenderer = renderer
             self.stale = False
     
         def draw_artist(self, a):
    @@ -2590,10 +2588,10 @@ class _AxesBase(martist.Artist):
             caches the renderer.  It is used to efficiently update Axes
             data (axis ticks, labels, etc are not updated)
             """
    -        if self._cachedRenderer is None:
    +        if self.figure._cachedRenderer is None:
                 raise AttributeError("draw_artist can only be used after an "
                                      "initial draw which caches the renderer")
    -        a.draw(self._cachedRenderer)
    +        a.draw(self.figure._cachedRenderer)
     
         def redraw_in_frame(self):
             """
    @@ -2601,13 +2599,13 @@ class _AxesBase(martist.Artist):
             caches the renderer.  It is used to efficiently update Axes
             data (axis ticks, labels, etc are not updated)
             """
    -        if self._cachedRenderer is None:
    +        if self.figure._cachedRenderer is None:
                 raise AttributeError("redraw_in_frame can only be used after an "
                                      "initial draw which caches the renderer")
    -        self.draw(self._cachedRenderer, inframe=True)
    +        self.draw(self.figure._cachedRenderer, inframe=True)
     
         def get_renderer_cache(self):
    -        return self._cachedRenderer
    +        return self.figure._cachedRenderer
     
         # Axes rectangle characteristics
    

    Now the animation runs mostly fine... except that the first draw is not cleared and stays drawn (but that's hopefully not too hard to fix).

  16. jklymak commented on Feb 11, 2019

    @jklymak
    Member

    This works on master, so I think the above PRs fixed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions