Skip to content

Commit 2c895fb

Browse files
committed
Add styling support to Check and Radio buttons
1 parent bcfd5e6 commit 2c895fb

File tree

3 files changed

+131
-42
lines changed

3 files changed

+131
-42
lines changed

examples/widgets/check_buttons.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
s2 = np.sin(6*np.pi*t)
2121

2222
fig, ax = plt.subplots()
23-
l0, = ax.plot(t, s0, visible=False, lw=2, color='k', label='2 Hz')
24-
l1, = ax.plot(t, s1, lw=2, color='r', label='4 Hz')
25-
l2, = ax.plot(t, s2, lw=2, color='g', label='6 Hz')
23+
l0, = ax.plot(t, s0, visible=False, lw=2, color='k', label='1 Hz')
24+
l1, = ax.plot(t, s1, lw=2, color='r', label='2 Hz')
25+
l2, = ax.plot(t, s2, lw=2, color='g', label='3 Hz')
2626
fig.subplots_adjust(left=0.2)
2727

2828
lines_by_label = {l.get_label(): l for l in [l0, l1, l2]}
@@ -32,7 +32,10 @@
3232
check = CheckButtons(
3333
ax=rax,
3434
labels=lines_by_label.keys(),
35-
actives=[l.get_visible() for l in lines_by_label.values()]
35+
actives=[l.get_visible() for l in lines_by_label.values()],
36+
label_props=[{'color': 'k'}, {'color': 'r'}, {'color': 'g'}],
37+
frame_props={'edgecolor': ['k', 'r', 'g']},
38+
check_props={'facecolor': ['k', 'r', 'g']},
3639
)
3740

3841

examples/widgets/radio_buttons.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,30 @@
2525

2626
axcolor = 'lightgoldenrodyellow'
2727
rax = fig.add_axes([0.05, 0.7, 0.15, 0.15], facecolor=axcolor)
28-
radio = RadioButtons(rax, ('2 Hz', '4 Hz', '8 Hz'))
28+
radio = RadioButtons(rax, ('1 Hz', '2 Hz', '4 Hz'),
29+
radio_props={'s': [16, 32, 64]})
2930

3031

3132
def hzfunc(label):
32-
hzdict = {'2 Hz': s0, '4 Hz': s1, '8 Hz': s2}
33+
hzdict = {'1 Hz': s0, '2 Hz': s1, '4 Hz': s2}
3334
ydata = hzdict[label]
3435
l.set_ydata(ydata)
35-
plt.draw()
36+
fig.canvas.draw()
3637
radio.on_clicked(hzfunc)
3738

3839
rax = fig.add_axes([0.05, 0.4, 0.15, 0.15], facecolor=axcolor)
39-
radio2 = RadioButtons(rax, ('red', 'blue', 'green'))
40+
radio2 = RadioButtons(
41+
rax, ('red', 'blue', 'green'),
42+
label_props=[{'color': 'red'}, {'color': 'blue'}, {'color': 'green'}],
43+
radio_props={
44+
'facecolor': ['red', 'blue', 'green'],
45+
'edgecolor': ['darkred', 'darkblue', 'darkgreen'],
46+
})
4047

4148

4249
def colorfunc(label):
4350
l.set_color(label)
44-
plt.draw()
51+
fig.canvas.draw()
4552
radio2.on_clicked(colorfunc)
4653

4754
rax = fig.add_axes([0.05, 0.1, 0.15, 0.15], facecolor=axcolor)
@@ -50,7 +57,7 @@ def colorfunc(label):
5057

5158
def stylefunc(label):
5259
l.set_linestyle(label)
53-
plt.draw()
60+
fig.canvas.draw()
5461
radio3.on_clicked(stylefunc)
5562

5663
plt.show()

lib/matplotlib/widgets.py

