From 762403fe12d7e71cd7b64565714fb27001bef05a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 14 Sep 2023 18:34:16 +0200 Subject: [PATCH] Connect the Animation event source callback in the constructor. Currently, there is no "official" way of knowing whether a Figure hosts an Animation. Yet, this information can be of interest e.g. for third-party backends. For example, a variant of the ipython inline backend could choose to actually animate its (inline) output in that case; indeed, this feature was already supported by the itermplot backend. itermplot is actually now outdated but the general idea can still be made to work -- I have a working patch for mplterm. The idea is for the backend to override FigureCanvas.new_timer (on the backend-specific canvas subclass) to internally register any timer that gets created (e.g. by the animation module), then, when the canvas is show()n, to first trigger a fake draw_event so that animation callbacks (if any) get attached to the timer, and then to introspect what callbacks have been attached to the timer and retrieve any Animation instance from that. While this all works, there is brittleness on a specific point, namely the need to trigger the fake draw_event (as the callback only gets attached in Animation._start) early in show(): at that point there may be no renderer available so one cannot construct a real DrawEvent (in fact, itermplot just sets renderer to None). If there are additional (end-user-provided) draw_event handlers connected, they may well error out on this fake draw_event. Yet, it is easy to work around this problem, by connecting the animation stepping callback immediately to the timer when the Animation is constructed, rather than waiting for the first draw. The timer is still only *started* at the first draw, so nothing changes from the point of view of the end user. Note that this does not create a strong reference holding the Animation in the "usual" case (of backends that don't keep a reference to the timers they create) -- it's just a reference loop of the Animation holding the Timer holding an Animation method as a callback. --- lib/matplotlib/animation.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index a87f00201124..97e2cbc64ede 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -891,6 +891,7 @@ def __init__(self, fig, event_source=None, blit=False): # that cause the frame sequence to be iterated. self.frame_seq = self.new_frame_seq() self.event_source = event_source + self.event_source.add_callback(self._step) # Instead of starting the event source now, we connect to the figure's # draw_event, so that we only start once the figure has been drawn. @@ -923,13 +924,9 @@ def _start(self, *args): return # First disconnect our draw event handler self._fig.canvas.mpl_disconnect(self._first_draw_id) - # Now do any initial draw self._init_draw() - - # Add our callback for stepping the animation and - # actually start the event_source. - self.event_source.add_callback(self._step) + # Actually start the event_source. self.event_source.start() def _stop(self, *args):