Skip to content

[Bug]: Regression in animation from #22175 #22921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
larsoner opened this issue Apr 27, 2022 · 4 comments · Fixed by #22945 or #22946
Closed

[Bug]: Regression in animation from #22175 #22921

larsoner opened this issue Apr 27, 2022 · 4 comments · Fixed by #22945 or #22946
Milestone

Comments

@larsoner
Copy link
Contributor

larsoner commented Apr 27, 2022

Bug summary

On a9dd8b9 things are fine, on the next commit 396a010 from #22175 we get an error with previously working animation code.

Code for reproduction

import numpy as np
import matplotlib
from matplotlib import pyplot as plt, animation, patches
matplotlib.use('agg', force=True)
fig, ax = plt.subplots()


def _init_anim():
    patch_ = patches.Ellipse((0, 0), 1, 1)
    Xi, Yi = np.meshgrid(np.arange(4), np.arange(3))
    cont = ax.contour(Xi, Yi, Yi, levels=1)
    cont.collections[0].set_clip_path(patch_)
    fig.tight_layout()
    return tuple(cont.collections)


animate_func = lambda: None
anim = animation.FuncAnimation(fig, animate_func, init_func=_init_anim,
                                frames=1, blit=True)

This is nowhere near self-contained, but it will probably take me some time to make a self-contained example. In the meantime I wanted to post this issue in case it was clear from the traceback and git bisect what the problem is.

