X Tutup
Skip to content

[Bug]: Crash when Removing Suptitle in a Figure with Constrained Layout #31073

@williamlus

Description

@williamlus

Bug summary

The renderer crashes after adding a suptitle and removing it in a figure with constrained layout. The plot is expected to remain the same after performing the insertion and removal of the suptitle.

Code for reproduction

import matplotlib
print(matplotlib.__version__)  # 3.10.8
import matplotlib.pyplot as plt
def remove_suptitle_v1():
    fig, ax = plt.subplots()
    sup = fig.suptitle('My Suptitle')
    sup.remove()
    plt.show()
def remove_suptitle_v2():
    fig = plt.figure(constrained_layout=True)
    ax = fig.add_subplot()
    sup = fig.suptitle('My Suptitle')
    sup.remove()
    plt.show()
remove_suptitle_v1() # this works
remove_suptitle_v2() # this crashes

Actual outcome

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
File ~/anaconda3/lib/python3.12/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
    276 def _draw_all_if_interactive() -> None:
    277     if matplotlib.is_interactive():
--> [278](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/pyplot.py:278)         draw_all()

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
    129 for manager in cls.get_all_fig_managers():
    130     if force or manager.canvas.figure.stale:
--> [131](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/_pylab_helpers.py:131)         manager.canvas.draw_idle()

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1891 if not self._is_idle_drawing:
   1892     with self._idle_draw_cntx():
-> [1893](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/backend_bases.py:1893)         self.draw(*args, **kwargs)

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
    379 # Acquire a lock on the shared font cache.
    380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    381       else nullcontext()):
--> [382](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/backends/backend_agg.py:382)     self.figure.draw(self.renderer)
    383     # A GUI class may be need to update a window using this draw, so
    384     # don't forget to call the superclass.
    385     super().draw()

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> [94](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:94)     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> [71](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:71)     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
   3249 if self.axes and self.get_layout_engine() is not None:
   3250     try:
-> [3251](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/figure.py:3251)         self.get_layout_engine().execute(self)
   3252     except ValueError:
   3253         pass

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> [278](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/layout_engine.py:278) return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/_constrained_layout.py:118, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)
    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
    116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
--> [118](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/_constrained_layout.py:118)     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.
    124     match_submerged_margins(layoutgrids, fig)

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/_constrained_layout.py:480, in make_margin_suptitles(layoutgrids, fig, renderer, w_pad, h_pad)
    478     if getattr(fig._suptitle, '_autopos', False):
    479         fig._suptitle.set_position((p[0], 1 - h_pad_local))
--> [480](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/_constrained_layout.py:480)         bbox = inv_trans_fig(fig._suptitle.get_tightbbox(renderer))
    481         layoutgrids[fig].edit_margin_min('top', bbox.height + 2 * h_pad)
    483 if fig._supxlabel is not None and fig._supxlabel.get_in_layout():

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:364, in Artist.get_tightbbox(self, renderer)
    348 def get_tightbbox(self, renderer=None):
    349     """
    350     Like `.Artist.get_window_extent`, but includes any clipping.
    351 
   (...)
    362         Returns None if clipping results in no intersection.
    363     """
--> [364](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:364)     bbox = self.get_window_extent(renderer)
    365     if self.get_clip_on():
    366         clip_box = self.get_clip_box()

File ~/anaconda3/lib/python3.12/site-packages/matplotlib/text.py:953, in Text.get_window_extent(self, renderer, dpi)
    951 fig = self.get_figure(root=True)
    952 if dpi is None:
--> [953](https://vscode-remote+ssh-002dremote-002bsccpu5-002ecse-002eust-002ehk.vscode-resource.vscode-cdn.net/data/wluak/dataviz-render-update-testing/~/anaconda3/lib/python3.12/site-packages/matplotlib/text.py:953)     dpi = fig.dpi
    954 if self.get_text() == '':
    955     with cbook._setattr_cm(fig, dpi=dpi):

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

Expected outcome

Image

Additional information

No response

Operating system

No response

Matplotlib Version

3.10.8

Matplotlib Backend

No response

Python version

No response

Jupyter version

No response

Installation

None

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      X Tutup