🌐 AI搜索 & 代理 主页
Skip to content

Commit d3d59d1

Browse files
committed
FIX: Make widget blitting compatible with swapped canvas
1 parent e7e5865 commit d3d59d1

File tree

2 files changed

+88
-17
lines changed

2 files changed

+88
-17
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,10 @@ class FigureCanvasBase:
17401740

17411741
filetypes = _default_filetypes
17421742

1743+
# global counter to assign unique ids to blit backgrounds
1744+
# see _get_blit_background_id()
1745+
_last_blit_background_id = 0
1746+
17431747
@_api.classproperty
17441748
def supports_blit(cls):
17451749
"""If this Canvas sub-class supports blitting."""
@@ -1765,6 +1769,7 @@ def __init__(self, figure=None):
17651769
# We don't want to scale up the figure DPI more than once.
17661770
figure._original_dpi = figure.dpi
17671771
self._device_pixel_ratio = 1
1772+
self._blit_backgrounds = {}
17681773
super().__init__() # Typically the GUI widget init (if any).
17691774

17701775
callbacks = property(lambda self: self.figure._canvas_callbacks)
@@ -1840,6 +1845,48 @@ def is_saving(self):
18401845
def blit(self, bbox=None):
18411846
"""Blit the canvas in bbox (default entire canvas)."""
18421847

1848+
@classmethod
1849+
def _get_blit_background_id(cls):
1850+
"""
1851+
Get a globally unique id that can be used to store a blit background.
1852+
1853+
Blitting support is canvas-dependent, so blitting mechanisms should
1854+
store their backgrounds in the canvas, more precisely in
1855+
``canvas._blit_backgrounds[id]``. The id must be obtained via this
1856+
function to ensure it is globally unique.
1857+
1858+
The content of ``canvas._blit_backgrounds[id]`` is not specified.
1859+
We leave this freedom to the blitting mechanism.
1860+
1861+
Blitting mechanisms must not expect that a background that they
1862+
have stored is still there at a later time. The canvas may have
1863+
been switched out, or we may add other mechanisms later that
1864+
invalidate blit backgrounds (e.g. dpi changes).
1865+
Therefore, always query as `_blit_backgrounds.get(id)` and be
1866+
prepared for a None return value.
1867+
1868+
Note: The blit background API is still experimental and may change
1869+
in the future without warning.
1870+
"""
1871+
cls._last_blit_background_id += 1
1872+
return cls._last_blit_background_id
1873+
1874+
@classmethod
1875+
def _release_blit_background_id(cls, id):
1876+
"""
1877+
Release a blit background id that is no longer needed.
1878+
1879+
This removes the respective entry from the internal storage, i.e.
1880+
the ``canvas._blit_backgrounds`` dict, and thus allows to free
1881+
associated memory.
1882+
1883+
After releasing the id you must not use it anymore.
1884+
1885+
Note: The blit background API is still experimental and may change
1886+
in the future without warning.
1887+
"""
1888+
del cls._blit_backgrounds[id]
1889+
18431890
def inaxes(self, xy):
18441891
"""
18451892
Return the topmost visible `~.axes.Axes` containing the point *xy*.

lib/matplotlib/widgets.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ class AxesWidget(Widget):
117117
def __init__(self, ax):
118118
self.ax = ax
119119
self._cids = []
120+
self._blit_background_id = None
121+
122+
def __del__(self):
123+
if self._blit_background_id is not None:
124+
self.canvas._release_blit_background_id(self._blit_background_id)
120125

121126
canvas = property(
122127
lambda self: getattr(self.ax.get_figure(root=True), 'canvas', None)
@@ -155,6 +160,26 @@ def _set_cursor(self, cursor):
155160
"""Update the canvas cursor."""
156161
self.ax.get_figure(root=True).canvas.set_cursor(cursor)
157162

163+
def _save_blit_background(self, background):
164+
"""
165+
Save a blit background.
166+
167+
The background is stored on the canvas in a uniquely identifiable way.
168+
It should be read back via `._load_blit_background`. Be prepared that
169+
some events may invalidate the background, in which case
170+
`._load_blit_background` will return None.
171+
172+
This currently allows at most one background per widget, which is
173+
good enough for all existing widgets.
174+
"""
175+
if self._blit_background_id is None:
176+
self._blit_background_id = self.canvas._get_blit_background_id()
177+
self.canvas._blit_backgrounds[self._blit_background_id] = background
178+
179+
def _load_blit_background(self):
180+
"""Load a blit background; may be None at any time."""
181+
return self.canvas._blit_backgrounds.get(self._blit_background_id)
182+
158183

159184
class Button(AxesWidget):
160185
"""
@@ -1063,7 +1088,6 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
10631088
actives = [False] * len(labels)
10641089