Actual outcome

  File "/home/larsoner/Desktop/rep.py", line 18, in <module>
    anim = animation.FuncAnimation(fig, animate_func, init_func=_init_anim,
  File "/home/larsoner/python/matplotlib/lib/matplotlib/animation.py", line 1648, in __init__
    super().__init__(fig, **kwargs)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/animation.py", line 1398, in __init__
    super().__init__(fig, event_source=event_source, *args, **kwargs)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/animation.py", line 879, in __init__
    self._setup_blit()
  File "/home/larsoner/python/matplotlib/lib/matplotlib/animation.py", line 1191, in _setup_blit
    self._post_draw(None, self._blit)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/animation.py", line 1146, in _post_draw
    self._fig.canvas.draw_idle()
  File "/home/larsoner/python/matplotlib/lib/matplotlib/backend_bases.py", line 1982, in draw_idle
    self.draw(*args, **kwargs)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/backends/backend_agg.py", line 409, in draw
    self.figure.draw(self.renderer)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/artist.py", line 73, in draw_wrapper
    result = draw(artist, renderer, *args, **kwargs)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/artist.py", line 50, in draw_wrapper
    return draw(artist, renderer)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/figure.py", line 2901, in draw
    self.canvas.draw_event(renderer)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/backend_bases.py", line 1700, in draw_event
    self.callbacks.process(s, event)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/cbook/__init__.py", line 301, in process
    self.exception_handler(exc)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/cbook/__init__.py", line 84, in _exception_printer
    raise exc
  File "/home/larsoner/python/matplotlib/lib/matplotlib/cbook/__init__.py", line 296, in process
    func(*args, **kwargs)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/animation.py", line 903, in _start
    self._init_draw()
  File "/home/larsoner/python/matplotlib/lib/matplotlib/animation.py", line 1712, in _init_draw
    self._drawn_artists = self._init_func()
  File "/home/larsoner/Desktop/rep.py", line 13, in _init_anim
    fig.tight_layout()
  File "/home/larsoner/python/matplotlib/lib/matplotlib/figure.py", line 3270, in tight_layout
    engine.execute(self)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/layout_engine.py", line 159, in execute
    kwargs = get_tight_layout_figure(
  File "/home/larsoner/python/matplotlib/lib/matplotlib/_tight_layout.py", line 316, in get_tight_layout_figure
    kwargs = _auto_adjust_subplotpars(fig, renderer,
  File "/home/larsoner/python/matplotlib/lib/matplotlib/_tight_layout.py", line 81, in _auto_adjust_subplotpars
    bb += [martist._get_tightbbox_for_layout_only(ax, renderer)]
  File "/home/larsoner/python/matplotlib/lib/matplotlib/artist.py", line 1363, in _get_tightbbox_for_layout_only
    return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
  File "/home/larsoner/python/matplotlib/lib/matplotlib/axes/_base.py", line 4516, in get_tightbbox
    bbox = a.get_tightbbox(renderer)
  File "/home/larsoner/python/matplotlib/lib/matplotlib/artist.py", line 344, in get_tightbbox
    bbox = Bbox.intersection(bbox, clip_path.get_extents())
  File "/home/larsoner/python/matplotlib/lib/matplotlib/transforms.py", line 663, in intersection
    x0 = np.maximum(bbox1.xmin, bbox2.xmin)
AttributeError: 'NoneType' object has no attribute 'xmin'

Expected outcome

No error

Additional information

No response

Operating system

Ubuntu 22.04

Matplotlib Version

396a010

Matplotlib Backend

QtAgg (PyQt6)

Python version

3.10.4

Jupyter version

No response

Installation

git checkout

@larsoner
Copy link
Contributor Author

Okay the minimal example wasn't so bad, updated top comment.

@larsoner
Copy link
Contributor Author

Argh actually on main git revert f93a0fc251f7aa0a8da71f92e97e54faa25b8cd7 (f93a0fc) seems to fix it, not git revert 396a010. Apparently I am bad at git bisect. So maybe it's actually #22476?

@QuLogic
Copy link
Member

QuLogic commented Apr 27, 2022

I can confirm it started failing in f93a0fc.

@QuLogic QuLogic added this to the v3.5.2 milestone Apr 27, 2022
@greglucas
Copy link
Contributor

I think this ultimately stems from removing offsetsNone back in #20717
We need some way of tracking that the (0, 0) case was actually passed in and desired, or if that is just the default (0, 0) from initialization. Seems like adding that flag (maybe renaming it too) is the quick fix?

index 49485bd900..0a62cd49e4 100644
--- a/lib/matplotlib/collections.py
+++ b/lib/matplotlib/collections.py
@@ -195,8 +195,9 @@ class Collection(artist.Artist, cm.ScalarMappable):
 
         # default to zeros
         self._offsets = np.zeros((1, 2))
+        self._has_offsets = offsets is not None
 
-        if offsets is not None:
+        if self._has_offsets:
             offsets = np.asanyarray(offsets, float)
             # Broadcast (2,) -> (1, 2) but nothing else.
             if offsets.shape == (2,):
@@ -290,18 +291,19 @@ class Collection(artist.Artist, cm.ScalarMappable):
                     offset_trf.transform_non_affine(offsets),
                     offset_trf.get_affine().frozen())
 
-            # this is for collections that have their paths (shapes)
-            # in physical, axes-relative, or figure-relative units
-            # (i.e. like scatter). We can't uniquely set limits based on
-            # those shapes, so we just set the limits based on their
-            # location.
-            offsets = (offset_trf - transData).transform(offsets)
-            # note A-B means A B^{-1}
-            offsets = np.ma.masked_invalid(offsets)
-            if not offsets.mask.all():
-                bbox = transforms.Bbox.null()
-                bbox.update_from_data_xy(offsets)
-                return bbox
+            if self._has_offsets:
+                # this is for collections that have their paths (shapes)
+                # in physical, axes-relative, or figure-relative units
+                # (i.e. like scatter). We can't uniquely set limits based on
+                # those shapes, so we just set the limits based on their
+                # location.
+                offsets = (offset_trf - transData).transform(offsets)
+                # note A-B means A B^{-1}
+                offsets = np.ma.masked_invalid(offsets)
+                if not offsets.mask.all():
+                    bbox = transforms.Bbox.null()
+                    bbox.update_from_data_xy(offsets)
+                    return bbox
         return transforms.Bbox.null()
 
     def get_window_extent(self, renderer):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants