From 1a31f4f1b3b6b03ef39f8c90ee883b67919aae1e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 29 Dec 2022 02:55:42 -0500 Subject: [PATCH 1/4] Add styling support to Check and Radio buttons --- examples/widgets/check_buttons.py | 12 +- examples/widgets/radio_buttons.py | 20 +- .../test_widgets/check_radio_buttons.png | Bin 14567 -> 24315 bytes lib/matplotlib/tests/test_widgets.py | 75 ++++- lib/matplotlib/widgets.py | 269 +++++++++++++++--- 5 files changed, 311 insertions(+), 65 deletions(-) diff --git a/examples/widgets/check_buttons.py b/examples/widgets/check_buttons.py index d5894fdad705..934e0ecfefb2 100644 --- a/examples/widgets/check_buttons.py +++ b/examples/widgets/check_buttons.py @@ -20,19 +20,23 @@ s2 = np.sin(6*np.pi*t) fig, ax = plt.subplots() -l0, = ax.plot(t, s0, visible=False, lw=2, color='k', label='2 Hz') -l1, = ax.plot(t, s1, lw=2, color='r', label='4 Hz') -l2, = ax.plot(t, s2, lw=2, color='g', label='6 Hz') +l0, = ax.plot(t, s0, visible=False, lw=2, color='black', label='1 Hz') +l1, = ax.plot(t, s1, lw=2, color='red', label='2 Hz') +l2, = ax.plot(t, s2, lw=2, color='green', label='3 Hz') fig.subplots_adjust(left=0.2) lines_by_label = {l.get_label(): l for l in [l0, l1, l2]} +line_colors = [l.get_color() for l in lines_by_label.values()] # Make checkbuttons with all plotted lines with correct visibility rax = fig.add_axes([0.05, 0.4, 0.1, 0.15]) check = CheckButtons( ax=rax, labels=lines_by_label.keys(), - actives=[l.get_visible() for l in lines_by_label.values()] + actives=[l.get_visible() for l in lines_by_label.values()], + label_props={'color': line_colors}, + frame_props={'edgecolor': line_colors}, + check_props={'facecolor': line_colors}, ) diff --git a/examples/widgets/radio_buttons.py b/examples/widgets/radio_buttons.py index c1600b369be7..8180ab8b9d42 100644 --- a/examples/widgets/radio_buttons.py +++ b/examples/widgets/radio_buttons.py @@ -25,23 +25,31 @@ axcolor = 'lightgoldenrodyellow' rax = fig.add_axes([0.05, 0.7, 0.15, 0.15], facecolor=axcolor) -radio = RadioButtons(rax, ('2 Hz', '4 Hz', '8 Hz')) +radio = RadioButtons(rax, ('1 Hz', '2 Hz', '4 Hz'), + label_props={'color': 'cmy', 'fontsize': [12, 14, 16]}, + radio_props={'s': [16, 32, 64]}) def hzfunc(label): - hzdict = {'2 Hz': s0, '4 Hz': s1, '8 Hz': s2} + hzdict = {'1 Hz': s0, '2 Hz': s1, '4 Hz': s2} ydata = hzdict[label] l.set_ydata(ydata) - plt.draw() + fig.canvas.draw() radio.on_clicked(hzfunc) rax = fig.add_axes([0.05, 0.4, 0.15, 0.15], facecolor=axcolor) -radio2 = RadioButtons(rax, ('red', 'blue', 'green')) +radio2 = RadioButtons( + rax, ('red', 'blue', 'green'), + label_props={'color': ['red', 'blue', 'green']}, + radio_props={ + 'facecolor': ['red', 'blue', 'green'], + 'edgecolor': ['darkred', 'darkblue', 'darkgreen'], + }) def colorfunc(label): l.set_color(label) - plt.draw() + fig.canvas.draw() radio2.on_clicked(colorfunc) rax = fig.add_axes([0.05, 0.1, 0.15, 0.15], facecolor=axcolor) @@ -50,7 +58,7 @@ def colorfunc(label): def stylefunc(label): l.set_linestyle(label) - plt.draw() + fig.canvas.draw() radio3.on_clicked(stylefunc) plt.show() 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 e96085d9bffd7a38297b14b2011a7061beb2a287..d6e6004a1732e7f6c449ce3ce7cc87b70743acc7 100644 GIT binary patch literal 24315 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 z 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 RQh=I=7#W%vR9tX;@NW~Cpx*!h literal 14567 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 diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 4c4f2fdc240f..36445d1469b7 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -992,21 +992,38 @@ def test_TextBox(ax, toolbar): @image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True) def test_check_radio_buttons_image(): ax = get_ax() - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 + fig = ax.figure + fig.subplots_adjust(left=0.3) - plt.subplots_adjust(left=0.3) - rax1 = plt.axes([0.05, 0.7, 0.15, 0.15]) - rax2 = plt.axes([0.05, 0.2, 0.15, 0.15]) - rb = widgets.RadioButtons(rax1, ('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')) with pytest.warns(DeprecationWarning, match='The circles attribute was deprecated'): - rb.circles # Trigger the old-style elliptic radiobuttons. - cb = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), - (False, True, True)) + rb1.circles # Trigger the old-style elliptic radiobuttons. + + rax2 = fig.add_axes([0.05, 0.5, 0.2, 0.15]) + cb1 = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), + (False, True, True)) with pytest.warns(DeprecationWarning, match='The rectangles attribute was deprecated'): - cb.rectangles # Trigger old-style Rectangle check boxes + cb1.rectangles # Trigger old-style Rectangle check boxes + + rax3 = fig.add_axes([0.05, 0.3, 0.2, 0.15]) + rb3 = widgets.RadioButtons( + rax3, ('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']}) + + rax4 = fig.add_axes([0.05, 0.1, 0.2, 0.15]) + cb4 = widgets.CheckButtons( + rax4, ('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']}) @check_figures_equal(extensions=["png"]) @@ -1019,6 +1036,21 @@ def test_radio_buttons(fig_test, fig_ref): ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") +@check_figures_equal(extensions=['png']) +def test_radio_buttons_props(fig_test, fig_ref): + label_props = {'color': ['red'], 'fontsize': [24]} + radio_props = {'facecolor': 'green', 'edgecolor': 'blue', 'linewidth': 2} + + widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'], + label_props=label_props, radio_props=radio_props) + + cb = widgets.RadioButtons(fig_test.subplots(), ['tea', 'coffee']) + cb.set_label_props(label_props) + # Setting the label size automatically increases default marker size, so we + # need to do that here as well. + cb.set_radio_props({**radio_props, 's': (24 / 2)**2}) + + @check_figures_equal(extensions=["png"]) def test_check_buttons(fig_test, fig_ref): widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True]) @@ -1031,6 +1063,29 @@ def test_check_buttons(fig_test, fig_ref): ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") +@check_figures_equal(extensions=['png']) +def test_check_button_props(fig_test, fig_ref): + label_props = {'color': ['red'], 'fontsize': [24]} + frame_props = {'facecolor': 'green', 'edgecolor': 'blue', 'linewidth': 2} + check_props = {'facecolor': 'red', 'linewidth': 2} + + widgets.CheckButtons(fig_ref.subplots(), ['tea', 'coffee'], [True, True], + label_props=label_props, frame_props=frame_props, + check_props=check_props) + + cb = widgets.CheckButtons(fig_test.subplots(), ['tea', 'coffee'], + [True, True]) + cb.set_label_props(label_props) + # Setting the label size automatically increases default marker size, so we + # need to do that here as well. + cb.set_frame_props({**frame_props, 's': (24 / 2)**2}) + # FIXME: Axes.scatter promotes facecolor to edgecolor on unfilled markers, + # but Collection.update doesn't do that (it forgot the marker already). + # This means we cannot pass facecolor to both setters directly. + check_props['edgecolor'] = check_props.pop('facecolor') + cb.set_check_props({**check_props, 's': (24 / 2)**2}) + + @check_figures_equal(extensions=["png"]) def test_check_buttons_rectangles(fig_test, fig_ref): # Test should be removed once .rectangles is removed diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 1dc1dd5f77ae..bdcee1d21ca7 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -11,13 +11,15 @@ from contextlib import ExitStack import copy +import itertools from numbers import Integral, Number +from cycler import cycler import numpy as np import matplotlib as mpl -from . import (_api, _docstring, backend_tools, cbook, colors, ticker, - transforms) +from . import (_api, _docstring, backend_tools, cbook, collections, colors, + text as mtext, ticker, transforms) from .lines import Line2D from .patches import Circle, Rectangle, Ellipse, Polygon from .transforms import TransformedPatchPath, Affine2D @@ -966,6 +968,11 @@ def on_changed(self, func): return self._observers.connect('changed', lambda val: func(val)) +def _expand_text_props(props): + props = cbook.normalize_kwargs(props, mtext.Text) + return cycler(**props)() if props else itertools.repeat({}) + + class CheckButtons(AxesWidget): r""" A GUI neutral set of check buttons. @@ -989,7 +996,8 @@ class CheckButtons(AxesWidget): each box, but have ``set_visible(False)`` when its box is not checked. """ - def __init__(self, ax, labels, actives=None, *, useblit=True): + def __init__(self, ax, labels, actives=None, *, useblit=True, + label_props=None, frame_props=None, check_props=None): """ Add check buttons to `matplotlib.axes.Axes` instance *ax*. @@ -1005,9 +1013,22 @@ def __init__(self, ax, labels, actives=None, *, useblit=True): useblit : bool, default: True Use blitting for faster drawing if supported by the backend. See the tutorial :doc:`/tutorials/advanced/blitting` for details. + label_props : dict, optional + Dictionary of `.Text` properties to be used for the labels. + frame_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + check button frame. Defaults (label font size / 2)**2 size, black + edgecolor, no facecolor, and 1.0 linewidth. + check_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + check button check. Defaults to (label font size / 2)**2 size, + black color, and 1.0 linewidth. """ super().__init__(ax) + _api.check_isinstance((dict, None), label_props=label_props, + frame_props=frame_props, check_props=check_props) + ax.set_xticks([]) ax.set_yticks([]) ax.set_navigate(False) @@ -1019,22 +1040,39 @@ def __init__(self, ax, labels, actives=None, *, useblit=True): self._background = None ys = np.linspace(1, 0, len(labels)+2)[1:-1] - text_size = mpl.rcParams["font.size"] / 2 + label_props = _expand_text_props(label_props) self.labels = [ ax.text(0.25, y, label, transform=ax.transAxes, - horizontalalignment="left", verticalalignment="center") - for y, label in zip(ys, labels)] - - self._squares = ax.scatter( - [0.15] * len(ys), ys, marker='s', s=text_size**2, - c="none", linewidth=1, transform=ax.transAxes, edgecolor="k" - ) - self._crosses = ax.scatter( - [0.15] * len(ys), ys, marker='x', linewidth=1, s=text_size**2, - c=["k" if active else "none" for active in actives], - transform=ax.transAxes, animated=self._useblit, - ) + horizontalalignment="left", verticalalignment="center", + **props) + for y, label, props in zip(ys, labels, label_props)] + text_size = np.array([text.get_fontsize() for text in self.labels]) / 2 + + frame_props = { + 's': text_size**2, + 'linewidth': 1, + **cbook.normalize_kwargs(frame_props, collections.PathCollection), + 'marker': 's', + 'transform': ax.transAxes, + } + 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) + check_props = { + 'linewidth': 1, + 's': text_size**2, + **cbook.normalize_kwargs(check_props, collections.PathCollection), + 'marker': 'x', + 'transform': ax.transAxes, + 'animated': self._useblit, + } + check_props.setdefault('facecolor', check_props.pop('color', 'black')) + self._checks = ax.scatter([0.15] * len(ys), 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. + self._init_status(actives) self.connect_event('button_press_event', self._clicked) if self._useblit: @@ -1047,7 +1085,7 @@ def _clear(self, event): if self.ignore(event): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) - self.ax.draw_artist(self._crosses) + self.ax.draw_artist(self._checks) if hasattr(self, '_lines'): for l1, l2 in self._lines: self.ax.draw_artist(l1) @@ -1066,18 +1104,65 @@ def _clicked(self, event): and y0 <= pclicked[1] <= y0 + p.get_height())): distances[i] = np.linalg.norm(pclicked - p.get_center()) else: - _, square_inds = self._squares.contains(event) - coords = self._squares.get_offset_transform().transform( - self._squares.get_offsets() + _, frame_inds = self._frames.contains(event) + coords = self._frames.get_offset_transform().transform( + self._frames.get_offsets() ) for i, t in enumerate(self.labels): - if (i in square_inds["ind"] + if (i in frame_inds["ind"] or t.get_window_extent().contains(event.x, event.y)): distances[i] = np.linalg.norm(pclicked - coords[i]) if len(distances) > 0: closest = min(distances, key=distances.get) self.set_active(closest) + def set_label_props(self, props): + """ + Set properties of the `.Text` labels. + + Parameters + ---------- + props : dict + Dictionary of `.Text` properties to be used for the labels. + """ + _api.check_isinstance(dict, props=props) + props = _expand_text_props(props) + for text, prop in zip(self.labels, props): + text.update(prop) + + def set_frame_props(self, props): + """ + Set properties of the check button frames. + + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the check + button frames. + """ + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + self._frames.update(props) + + def set_check_props(self, props): + """ + Set properties of the check button checks. + + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the check + button check. + """ + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + actives = self.get_status() + self._checks.update(props) + # If new colours are supplied, then we must re-apply the status. + self._init_status(actives) + def set_active(self, index): """ Toggle (activate or deactivate) a check button by index. @@ -1097,15 +1182,15 @@ def set_active(self, index): if index not in range(len(self.labels)): raise ValueError(f'Invalid CheckButton index: {index}') - cross_facecolors = self._crosses.get_facecolor() - cross_facecolors[index] = colors.to_rgba( - "black" - if colors.same_color( - cross_facecolors[index], colors.to_rgba("none") - ) - else "none" + invisible = colors.to_rgba('none') + + facecolors = self._checks.get_facecolor() + facecolors[index] = ( + self._active_check_colors[index] + if colors.same_color(facecolors[index], invisible) + else invisible ) - self._crosses.set_facecolor(cross_facecolors) + self._checks.set_facecolor(facecolors) if hasattr(self, "_lines"): l1, l2 = self._lines[index] @@ -1116,7 +1201,7 @@ def set_active(self, index): if self._useblit: if self._background is not None: self.canvas.restore_region(self._background) - self.ax.draw_artist(self._crosses) + self.ax.draw_artist(self._checks) if hasattr(self, "_lines"): for l1, l2 in self._lines: self.ax.draw_artist(l1) @@ -1128,12 +1213,28 @@ def set_active(self, index): if self.eventson: self._observers.process('clicked', self.labels[index].get_text()) + def _init_status(self, actives): + """ + Initialize properties to match active status. + + The user may have passed custom colours in *check_props* to the + constructor, or to `.set_check_props`, so we need to modify the + visibility after getting whatever the user set. + """ + self._active_check_colors = self._checks.get_facecolor() + if len(self._active_check_colors) == 1: + self._active_check_colors = np.repeat(self._active_check_colors, + len(actives), axis=0) + self._checks.set_facecolor( + [ec if active else "none" + for ec, active in zip(self._active_check_colors, actives)]) + def get_status(self): """ Return a list of the status (True/False) of all of the check buttons. """ return [not colors.same_color(color, colors.to_rgba("none")) - for color in self._crosses.get_facecolors()] + for color in self._checks.get_facecolors()] def on_clicked(self, func): """ @@ -1147,7 +1248,8 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) - @_api.deprecated("3.7") + @_api.deprecated("3.7", + addendum="Any custom property styling may be lost.") @property def rectangles(self): if not hasattr(self, "_rectangles"): @@ -1162,7 +1264,7 @@ def rectangles(self): ) for i, y in enumerate(ys) ] - self._squares.set_visible(False) + self._frames.set_visible(False) for rectangle in rectangles: self.ax.add_patch(rectangle) if not hasattr(self, "_lines"): @@ -1170,12 +1272,13 @@ def rectangles(self): _ = self.lines return self._rectangles - @_api.deprecated("3.7") + @_api.deprecated("3.7", + addendum="Any custom property styling may be lost.") @property def lines(self): if not hasattr(self, "_lines"): ys = np.linspace(1, 0, len(self.labels)+2)[1:-1] - self._crosses.set_visible(False) + self._checks.set_visible(False) dy = 1. / (len(self.labels) + 1) w, h = dy / 2, dy / 2 self._lines = [] @@ -1485,7 +1588,7 @@ class RadioButtons(AxesWidget): """ def __init__(self, ax, labels, active=0, activecolor='blue', *, - useblit=True): + useblit=True, label_props=None, radio_props=None): """ Add radio buttons to an `~.axes.Axes`. @@ -1499,9 +1602,25 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, The index of the initially selected button. activecolor : color The color of the selected button. + + .. note:: + If a facecolor is supplied in *radio_props*, it will override + *activecolor*. This may be used to provide an active color per + button. useblit : bool, default: True Use blitting for faster drawing if supported by the backend. See the tutorial :doc:`/tutorials/advanced/blitting` for details. + label_props : dict or list of dict, optional + Dictionary of `.Text` properties to be used for the labels. + radio_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + radio buttons. Defaults to (label font size / 2)**2 size, black + edgecolor, and *activecolor* facecolor (when active). + + .. note:: + If a facecolor is supplied in *radio_props*, it will override + *activecolor*. This may be used to provide an active color per + button. """ super().__init__(ax) self.activecolor = activecolor @@ -1512,19 +1631,42 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, ax.set_navigate(False) ys = np.linspace(1, 0, len(labels) + 2)[1:-1] - text_size = mpl.rcParams["font.size"] / 2 self._useblit = useblit and self.canvas.supports_blit self._background = None + _api.check_isinstance((dict, None), label_props=label_props, + radio_props=radio_props) + + label_props = _expand_text_props(label_props) self.labels = [ ax.text(0.25, y, label, transform=ax.transAxes, - horizontalalignment="left", verticalalignment="center") - for y, label in zip(ys, labels)] - self._buttons = ax.scatter( - [.15] * len(ys), ys, transform=ax.transAxes, s=text_size**2, - c=[activecolor if i == active else "none" for i in range(len(ys))], - edgecolor="black", animated=self._useblit) + horizontalalignment="left", verticalalignment="center", + **props) + for y, label, props in zip(ys, labels, label_props)] + text_size = np.array([text.get_fontsize() for text in self.labels]) / 2 + + radio_props = { + 's': text_size**2, + **cbook.normalize_kwargs(radio_props, collections.PathCollection), + 'marker': 'o', + 'transform': ax.transAxes, + 'animated': self._useblit, + } + 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) + # 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. + self._active_colors = self._buttons.get_facecolor() + if len(self._active_colors) == 1: + self._active_colors = np.repeat(self._active_colors, len(labels), + axis=0) + self._buttons.set_facecolor( + [activecolor if i == active else "none" + for i, activecolor in enumerate(self._active_colors)]) self.connect_event('button_press_event', self._clicked) if self._useblit: @@ -1564,6 +1706,42 @@ def _clicked(self, event): closest = min(distances, key=distances.get) self.set_active(closest) + def set_label_props(self, props): + """ + Set properties of the `.Text` labels. + + Parameters + ---------- + props : dict + Dictionary of `.Text` properties to be used for the labels. + """ + _api.check_isinstance(dict, props=props) + props = _expand_text_props(props) + for text, prop in zip(self.labels, props): + text.update(prop) + + def set_radio_props(self, props): + """ + Set properties of the `.Text` labels. + + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the radio + buttons. + """ + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + self._buttons.update(props) + self._active_colors = self._buttons.get_facecolor() + if len(self._active_colors) == 1: + self._active_colors = np.repeat(self._active_colors, + len(self.labels), axis=0) + self._buttons.set_facecolor( + [activecolor if text.get_text() == self.value_selected else "none" + for text, activecolor in zip(self.labels, self._active_colors)]) + def set_active(self, index): """ Select button with number *index*. @@ -1575,7 +1753,7 @@ def set_active(self, index): self.value_selected = self.labels[index].get_text() button_facecolors = self._buttons.get_facecolor() button_facecolors[:] = colors.to_rgba("none") - button_facecolors[index] = colors.to_rgba(self.activecolor) + button_facecolors[index] = colors.to_rgba(self._active_colors[index]) self._buttons.set_facecolor(button_facecolors) if hasattr(self, "_circles"): # Remove once circles is removed. for i, p in enumerate(self._circles): @@ -1609,7 +1787,8 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) - @_api.deprecated("3.7") + @_api.deprecated("3.7", + addendum="Any custom property styling may be lost.") @property def circles(self): if not hasattr(self, "_circles"): From 1a77aa6b5a60f8ef020263fd48ead7252886078c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Jan 2023 23:43:39 -0500 Subject: [PATCH 2/4] Warn if activecolor and a facecolor are provided --- lib/matplotlib/tests/test_widgets.py | 9 +++++++++ lib/matplotlib/widgets.py | 30 ++++++++++++++++++---------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 36445d1469b7..871faa93ff02 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1051,6 +1051,15 @@ def test_radio_buttons_props(fig_test, fig_ref): cb.set_radio_props({**radio_props, 's': (24 / 2)**2}) +def test_radio_button_active_conflict(ax): + with pytest.warns(UserWarning, + match=r'Both the \*activecolor\* parameter'): + rb = widgets.RadioButtons(ax, ['tea', 'coffee'], activecolor='red', + radio_props={'facecolor': 'green'}) + # *radio_props*' facecolor wins over *activecolor* + assert mcolors.same_color(rb._buttons.get_facecolor(), ['green', 'none']) + + @check_figures_equal(extensions=["png"]) def test_check_buttons(fig_test, fig_ref): widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True]) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index bdcee1d21ca7..63ae6b605de5 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1587,7 +1587,7 @@ class RadioButtons(AxesWidget): The label text of the currently selected button. """ - def __init__(self, ax, labels, active=0, activecolor='blue', *, + def __init__(self, ax, labels, active=0, activecolor=None, *, useblit=True, label_props=None, radio_props=None): """ Add radio buttons to an `~.axes.Axes`. @@ -1601,12 +1601,8 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, active : int The index of the initially selected button. activecolor : color - The color of the selected button. - - .. note:: - If a facecolor is supplied in *radio_props*, it will override - *activecolor*. This may be used to provide an active color per - button. + The color of the selected button. The default is ``'blue'`` if not + specified here or in *radio_props*. useblit : bool, default: True Use blitting for faster drawing if supported by the backend. See the tutorial :doc:`/tutorials/advanced/blitting` for details. @@ -1623,6 +1619,21 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, button. """ super().__init__(ax) + + _api.check_isinstance((dict, None), label_props=label_props, + radio_props=radio_props) + + radio_props = cbook.normalize_kwargs(radio_props, + collections.PathCollection) + if activecolor is not None: + if 'facecolor' in radio_props: + _api.warn_external( + 'Both the *activecolor* parameter and the *facecolor* ' + 'key in the *radio_props* parameter has been specified. ' + '*activecolor* will be ignored.') + else: + activecolor = 'blue' # Default. + self.activecolor = activecolor self.value_selected = labels[active] @@ -1635,9 +1646,6 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, self._useblit = useblit and self.canvas.supports_blit self._background = None - _api.check_isinstance((dict, None), label_props=label_props, - radio_props=radio_props) - label_props = _expand_text_props(label_props) self.labels = [ ax.text(0.25, y, label, transform=ax.transAxes, @@ -1648,7 +1656,7 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, radio_props = { 's': text_size**2, - **cbook.normalize_kwargs(radio_props, collections.PathCollection), + **radio_props, 'marker': 'o', 'transform': ax.transAxes, 'animated': self._useblit, From 5c9e1fd84c54fc319a6c8c9c7f9176407c1cc4b7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 19 Jan 2023 02:18:39 -0500 Subject: [PATCH 3/4] Handle modification of RadioButtons.activecolor --- lib/matplotlib/tests/test_widgets.py | 11 +++++++++++ lib/matplotlib/widgets.py | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 871faa93ff02..5eb32e69901a 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1060,6 +1060,17 @@ def test_radio_button_active_conflict(ax): assert mcolors.same_color(rb._buttons.get_facecolor(), ['green', 'none']) +@check_figures_equal(extensions=['png']) +def test_radio_buttons_activecolor_change(fig_test, fig_ref): + widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'], + activecolor='green') + + # Test property setter. + cb = widgets.RadioButtons(fig_test.subplots(), ['tea', 'coffee'], + activecolor='red') + cb.activecolor = 'green' + + @check_figures_equal(extensions=["png"]) def test_check_buttons(fig_test, fig_ref): widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True]) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 63ae6b605de5..5f86cfffb0b4 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1634,7 +1634,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, else: activecolor = 'blue' # Default. - self.activecolor = activecolor + self._activecolor = activecolor self.value_selected = labels[active] ax.set_xticks([]) @@ -1750,6 +1750,21 @@ def set_radio_props(self, props): [activecolor if text.get_text() == self.value_selected else "none" for text, activecolor in zip(self.labels, self._active_colors)]) + @property + def activecolor(self): + return self._activecolor + + @activecolor.setter + def activecolor(self, activecolor): + colors._check_color_like(activecolor=activecolor) + self._activecolor = activecolor + self.set_radio_props({'facecolor': activecolor}) + # Make sure the deprecated version is updated. + # Remove once circles is removed. + labels = [label.get_text() for label in self.labels] + with cbook._setattr_cm(self, eventson=False): + self.set_active(labels.index(self.value_selected)) + def set_active(self, index): """ Select button with number *index*. From 538a7d5071c697ded1dc655f677af3f44f0f3870 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 19 Jan 2023 14:48:14 -0500 Subject: [PATCH 4/4] Add what's new docs for button styling --- .../next_whats_new/widget_button_styling.rst | 31 +++++++++++++++++++ lib/matplotlib/widgets.py | 20 ++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 doc/users/next_whats_new/widget_button_styling.rst diff --git a/doc/users/next_whats_new/widget_button_styling.rst b/doc/users/next_whats_new/widget_button_styling.rst new file mode 100644 index 000000000000..4307e6977fc5 --- /dev/null +++ b/doc/users/next_whats_new/widget_button_styling.rst @@ -0,0 +1,31 @@ +Custom styling of button widgets +-------------------------------- + +Additional custom styling of button widgets may be achieved via the +*label_props* and *radio_props* arguments to `.RadioButtons`; and the +*label_props*, *frame_props*, and *check_props* arguments to `.CheckButtons`. + +.. plot:: + + from matplotlib.widgets import CheckButtons, RadioButtons + + fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(5, 2), width_ratios=[1, 2]) + default_rb = RadioButtons(ax[0, 0], ['Apples', 'Oranges']) + styled_rb = RadioButtons(ax[0, 1], ['Apples', 'Oranges'], + label_props={'color': ['red', 'orange'], + 'fontsize': [16, 20]}, + radio_props={'edgecolor': ['red', 'orange'], + 'facecolor': ['mistyrose', 'peachpuff']}) + + default_cb = CheckButtons(ax[1, 0], ['Apples', 'Oranges'], + actives=[True, True]) + styled_cb = CheckButtons(ax[1, 1], ['Apples', 'Oranges'], + actives=[True, True], + label_props={'color': ['red', 'orange'], + 'fontsize': [16, 20]}, + frame_props={'edgecolor': ['red', 'orange'], + 'facecolor': ['mistyrose', 'peachpuff']}, + check_props={'color': ['darkred', 'darkorange']}) + + ax[0, 0].set_title('Default') + ax[0, 1].set_title('Stylized') diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 5f86cfffb0b4..2365220770cc 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1015,14 +1015,20 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, See the tutorial :doc:`/tutorials/advanced/blitting` for details. label_props : dict, optional Dictionary of `.Text` properties to be used for the labels. + + .. versionadded:: 3.7 frame_props : dict, optional Dictionary of scatter `.Collection` properties to be used for the check button frame. Defaults (label font size / 2)**2 size, black edgecolor, no facecolor, and 1.0 linewidth. + + .. versionadded:: 3.7 check_props : dict, optional Dictionary of scatter `.Collection` properties to be used for the check button check. Defaults to (label font size / 2)**2 size, black color, and 1.0 linewidth. + + .. versionadded:: 3.7 """ super().__init__(ax) @@ -1120,6 +1126,8 @@ def set_label_props(self, props): """ Set properties of the `.Text` labels. + .. versionadded:: 3.7 + Parameters ---------- props : dict @@ -1134,6 +1142,8 @@ def set_frame_props(self, props): """ Set properties of the check button frames. + .. versionadded:: 3.7 + Parameters ---------- props : dict @@ -1149,6 +1159,8 @@ def set_check_props(self, props): """ Set properties of the check button checks. + .. versionadded:: 3.7 + Parameters ---------- props : dict @@ -1608,6 +1620,8 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, See the tutorial :doc:`/tutorials/advanced/blitting` for details. label_props : dict or list of dict, optional Dictionary of `.Text` properties to be used for the labels. + + .. versionadded:: 3.7 radio_props : dict, optional Dictionary of scatter `.Collection` properties to be used for the radio buttons. Defaults to (label font size / 2)**2 size, black @@ -1617,6 +1631,8 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, If a facecolor is supplied in *radio_props*, it will override *activecolor*. This may be used to provide an active color per button. + + .. versionadded:: 3.7 """ super().__init__(ax) @@ -1718,6 +1734,8 @@ def set_label_props(self, props): """ Set properties of the `.Text` labels. + .. versionadded:: 3.7 + Parameters ---------- props : dict @@ -1732,6 +1750,8 @@ def set_radio_props(self, props): """ Set properties of the `.Text` labels. + .. versionadded:: 3.7 + Parameters ---------- props : dict pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy