From bc3117798d70f72219dfb08a624f33780a309e20 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 1 Apr 2025 23:41:13 +0200 Subject: [PATCH 01/12] ENH: Allow to register standalone figures with pyplot It may be fundamentally nice not to have to create the figure though pyplot to be able to use it in pyplot afterwards. You can now do ``` from matplotlib.figure import Figure import matplotlib.pyplot as plt fig = Figure() fig.subplots().plot([1, 3, 2]) plt.figure(fig) # fig is now tracked in pyplot plt.show() ``` This also opens up the possibility to more dynamically track and untrack figures in pyplot, which opens up the road to optimized figure tracking in pyplot (#29849) --- lib/matplotlib/pyplot.py | 23 +++++++++++++++++++---- lib/matplotlib/tests/test_figure.py | 2 -- lib/matplotlib/tests/test_pyplot.py | 24 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index e916d57f8871..e6c609c2b84a 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -933,6 +933,10 @@ def figure( window title is set to this value. If num is a ``SubFigure``, its parent ``Figure`` is activated. + If *num* is a Figure instance that is already tracked in pyplot, it is + activated. If *num* is a Figure instance that is not tracked in pyplot, + it is added to the tracked figures and activated. + figsize : (float, float) or (float, float, str), default: :rc:`figure.figsize` The figure dimensions. This can be @@ -1019,21 +1023,32 @@ def figure( in the matplotlibrc file. """ allnums = get_fignums() + next_num = max(allnums) + 1 if allnums else 1 if isinstance(num, FigureBase): # type narrowed to `Figure | SubFigure` by combination of input and isinstance + has_figure_property_parameters = ( + any(param is not None for param in [figsize, dpi, facecolor, edgecolor]) + or not frameon or kwargs + ) + root_fig = num.get_figure(root=True) if root_fig.canvas.manager is None: - raise ValueError("The passed figure is not managed by pyplot") - elif (any(param is not None for param in [figsize, dpi, facecolor, edgecolor]) - or not frameon or kwargs) and root_fig.canvas.manager.num in allnums: + if has_figure_property_parameters: + raise ValueError( + "You cannot pass figure properties when calling figure() with " + "an existing Figure instance") + backend = _get_backend_mod() + manager_ = backend.new_figure_manager_given_figure(next_num, root_fig) + _pylab_helpers.Gcf._set_new_active_manager(manager_) + return manager_.canvas.figure + elif has_figure_property_parameters and root_fig.canvas.manager.num in allnums: _api.warn_external( "Ignoring specified arguments in this call because figure " f"with num: {root_fig.canvas.manager.num} already exists") _pylab_helpers.Gcf.set_active(root_fig.canvas.manager) return root_fig - next_num = max(allnums) + 1 if allnums else 1 fig_label = '' if num is None: num = next_num diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index c5890a2963b3..5f0e68648966 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -147,8 +147,6 @@ def test_figure_label(): assert plt.get_figlabels() == ['', 'today'] plt.figure(fig_today) assert plt.gcf() == fig_today - with pytest.raises(ValueError): - plt.figure(Figure()) def test_figure_label_replaced(): diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 55f7c33cb52e..44555a333a8c 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -471,6 +471,30 @@ def test_multiple_same_figure_calls(): assert fig is fig3 +def test_register_existing_figure_with_pyplot(): + from matplotlib.figure import Figure + # start with a standalone figure + fig = Figure() + assert fig.canvas.manager is None + with pytest.raises(AttributeError): + # Heads-up: This will change to returning None in the future + # See docstring for the Figure.number property + fig.number + # register the Figure with pyplot + plt.figure(fig) + assert fig.number == 1 + # the figure can now be used in pyplot + plt.suptitle("my title") + assert fig.get_suptitle() == "my title" + # it also has a manager that is properly wired up in the pyplot state + assert plt._pylab_helpers.Gcf.get_fig_manager(fig.number) is fig.canvas.manager + # and we can regularly switch the pyplot state + fig2 = plt.figure() + assert fig2.number == 2 + assert plt.figure(1) is fig + assert plt.gcf() is fig + + def test_close_all_warning(): fig1 = plt.figure() From c718eae4dd7761051ba309d5c962077797f1e954 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 27 Jul 2025 23:30:52 +0200 Subject: [PATCH 02/12] Clarifying comment --- lib/matplotlib/_pylab_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index e3c3d98cb156..05f6d8aa02b3 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -53,6 +53,8 @@ def destroy(cls, num): two managers share the same number. """ if all(hasattr(num, attr) for attr in ["num", "destroy"]): + # num is a manager-like instance (not necessarily a + # FigureManagerBase subclass) manager = num if cls.figs.get(manager.num) is manager: cls.figs.pop(manager.num) From a38f9bb624f59cc01320840a5a53b6ab32e72867 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 27 Jul 2025 23:32:44 +0200 Subject: [PATCH 03/12] Fix: properly decouple from pyplot and specific backends when destroying When destroying a manager, replace the figure's canvas by a figure canvas base. --- lib/matplotlib/backend_bases.py | 4 +++- lib/matplotlib/backends/_backend_gtk.py | 1 + lib/matplotlib/backends/_backend_tk.py | 1 + lib/matplotlib/backends/backend_gtk3.py | 1 + lib/matplotlib/backends/backend_gtk4.py | 1 + lib/matplotlib/backends/backend_nbagg.py | 1 + lib/matplotlib/backends/backend_qt.py | 1 + lib/matplotlib/backends/backend_wx.py | 1 + lib/matplotlib/figure.py | 11 ++++++++++- lib/matplotlib/tests/test_backends_interactive.py | 9 ++++++--- 10 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 4adaecb7f8c0..51db8dc054e5 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2723,7 +2723,9 @@ def show(self): f"shown") def destroy(self): - pass + # managers may have swapped the canvas to a GUI-framework specific one. + # restore the base canvas when the manager is destroyed. + self.canvas.figure._set_base_canvas() def full_screen_toggle(self): pass diff --git a/lib/matplotlib/backends/_backend_gtk.py b/lib/matplotlib/backends/_backend_gtk.py index ac443730e28a..ce6982a72526 100644 --- a/lib/matplotlib/backends/_backend_gtk.py +++ b/lib/matplotlib/backends/_backend_gtk.py @@ -195,6 +195,7 @@ def destroy(self, *args): self._destroying = True self.window.destroy() self.canvas.destroy() + super().destroy() @classmethod def start_main_loop(cls): diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index eaf868fd8bec..3cd349cb9e17 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -606,6 +606,7 @@ def delayed_destroy(): else: self.window.update() delayed_destroy() + super().destroy() def get_window_title(self): return self.window.wm_title() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 888f5a770f5d..68ba3a329b5e 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -89,6 +89,7 @@ def __init__(self, figure=None): def destroy(self): CloseEvent("close_event", self)._process() + super().destroy() def set_cursor(self, cursor): # docstring inherited diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index cd38968779ed..8c9045e8cca3 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -90,6 +90,7 @@ def __init__(self, figure=None): def destroy(self): CloseEvent("close_event", self)._process() + super().destroy() def set_cursor(self, cursor): # docstring inherited diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 4d18e1e9fb88..543454ab25fd 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -137,6 +137,7 @@ def _create_comm(self): return comm def destroy(self): + super().destroy() self._send_event('close') # need to copy comms as callbacks will modify this list for comm in list(self.web_sockets): diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 401ce0b0b754..68d89e1990bb 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -664,6 +664,7 @@ def show(self): self.window.raise_() def destroy(self, *args): + super().destroy() # check for qApp first, as PySide deletes it in its atexit handler if QtWidgets.QApplication.instance() is None: return diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index f83a69d8361e..5219042e7971 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1007,6 +1007,7 @@ def show(self): def destroy(self, *args): # docstring inherited _log.debug("%s - destroy()", type(self)) + super().destroy() frame = self.frame if frame: # Else, may have been already deleted, e.g. when closing. # As this can be called from non-GUI thread from plt.close use diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 03549dd53bc1..00a21343eef6 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2642,7 +2642,7 @@ def __init__(self, self._set_artist_props(self.patch) self.patch.set_antialiased(False) - FigureCanvasBase(self) # Set self.canvas. + self._set_base_canvas() if subplotpars is None: subplotpars = SubplotParams() @@ -2996,6 +2996,15 @@ def get_constrained_layout_pads(self, relative=False): return w_pad, h_pad, wspace, hspace + def _set_base_canvas(self): + """ + Initialize self.canvas with a FigureCanvasBase instance. + + This is used upon initialization of the Figure, but also + to reset the canvas when decoupling from pyplot. + """ + FigureCanvasBase(self) # Set self.canvas as a side-effect + def set_canvas(self, canvas): """ Set the canvas that contains the figure diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 9f8522a9df4a..5a6740d6e7ac 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -227,12 +227,15 @@ def check_alt_backend(alt_backend): # Ensure that the window is really closed. plt.pause(0.5) - # Test that saving works after interactive window is closed, but the figure - # is not deleted. + # When the figure is closed, it's manager is removed and the canvas is reset to + # FigureCanvasBase. Saving should still be possible. result_after = io.BytesIO() fig.savefig(result_after, format='png') - assert result.getvalue() == result_after.getvalue() + if backend.endswith("agg"): + # agg-based interactive backends should save the same image as a non-interactive + # figure + assert result.getvalue() == result_after.getvalue() @pytest.mark.parametrize("env", _get_testable_interactive_backends()) From eab4a8934249af016cfde396a1de55f897e3690a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 30 Jul 2025 10:34:00 -0400 Subject: [PATCH 04/12] TST: do not use non-baseclass methods to get renderer in tests There in now machinery for all of the public API that takes a renderer is input to get it from the current canvas on the root figure. Use that machinery instead. --- lib/matplotlib/tests/test_axes.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index c96173e340f7..e0b651095cb5 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8223,10 +8223,9 @@ def color_boxes(fig, ax): """ fig.canvas.draw() - renderer = fig.canvas.get_renderer() bbaxis = [] for nn, axx in enumerate([ax.xaxis, ax.yaxis]): - bb = axx.get_tightbbox(renderer) + bb = axx.get_tightbbox() if bb: axisr = mpatches.Rectangle( (bb.x0, bb.y0), width=bb.width, height=bb.height, @@ -8237,7 +8236,7 @@ def color_boxes(fig, ax): bbspines = [] for nn, a in enumerate(['bottom', 'top', 'left', 'right']): - bb = ax.spines[a].get_window_extent(renderer) + bb = ax.spines[a].get_window_extent() spiner = mpatches.Rectangle( (bb.x0, bb.y0), width=bb.width, height=bb.height, linewidth=0.7, edgecolor="green", facecolor="none", transform=None, @@ -8253,7 +8252,7 @@ def color_boxes(fig, ax): fig.add_artist(rect2) bbax = bb - bb2 = ax.get_tightbbox(renderer) + bb2 = ax.get_tightbbox() rect2 = mpatches.Rectangle( (bb2.x0, bb2.y0), width=bb2.width, height=bb2.height, linewidth=3, edgecolor="red", facecolor="none", transform=None, From 2396e9011c0b7da4e230ebc09b8e204b51672e99 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 30 Jul 2025 11:05:52 -0400 Subject: [PATCH 05/12] TST: fix wx window closing test Flush the wx canvas, not the base canvas that is installed on close. --- lib/matplotlib/tests/test_backends_interactive.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 5a6740d6e7ac..0a1c7f703cad 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -288,10 +288,13 @@ def _test_thread_impl(): future = ThreadPoolExecutor().submit(fig.canvas.draw) plt.pause(0.5) # flush_events fails here on at least Tkagg (bpo-41176) future.result() # Joins the thread; rethrows any exception. + # stash the current canvas as closing the figure will reset the canvas on + # the figure + canvas = fig.canvas plt.close() # backend is responsible for flushing any events here if plt.rcParams["backend"].lower().startswith("wx"): # TODO: debug why WX needs this only on py >= 3.8 - fig.canvas.flush_events() + canvas.flush_events() _thread_safe_backends = _get_testable_interactive_backends() From b7a5cfd99289d7a257966044479cd4f68bb7c438 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 30 Jul 2025 11:10:08 -0400 Subject: [PATCH 06/12] TST: try forcing the dpi when testing saving after closing window --- lib/matplotlib/tests/test_backends_interactive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 0a1c7f703cad..671ad8466aee 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -220,7 +220,7 @@ def check_alt_backend(alt_backend): fig.canvas.mpl_connect("close_event", print) result = io.BytesIO() - fig.savefig(result, format='png') + fig.savefig(result, format='png', dpi=100) plt.show() @@ -230,7 +230,7 @@ def check_alt_backend(alt_backend): # When the figure is closed, it's manager is removed and the canvas is reset to # FigureCanvasBase. Saving should still be possible. result_after = io.BytesIO() - fig.savefig(result_after, format='png') + fig.savefig(result_after, format='png', dpi=100) if backend.endswith("agg"): # agg-based interactive backends should save the same image as a non-interactive From c578ec6bdf1528efe60802eedf30a6b0523ff6cc Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 30 Jul 2025 11:38:04 -0400 Subject: [PATCH 07/12] FIX: do not add super().destroy to Canvas destroy method This goes up the chain to the GUI classes that do not have this method. --- lib/matplotlib/backends/backend_gtk4.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 8c9045e8cca3..cd38968779ed 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -90,7 +90,6 @@ def __init__(self, figure=None): def destroy(self): CloseEvent("close_event", self)._process() - super().destroy() def set_cursor(self, cursor): # docstring inherited From 06b97a8ff5b74ca4dc6ef7038fb66e28c1884cbd Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 30 Jul 2025 23:24:29 +0200 Subject: [PATCH 08/12] Add what's new note --- .../next_whats_new/pyplot-register-figure.rst | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 doc/users/next_whats_new/pyplot-register-figure.rst diff --git a/doc/users/next_whats_new/pyplot-register-figure.rst b/doc/users/next_whats_new/pyplot-register-figure.rst new file mode 100644 index 000000000000..9132e961e540 --- /dev/null +++ b/doc/users/next_whats_new/pyplot-register-figure.rst @@ -0,0 +1,54 @@ +Figures can be attached to and removed from pyplot +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Figures can now be attached to and removed from management through pyplot, which in +the background also means a less strict coupling to backends. + +In particular, standalone figures (created with the `.Figure` constructor) can now be +registered with the `.pyplot` module by calling ``plt.figure(fig)``. This allows to +show them with ``plt.show()`` as you would do with any figure created with pyplot +factory methods such as ``plt.figure()`` or ``plt.subplots()``. + +When closing a shown figure window, the related figure is reset to the standalone +state, i.e. it's not visible to pyplot anymore, but if you still hold a reference +to it, you can continue to work with it (e.g. do ``fig.savefig()``, or re-add it +to pyplot with ``plt.figure(fig)`` and then show it again). + +The following is now possible - though the example is exaggerated to show what's +possible. In practice, you'll stick with much simpler versions for better +consistency :: + + import matplotlib.pyplot as plt + from matplotlib.figure import Figure + + # Create a standalone figure + fig = Figure() + ax = fig.add_subplot() + ax.plot([1, 2, 3], [4, 5, 6]) + + # Register it with pyplot + plt.figure(fig) + + # Modify the figure through pyplot + plt.xlabel("x label") + + # Show the figure + plt.show() + + # Close the figure window through the GUI + + # Continue to work on the figure + fig.savefig("my_figure.png") + ax.set_ylabel("y label") + + # Re-register the figure and show it again + plt.figure(fig) + plt.show() + +Technical detail: Standalone figures use `.FigureCanvasBase` as canvas. This is +replaced by a backend-dependent subclass when registering with pyplot, and is +reset to `.FigureCanvasBase` when the figure is closed. `.Figure.savefig` uses +the current canvas to save the figure (if possible). Since `.FigureCanvasBase` +is Agg-based any Agg-based backend will create the same file output. There may +be slight differences for non-Agg backends; e.g. if you use "GTK4Cairo" as +interactive backend, ``fig.savefig("file.png")`` may create a slightly different +image depending on whether the figure is registered with pyplot or not. From db30cbe4a1c45c05d295e57d8aac287fede7d75b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 31 Jul 2025 16:34:09 -0400 Subject: [PATCH 09/12] MNT: restore the old DPI when resetting the canvas This prevent the figures from growing on hi-dpi screens. --- lib/matplotlib/figure.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 00a21343eef6..330019f0e07c 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3003,7 +3003,12 @@ def _set_base_canvas(self): This is used upon initialization of the Figure, but also to reset the canvas when decoupling from pyplot. """ + # check if we have changed the API due to hi-dpi screens + orig_dpi = getattr(self, '_original_dpi', self._dpi) FigureCanvasBase(self) # Set self.canvas as a side-effect + # put it back to what it was + if orig_dpi != self._dpi: + self.dpi = orig_dpi def set_canvas(self, canvas): """ From 1ea856d77450d61a2498228ef896212f7faeccb4 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 31 Jul 2025 16:56:59 -0400 Subject: [PATCH 10/12] TST: verify that dpi is restored on close --- lib/matplotlib/tests/test_backend_qt.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index 6e147fd14380..5bb81e5c1e2d 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -215,6 +215,10 @@ def set_device_pixel_ratio(ratio): assert qt_canvas.get_width_height() == (600, 240) assert (fig.get_size_inches() == (5, 2)).all() + # check that closing the figure restores the original dpi + plt.close(fig) + assert fig.dpi == 120 + @pytest.mark.backend('QtAgg', skip_on_importerror=True) def test_subplottool(): From 0e078a7af0a08c3d9a0ca381b33816e69e21aa00 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:26:31 +0200 Subject: [PATCH 11/12] Update lib/matplotlib/figure.py Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- lib/matplotlib/figure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 330019f0e07c..eba873cdc221 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3003,7 +3003,7 @@ def _set_base_canvas(self): This is used upon initialization of the Figure, but also to reset the canvas when decoupling from pyplot. """ - # check if we have changed the API due to hi-dpi screens + # check if we have changed the DPI due to hi-dpi screens orig_dpi = getattr(self, '_original_dpi', self._dpi) FigureCanvasBase(self) # Set self.canvas as a side-effect # put it back to what it was From 1784c73aba335c8eb57f9656803af0ba49957717 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 2 Aug 2025 03:30:16 +0200 Subject: [PATCH 12/12] Add docs to state that you should not keep a reference to the canvas --- doc/users/next_whats_new/pyplot-register-figure.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/users/next_whats_new/pyplot-register-figure.rst b/doc/users/next_whats_new/pyplot-register-figure.rst index 9132e961e540..1acc0d0bf767 100644 --- a/doc/users/next_whats_new/pyplot-register-figure.rst +++ b/doc/users/next_whats_new/pyplot-register-figure.rst @@ -51,4 +51,8 @@ the current canvas to save the figure (if possible). Since `.FigureCanvasBase` is Agg-based any Agg-based backend will create the same file output. There may be slight differences for non-Agg backends; e.g. if you use "GTK4Cairo" as interactive backend, ``fig.savefig("file.png")`` may create a slightly different -image depending on whether the figure is registered with pyplot or not. +image depending on whether the figure is registered with pyplot or not. In +general, you should not store a reference to the canvas, but rather always +obtain it from the figure with ``fig.canvas``. This will return the current +canvas, which is either the original `.FigureCanvasBase` or a backend-dependent +subclass, depending on whether the figure is registered with pyplot or not. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy