diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 99216f5f75eb..64fae8d44562 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -54,7 +54,6 @@ matplotlib.cm.register_cmap matplotlib.cm.unregister_cmap matplotlib.collections.PolyCollection.span_where matplotlib.gridspec.GridSpecBase.get_grid_positions -matplotlib.widgets.MultiCursor.needclear # 3.8 deprecations matplotlib.cbook.get_sample_data diff --git a/doc/api/next_api_changes/removals/26853-OG.rst b/doc/api/next_api_changes/removals/26853-OG.rst new file mode 100644 index 000000000000..dc5c37e38db5 --- /dev/null +++ b/doc/api/next_api_changes/removals/26853-OG.rst @@ -0,0 +1,26 @@ +Most arguments to widgets have been made keyword-only +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Passing all but the very few first arguments positionally in the constructors +of Widgets is now keyword-only. In general, all optional arguments are keyword-only. + +``RadioButtons.circles`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +... is removed. (``RadioButtons`` now draws itself using `~.Axes.scatter`.) + +``CheckButtons.rectangles`` and ``CheckButtons.lines`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``CheckButtons.rectangles`` and ``CheckButtons.lines`` are removed. +(``CheckButtons`` now draws itself using `~.Axes.scatter`.) + +Remove unused parameter *x* to ``TextBox.begin_typing`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This parameter was unused in the method, but was a required argument. + +``MultiCursor.needclear`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +... is removed. 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 d6e6004a1732..f0d5023008ca 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 1ecb4b9a1df7..3e41712ba530 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -7,8 +7,6 @@ import matplotlib.colors as mcolors import matplotlib.widgets as widgets import matplotlib.pyplot as plt -from matplotlib.patches import Rectangle -from matplotlib.lines import Line2D from matplotlib.testing.decorators import check_figures_equal, image_comparison from matplotlib.testing.widgets import (click_and_drag, do_event, get_ax, mock_event, noop) @@ -1055,16 +1053,10 @@ def test_check_radio_buttons_image(): 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'): - 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'): - cb1.rectangles # Trigger old-style Rectangle check boxes rax3 = fig.add_axes((0.05, 0.3, 0.2, 0.15)) rb3 = widgets.RadioButtons( @@ -1164,57 +1156,6 @@ def test_check_button_props(fig_test, fig_ref): 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 - cb = widgets.CheckButtons(fig_test.subplots(), ["", ""], - [False, False]) - with pytest.warns(DeprecationWarning, - match='The rectangles attribute was deprecated'): - cb.rectangles - ax = fig_ref.add_subplot(xticks=[], yticks=[]) - ys = [2/3, 1/3] - dy = 1/3 - w, h = dy / 2, dy / 2 - rectangles = [ - Rectangle(xy=(0.05, ys[i] - h / 2), width=w, height=h, - edgecolor="black", - facecolor="none", - transform=ax.transAxes - ) - for i, y in enumerate(ys) - ] - for rectangle in rectangles: - ax.add_patch(rectangle) - - -@check_figures_equal(extensions=["png"]) -def test_check_buttons_lines(fig_test, fig_ref): - # Test should be removed once .lines is removed - cb = widgets.CheckButtons(fig_test.subplots(), ["", ""], [True, True]) - with pytest.warns(DeprecationWarning, - match='The lines attribute was deprecated'): - cb.lines - for rectangle in cb._rectangles: - rectangle.set_visible(False) - ax = fig_ref.add_subplot(xticks=[], yticks=[]) - ys = [2/3, 1/3] - dy = 1/3 - w, h = dy / 2, dy / 2 - lineparams = {'color': 'k', 'linewidth': 1.25, - 'transform': ax.transAxes, - 'solid_capstyle': 'butt'} - for i, y in enumerate(ys): - x, y = 0.05, y - h / 2 - l1 = Line2D([x, x + w], [y + h, y], **lineparams) - l2 = Line2D([x, x + w], [y, y + h], **lineparams) - - l1.set_visible(True) - l2.set_visible(True) - ax.add_line(l1) - ax.add_line(l2) - - def test_slider_slidermin_slidermax_invalid(): fig, ax = plt.subplots() # test min/max with floats diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 0a31a9dd2529..f24be7904690 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -21,7 +21,7 @@ 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 .patches import Rectangle, Ellipse, Polygon from .transforms import TransformedPatchPath, Affine2D @@ -355,11 +355,10 @@ class Slider(SliderBase): Slider value. """ - @_api.make_keyword_only("3.7", name="valinit") - def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, + def __init__(self, ax, label, valmin, valmax, *, valinit=0.5, valfmt=None, closedmin=True, closedmax=True, slidermin=None, slidermax=None, dragging=True, valstep=None, - orientation='horizontal', *, initcolor='r', + orientation='horizontal', initcolor='r', track_color='lightgrey', handle_style=None, **kwargs): """ Parameters @@ -627,13 +626,13 @@ class RangeSlider(SliderBase): Slider value. """ - @_api.make_keyword_only("3.7", name="valinit") def __init__( self, ax, label, valmin, valmax, + *, valinit=None, valfmt=None, closedmin=True, @@ -1115,32 +1114,20 @@ def _clear(self, event): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._checks) - if hasattr(self, '_lines'): - for l1, l2 in self._lines: - self.ax.draw_artist(l1) - self.ax.draw_artist(l2) def _clicked(self, event): if self.ignore(event) or event.button != 1 or not self.ax.contains(event)[0]: return pclicked = self.ax.transAxes.inverted().transform((event.x, event.y)) distances = {} - if hasattr(self, "_rectangles"): - for i, (p, t) in enumerate(zip(self._rectangles, self.labels)): - x0, y0 = p.get_xy() - if (t.get_window_extent().contains(event.x, event.y) - or (x0 <= pclicked[0] <= x0 + p.get_width() - and y0 <= pclicked[1] <= y0 + p.get_height())): - distances[i] = np.linalg.norm(pclicked - p.get_center()) - else: - _, 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 frame_inds["ind"] - or t.get_window_extent().contains(event.x, event.y)): - distances[i] = np.linalg.norm(pclicked - coords[i]) + _, 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 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) @@ -1227,20 +1214,11 @@ def set_active(self, index): ) self._checks.set_facecolor(facecolors) - if hasattr(self, "_lines"): - l1, l2 = self._lines[index] - l1.set_visible(not l1.get_visible()) - l2.set_visible(not l2.get_visible()) - if self.drawon: if self._useblit: if self._background is not None: self.canvas.restore_region(self._background) self.ax.draw_artist(self._checks) - if hasattr(self, "_lines"): - for l1, l2 in self._lines: - self.ax.draw_artist(l1) - self.ax.draw_artist(l2) self.canvas.blit(self.ax.bbox) else: self.canvas.draw() @@ -1283,60 +1261,6 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) - @_api.deprecated("3.7", - addendum="Any custom property styling may be lost.") - @property - def rectangles(self): - if not hasattr(self, "_rectangles"): - ys = np.linspace(1, 0, len(self.labels)+2)[1:-1] - dy = 1. / (len(self.labels) + 1) - w, h = dy / 2, dy / 2 - rectangles = self._rectangles = [ - Rectangle(xy=(0.05, ys[i] - h / 2), width=w, height=h, - edgecolor="black", - facecolor="none", - transform=self.ax.transAxes - ) - for i, y in enumerate(ys) - ] - self._frames.set_visible(False) - for rectangle in rectangles: - self.ax.add_patch(rectangle) - if not hasattr(self, "_lines"): - with _api.suppress_matplotlib_deprecation_warning(): - _ = self.lines - return self._rectangles - - @_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._checks.set_visible(False) - dy = 1. / (len(self.labels) + 1) - w, h = dy / 2, dy / 2 - self._lines = [] - current_status = self.get_status() - lineparams = {'color': 'k', 'linewidth': 1.25, - 'transform': self.ax.transAxes, - 'solid_capstyle': 'butt', - 'animated': self._useblit} - for i, y in enumerate(ys): - x, y = 0.05, y - h / 2 - l1 = Line2D([x, x + w], [y + h, y], **lineparams) - l2 = Line2D([x, x + w], [y, y + h], **lineparams) - - l1.set_visible(current_status[i]) - l2.set_visible(current_status[i]) - self._lines.append((l1, l2)) - self.ax.add_line(l1) - self.ax.add_line(l2) - if not hasattr(self, "_rectangles"): - with _api.suppress_matplotlib_deprecation_warning(): - _ = self.rectangles - return self._lines - class TextBox(AxesWidget): """ @@ -1361,8 +1285,7 @@ class TextBox(AxesWidget): The color of the text box when hovering. """ - @_api.make_keyword_only("3.7", name="color") - def __init__(self, ax, label, initial='', + def __init__(self, ax, label, initial='', *, color='.95', hovercolor='1', label_pad=.01, textalignment="left"): """ @@ -1513,8 +1436,7 @@ def set_val(self, val): self._observers.process('change', self.text) self._observers.process('submit', self.text) - @_api.delete_parameter("3.7", "x") - def begin_typing(self, x=None): + def begin_typing(self): self.capturekeystrokes = True # Disable keypress shortcuts, which may otherwise cause the figure to # be saved, closed, etc., until the user stops typing. The way to @@ -1730,9 +1652,6 @@ def _clear(self, event): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._buttons) - if hasattr(self, "_circles"): - for circle in self._circles: - self.ax.draw_artist(circle) def _clicked(self, event): if self.ignore(event) or event.button != 1 or not self.ax.contains(event)[0]: @@ -1742,16 +1661,10 @@ def _clicked(self, event): coords = self._buttons.get_offset_transform().transform( self._buttons.get_offsets()) distances = {} - 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]) + 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) @@ -1824,19 +1737,12 @@ def set_active(self, index): button_facecolors[:] = colors.to_rgba("none") 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): - p.set_facecolor(self.activecolor if i == index else "none") - if self.drawon and self._useblit: - self.ax.draw_artist(p) + if self.drawon: if self._useblit: if self._background is not None: self.canvas.restore_region(self._background) self.ax.draw_artist(self._buttons) - if hasattr(self, "_circles"): - for p in self._circles: - self.ax.draw_artist(p) self.canvas.blit(self.ax.bbox) else: self.canvas.draw() @@ -1856,23 +1762,6 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) - @_api.deprecated("3.7", - addendum="Any custom property styling may be lost.") - @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, - animated=self._useblit) - 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): """ @@ -1974,8 +1863,7 @@ class Cursor(AxesWidget): -------- See :doc:`/gallery/widgets/cursor`. """ - @_api.make_keyword_only("3.7", "horizOn") - def __init__(self, ax, horizOn=True, vertOn=True, useblit=False, + def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False, **lineprops): super().__init__(ax) @@ -2109,8 +1997,6 @@ def __init__(self, canvas, axes, *, useblit=True, horizOn=False, vertOn=True, self.connect() - needclear = _api.deprecated("3.7")(lambda self: False) - def connect(self): """Connect events.""" for canvas, info in self._canvas_infos.items(): @@ -2613,8 +2499,7 @@ class SpanSelector(_SelectorWidget): See also: :doc:`/gallery/widgets/span_selector` """ - @_api.make_keyword_only("3.7", name="minspan") - def __init__(self, ax, onselect, direction, minspan=0, useblit=False, + def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False, props=None, onmove_callback=None, interactive=False, button=None, handle_props=None, grab_range=10, state_modifier_keys=None, drag_from_anywhere=False, @@ -2957,8 +2842,7 @@ class ToolLineHandles: for details. """ - @_api.make_keyword_only("3.7", "line_props") - def __init__(self, ax, positions, direction, line_props=None, + def __init__(self, ax, positions, direction, *, line_props=None, useblit=True): self.ax = ax @@ -3068,8 +2952,7 @@ class ToolHandles: for details. """ - @_api.make_keyword_only("3.7", "marker") - def __init__(self, ax, x, y, marker='o', marker_props=None, useblit=True): + def __init__(self, ax, x, y, *, marker='o', marker_props=None, useblit=True): self.ax = ax props = {'marker': marker, 'markersize': 7, 'markerfacecolor': 'w', 'linestyle': 'none', 'alpha': 0.5, 'visible': False, @@ -3771,8 +3654,7 @@ def onselect(verts): which corresponds to all buttons. """ - @_api.make_keyword_only("3.7", name="useblit") - def __init__(self, ax, onselect, useblit=True, props=None, button=None): + def __init__(self, ax, onselect, *, useblit=True, props=None, button=None): super().__init__(ax, onselect, useblit=useblit, button=button) self.verts = None props = { @@ -3882,9 +3764,8 @@ class PolygonSelector(_SelectorWidget): point. """ - @_api.make_keyword_only("3.7", name="useblit") - def __init__(self, ax, onselect, useblit=False, - props=None, handle_props=None, grab_range=10, *, + def __init__(self, ax, onselect, *, useblit=False, + props=None, handle_props=None, grab_range=10, draw_bounding_box=False, box_handle_props=None, box_props=None): # The state modifiers 'move', 'square', and 'center' are expected by @@ -4199,8 +4080,7 @@ class Lasso(AxesWidget): for details. """ - @_api.make_keyword_only("3.7", name="useblit") - def __init__(self, ax, xy, callback, useblit=True): + def __init__(self, ax, xy, callback, *, useblit=True): super().__init__(ax) self.useblit = useblit and self.canvas.supports_blit diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index 00c2d0da8a7e..ca1e5a71a288 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -164,10 +164,6 @@ class CheckButtons(AxesWidget): def get_status(self) -> list[bool]: ... def on_clicked(self, func: Callable[[str], Any]) -> int: ... def disconnect(self, cid: int) -> None: ... - @property - def lines(self) -> list[tuple[Line2D, Line2D]]: ... - @property - def rectangles(self) -> list[Rectangle]: ... class TextBox(AxesWidget): label: Text @@ -191,7 +187,7 @@ class TextBox(AxesWidget): @property def text(self) -> str: ... def set_val(self, val: str) -> None: ... - def begin_typing(self, x = ...) -> None: ... + def begin_typing(self) -> None: ... def stop_typing(self) -> None: ... def on_text_change(self, func: Callable[[str], Any]) -> int: ... def on_submit(self, func: Callable[[str], Any]) -> int: ... @@ -217,8 +213,6 @@ class RadioButtons(AxesWidget): def set_active(self, index: int) -> None: ... def on_clicked(self, func: Callable[[str], Any]) -> int: ... def disconnect(self, cid: int) -> None: ... - @property - def circles(self) -> list[Circle]: ... class SubplotTool(Widget): figure: Figure @@ -253,7 +247,6 @@ class MultiCursor(Widget): vertOn: bool visible: bool useblit: bool - needclear: bool vlines: list[Line2D] hlines: list[Line2D] def __init__(