Skip to content

[Bug]: fig.tight_layout when quiver collection is clipped produces AttributeError #24104

@ondrolexa

Description

@ondrolexa

Bug summary

When I set a clipping path to quiver object with set_clip_path(), Figure.tight_layout() produces AttributeError: 'NoneType' object has no attribute 'xmin'. Without tight_layout() everything works.

Code for reproduction

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle

x, y = np.mgrid[-1:1:0.1, -1:1:0.1]
z = np.sin(x) + np.cos(y)
xg, yg = np.gradient(z)

f, ax = plt.subplots()
h = ax.quiver(x, y, xg, yg, label='Test')
c = Circle((0, 0), radius=1, edgecolor="black", fill=False)
ax.add_patch(c)
h.set_clip_path(c)
ax.set_aspect(1)
f.tight_layout()

Actual outcome

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In [42], line 15
     13 h.set_clip_path(c)
     14 ax.set_aspect(1)
---> 15 f.tight_layout()

File ~/miniforge3/envs/apsg/lib/python3.10/site-packages/matplotlib/figure.py:3443, in Figure.tight_layout(self, pad, h_pad, w_pad, rect)
   3441 try:
   3442     self.set_layout_engine(engine)
-> 3443     engine.execute(self)
   3444 finally:
   3445     self.set_layout_engine(None)

File ~/miniforge3/envs/apsg/lib/python3.10/site-packages/matplotlib/layout_engine.py:180, in TightLayoutEngine.execute(self, fig)
    178 renderer = fig._get_renderer()
    179 with getattr(renderer, "_draw_disabled", nullcontext)():
--> 180     kwargs = get_tight_layout_figure(
    181         fig, fig.axes, subplotspec_list, renderer,
    182         pad=info['pad'], h_pad=info['h_pad'], w_pad=info['w_pad'],
    183         rect=info['rect'])
    184 if kwargs:
    185     fig.subplots_adjust(**kwargs)

File ~/miniforge3/envs/apsg/lib/python3.10/site-packages/matplotlib/_tight_layout.py:305, in get_tight_layout_figure(fig, axes_list, subplotspec_list, renderer, pad, h_pad, w_pad, rect)
    300         return {}
    301     span_pairs.append((
    302         slice(ss.rowspan.start * div_row, ss.rowspan.stop * div_row),
    303         slice(ss.colspan.start * div_col, ss.colspan.stop * div_col)))
--> 305 kwargs = _auto_adjust_subplotpars(fig, renderer,
    306                                   shape=(max_nrows, max_ncols),
    307                                   span_pairs=span_pairs,
    308                                   subplot_list=subplot_list,
    309                                   ax_bbox_list=ax_bbox_list,
    310                                   pad=pad, h_pad=h_pad, w_pad=w_pad)
    312 # kwargs can be none if tight_layout fails...
    313 if rect is not None and kwargs is not None:
    314     # if rect is given, the whole subplots area (including
    315     # labels) will fit into the rect instead of the
   (...)
    319     # auto_adjust_subplotpars twice, where the second run
    320     # with adjusted rect parameters.

File ~/miniforge3/envs/apsg/lib/python3.10/site-packages/matplotlib/_tight_layout.py:82, in _auto_adjust_subplotpars(fig, renderer, shape, span_pairs, subplot_list, ax_bbox_list, pad, h_pad, w_pad, rect)
     80 for ax in subplots:
     81     if ax.get_visible():
---> 82         bb += [martist._get_tightbbox_for_layout_only(ax, renderer)]
     84 tight_bbox_raw = Bbox.union(bb)
     85 tight_bbox = fig.transFigure.inverted().transform_bbox(tight_bbox_raw)

File ~/miniforge3/envs/apsg/lib/python3.10/site-packages/matplotlib/artist.py:1378, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1372 """
   1373 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1374 *for_layout_only* kwarg; this helper tries to uses the kwarg but skips it
   1375 when encountering third-party subclasses that do not support it.
   1376 """
   1377 try:
-> 1378     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1379 except TypeError:
   1380     return obj.get_tightbbox(*args, **kwargs)

File ~/miniforge3/envs/apsg/lib/python3.10/site-packages/matplotlib/axes/_base.py:4450, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4447     bbox_artists = self.get_default_bbox_extra_artists()
   4449 for a in bbox_artists:
-> 4450     bbox = a.get_tightbbox(renderer)
   4451     if (bbox is not None
   4452             and 0 < bbox.width < np.inf
   4453             and 0 < bbox.height < np.inf):
   4454         bb.append(bbox)

File ~/miniforge3/envs/apsg/lib/python3.10/site-packages/matplotlib/artist.py:345, in Artist.get_tightbbox(self, renderer)
    343     if clip_path is not None:
    344         clip_path = clip_path.get_fully_transformed_path()
--> 345         bbox = Bbox.intersection(bbox, clip_path.get_extents())
    346 return bbox

File ~/miniforge3/envs/apsg/lib/python3.10/site-packages/matplotlib/transforms.py:666, in BboxBase.intersection(bbox1, bbox2)
    660 @staticmethod
    661 def intersection(bbox1, bbox2):
    662     """
    663     Return the intersection of *bbox1* and *bbox2* if they intersect, or
    664     None if they don't.
    665     """
--> 666     x0 = np.maximum(bbox1.xmin, bbox2.xmin)
    667     x1 = np.minimum(bbox1.xmax, bbox2.xmax)
    668     y0 = np.maximum(bbox1.ymin, bbox2.ymin)

AttributeError: 'NoneType' object has no attribute 'xmin'

Expected outcome

No error

Additional information

No response

Operating system

Ubuntu

Matplotlib Version

3.6.0

Matplotlib Backend

module://matplotlib_inline.backend_inline

Python version

3.10.6

Jupyter version

3.4.8

Installation

conda

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions