Skip to content

Horizontal radio buttons #13374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions doc/users/next_whats_new/widget_horizontal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
RadioButtons widget may now be laid out horizontally
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The `.RadioButtons` widget's primary layout direction may now be specified with
the *layout_direction* keyword argument:

.. plot::
:include-source:

import matplotlib.pyplot as plt
from matplotlib.widgets import RadioButtons

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

# Default orientation is vertical:
rbv = RadioButtons(fig.add_axes((0.05, 0.6, 0.2, 0.35)),
('Radio 1', 'Radio 2', 'Radio 3'),
layout_direction='vertical')

# Alternatively, a horizontal orientation may be used:
rbh = RadioButtons(fig.add_axes((0.3, 0.6, 0.6, 0.35)),
('Radio 1', 'Radio 2', 'Radio 3'),
layout_direction='horizontal')
6 changes: 4 additions & 2 deletions lib/matplotlib/tests/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1097,8 +1097,10 @@ def test_TextBox(ax, toolbar):
assert text_change_event.call_count == 3


def test_RadioButtons(ax):
radio = widgets.RadioButtons(ax, ('Radio 1', 'Radio 2', 'Radio 3'))
@pytest.mark.parametrize('layout_direction', ['vertical', 'horizontal'])
def test_RadioButtons(ax, layout_direction):
radio = widgets.RadioButtons(ax, ('Radio 1', 'Radio 2', 'Radio 3'),
layout_direction=layout_direction)
radio.set_active(1)
assert radio.value_selected == 'Radio 2'
assert radio.index_selected == 1
Expand Down
35 changes: 29 additions & 6 deletions lib/matplotlib/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1557,7 +1557,8 @@ class RadioButtons(AxesWidget):
"""

def __init__(self, ax, labels, active=0, activecolor=None, *,
useblit=True, label_props=None, radio_props=None):
useblit=True, label_props=None, radio_props=None,
layout_direction='vertical'):
"""
Add radio buttons to an `~.axes.Axes`.

Expand Down Expand Up @@ -1593,9 +1594,16 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
button.

.. versionadded:: 3.7
layout_direction : {'vertical', 'horizontal'}
The orientation of the buttons: 'vertical' places buttons from top
to bottom, 'horizontal' places buttons from left to right.
Copy link
Member

@timhoffm timhoffm Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
to bottom, 'horizontal' places buttons from left to right.
to bottom, 'horizontal' places buttons from left to right, distributing
the buttons across the whole Axes. The Axes is divided into equal-sized
boxes, and each button is left-aligned in the box. There is currently no
size check with the associated labels, so that long labels may overlap
with the next box.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I did not check what this actually does:

grafik

I strongly think we should follow standard conventions that labels are right of the button not above, because I consider the unusual layout a UX issue. See also #27946 (comment).

grafik


.. versionadded:: 3.10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.. versionadded:: 3.10
.. versionadded:: 3.11

"""
super().__init__(ax)

_api.check_in_list(['vertical', 'horizontal'],
layout_direction=layout_direction)
_api.check_isinstance((dict, None), label_props=label_props,
radio_props=radio_props)

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

ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
if layout_direction == 'vertical':
# Place buttons from top to bottom with buttons at (0.15, y) and labels
# at (0.25, y), where y is evenly spaced within the Axes.
button_ys = label_ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
button_xs = np.full_like(button_ys, 0.15)
label_xs = np.full_like(label_ys, 0.25)
label_ha = 'left'
label_va = 'center'
else:
# Place buttons from left to right with buttons at (x, 0.15) and labels
# at (x, 0.25), where x is evenly spaced within the Axes.
button_xs = label_xs = np.linspace(0, 1, len(labels) + 2)[1:-1]
button_ys = np.full_like(button_xs, 0.15)
label_ys = np.full_like(label_xs, 0.25)
label_ha = 'center'
label_va = 'bottom'

self._useblit = useblit and self.canvas.supports_blit
self._background = None

label_props = _expand_text_props(label_props)
self.labels = [
ax.text(0.25, y, label, transform=ax.transAxes,
horizontalalignment="left", verticalalignment="center",
ax.text(x, y, label, transform=ax.transAxes,
horizontalalignment=label_ha, verticalalignment=label_va,
**props)
for y, label, props in zip(ys, labels, label_props)]
for x, y, label, props in zip(label_xs, label_ys, labels, label_props)]
text_size = np.array([text.get_fontsize() for text in self.labels]) / 2

radio_props = {
Expand All @@ -1642,7 +1665,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
radio_props.setdefault('edgecolor', radio_props.get('color', 'black'))
radio_props.setdefault('facecolor',
radio_props.pop('color', activecolor))
self._buttons = ax.scatter([.15] * len(ys), ys, **radio_props)
self._buttons = ax.scatter(button_xs, button_ys, **radio_props)
# The user may have passed custom colours in radio_props, so we need to
# create the radios, and modify the visibility after getting whatever
# the user set.
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/widgets.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ class RadioButtons(AxesWidget):
useblit: bool = ...,
label_props: dict[str, Any] | Sequence[dict[str, Any]] | None = ...,
radio_props: dict[str, Any] | None = ...,
layout_direction: Literal["vertical", "horizontal"] = ...,
) -> None: ...
def set_label_props(self, props: dict[str, Any]) -> None: ...
def set_radio_props(self, props: dict[str, Any]) -> None: ...
Expand Down
Loading