8000 Merge pull request #23457 from QuLogic/blit-button · matplotlib/matplotlib@5f836e5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5f836e5

Browse files
authored
Merge pull request #23457 from QuLogic/blit-button
Add blitting support to button widgets
2 parents 3393a4f + 2eeaae8 commit 5f836e5

File tree

2 files changed

+89
-12
lines changed

2 files changed

+89
-12
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Blitting in Button widgets
2+
--------------------------
3+
4+
The `.Button`, `.CheckButtons`, and `.RadioButtons` widgets now support
5+
blitting for faster rendering, on backends that support it, by passing
6+
``useblit=True`` to the constructor. Blitting is enabled by default on
7+
supported backends.

lib/matplotlib/widgets.py

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class Button(AxesWidget):
152152
"""
153153

154154
def __init__(self, ax, label, image=None,
155-
color='0.85', hovercolor='0.95'):
155+
color='0.85', hovercolor='0.95', *, useblit=True):
156156
"""
157157
Parameters
158158
----------
@@ -167,6 +167,9 @@ def __init__(self, ax, label, image=None,
167167
The color of the button when not activated.
168168
hovercolor : color
169169
The color of the button when the mouse is over it.
170+
useblit : bool, default: True
171+
Use blitting for faster drawing if supported by the backend.
172+
See the tutorial :doc:`/tutorials/advanced/blitting` for details.
170173
"""
171174
super().__init__(ax)
172175

@@ -177,6 +180,8 @@ def __init__(self, ax, label, image=None,
177180
horizontalalignment='center',
178181
transform=ax.transAxes)
179182

183+
self._useblit = useblit and self.canvas.supports_blit
184+
180185
self._observers = cbook.CallbackRegistry(signals=["clicked"])
181186

182187
self.connect_event('button_press_event', self._click)
@@ -209,7 +214,11 @@ def _motion(self, event):
209214
if not colors.same_color(c, self.ax.get_facecolor()):
210215
self.ax.set_facecolor(c)
211216
if self.drawon:
212-
self.ax.figure.canvas.draw()
217+
if self._useblit:
218+
self.ax.draw_artist(self.ax)
219+
self.canvas.blit(self.ax.bbox)
220+
else:
221+
self.canvas.draw()
213222

214223
def on_clicked(self, func):
215224
"""
@@ -968,6 +977,7 @@ class CheckButtons(AxesWidget):
968977
----------
969978
ax : `~matplotlib.axes.Axes`
970979
The parent Axes for the widget.
980+
971981
labels : list of `.Text`
972982
973983
rectangles : list of `.Rectangle`
@@ -977,21 +987,22 @@ class CheckButtons(AxesWidget):
977987
each box, but have ``set_visible(False)`` when its box is not checked.
978988
"""
979989

980-
def __init__(self, ax, labels, actives=None):
990+
def __init__(self, ax, labels, actives=None, *, useblit=True):
981991
"""
982992
Add check buttons to `matplotlib.axes.Axes` instance *ax*.
983993
984994
Parameters
985995
----------
986996
ax : `~matplotlib.axes.Axes`
987997
The parent Axes for the widget.
988-
989998
labels : list of str
990999
The labels of the check buttons.
991-
9921000
actives : list of bool, optional
9931001
The initial check states of the buttons. The list must have the
9941002
same length as *labels*. If not given, all buttons are unchecked.
1003+
useblit : bool, default: True
1004+
Use blitting for faster drawing if supported by the backend.
1005+
See the tutorial :doc:`/tutorials/advanced/blitting` for details.
9951006
"""
9961007
super().__init__(ax)
9971008

@@ -1002,6 +1013,9 @@ def __init__(self, ax, labels, actives=None):
10021013
if actives is None:
10031014
actives = [False] * len(labels)
10041015

1016+
self._useblit = useblit and self.canvas.supports_blit
1017+
self._background = None
1018+
10051019
ys = np.linspace(1, 0, len(labels)+2)[1:-1]
10061020
text_size = mpl.rcParams["font.size"] / 2
10071021

@@ -1017,13 +1031,26 @@ def __init__(self, ax, labels, actives=None):
10171031
self._crosses = ax.scatter(
10181032
F438 [0.15] * len(ys), ys, marker='x', linewidth=1, s=text_size**2,
10191033
c=["k" if active else "none" for active in actives],
1020-
transform=ax.transAxes
1034+
transform=ax.transAxes, animated=self._useblit,
10211035
)
10221036

10231037
self.connect_event('button_press_event', self._clicked)
1038+
if self._useblit:
1039+
self.connect_event('draw_event', self._clear)
10241040

10251041
self._observers = cbook.CallbackRegistry(signals=["clicked"])
10261042

1043+
def _clear(self, event):
1044+
"""Internal event handler to clear the buttons."""
1045+
if self.ignore(event):
1046+
return
1047+
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
1048+
self.ax.draw_artist(self._crosses)
1049+
if hasattr(self, '_lines'):
1050+
for l1, l2 in self._lines:
1051+
self.ax.draw_artist(l1)
1052+
self.ax.draw_artist(l2)
1053+
10271054
def _clicked(self, event):
10281055
if self.ignore(event) or event.button != 1 or event.inaxes != self.ax:
10291056
return
@@ -1084,7 +1111,17 @@ def set_active(self, index):
10841111
l2.set_visible(not l2.get_visible())
10851112

10861113
if self.drawon:
1087-
self.ax.figure.canvas.draw()
1114+
if self._useblit:
1115+
if self._background is not None:
1116+
self.canvas.restore_region(self._background)
1117+
self.ax.draw_artist(self._crosses)
1118+
if hasattr(self, "_lines"):
1119+
for l1, l2 in self._lines:
1120+
self.ax.draw_artist(l1)
1121+
self.ax.draw_artist(l2)
1122+
self.canvas.blit(self.ax.bbox)
1123+
else:
1124+
self.canvas.draw()
10881125

10891126
if self.eventson:
10901127
self._observers.process('clicked', self.labels[index].get_text())
@@ -1143,7 +1180,8 @@ def lines(self):
11431180
current_status = self.get_status()
11441181
lineparams = {'color': 'k', 'linewidth': 1.25,
11451182
'transform': self.ax.transAxes,
1146-
'solid_capstyle': 'butt'}
1183+
'solid_capstyle': 'butt',
1184+
'animated': self._useblit}
11471185
for i, y in enumerate(ys):
11481186
x, y = 0.05, y - h / 2
11491187
l1 = Line2D([x, x + w], [y + h, y], **lineparams)
@@ -1447,7 +1485,8 @@ class RadioButtons(AxesWidget):
14471485
The label text of the currently selected button.
14481486
"""
14491487

1450-
def __init__(self, ax, labels, active=0, activecolor='blue'):
1488+
def __init__(self, ax, labels, active=0, activecolor='blue', *,
1489+
useblit=True):
14511490
"""
14521491
Add radio buttons to an `~.axes.Axes`.
14531492
@@ -1461,6 +1500,9 @@ def __init__(self, ax, labels, active=0, activecolor='blue'):
14611500
The index of the initially selected button.
14621501
activecolor : color
14631502
The color of the selected button.
1503+
useblit : bool, default: True
1504+
Use blitting for faster drawing if supported by the backend.
1505+
See the tutorial :doc:`/tutorials/advanced/blitting` for details.
14641506
"""
14651507
super().__init__(ax)
14661508
self.activecolor = activecolor
@@ -1473,19 +1515,34 @@ def __init__(self, ax, labels, active=0, activecolor='blue'):
14731515
ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
14741516
text_size = mpl.rcParams["font.size"] / 2
14751517

1518+
self._useblit = useblit and self.canvas.supports_blit
1519+
self._background = None
1520+
14761521
self.labels = [
14771522
ax.text(0.25, y, label, transform=ax.transAxes,
14781523
horizontalalignment="left", verticalalignment="center")
14791524
for y, label in zip(ys, labels)]
14801525
self._buttons = ax.scatter(
14811526
[.15] * len(ys), ys, transform=ax.transAxes, s=text_size**2,
14821527
c=[activecolor if i == active else "none" for i in range(len(ys))],
1483-
edgecolor="black")
1528+
edgecolor="black", animated=self._useblit)
14841529

