From b82537a644590c0eccf2e3d8e202307d96bfbdba Mon Sep 17 00:00:00 2001 From: mromanie Date: Wed, 6 Feb 2019 09:18:41 +0100 Subject: [PATCH] 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. --- .../next_whats_new/widget_horizontal.rst | 23 ++++++++++++ lib/matplotlib/tests/test_widgets.py | 6 ++-- lib/matplotlib/widgets.py | 35 +++++++++++++++---- lib/matplotlib/widgets.pyi | 1 + 4 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 doc/users/next_whats_new/widget_horizontal.rst diff --git a/doc/users/next_whats_new/widget_horizontal.rst b/doc/users/next_whats_new/widget_horizontal.rst new file mode 100644 index 000000000000..8785ed191bb7 --- /dev/null +++ b/doc/users/next_whats_new/widget_horizontal.rst @@ -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') diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index d559ad99ef0f..339a19919445 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -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 diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 5e4c52ed2478..2c5cfad2fcc9 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -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`. @@ -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. + + .. versionadded:: 3.10 """ 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) @@ -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 = { @@ -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. diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index f5de6cb62414..5b0eb5b2fdcd 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -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: ...