10651090
self._useblit = useblit and self.canvas.supports_blit
1066-
self._background = None
10671091

10681092
ys = np.linspace(1, 0, len(labels)+2)[1:-1]
10691093

@@ -1110,7 +1134,7 @@ def _clear(self, event):
11101134
"""Internal event handler to clear the buttons."""
11111135
if self.ignore(event) or self.canvas.is_saving():
11121136
return
1113-
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
1137+
self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox))
11141138
self.ax.draw_artist(self._checks)
11151139

11161140
def _clicked(self, event):
@@ -1215,8 +1239,9 @@ def set_active(self, index, state=None):
12151239

12161240
if self.drawon:
12171241
if self._useblit:
1218-
if self._background is not None:
1219-
self.canvas.restore_region(self._background)
1242+
background = self._load_blit_background()
1243+
if background is not None:
1244+
self.canvas.restore_region(background)
12201245
self.ax.draw_artist(self._checks)
12211246
self.canvas.blit(self.ax.bbox)
12221247
else:
@@ -1650,7 +1675,6 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16501675
ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
16511676

16521677
self._useblit = useblit and self.canvas.supports_blit
1653-
self._background = None
16541678

16551679
label_props = _expand_text_props(label_props)
16561680
self.labels = [
@@ -1692,7 +1716,7 @@ def _clear(self, event):
16921716
"""Internal event handler to clear the buttons."""
16931717
if self.ignore(event) or self.canvas.is_saving():
16941718
return
1695-
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
1719+
self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox))
16961720
self.ax.draw_artist(self._buttons)
16971721

16981722
def _clicked(self, event):
@@ -1785,8 +1809,9 @@ def set_active(self, index):
17851809

17861810
if self.drawon:
17871811
if self._useblit:
1788-
if self._background is not None:
1789-
self.canvas.restore_region(self._background)
1812+
background = self._load_blit_background()
1813+
if background is not None:
1814+
self.canvas.restore_region(background)
17901815
self.ax.draw_artist(self._buttons)
17911816
self.canvas.blit(self.ax.bbox)
17921817
else:
@@ -1942,15 +1967,14 @@ def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False,
19421967
self.lineh = ax.axhline(ax.get_ybound()[0], visible=False, **lineprops)
19431968
self.linev = ax.axvline(ax.get_xbound()[0], visible=False, **lineprops)
19441969

1945-
self.background = None
19461970
self.needclear = False
19471971

19481972
def clear(self, event):
19491973
"""Internal event handler to clear the cursor."""
19501974
if self.ignore(event) or self.canvas.is_saving():
19511975
return
19521976
if self.useblit:
1953-
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
1977+
self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox))
19541978

19551979
def onmove(self, event):
19561980
"""Internal event handler to draw the cursor when the mouse moves."""
@@ -1975,8 +1999,9 @@ def onmove(self, event):
19751999
return
19762000
# Redraw.
19772001
if self.useblit:
1978-
if self.background is not None:
1979-
self.canvas.restore_region(self.background)
2002+
background = self._load_blit_background()
2003+
if background is not None:
2004+
self.canvas.restore_region(background)
19802005
self.ax.draw_artist(self.linev)
19812006
self.ax.draw_artist(self.lineh)
19822007
self.canvas.blit(self.ax.bbox)
@@ -2137,8 +2162,6 @@ def __init__(self, ax, onselect=None, useblit=False, button=None,
21372162
self._state_modifier_keys.update(state_modifier_keys or {})
21382163
self._use_data_coordinates = use_data_coordinates
21392164

2140-
self.background = None
2141-
21422165
if isinstance(button, Integral):
21432166
self.validButtons = [button]
21442167
else:
@@ -2194,7 +2217,7 @@ def update_background(self, event):
21942217
for artist in artists:
21952218
stack.enter_context(artist._cm_set(visible=False))
21962219
self.canvas.draw()
2197-
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
2220+
self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox))
21982221
if needs_redraw:
21992222
for artist in artists:
22002223
self.ax.draw_artist(artist)
@@ -2241,8 +2264,9 @@ def update(self):
22412264
self.ax.get_figure(root=True)._get_renderer() is None):
22422265
return
22432266
if self.useblit:
2244-
if self.background is not None:
2245-
self.canvas.restore_region(self.background)
2267+
background = self._load_blit_background()
2268+
if background is not None:
2269+
self.canvas.restore_region(background)
22462270
else:
22472271
self.update_background(None)
22482272
# We need to draw all artists, which are not included in the

0 commit comments

Comments
 (0)