Lines changed: 111 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,8 @@ class CheckButtons(AxesWidget):
987987
each box, but have ``set_visible(False)`` when its box is not checked.
988988
"""
989989

990-
def __init__(self, ax, labels, actives=None, *, useblit=True):
990+
def __init__(self, ax, labels, actives=None, *, useblit=True,
991+
label_props=None, frame_props=None, check_props=None):
991992
"""
992993
Add check buttons to `matplotlib.axes.Axes` instance *ax*.
993994
@@ -1003,6 +1004,17 @@ def __init__(self, ax, labels, actives=None, *, useblit=True):
10031004
useblit : bool, default: True
10041005
Use blitting for faster drawing if supported by the backend.
10051006
See the tutorial :doc:`/tutorials/advanced/blitting` for details.
1007+
label_props : dict or list of dict, optional
1008+
Dictionary of `.Text` properties to be used for the labels. May
1009+
also be a list of dictionaries, one for each label.
1010+
frame_props : dict, optional
1011+
Dictionary of scatter `.Collection` properties to be used for the
1012+
check button frame. Defaults to 's' marker, (label font size /
1013+
2)**2 size, black edgecolor, no facecolor, and 1.0 linewidth.
1014+
check_props : dict, optional
1015+
Dictionary of scatter `.Collection` properties to be used for the
1016+
check button check. Defaults to 'x' marker, (label font size /
1017+
2)**2 size, black color, and 1.0 linewidth.
10061018
"""
10071019
super().__init__(ax)
10081020

@@ -1017,22 +1029,49 @@ def __init__(self, ax, labels, actives=None, *, useblit=True):
10171029
self._background = None
10181030

10191031
ys = np.linspace(1, 0, len(labels)+2)[1:-1]
1020-
text_size = mpl.rcParams["font.size"] / 2
10211032

1033+
if label_props is None:
1034+
label_props = [{}] * len(labels)
1035+
if isinstance(label_props, dict):
1036+
label_props = [label_props] * len(labels)
10221037
self.labels = [
10231038
ax.text(0.25, y, label, transform=ax.transAxes,
1024-
horizontalalignment="left", verticalalignment="center")
1025-
for y, label in zip(ys, labels)]
1026-
1027-
self._squares = ax.scatter(
1028-
[0.15] * len(ys), ys, marker='s', s=text_size**2,
1029-
c="none", linewidth=1, transform=ax.transAxes, edgecolor="k"
1030-
)
1031-
self._crosses = ax.scatter(
1032-
[0.15] * len(ys), ys, marker='x', linewidth=1, s=text_size**2,
1033-
c=["k" if active else "none" for active in actives],
1034-
transform=ax.transAxes, animated=self._useblit,
1035-
)
1039+
horizontalalignment="left", verticalalignment="center",
1040+
**props)
1041+
for y, label, props in zip(ys, labels, label_props)]
1042+
text_size = np.array([text.get_fontsize() for text in self.labels]) / 2
1043+
1044+
frame_props = {
1045+
'marker': 's', 's': text_size**2,
1046+
'facecolor': 'none', 'linewidth': 1, 'edgecolor': 'k',
1047+
**(frame_props or {}),
1048+
'transform': ax.transAxes,
1049+
}
1050+
self._squares = ax.scatter([0.15] * len(ys), ys, **frame_props)
1051+
check_props = {
1052+
'marker': 'x', 'linewidth': 1, 's': text_size**2, 'c': 'k',
1053+
**(check_props or {}),
1054+
'transform': ax.transAxes,
1055+
'animated': self._useblit,
1056+
}
1057+
self._checks = ax.scatter([0.15] * len(ys), ys, **check_props)
1058+
# The user may have passed custom marker and colours in check_props, so
1059+
# we need to create the checks, and modify the visibility after getting
1060+
# whatever the user set.
1061+
self._check_edgecolors = self._checks.get_edgecolor()
1062+
if len(self._check_edgecolors) == 1:
1063+
self._check_edgecolors = np.repeat(self._check_edgecolors,
1064+
len(labels), axis=0)
1065+
self._checks.set_edgecolor(
1066+
[ec if active else "none"
1067+
for ec, active in zip(self._check_edgecolors, actives)])
1068+
self._check_facecolors = self._checks.get_facecolor()
1069+
if len(self._check_facecolors) == 1:
1070+
self._check_facecolors = np.repeat(self._check_facecolors,
1071+
len(labels), axis=0)
1072+
self._checks.set_facecolor(
1073+
[fc if active else "none"
1074+
for fc, active in zip(self._check_facecolors, actives)])
10361075

10371076
self.connect_event('button_press_event', self._clicked)
10381077
if self._useblit:
@@ -1045,7 +1084,7 @@ def _clear(self, event):
10451084
if self.ignore(event):
10461085
return
10471086
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
1048-
self.ax.draw_artist(self._crosses)
1087+
self.ax.draw_artist(self._checks)
10491088
if hasattr(self, '_lines'):
10501089
for l1, l2 in self._lines:
10511090
self.ax.draw_artist(l1)
@@ -1095,15 +1134,25 @@ def set_active(self, index):
10951134
if index not in range(len(self.labels)):
10961135
raise ValueError(f'Invalid CheckButton index: {index}')
10971136

1098-
cross_facecolors = self._crosses.get_facecolor()
1099-
cross_facecolors[index] = colors.to_rgba(
1100-
"black"
1137+
check_facecolors = self._checks.get_facecolor()
1138+
check_facecolors[index] = colors.to_rgba(
1139+
self._check_facecolors[index]
1140+
if colors.same_color(
1141+
check_facecolors[index], colors.to_rgba("none")
1142+
)
1143+
else "none"
1144+
)
1145+
self._checks.set_facecolor(check_facecolors)
1146+
1147+
check_edgecolors = self._checks.get_edgecolor()
1148+
check_edgecolors[index] = colors.to_rgba(
1149+
self._check_edgecolors[index]
11011150
if colors.same_color(
1102-
cross_facecolors[index], colors.to_rgba("none")
1151+
check_edgecolors[index], colors.to_rgba("none")
11031152
)
11041153
else "none"
11051154
)
1106-
self._crosses.set_facecolor(cross_facecolors)
1155+
self._checks.set_edgecolor(check_edgecolors)
11071156

11081157
if hasattr(self, "_lines"):
11091158
l1, l2 = self._lines[index]
@@ -1114,7 +1163,7 @@ def set_active(self, index):
11141163
if self._useblit:
11151164
if self._background is not None:
11161165
self.canvas.restore_region(self._background)
1117-
self.ax.draw_artist(self._crosses)
1166+
self.ax.draw_artist(self._checks)
11181167
if hasattr(self, "_lines"):
11191168
for l1, l2 in self._lines:
11201169
self.ax.draw_artist(l1)
@@ -1131,7 +1180,7 @@ def get_status(self):
11311180
Return a list of the status (True/False) of all of the check buttons.
11321181
"""
11331182
return [not colors.same_color(color, colors.to_rgba("none"))
1134-
for color in self._crosses.get_facecolors()]
1183+
for color in self._checks.get_facecolors()]
11351184

