diff --git a/doc/api/next_api_changes/deprecations/24455-AL.rst b/doc/api/next_api_changes/deprecations/24455-AL.rst new file mode 100644 index 000000000000..8a8f3e497260 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/24455-AL.rst @@ -0,0 +1,3 @@ +``RadioButtons.circles`` +~~~~~~~~~~~~~~~~~~~~~~~~ +... is deprecated. (RadioButtons now draws itself using `~.Axes.scatter`.) diff --git a/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png deleted file mode 100644 index e071860dfde6..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png and /dev/null differ diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 446e272610ab..4e1455660da9 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1003,18 +1003,21 @@ def test_check_radio_buttons_image(): 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]) - widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3')) + rb = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3')) + with pytest.warns(DeprecationWarning): + rb.circles # Trigger the old-style elliptic radiobuttons. widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), (False, True, True)) -@image_comparison(['check_bunch_of_radio_buttons.png'], - style='mpl20', remove_text=True) -def test_check_bunch_of_radio_buttons(): - rax = plt.axes([0.05, 0.1, 0.15, 0.7]) - widgets.RadioButtons(rax, ('B1', 'B2', 'B3', 'B4', 'B5', 'B6', - 'B7', 'B8', 'B9', 'B10', 'B11', 'B12', - 'B13', 'B14', 'B15')) +@check_figures_equal(extensions=["png"]) +def test_radio_buttons(fig_test, fig_ref): + widgets.RadioButtons(fig_test.subplots(), ["tea", "coffee"]) + ax = fig_ref.add_subplot(xticks=[], yticks=[]) + ax.scatter([.15, .15], [2/3, 1/3], transform=ax.transAxes, + s=(plt.rcParams["font.size"] / 2) ** 2, c=["C0", "none"]) + ax.text(.25, 2/3, "tea", transform=ax.transAxes, va="center") + ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") def test_slider_slidermin_slidermax_invalid(): diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 807e9d360071..c7a0eb0a112e 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1404,41 +1404,23 @@ def __init__(self, ax, labels, active=0, activecolor='blue'): """ super().__init__(ax) self.activecolor = activecolor - self.value_selected = None + self.value_selected = labels[active] ax.set_xticks([]) ax.set_yticks([]) ax.set_navigate(False) - dy = 1. / (len(labels) + 1) - ys = np.linspace(1 - dy, dy, len(labels)) - cnt = 0 - axcolor = ax.get_facecolor() - - # scale the radius of the circle with the spacing between each one - circle_radius = dy / 2 - 0.01 - # default to hard-coded value if the radius becomes too large - circle_radius = min(circle_radius, 0.05) - - self.labels = [] - self.circles = [] - for y, label in zip(ys, labels): - t = ax.text(0.25, y, label, transform=ax.transAxes, - horizontalalignment='left', - verticalalignment='center') - if cnt == active: - self.value_selected = label - facecolor = activecolor - else: - facecolor = axcolor + ys = np.linspace(1, 0, len(labels) + 2)[1:-1] + text_size = mpl.rcParams["font.size"] / 2 - p = Circle(xy=(0.15, y), radius=circle_radius, edgecolor='black', - facecolor=facecolor, transform=ax.transAxes) - - self.labels.append(t) - self.circles.append(p) - ax.add_patch(p) - cnt += 1 + 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") self.connect_event('button_press_event', self._clicked) @@ -1448,11 +1430,20 @@ def _clicked(self, event): if self.ignore(event) or event.button != 1 or event.inaxes != self.ax: return pclicked = self.ax.transAxes.inverted().transform((event.x, event.y)) + _, inds = self._buttons.contains(event) + coords = self._buttons.get_offset_transform().transform( + self._buttons.get_offsets()) distances = {} - for i, (p, t) in enumerate(zip(self.circles, self.labels)): - if (t.get_window_extent().contains(event.x, event.y) - or np.linalg.norm(pclicked - p.center) < p.radius): - distances[i] = np.linalg.norm(pclicked - p.center) + if hasattr(self, "_circles"): # Remove once circles is removed. + for i, (p, t) in enumerate(zip(self._circles, self.labels)): + if (t.get_window_extent().contains(event.x, event.y) + or np.linalg.norm(pclicked - p.center) < p.radius): + distances[i] = np.linalg.norm(pclicked - p.center) + else: + for i, t in enumerate(self.labels): + if (i in 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) @@ -1465,19 +1456,14 @@ def set_active(self, index): """ if index not in range(len(self.labels)): raise ValueError(f'Invalid RadioButton index: {index}') - self.value_selected = self.labels[index].get_text() - - for i, p in enumerate(self.circles): - if i == index: - color = self.activecolor - else: - color = self.ax.get_facecolor() - p.set_facecolor(color) - + self._buttons.get_facecolor()[:] = colors.to_rgba("none") + self._buttons.get_facecolor()[index] = colors.to_rgba(self.activecolor) + if hasattr(self, "_circles"): # Remove once circles is removed. + for i, p in enumerate(self._circles): + p.set_facecolor(self.activecolor if i == index else "none") if self.drawon: self.ax.figure.canvas.draw() - if self.eventson: self._observers.process('clicked', self.labels[index].get_text()) @@ -1493,6 +1479,21 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) + @_api.deprecated("3.7") + @property + def circles(self): + if not hasattr(self, "_circles"): + radius = min(.5 / (len(self.labels) + 1) - .01, .05) + circles = self._circles = [ + Circle(xy=self._buttons.get_offsets()[i], edgecolor="black", + facecolor=self._buttons.get_facecolor()[i], + radius=radius, transform=self.ax.transAxes) + for i in range(len(self.labels))] + self._buttons.set_visible(False) + for circle in circles: + self.ax.add_patch(circle) + return self._circles + class SubplotTool(Widget): """
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: