Skip to content

Commit f92a95f

Browse files
committed
Add support for horizontal CheckButtons
1 parent 035517e commit f92a95f

File tree

5 files changed

+82
-25
lines changed

5 files changed

+82
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
1-
RadioButtons widget may now be laid out horizontally
2-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1+
CheckButtons / RadioButtons widget may now be laid out horizontally
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33

4-
The `.RadioButtons` widget's primary layout direction may now be specified with
5-
the *orientation* keyword argument:
4+
The `.CheckButtons` and `.RadioButtons` widget's primary layout direction may
5+
now be specified with the *orientation* keyword argument:
66

77
.. plot::
88
:include-source:
99

1010
import matplotlib.pyplot as plt
11-
from matplotlib.widgets import RadioButtons
11+
from matplotlib.widgets import CheckButtons, RadioButtons
1212

1313
fig = plt.figure(figsize=(4, 2))
1414

1515
# Default orientation is vertical:
1616
rbv = RadioButtons(fig.add_axes((0.05, 0.6, 0.2, 0.35)),
1717
('Radio 1', 'Radio 2', 'Radio 3'),
1818
orientation='vertical')
19+
cbv = CheckButtons(fig.add_axes((0.05, 0.2, 0.2, 0.35)),
20+
('Check 1', 'Check 2', 'Check 3'),
21+
orientation='vertical')
1922

2023
# Alternatively, a horizontal orientation may be used:
2124
rbh = RadioButtons(fig.add_axes((0.3, 0.6, 0.6, 0.35)),
2225
('Radio 1', 'Radio 2', 'Radio 3'),
2326
orientation='horizontal')
27+
cbh = CheckButtons(fig.add_axes((0.3, 0.2, 0.6, 0.35)),
28+
('Check 1', 'Check 2', 'Check 3'),
29+
orientation='horizontal')

lib/matplotlib/tests/test_widgets.py

+41-13
Original file line numberDiff line numberDiff line change
@@ -1041,9 +1041,11 @@ def test_lasso_set_props(ax):
10411041
assert line.get_alpha() == 0.3
10421042

10431043

1044-
def test_CheckButtons(ax):
1044+
@pytest.mark.parametrize('orientation', ['vertical', 'horizontal'])
1045+
def test_CheckButtons(ax, orientation):
10451046
labels = ('a', 'b', 'c')
1046-
check = widgets.CheckButtons(ax, labels, (True, False, True))
1047+
check = widgets.CheckButtons(ax, labels, (True, False, True),
1048+
orientation=orientation)
10471049
assert check.get_status() == [True, False, True]
10481050
check.set_active(0)
10491051
assert check.get_status() == [False, False, True]
@@ -1110,28 +1112,54 @@ def test_RadioButtons(ax, orientation):
11101112

11111113
@image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True)
11121114
def test_check_radio_buttons_image():
1113-
ax = get_ax()
1114-
fig = ax.figure
1115-
fig.subplots_adjust(left=0.3)
1115+
fig = plt.figure()
1116+
1117+
rb1 = widgets.RadioButtons(fig.add_axes((0.05, 0.7, 0.2, 0.15)),
1118+
('Radio 1', 'Radio 2', 'Radio 3'))
11161119

1117-
rax1 = fig.add_axes((0.05, 0.7, 0.2, 0.15))
1118-
rb1 = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3'))
1120+
rb2 = widgets.RadioButtons(fig.add_axes((0.3, 0.7, 0.6, 0.15)),
1121+
('Radio 1', 'Radio 2', 'Radio 3'),
1122+
orientation='horizontal')
11191123

1120-
rax2 = fig.add_axes((0.05, 0.5, 0.2, 0.15))
1121-
cb1 = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'),
1124+
cb1 = widgets.CheckButtons(fig.add_axes((0.05, 0.5, 0.2, 0.15)),
1125+
('Check 1', 'Check 2', 'Check 3'),
11221126
(False, True, True))
11231127

1124-
rax3 = fig.add_axes((0.05, 0.3, 0.2, 0.15))
1128+
cb2 = widgets.CheckButtons(fig.add_axes((0.3, 0.5, 0.6, 0.15)),
1129+
('Check 1', 'Check 2', 'Check 3'),
1130+
(False, True, True),
1131+
orientation='horizontal')
1132+
11251133
rb3 = widgets.RadioButtons(
1126-
rax3, ('Radio 1', 'Radio 2', 'Radio 3'),
1134+
fig.add_axes((0.05, 0.3, 0.2, 0.15)),
1135+
('Radio 1', 'Radio 2', 'Radio 3'),
1136+
label_props={'fontsize': [8, 12, 16],
1137+
'color': ['red', 'green', 'blue']},
1138+
radio_props={'edgecolor': ['red', 'green', 'blue'],
1139+
'facecolor': ['mistyrose', 'palegreen', 'lightblue']})
1140+
1141+
rb4 = widgets.RadioButtons(
1142+
fig.add_axes((0.3, 0.3, 0.6, 0.15)),
1143+
('Radio 1', 'Radio 2', 'Radio 3'),
1144+
orientation='horizontal',
11271145
label_props={'fontsize': [8, 12, 16],
11281146
'color': ['red', 'green', 'blue']},
11291147
radio_props={'edgecolor': ['red', 'green', 'blue'],
11301148
'facecolor': ['mistyrose', 'palegreen', 'lightblue']})
11311149