11361185
def on_clicked(self, func):
11371186
"""
@@ -1173,7 +1222,7 @@ def rectangles(self):
11731222
def lines(self):
11741223
if not hasattr(self, "_lines"):
11751224
ys = np.linspace(1, 0, len(self.labels)+2)[1:-1]
1176-
self._crosses.set_visible(False)
1225+
self._checks.set_visible(False)
11771226
dy = 1. / (len(self.labels) + 1)
11781227
w, h = dy / 2, dy / 2
11791228
self._lines = []
@@ -1486,7 +1535,7 @@ class RadioButtons(AxesWidget):
14861535
"""
14871536

14881537
def __init__(self, ax, labels, active=0, activecolor='blue', *,
1489-
useblit=True):
1538+
useblit=True, label_props=None, radio_props=None):
14901539
"""
14911540
Add radio buttons to an `~.axes.Axes`.
14921541
@@ -1503,6 +1552,14 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *,
15031552
useblit : bool, default: True
15041553
Use blitting for faster drawing if supported by the backend.
15051554
See the tutorial :doc:`/tutorials/advanced/blitting` for details.
1555+
label_props : dict or list of dict, optional
1556+
Dictionary of `.Text` properties to be used for the labels. May
1557+
also be a list of dictionaries, one for each label.
1558+
radio_props : dict, optional
1559+
Dictionary of scatter `.Collection` properties to be used for the
1560+
check button frame. Defaults to 'o' marker, (label font size /
1561+
2)**2 size, black edgecolor, and *activecolor* facecolor (when
1562+
active).
15061563
"""
15071564
super().__init__(ax)
15081565
self.activecolor = activecolor
@@ -1513,19 +1570,41 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *,
15131570
ax.set_navigate(False)
15141571