14851530
self.connect_event('button_press_event', self._clicked)
1531+
if self._useblit:
1532+
self.connect_event('draw_event', self._clear)
14861533

14871534
self._observers = cbook.CallbackRegistry(signals=["clicked"])
14881535

1536+
def _clear(self, event):
1537+
"""Internal event handler to clear the buttons."""
1538+
if self.ignore(event):
1539+
return
1540+
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
1541+
self.ax.draw_artist(self._buttons)
1542+
if hasattr(self, "_circles"):
1543+
for circle in self._circles:
1544+
self.ax.draw_artist(circle)
1545+
14891546
def _clicked(self, event):
14901547
if self.ignore(event) or event.button != 1 or event.inaxes != self.ax:
14911548
return
@@ -1524,8 +1581,20 @@ def set_active(self, index):
15241581
if hasattr(self, "_circles"): # Remove once circles is removed.
15251582
for i, p in enumerate(self._circles):
15261583
p.set_facecolor(self.activecolor if i == index else "none")
1584+
if self.drawon and self._useblit:
1585+
self.ax.draw_artist(p)
15271586
if self.drawon:
1528-
self.ax.figure.canvas.draw()
1587+
if self._useblit:
1588+
if self._background is not None:
1589+
self.canvas.restore_region(self._background)
1590+
self.ax.draw_artist(self._buttons)
1591+
if hasattr(self, "_circles"):
1592+
for p in self._circles:
1593+
self.ax.draw_artist(p)
1594+
self.canvas.blit(self.ax.bbox)
1595+
else:
1596+
self.canvas.draw()
1597+
15291598
if self.eventson:
15301599
self._observers.process('clicked', self.labels[index].get_text())
15311600

@@ -1549,7 +1618,8 @@ def circles(self):
15491618
circles = self._circles = [
15501619
Circle(xy=self._buttons.get_offsets()[i], edgecolor="black",
15511620
facecolor=self._buttons.get_facecolor()[i],
1552-
radius=radius, transform=self.ax.transAxes)
1621+
radius=radius, transform=self.ax.transAxes,
1622+
animated=self._useblit)
15531623
for i in range(len(self.labels))]
15541624
self._buttons.set_visible(False)
15551625
for circle in circles:

0 commit comments

Comments
 (0)
0