Title: [Bug]: Crash when Removing Suptitle in a Figure with Constrained Layout · Issue #31073 · matplotlib/matplotlib · GitHub
Open Graph Title: [Bug]: Crash when Removing Suptitle in a Figure with Constrained Layout · Issue #31073 · matplotlib/matplotlib
X Title: [Bug]: Crash when Removing Suptitle in a Figure with Constrained Layout · Issue #31073 · matplotlib/matplotlib
Description: 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 impor...
Open Graph Description: 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 t...
X Description: 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 t...
Opengraph URL: https://github.com/matplotlib/matplotlib/issues/31073
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"[Bug]: Crash when Removing Suptitle in a Figure with Constrained Layout","articleBody":"### Bug summary\n\nThe 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.\n\n### Code for reproduction\n\n```Python\nimport matplotlib\nprint(matplotlib.__version__) # 3.10.8\nimport matplotlib.pyplot as plt\ndef remove_suptitle_v1():\n fig, ax = plt.subplots()\n sup = fig.suptitle('My Suptitle')\n sup.remove()\n plt.show()\ndef remove_suptitle_v2():\n fig = plt.figure(constrained_layout=True)\n ax = fig.add_subplot()\n sup = fig.suptitle('My Suptitle')\n sup.remove()\n plt.show()\nremove_suptitle_v1() # this works\nremove_suptitle_v2() # this crashes\n```\n\n### Actual outcome\n\n\n```python\n---------------------------------------------------------------------------\nAttributeError Traceback (most recent call last)\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()\n 276 def _draw_all_if_interactive() -\u003e None:\n 277 if matplotlib.is_interactive():\n--\u003e [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()\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)\n 129 for manager in cls.get_all_fig_managers():\n 130 if force or manager.canvas.figure.stale:\n--\u003e [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()\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)\n 1891 if not self._is_idle_drawing:\n 1892 with self._idle_draw_cntx():\n-\u003e [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)\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)\n 379 # Acquire a lock on the shared font cache.\n 380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar\n 381 else nullcontext()):\n--\u003e [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)\n 383 # A GUI class may be need to update a window using this draw, so\n 384 # don't forget to call the superclass.\n 385 super().draw()\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.\u003clocals\u003e.draw_wrapper(artist, renderer, *args, **kwargs)\n 92 @wraps(draw)\n 93 def draw_wrapper(artist, renderer, *args, **kwargs):\n---\u003e [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)\n 95 if renderer._rasterizing:\n 96 renderer.stop_rasterizing()\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:71, in allow_rasterization.\u003clocals\u003e.draw_wrapper(artist, renderer)\n 68 if artist.get_agg_filter() is not None:\n 69 renderer.start_filter()\n---\u003e [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)\n 72 finally:\n 73 if artist.get_agg_filter() is not None:\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)\n 3249 if self.axes and self.get_layout_engine() is not None:\n 3250 try:\n-\u003e [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)\n 3252 except ValueError:\n 3253 pass\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)\n 275 w_pad = self._params['w_pad'] / width\n 276 h_pad = self._params['h_pad'] / height\n--\u003e [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,\n 279 wspace=self._params['wspace'],\n 280 hspace=self._params['hspace'],\n 281 rect=self._params['rect'],\n 282 compress=self._compress)\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/_constrained_layout.py:118, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)\n 108 for _ in range(2):\n 109 # do the algorithm twice. This has to be done because decorations\n 110 # change size after the first re-position (i.e. x/yticklabels get\n (...)\n 114 # make margins for all the Axes and subfigures in the\n 115 # figure. Add margins for colorbars...\n 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,\n 117 w_pad=w_pad, hspace=hspace, wspace=wspace)\n--\u003e [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,\n 119 w_pad=w_pad)\n 121 # if a layout is such that a columns (or rows) margin has no\n 122 # constraints, we need to make all such instances in the grid\n 123 # match in margin size.\n 124 match_submerged_margins(layoutgrids, fig)\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/_constrained_layout.py:480, in make_margin_suptitles(layoutgrids, fig, renderer, w_pad, h_pad)\n 478 if getattr(fig._suptitle, '_autopos', False):\n 479 fig._suptitle.set_position((p[0], 1 - h_pad_local))\n--\u003e [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))\n 481 layoutgrids[fig].edit_margin_min('top', bbox.height + 2 * h_pad)\n 483 if fig._supxlabel is not None and fig._supxlabel.get_in_layout():\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/artist.py:364, in Artist.get_tightbbox(self, renderer)\n 348 def get_tightbbox(self, renderer=None):\n 349 \"\"\"\n 350 Like `.Artist.get_window_extent`, but includes any clipping.\n 351 \n (...)\n 362 Returns None if clipping results in no intersection.\n 363 \"\"\"\n--\u003e [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)\n 365 if self.get_clip_on():\n 366 clip_box = self.get_clip_box()\n\nFile ~/anaconda3/lib/python3.12/site-packages/matplotlib/text.py:953, in Text.get_window_extent(self, renderer, dpi)\n 951 fig = self.get_figure(root=True)\n 952 if dpi is None:\n--\u003e [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\n 954 if self.get_text() == '':\n 955 with cbook._setattr_cm(fig, dpi=dpi):\n\nAttributeError: 'NoneType' object has no attribute 'dpi'\n```\n\n\n### Expected outcome\n\n\u003cimg width=\"558\" height=\"418\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/03832974-293e-45c0-a413-f7a42b255921\" /\u003e\n\n### Additional information\n\n_No response_\n\n### Operating system\n\n_No response_\n\n### Matplotlib Version\n\n3.10.8\n\n### Matplotlib Backend\n\n_No response_\n\n### Python version\n\n_No response_\n\n### Jupyter version\n\n_No response_\n\n### Installation\n\nNone","author":{"url":"https://github.com/williamlus","@type":"Person","name":"williamlus"},"datePublished":"2026-02-04T07:30:14.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/31073/matplotlib/issues/31073"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:57b46603-e609-1320-261f-cbcf70f4dcc9 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | D79A:10637F:15A21AD:1CA573F:6A51C085 |
| html-safe-nonce | 8846c647c9a7db7cfcb2c740ebc4577eb5e5c819c1776eed2d5064260c211c91 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJENzlBOjEwNjM3RjoxNUEyMUFEOjFDQTU3M0Y6NkE1MUMwODUiLCJ2aXNpdG9yX2lkIjoiMjQ4OTEyNjgyMzM0NDA2MjU5NyIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 4ed967490f305a7031725343a411a245fd8fb1f5ca27e169b286474291813a27 |
| hovercard-subject-tag | issue:3895110785 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/matplotlib/matplotlib/31073/issue_layout |
| twitter:image | https://opengraph.githubassets.com/5660c253e1465bf7aa7740580708030308b485626b044ac80d5a5fdf838ff8a0/matplotlib/matplotlib/issues/31073 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/5660c253e1465bf7aa7740580708030308b485626b044ac80d5a5fdf838ff8a0/matplotlib/matplotlib/issues/31073 |
| og:image:alt | 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 t... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | williamlus |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/matplotlib/matplotlib git https://github.com/matplotlib/matplotlib.git |
| octolytics-dimension-user_id | 215947 |
| octolytics-dimension-user_login | matplotlib |
| octolytics-dimension-repository_id | 1385122 |
| octolytics-dimension-repository_nwo | matplotlib/matplotlib |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1385122 |
| octolytics-dimension-repository_network_root_nwo | matplotlib/matplotlib |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7aed05249554b889eb33d002851a973eebcc7e91 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width