15151572
ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
1516-
text_size = mpl.rcParams["font.size"] / 2
15171573

15181574
self._useblit = useblit and self.canvas.supports_blit
15191575
self._background = None
15201576

1577+
if label_props is None:
1578+
label_props = [{}] * len(labels)
1579+
elif isinstance(label_props, dict):
1580+
label_props = [label_props] * len(labels)
15211581
self.labels = [
15221582
ax.text(0.25, y, label, transform=ax.transAxes,
1523-
horizontalalignment="left", verticalalignment="center")
1524-
for y, label in zip(ys, labels)]
1525-
self._buttons = ax.scatter(
1526-
[.15] * len(ys), ys, transform=ax.transAxes, s=text_size**2,
1527-
c=[activecolor if i == active else "none" for i in range(len(ys))],
1528-
edgecolor="black", animated=self._useblit)
1583+
horizontalalignment="left", verticalalignment="center",
1584+
**props)
1585+
for y, label, props in zip(ys, labels, label_props)]
1586+
text_size = np.array([text.get_fontsize() for text in self.labels]) / 2
1587+
1588+
radio_props = {
1589+
'marker': 'o',
1590+
's': text_size**2,
1591+
'facecolor': activecolor,
1592+
'edgecolor': 'black',
1593+
**(radio_props or {}),
1594+
'transform': ax.transAxes,
1595+
'animated': self._useblit,
1596+
}
1597+
self._buttons = ax.scatter([.15] * len(ys), ys, **radio_props)
1598+
# The user may have passed custom marker and colours in radio_props, so
1599+
# we need to create the radios, and modify the visibility after getting
1600+
# whatever the user set.
1601+
self._active_colors = self._buttons.get_facecolor()
1602+
if len(self._active_colors) == 1:
1603+
self._active_colors = np.repeat(self._active_colors, len(labels),
1604+
axis=0)
1605+
self._buttons.set_facecolor(
1606+
[activecolor if i == active else "none"
1607+
for i, activecolor in enumerate(self._active_colors)])
15291608

15301609
self.connect_event('button_press_event', self._clicked)
15311610
if self._useblit:
@@ -1576,7 +1655,7 @@ def set_active(self, index):
15761655
self.value_selected = self.labels[index].get_text()
15771656
button_facecolors = self._buttons.get_facecolor()
15781657
button_facecolors[:] = colors.to_rgba("none")
1579-
button_facecolors[index] = colors.to_rgba(self.activecolor)
1658+
button_facecolors[index] = colors.to_rgba(self._active_colors[index])
15801659
self._buttons.set_facecolor(button_facecolors)
15811660
if hasattr(self, "_circles"): # Remove once circles is removed.
15821661
for i, p in enumerate(self._circles):

0 commit comments

Comments
 (0)