From 1ff61cf55f7a31e1a062146bf3af99eaa25963b6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 25 Jan 2023 16:19:09 -0500 Subject: [PATCH 1/3] FIX: only try to update blit caches if the canvas we expect Because we may switch the canvas to a different type (that may not support blitting) when saving, check it `_clear` that the current canvas is the canvas we expect when we update the blitting caches on the draw_event callback. Closes #25075 --- lib/matplotlib/tests/test_widgets.py | 40 ++++++++++++++++++++++++++++ lib/matplotlib/widgets.py | 8 +++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 53fd95526ef9..92b87a96b3d2 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1,4 +1,5 @@ import functools +import io from unittest import mock from matplotlib._api.deprecation import MatplotlibDeprecationWarning @@ -23,6 +24,45 @@ def ax(): return get_ax() +def test_save_blitted_widget_as_pdf(): + from matplotlib.widgets import CheckButtons, RadioButtons + from matplotlib.cbook import _get_running_interactive_framework + if _get_running_interactive_framework() not in ['headless', None]: + pytest.xfail("Callback exceptions are not raised otherwise.") + + fig, ax = plt.subplots( + nrows=2, ncols=2, figsize=(5, 2), width_ratios=[1, 2] + ) + default_rb = RadioButtons(ax[0, 0], ['Apples', 'Oranges']) + styled_rb = RadioButtons( + ax[0, 1], ['Apples', 'Oranges'], + label_props={'color': ['red', 'orange'], + 'fontsize': [16, 20]}, + radio_props={'edgecolor': ['red', 'orange'], + 'facecolor': ['mistyrose', 'peachpuff']} + ) + + default_cb = CheckButtons(ax[1, 0], ['Apples', 'Oranges'], + actives=[True, True]) + styled_cb = CheckButtons( + ax[1, 1], ['Apples', 'Oranges'], + actives=[True, True], + label_props={'color': ['red', 'orange'], + 'fontsize': [16, 20]}, + frame_props={'edgecolor': ['red', 'orange'], + 'facecolor': ['mistyrose', 'peachpuff']}, + check_props={'color': ['darkred', 'darkorange']} + ) + + ax[0, 0].set_title('Default') + ax[0, 1].set_title('Stylized') + # force an Agg render + fig.canvas.draw() + # force a pdf save + with io.BytesIO() as result_after: + fig.savefig(result_after, format='pdf') + + @pytest.mark.parametrize('kwargs', [ dict(), dict(useblit=True, button=1), diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 6315450a1573..03816de95430 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1088,7 +1088,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, def _clear(self, event): """Internal event handler to clear the buttons.""" - if self.ignore(event): + if self.ignore(event) or self.canvas is not self.ax.figure.canvas: return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._checks) @@ -1700,7 +1700,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, def _clear(self, event): """Internal event handler to clear the buttons.""" - if self.ignore(event): + if self.ignore(event) or self.canvas is not self.ax.figure.canvas: return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._buttons) @@ -1971,7 +1971,7 @@ def __init__(self, ax, horizOn=True, vertOn=True, useblit=False, def clear(self, event): """Internal event handler to clear the cursor.""" - if self.ignore(event): + if self.ignore(event) or self.canvas is not self.ax.figure.canvas: return if self.useblit: self.background = self.canvas.copy_from_bbox(self.ax.bbox) @@ -2106,7 +2106,7 @@ def disconnect(self): def clear(self, event): """Clear the cursor.""" - if self.ignore(event): + if self.ignore(event) or self.canvas is not self.ax.figure.canvas: return if self.useblit: for canvas, info in self._canvas_infos.items(): From faa4816bfe7f299049f0c14802e7e85c3c180391 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 25 Jan 2023 17:13:42 -0500 Subject: [PATCH 2/3] FIX: handle the change detection differently for multicursor There is not a singular canvas --- lib/matplotlib/widgets.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 03816de95430..5ae3a53c980b 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2106,10 +2106,16 @@ def disconnect(self): def clear(self, event): """Clear the cursor.""" - if self.ignore(event) or self.canvas is not self.ax.figure.canvas: + if self.ignore(event): return if self.useblit: for canvas, info in self._canvas_infos.items(): + # someone has switched the canvas on us! This happens if + # `savefig` needs to save to a format the previous backend did + # not support (e.g. saving a figure using an Agg based backend + # saved to a vector format). + if canvas is not canvas.figure.canvas: + continue info["background"] = canvas.copy_from_bbox(canvas.figure.bbox) def onmove(self, event): From 6bb208d5a9c46ded307c7baef4fd255cdbc80c46 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 9 Feb 2023 14:29:46 -0500 Subject: [PATCH 3/3] MNT: put changed canvas logic in private method with docstring --- lib/matplotlib/widgets.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 5ae3a53c980b..dfc0450132c6 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -90,6 +90,22 @@ def ignore(self, event): """ return not self.active + def _changed_canvas(self): + """ + Someone has switched the canvas on us! + + This happens if `savefig` needs to save to a format the previous + backend did not support (e.g. saving a figure using an Agg based + backend saved to a vector format). + + Returns + ------- + bool + True if the canvas has been changed. + + """ + return self.canvas is not self.ax.figure.canvas + class AxesWidget(Widget): """ @@ -1088,7 +1104,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, def _clear(self, event): """Internal event handler to clear the buttons.""" - if self.ignore(event) or self.canvas is not self.ax.figure.canvas: + if self.ignore(event) or self._changed_canvas(): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._checks) @@ -1700,7 +1716,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, def _clear(self, event): """Internal event handler to clear the buttons.""" - if self.ignore(event) or self.canvas is not self.ax.figure.canvas: + if self.ignore(event) or self._changed_canvas(): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._buttons) @@ -1971,7 +1987,7 @@ def __init__(self, ax, horizOn=True, vertOn=True, useblit=False, def clear(self, event): """Internal event handler to clear the cursor.""" - if self.ignore(event) or self.canvas is not self.ax.figure.canvas: + if self.ignore(event) or self._changed_canvas(): return if self.useblit: self.background = self.canvas.copy_from_bbox(self.ax.bbox) 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