diff --git a/doc/users/next_whats_new/widget_horizontal.rst b/doc/users/next_whats_new/widget_horizontal.rst index 51a8e177a1ae..1d8b356cb88f 100644 --- a/doc/users/next_whats_new/widget_horizontal.rst +++ b/doc/users/next_whats_new/widget_horizontal.rst @@ -1,14 +1,14 @@ -RadioButtons widget may now be laid out horizontally -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +CheckButtons / RadioButtons widget may now be laid out horizontally +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The `.RadioButtons` widget's primary layout direction may now be specified with -the *orientation* keyword argument: +The `.CheckButtons` and `.RadioButtons` widget's primary layout direction may +now be specified with the *orientation* keyword argument: .. plot:: :include-source: import matplotlib.pyplot as plt - from matplotlib.widgets import RadioButtons + from matplotlib.widgets import CheckButtons, RadioButtons fig = plt.figure(figsize=(4, 2)) @@ -16,8 +16,14 @@ the *orientation* keyword argument: rbv = RadioButtons(fig.add_axes((0.05, 0.6, 0.2, 0.35)), ('Radio 1', 'Radio 2', 'Radio 3'), orientation='vertical') + cbv = CheckButtons(fig.add_axes((0.05, 0.2, 0.2, 0.35)), + ('Check 1', 'Check 2', 'Check 3'), + orientation='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'), orientation='horizontal') + cbh = CheckButtons(fig.add_axes((0.3, 0.2, 0.6, 0.35)), + ('Check 1', 'Check 2', 'Check 3'), + orientation='horizontal') diff --git a/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png index f0d5023008ca..2576dacc6bcd 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png and b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png differ diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index f252342c57ab..5217e749eed4 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1041,9 +1041,11 @@ def test_lasso_set_props(ax): assert line.get_alpha() == 0.3 -def test_CheckButtons(ax): +@pytest.mark.parametrize('orientation', ['vertical', 'horizontal']) +def test_CheckButtons(ax, orientation): labels = ('a', 'b', 'c') - check = widgets.CheckButtons(ax, labels, (True, False, True)) + check = widgets.CheckButtons(ax, labels, (True, False, True), + orientation=orientation) assert check.get_status() == [True, False, True] check.set_active(0) assert check.get_status() == [False, False, True] @@ -1110,28 +1112,54 @@ def test_RadioButtons(ax, orientation): @image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True) def test_check_radio_buttons_image(): - ax = get_ax() - fig = ax.figure - fig.subplots_adjust(left=0.3) + fig = plt.figure() + + rb1 = widgets.RadioButtons(fig.add_axes((0.05, 0.7, 0.2, 0.15)), + ('Radio 1', 'Radio 2', 'Radio 3')) - rax1 = fig.add_axes((0.05, 0.7, 0.2, 0.15)) - rb1 = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3')) + rb2 = widgets.RadioButtons(fig.add_axes((0.3, 0.7, 0.6, 0.15)), + ('Radio 1', 'Radio 2', 'Radio 3'), + orientation='horizontal') - rax2 = fig.add_axes((0.05, 0.5, 0.2, 0.15)) - cb1 = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), + cb1 = widgets.CheckButtons(fig.add_axes((0.05, 0.5, 0.2, 0.15)), + ('Check 1', 'Check 2', 'Check 3'), (False, True, True)) - rax3 = fig.add_axes((0.05, 0.3, 0.2, 0.15)) + cb2 = widgets.CheckButtons(fig.add_axes((0.3, 0.5, 0.6, 0.15)), + ('Check 1', 'Check 2', 'Check 3'), + (False, True, True), + orientation='horizontal') + rb3 = widgets.RadioButtons( - rax3, ('Radio 1', 'Radio 2', 'Radio 3'), + fig.add_axes((0.05, 0.3, 0.2, 0.15)), + ('Radio 1', 'Radio 2', 'Radio 3'), + label_props={'fontsize': [8, 12, 16], + 'color': ['red', 'green', 'blue']}, + radio_props={'edgecolor': ['red', 'green', 'blue'], + 'facecolor': ['mistyrose', 'palegreen', 'lightblue']}) + + rb4 = widgets.RadioButtons( + fig.add_axes((0.3, 0.3, 0.6, 0.15)), + ('Radio 1', 'Radio 2', 'Radio 3'), + orientation='horizontal', label_props={'fontsize': [8, 12, 16], 'color': ['red', 'green', 'blue']}, radio_props={'edgecolor': ['red', 'green', 'blue'], 'facecolor': ['mistyrose', 'palegreen', 'lightblue']}) - rax4 = fig.add_axes((0.05, 0.1, 0.2, 0.15)) + cb3 = widgets.CheckButtons( + fig.add_axes((0.05, 0.1, 0.2, 0.15)), + ('Check 1', 'Check 2', 'Check 3'), (False, True, True), + label_props={'fontsize': [8, 12, 16], + 'color': ['red', 'green', 'blue']}, + frame_props={'edgecolor': ['red', 'green', 'blue'], + 'facecolor': ['mistyrose', 'palegreen', 'lightblue']}, + check_props={'color': ['red', 'green', 'blue']}) + cb4 = widgets.CheckButtons( - rax4, ('Check 1', 'Check 2', 'Check 3'), (False, True, True), + fig.add_axes((0.3, 0.1, 0.6, 0.15)), + ('Check 1', 'Check 2', 'Check 3'), (False, True, True), + orientation='horizontal', label_props={'fontsize': [8, 12, 16], 'color': ['red', 'green', 'blue']}, frame_props={'edgecolor': ['red', 'green', 'blue'], diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 4d8b080607bf..5a600a19187c 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1012,7 +1012,8 @@ class CheckButtons(AxesWidget): """ def __init__(self, ax, labels, actives=None, *, useblit=True, - label_props=None, frame_props=None, check_props=None): + label_props=None, frame_props=None, check_props=None, + orientation='vertical'): """ Add check buttons to `~.axes.Axes` instance *ax*. @@ -1047,9 +1048,15 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, black color, and 1.0 linewidth. .. versionadded:: 3.7 + orientation : {'vertical', 'horizontal'} + The orientation of the buttons: 'vertical' places buttons from top + to bottom, 'horizontal' places buttons from left to right. + + .. versionadded:: 3.9 """ super().__init__(ax) + _api.check_in_list(['vertical', 'horizontal'], orientation=orientation) _api.check_isinstance((dict, None), label_props=label_props, frame_props=frame_props, check_props=check_props) @@ -1063,14 +1070,29 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, self._useblit = useblit and self.canvas.supports_blit self._background = None - ys = np.linspace(1, 0, len(labels)+2)[1:-1] + if orientation == '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' 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 frame_props = { @@ -1082,7 +1104,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, } frame_props.setdefault('facecolor', frame_props.get('color', 'none')) frame_props.setdefault('edgecolor', frame_props.pop('color', 'black')) - self._frames = ax.scatter([0.15] * len(ys), ys, **frame_props) + self._frames = ax.scatter(button_xs, button_ys, **frame_props) check_props = { 'linewidth': 1, 's': text_size**2, @@ -1092,7 +1114,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, 'animated': self._useblit, } check_props.setdefault('facecolor', check_props.pop('color', 'black')) - self._checks = ax.scatter([0.15] * len(ys), ys, **check_props) + self._checks = ax.scatter(button_xs, button_ys, **check_props) # The user may have passed custom colours in check_props, so we need to # create the checks (above), and modify the visibility after getting # whatever the user set.