From d3507858778ab911a927393bef82c249440207cd Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 9 Jan 2026 01:32:47 +0100 Subject: [PATCH] Prevent blitting errors after canvas swap in RadioButtons and CheckButtons This guards blitting-related functionality of RadioButtons and CheckButtons behind a `self.canvas.supports_blit` check, so that we don't get errors when trying to call blitting functionality on canvases that don't support it. Note: RadioButtons and CheckButtons do still not fully support canvas swapping as they carry persitent state that depends on blitting capability ("animated"). This is currently not updated when canvases are changed. However, this should at most lead to incorrect rendering in some edge cases after canvas swap. It will not error out anymore. In other words the "# TODO: make dynamic" is partially solved, and reduced to a smaller problem outlined in the new TODO. --- lib/matplotlib/widgets.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 79b2e3b7651c..bd36c855a606 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1108,7 +1108,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, if actives is None: actives = [False] * len(labels) - self._useblit = useblit and self.canvas.supports_blit # TODO: make dynamic + self._useblit = useblit ys = np.linspace(1, 0, len(labels)+2)[1:-1] @@ -1136,7 +1136,10 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, **cbook.normalize_kwargs(check_props, collections.PathCollection), 'marker': 'x', 'transform': ax.transAxes, - 'animated': self._useblit, + 'animated': self._useblit and self.canvas.supports_blit, + # TODO: This may need an update when switching out the canvas. + # Can set this to `_useblit` only and live with the animated=True + # overhead on unsupported backends. } check_props.setdefault('facecolor', check_props.pop('color', 'black')) self._checks = ax.scatter([0.15] * len(ys), ys, **check_props) @@ -1155,7 +1158,8 @@ def _clear(self, event): """Internal event handler to clear the buttons.""" if self.ignore(event) or self.canvas.is_saving(): return - self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) + if self._useblit and self.canvas.supports_blit: + self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) self.ax.draw_artist(self._checks) @_call_with_reparented_event @@ -1260,7 +1264,7 @@ def set_active(self, index, state=None): self._checks.set_facecolor(facecolors) if self.drawon: - if self._useblit: + if self._useblit and self.canvas.supports_blit: background = self._load_blit_background() if background is not None: self.canvas.restore_region(background) @@ -1701,7 +1705,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, ys = np.linspace(1, 0, len(labels) + 2)[1:-1] - self._useblit = useblit and self.canvas.supports_blit # TODO: make dynamic + self._useblit = useblit label_props = _expand_text_props(label_props) self.labels = [ @@ -1716,7 +1720,11 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, **radio_props, 'marker': 'o', 'transform': ax.transAxes, - 'animated': self._useblit, + 'animated': self._useblit and self.canvas.supports_blit, + # TODO: This may need an update when switching out the canvas. + # Can set this to `_useblit` only and live with the animated=True + # overhead on unsupported backends. + } radio_props.setdefault('edgecolor', radio_props.get('color', 'black')) radio_props.setdefault('facecolor', @@ -1743,7 +1751,8 @@ def _clear(self, event): """Internal event handler to clear the buttons.""" if self.ignore(event) or self.canvas.is_saving(): return - self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) + if self._useblit and self.canvas.supports_blit: + self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) self.ax.draw_artist(self._buttons) @_call_with_reparented_event @@ -1836,7 +1845,7 @@ def set_active(self, index): self._buttons.set_facecolor(button_facecolors) if self.drawon: - if self._useblit: + if self._useblit and self.canvas.supports_blit: background = self._load_blit_background() if background is not None: self.canvas.restore_region(background)