Skip to content

Commit b82537a

Browse files
mromanieQuLogic
authored andcommitted
Add possibility to have horizontal RadioButtons
A new optional keyword argument 'layout_direction' is introduced to control it. The new parameter defaults to 'vertical' for backwards compatibility.
1 parent 436a12a commit b82537a

File tree

4 files changed

+57
-8
lines changed

4 files changed

+57
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
RadioButtons widget may now be laid out horizontally
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The `.RadioButtons` widget's primary layout direction may now be specified with
5+
the *layout_direction* keyword argument:
6+
7+
.. plot::
8+
:include-source:
9+
10+
import matplotlib.pyplot as plt
11+
from matplotlib.widgets import RadioButtons
12+
13+
fig = plt.figure(figsize=(4, 2))
14+
15+
# Default orientation is vertical:
16+
rbv = RadioButtons(fig.add_axes((0.05, 0.6, 0.2, 0.35)),
17+
('Radio 1', 'Radio 2', 'Radio 3'),
18+
layout_direction='vertical')
19+
20+
# Alternatively, a horizontal orientation may be used:
21+
rbh = RadioButtons(fig.add_axes((0.3, 0.6, 0.6, 0.35)),
22+
('Radio 1', 'Radio 2', 'Radio 3'),
23+
layout_direction='horizontal')

lib/matplotlib/tests/test_widgets.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1097,8 +1097,10 @@ def test_TextBox(ax, toolbar):
10971097
assert text_change_event.call_count == 3
10981098

10991099

1100-
def test_RadioButtons(ax):
1101-
radio = widgets.RadioButtons(ax, ('Radio 1', 'Radio 2', 'Radio 3'))
1100+
@pytest.mark.parametrize('layout_direction', ['vertical', 'horizontal'])
1101+
def test_RadioButtons(ax, layout_direction):
1102+
radio = widgets.RadioButtons(ax, ('Radio 1', 'Radio 2', 'Radio 3'),
1103+
layout_direction=layout_direction)
11021104
radio.set_active(1)
11031105
assert radio.value_selected == 'Radio 2'
11041106
assert radio.index_selected == 1

lib/matplotlib/widgets.py

+29-6
Original file line numberDiff line numberDiff line change
@@ -1557,7 +1557,8 @@ class RadioButtons(AxesWidget):
15571557
"""
15581558

15591559
def __init__(self, ax, labels, active=0, activecolor=None, *,
1560-
useblit=True, label_props=None, radio_props=None):
1560+
useblit=True, label_props=None, radio_props=None,
1561+
layout_direction='vertical'):
15611562
"""
15621563
Add radio buttons to an `~.axes.Axes`.
15631564
@@ -1593,9 +1594,16 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
15931594
button.
15941595
15951596
.. versionadded:: 3.7
1597+
layout_direction : {'vertical', 'horizontal'}
1598+
The orientation of the buttons: 'vertical' places buttons from top
1599+
to bottom, 'horizontal' places buttons from left to right.
1600+
1601+
.. versionadded:: 3.10
15961602
"""
15971603
super().__init__(ax)
15981604

1605+
_api.check_in_list(['vertical', 'horizontal'],
1606+
layout_direction=layout_direction)
15991607
_api.check_isinstance((dict, None), label_props=label_props,
16001608
radio_props=radio_props)
16011609

@@ -1619,17 +1627,32 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16191627
ax.set_yticks([])
16201628
ax.set_navigate(False)
16211629

1622-
ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
1630+
if layout_direction == 'vertical':
1631+
# Place buttons from top to bottom with buttons at (0.15, y) and labels
1632+
# at (0.25, y), where y is evenly spaced within the Axes.
1633+
button_ys = label_ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
1634+
button_xs = np.full_like(button_ys, 0.15)
1635+
label_xs = np.full_like(label_ys, 0.25)
1636+
label_ha = 'left'
1637+
label_va = 'center'
1638+
else:
1639+
# Place buttons from left to right with buttons at (x, 0.15) and labels
1640+
# at (x, 0.25), where x is evenly spaced within the Axes.
1641+
button_xs = label_xs = np.linspace(0, 1, len(labels) + 2)[1:-1]
1642+
button_ys = np.full_like(button_xs, 0.15)
1643+
label_ys = np.full_like(label_xs, 0.25)
1644+
label_ha = 'center'
1645+
label_va = 'bottom'
16231646

16241647
self._useblit = useblit and self.canvas.supports_blit
16251648
self._background = None
16261649

16271650
label_props = _expand_text_props(label_props)
16281651
self.labels = [
1629-
ax.text(0.25, y, label, transform=ax.transAxes,
1630-
horizontalalignment="left", verticalalignment="center",
1652+
ax.text(x, y, label, transform=ax.transAxes,
1653+
horizontalalignment=label_ha, verticalalignment=label_va,
16311654
**props)
1632-
for y, label, props in zip(ys, labels, label_props)]
1655+
for x, y, label, props in zip(label_xs, label_ys, labels, label_props)]
16331656
text_size = np.array([text.get_fontsize() for text in self.labels]) / 2
16341657

16351658
radio_props = {
@@ -1642,7 +1665,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16421665
radio_props.setdefault('edgecolor', radio_props.get('color', 'black'))
16431666
radio_props.setdefault('facecolor',
16441667
radio_props.pop('color', activecolor))
1645-
self._buttons = ax.scatter([.15] * len(ys), ys, **radio_props)
1668+
self._buttons = ax.scatter(button_xs, button_ys, **radio_props)
16461669
# The user may have passed custom colours in radio_props, so we need to
16471670
# create the radios, and modify the visibility after getting whatever
16481671
# the user set.

lib/matplotlib/widgets.pyi

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ class RadioButtons(AxesWidget):
210210
useblit: bool = ...,
211211
label_props: dict[str, Any] | Sequence[dict[str, Any]] | None = ...,
212212
radio_props: dict[str, Any] | None = ...,
213+
layout_direction: Literal["vertical", "horizontal"] = ...,
213214
) -> None: ...
214215
def set_label_props(self, props: dict[str, Any]) -> None: ...
215216
def set_radio_props(self, props: dict[str, Any]) -> None: ...

0 commit comments

Comments
 (0)