1132-
rax4 = fig.add_axes((0.05, 0.1, 0.2, 0.15))
1150+
cb3 = widgets.CheckButtons(
1151+
fig.add_axes((0.05, 0.1, 0.2, 0.15)),
1152+
('Check 1', 'Check 2', 'Check 3'), (False, True, True),
1153+
label_props={'fontsize': [8, 12, 16],
1154+
'color': ['red', 'green', 'blue']},
1155+
frame_props={'edgecolor': ['red', 'green', 'blue'],
1156+
'facecolor': ['mistyrose', 'palegreen', 'lightblue']},
1157+
check_props={'color': ['red', 'green', 'blue']})
1158+
11331159
cb4 = widgets.CheckButtons(
1134-
rax4, ('Check 1', 'Check 2', 'Check 3'), (False, True, True),
1160+
fig.add_axes((0.3, 0.1, 0.6, 0.15)),
1161+
('Check 1', 'Check 2', 'Check 3'), (False, True, True),
1162+
orientation='horizontal',
11351163
label_props={'fontsize': [8, 12, 16],
11361164
'color': ['red', 'green', 'blue']},
11371165
frame_props={'edgecolor': ['red', 'green', 'blue'],

lib/matplotlib/widgets.py

+29-7
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,8 @@ class CheckButtons(AxesWidget):
10121012
"""
10131013

10141014
def __init__(self, ax, labels, actives=None, *, useblit=True,
1015-
label_props=None, frame_props=None, check_props=None):
1015+
label_props=None, frame_props=None, check_props=None,
1016+
orientation='vertical'):
10161017
"""
10171018
Add check buttons to `~.axes.Axes` instance *ax*.
10181019
@@ -1047,9 +1048,15 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
10471048
black color, and 1.0 linewidth.
10481049
10491050
.. versionadded:: 3.7
1051+
orientation : {'vertical', 'horizontal'}
1052+
The orientation of the buttons: 'vertical' places buttons from top
1053+
to bottom, 'horizontal' places buttons from left to right.
1054+
1055+
.. versionadded:: 3.9
10501056
"""
10511057
super().__init__(ax)
10521058

1059+
_api.check_in_list(['vertical', 'horizontal'], orientation=orientation)
10531060
_api.check_isinstance((dict, None), label_props=label_props,
10541061
frame_props=frame_props, check_props=check_props)
10551062

@@ -1063,14 +1070,29 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
10631070
self._useblit = useblit and self.canvas.supports_blit
10641071
self._background = None
10651072

1066-
ys = np.linspace(1, 0, len(labels)+2)[1:-1]
1073+
if orientation == 'vertical':
1074+
# Place buttons from top to bottom with buttons at (0.15, y) and labels
1075+
# at (0.25, y), where y is evenly spaced within the Axes.
1076+
button_ys = label_ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
1077+
button_xs = np.full_like(button_ys, 0.15)
1078+
label_xs = np.full_like(label_ys, 0.25)
1079+
label_ha = 'left'
1080+
label_va = 'center'
1081+
else:
1082+
# Place buttons from left to right with buttons at (x, 0.15) and labels
1083+
# at (x, 0.25), where x is evenly spaced within the Axes.
1084+
button_xs = label_xs = np.linspace(0, 1, len(labels) + 2)[1:-1]
1085+
button_ys = np.full_like(button_xs, 0.15)
1086+
label_ys = np.full_like(label_xs, 0.25)
1087+
label_ha = 'center'
1088+
label_va = 'bottom'
10671089

10681090
label_props = _expand_text_props(label_props)
10691091
self.labels = [
1070-
ax.text(0.25, y, label, transform=ax.transAxes,
1071-
horizontalalignment="left", verticalalignment="center",
1092+
ax.text(x, y, label, transform=ax.transAxes,
1093+
horizontalalignment=label_ha, verticalalignment=label_va,
10721094
**props)
1073-
for y, label, props in zip(ys, labels, label_props)]
1095+
for x, y, label, props in zip(label_xs, label_ys, labels, label_props)]
10741096
text_size = np.array([text.get_fontsize() for text in self.labels]) / 2
10751097

10761098
frame_props = {
@@ -1082,7 +1104,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
10821104
}
10831105
frame_props.setdefault('facecolor', frame_props.get('color', 'none'))
10841106
frame_props.setdefault('edgecolor', frame_props.pop('color', 'black'))
1085-
self._frames = ax.scatter([0.15] * len(ys), ys, **frame_props)
1107+
self._frames = ax.scatter(button_xs, button_ys, **frame_props)
10861108
check_props = {
10871109
'linewidth': 1,
10881110
's': text_size**2,
@@ -1092,7 +1114,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
10921114
'animated': self._useblit,
10931115
}
10941116
check_props.setdefault('facecolor', check_props.pop('color', 'black'))
1095-
self._checks = ax.scatter([0.15] * len(ys), ys, **check_props)
1117+
self._checks = ax.scatter(button_xs, button_ys, **check_props)
10961118
# The user may have passed custom colours in check_props, so we need to
10971119
# create the checks (above), and modify the visibility after getting
10981120
# whatever the user set.

lib/matplotlib/widgets.pyi

+1
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class CheckButtons(AxesWidget):
156156
label_props: dict[str, Any] | None = ...,
157157
frame_props: dict[str, Any] | None = ...,
158158
check_props: dict[str, Any] | None = ...,
159+
orientation: Literal["vertical", "horizontal"] = ...,
159160
) -> None: ...
160161
def set_label_props(self, props: dict[str, Any]) -> None: ...
161162
def set_frame_props(self, props: dict[str, Any]) -> None: ...

0 commit comments

Comments
 (0)