From 723cd86d7d7bdc14a4d3fc0e08c3a01e72d310b6 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Tue, 15 Nov 2022 22:06:56 -0600 Subject: [PATCH 1/7] Use scatter for check boxes instead of Rectangle With the current implementation, the boxes get stretched into rectangles if the aspect ratio is not maintained. To overcome this, the boxes are now created using scatter instead to maintain their shapes. --- lib/matplotlib/widgets.py | 116 +++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 44 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index c7a0eb0a112e..f3df8f021b1e 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1002,43 +1002,24 @@ def __init__(self, ax, labels, actives=None): if actives is None: actives = [False] * len(labels) - if len(labels) > 1: - dy = 1. / (len(labels) + 1) - ys = np.linspace(1 - dy, dy, len(labels)) - else: - dy = 0.25 - ys = [0.5] - - axcolor = ax.get_facecolor() - - self.labels = [] - self.lines = [] - self.rectangles = [] - - lineparams = {'color': 'k', 'linewidth': 1.25, - 'transform': ax.transAxes, 'solid_capstyle': 'butt'} - for y, label, active in zip(ys, labels, actives): - t = ax.text(0.25, y, label, transform=ax.transAxes, - horizontalalignment='left', - verticalalignment='center') - - w, h = dy / 2, dy / 2 - x, y = 0.05, y - h / 2 - - p = Rectangle(xy=(x, y), width=w, height=h, edgecolor='black', - facecolor=axcolor, transform=ax.transAxes) + ys = np.linspace(1, 0, len(labels)+2)[1:-1] + text_size = mpl.rcParams["font.size"] / 2 - l1 = Line2D([x, x + w], [y + h, y], **lineparams) - l2 = Line2D([x, x + w], [y, y + h], **lineparams) + self.labels = [ + ax.text(0.25, y, label, transform=ax.transAxes, + horizontalalignment="left", verticalalignment="center") + for y, label in zip(ys, labels)] - l1.set_visible(active) - l2.set_visible(active) - self.labels.append(t) - self.rectangles.append(p) - self.lines.append((l1, l2)) - ax.add_patch(p) - ax.add_line(l1) - ax.add_line(l2) + self._squares = ax.scatter( + [0.15] * len(ys), ys, marker='s', c="none", linewidth=1, + transform=ax.transAxes, edgecolor="k" + ) + mask = [not x for x in actives] + self._crosses = ax.scatter( + [0.15] * len(ys), ys, marker='x', linewidth=1, + c=["k" if actives[i] else "none" for i in range(len(ys))], + transform=ax.transAxes + ) self.connect_event('button_press_event', self._clicked) @@ -1047,11 +1028,29 @@ def __init__(self, ax, labels, actives=None): def _clicked(self, event): if self.ignore(event) or event.button != 1 or event.inaxes != self.ax: return - for i, (p, t) in enumerate(zip(self.rectangles, self.labels)): - if (t.get_window_extent().contains(event.x, event.y) or - p.get_window_extent().contains(event.x, event.y)): - self.set_active(i) - break + pclicked = self.ax.transAxes.inverted().transform((event.x, event.y)) + _, square_inds = self._squares.contains(event) + coords = self._squares.get_offset_transform().transform( + self._squares.get_offsets() + ) + distances = {} + if hasattr(self, "_rectangles"): + for i, (p, t) in enumerate(zip(self._rectangles, self.labels)): + if (t.get_window_extent().contains(event.x, event.y) + or ( + p.get_x() < event.x < p.get_x() + p.get_width() + and p.get_y() < event.y < p.get_y() + + p.get_height() + )): + distances[i] = np.linalg.norm(pclicked - p.get_center()) + else: + for i, t in enumerate(self.labels): + if (i in square_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_active(self, index): """ @@ -1072,9 +1071,18 @@ def set_active(self, index): if index not in range(len(self.labels)): raise ValueError(f'Invalid CheckButton index: {index}') - l1, l2 = self.lines[index] - l1.set_visible(not l1.get_visible()) - l2.set_visible(not l2.get_visible()) + if colors.same_color( + self._crosses.get_facecolor()[index], colors.to_rgba("none") + ): + self._crosses.get_facecolor()[index] = colors.to_rgba("k") + else: + self._crosses.get_facecolor()[index] = colors.to_rgba("none") + + if hasattr(self, "_rectangles"): + for i, p in enumerate(self._rectangles): + p.set_facecolor("k" if colors.same_color( + p.get_facecolor(), colors.to_rgba("none")) + else "none") if self.drawon: self.ax.figure.canvas.draw() @@ -1086,7 +1094,9 @@ def get_status(self): """ Return a tuple of the status (True/False) of all of the check buttons. """ - return [l1.get_visible() for (l1, l2) in self.lines] + return [False if colors.same_color( + self._crosses.get_facecolors()[i], colors.to_rgba("none")) + else True for i in range(len(self.labels))] def on_clicked(self, func): """ @@ -1100,6 +1110,24 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) + @property + def rectangles(self): + if not hasattr(self, "rectangles"): + dy = 1. / (len(self.labels) + 1) + w, h = dy / 2, dy / 2 + rectangles = self._rectangles = [ + Rectangle(xy=self._squares.get_offsets()[i], width=w, height=h, + edgecolor="black", + facecolor=self._squares.get_facecolor()[i], + transform=self.ax.transAxes + ) + for i in range(len(self.labels)) + ] + self._squares.set_visible(False) + for rectangle in rectangles: + self.ax.add_patch(rectangle) + return self._rectangles + class TextBox(AxesWidget): """ From 805f3656dc8f6609da628fca279d482eca7358ae Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 16 Nov 2022 17:06:31 -0600 Subject: [PATCH 2/7] Specify size for the buttons to be same as radio --- lib/matplotlib/widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index f3df8f021b1e..a8593c8dfc58 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1011,12 +1011,12 @@ def __init__(self, ax, labels, actives=None): for y, label in zip(ys, labels)] self._squares = ax.scatter( - [0.15] * len(ys), ys, marker='s', c="none", linewidth=1, - transform=ax.transAxes, edgecolor="k" + [0.15] * len(ys), ys, marker='s', c="none", s=text_size**2, + linewidth=1, transform=ax.transAxes, edgecolor="k" ) mask = [not x for x in actives] self._crosses = ax.scatter( - [0.15] * len(ys), ys, marker='x', linewidth=1, + [0.15] * len(ys), ys, marker='x', linewidth=1, s=text_size**2, c=["k" if actives[i] else "none" for i in range(len(ys))], transform=ax.transAxes ) From 61db294a49a56c22c7bb41f6c4b1929b862fa09e Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Thu, 17 Nov 2022 12:36:02 -0600 Subject: [PATCH 3/7] Use correct setter to set facecolor in checkbox --- lib/matplotlib/widgets.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index a8593c8dfc58..cf3c040851dd 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1071,12 +1071,15 @@ def set_active(self, index): if index not in range(len(self.labels)): raise ValueError(f'Invalid CheckButton index: {index}') - if colors.same_color( - self._crosses.get_facecolor()[index], colors.to_rgba("none") - ): - self._crosses.get_facecolor()[index] = colors.to_rgba("k") - else: - self._crosses.get_facecolor()[index] = colors.to_rgba("none") + 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 colors.to_rgba("none") + ) + self._crosses.set_facecolor(cross_facecolors) if hasattr(self, "_rectangles"): for i, p in enumerate(self._rectangles): From 04d8fdaabf97f2f1736b1afac4a9ac5893e45d46 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Thu, 17 Nov 2022 13:07:10 -0600 Subject: [PATCH 4/7] Update radio buttons to also use correct setter --- lib/matplotlib/widgets.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index cf3c040851dd..5aaa98292096 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1488,8 +1488,10 @@ 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() - self._buttons.get_facecolor()[:] = colors.to_rgba("none") - self._buttons.get_facecolor()[index] = colors.to_rgba(self.activecolor) + button_facecolors = self._buttons.get_facecolor() + button_facecolors[:] = colors.to_rgba("none") + button_facecolors[index] = colors.to_rgba(self.activecolor) + 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") From 0f09fa1318d75d5ca1cfc75b9186956b1a9e2bcc Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Thu, 17 Nov 2022 14:35:12 -0600 Subject: [PATCH 5/7] Set rectangles and lines property for backcompat --- .../prev_api_changes/api_changes_2.1.0.rst | 4 +- lib/matplotlib/tests/test_widgets.py | 21 +++++++- lib/matplotlib/widgets.py | 50 +++++++++++++++---- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/doc/api/prev_api_changes/api_changes_2.1.0.rst b/doc/api/prev_api_changes/api_changes_2.1.0.rst index 9673d24c719f..39ea78bdf587 100644 --- a/doc/api/prev_api_changes/api_changes_2.1.0.rst +++ b/doc/api/prev_api_changes/api_changes_2.1.0.rst @@ -422,8 +422,8 @@ The ``shading`` kwarg to `~matplotlib.axes.Axes.pcolor` has been removed. Set ``edgecolors`` appropriately instead. -Functions removed from the `.lines` module -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Functions removed from the ``lines`` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :mod:`matplotlib.lines` module no longer imports the ``pts_to_prestep``, ``pts_to_midstep`` and ``pts_to_poststep`` diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 4e1455660da9..c01ea4f2df14 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1006,8 +1006,13 @@ def test_check_radio_buttons_image(): 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)) + cb = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), + (False, True, True)) + with pytest.warns(DeprecationWarning): + # Trigger old-style Rectangle check boxes + cb.rectangles + with pytest.warns(DeprecationWarning): + cb.lines @check_figures_equal(extensions=["png"]) @@ -1020,6 +1025,18 @@ 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_check_buttons(fig_test, fig_ref): + widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True]) + ax = fig_ref.add_subplot(xticks=[], yticks=[]) + ax.scatter([.15, .15], [2/3, 1/3], marker='s', transform=ax.transAxes, + s=(plt.rcParams["font.size"] / 2) ** 2, c=["none", "none"]) + ax.scatter([.15, .15], [2/3, 1/3], marker='x', transform=ax.transAxes, + s=(plt.rcParams["font.size"] / 2) ** 2, c=["k", "k"]) + 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(): fig, ax = plt.subplots() # test min/max with floats diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 5aaa98292096..def2316c8611 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1011,7 +1011,8 @@ def __init__(self, ax, labels, actives=None): for y, label in zip(ys, labels)] self._squares = ax.scatter( - [0.15] * len(ys), ys, marker='s', c="none", s=text_size**2, + [0.15] * len(ys), ys, marker='s', s=text_size**2, + c=["none" for _ in range(len(ys))], linewidth=1, transform=ax.transAxes, edgecolor="k" ) mask = [not x for x in actives] @@ -1038,8 +1039,9 @@ def _clicked(self, event): for i, (p, t) in enumerate(zip(self._rectangles, self.labels)): if (t.get_window_extent().contains(event.x, event.y) or ( - p.get_x() < event.x < p.get_x() + p.get_width() - and p.get_y() < event.y < p.get_y() + p.get_x() <= pclicked[0] <= p.get_x() + + p.get_width() + and p.get_y() <= pclicked[1] <= p.get_y() + p.get_height() )): distances[i] = np.linalg.norm(pclicked - p.get_center()) @@ -1081,11 +1083,10 @@ def set_active(self, index): ) self._crosses.set_facecolor(cross_facecolors) - if hasattr(self, "_rectangles"): - for i, p in enumerate(self._rectangles): - p.set_facecolor("k" if colors.same_color( - p.get_facecolor(), colors.to_rgba("none")) - else "none") + 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: self.ax.figure.canvas.draw() @@ -1113,24 +1114,51 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) + @_api.deprecated("3.7") @property def rectangles(self): - if not hasattr(self, "rectangles"): + 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=self._squares.get_offsets()[i], width=w, height=h, + Rectangle(xy=(0.05, ys[i] - h / 2), width=w, height=h, edgecolor="black", facecolor=self._squares.get_facecolor()[i], transform=self.ax.transAxes ) - for i in range(len(self.labels)) + for i, y in enumerate(ys) ] self._squares.set_visible(False) for rectangle in rectangles: self.ax.add_patch(rectangle) return self._rectangles + @_api.deprecated("3.7") + @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) + 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'} + 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_patch(l1) + self.ax.add_patch(l2) + return self._lines + class TextBox(AxesWidget): """ From 143cb9a9332508a3f5006cc03f2c8543e13913fe Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 30 Nov 2022 11:02:07 -0600 Subject: [PATCH 6/7] Create rectangles/lines when the other is accessed The rectangles/lines attributes exist for backcompat and the expectation would be that when either one of them is accessed, the other would also be set in the way the older method does. So, whenever either of the property is accessed for the first time, it also sets the other property. This way, it maintains consistency with the old API. --- doc/api/next_api_changes/deprecations/24474_CM.rst | 3 +++ lib/matplotlib/tests/test_widgets.py | 2 -- lib/matplotlib/widgets.py | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/24474_CM.rst diff --git a/doc/api/next_api_changes/deprecations/24474_CM.rst b/doc/api/next_api_changes/deprecations/24474_CM.rst new file mode 100644 index 000000000000..0667f4b8dca0 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/24474_CM.rst @@ -0,0 +1,3 @@ +``CheckButtons`` +~~~~~~~~~~~~~~~~~~~~~~~~~ +``CheckButtons.rectangles`` and ``CheckButtons.lines`` are deprecated. (``CheckButtons`` now draws itself using `~.Axes.scatter`.) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index c01ea4f2df14..0e1045af70ad 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1011,8 +1011,6 @@ def test_check_radio_buttons_image(): with pytest.warns(DeprecationWarning): # Trigger old-style Rectangle check boxes cb.rectangles - with pytest.warns(DeprecationWarning): - cb.lines @check_figures_equal(extensions=["png"]) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index def2316c8611..26c30a041042 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1132,6 +1132,9 @@ def rectangles(self): self._squares.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") @@ -1157,6 +1160,9 @@ def lines(self): self._lines.append((l1, l2)) self.ax.add_patch(l1) self.ax.add_patch(l2) + if not hasattr(self, "_rectangles"): + with _api.suppress_matplotlib_deprecation_warning(): + _ = self.rectangles return self._lines From 788a0d1a5a1f96e10dd29a3aa1853dc6b2512015 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Thu, 1 Dec 2022 13:39:09 -0600 Subject: [PATCH 7/7] Add tests to check .rectangles and .lines fallback --- .../deprecations/24474_CM.rst | 7 +-- lib/matplotlib/tests/test_widgets.py | 54 ++++++++++++++++++- lib/matplotlib/widgets.py | 36 ++++++------- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/doc/api/next_api_changes/deprecations/24474_CM.rst b/doc/api/next_api_changes/deprecations/24474_CM.rst index 0667f4b8dca0..7e12ded0fdbf 100644 --- a/doc/api/next_api_changes/deprecations/24474_CM.rst +++ b/doc/api/next_api_changes/deprecations/24474_CM.rst @@ -1,3 +1,4 @@ -``CheckButtons`` -~~~~~~~~~~~~~~~~~~~~~~~~~ -``CheckButtons.rectangles`` and ``CheckButtons.lines`` are deprecated. (``CheckButtons`` now draws itself using `~.Axes.scatter`.) +``CheckButtons.rectangles`` and ``CheckButtons.lines`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``CheckButtons.rectangles`` and ``CheckButtons.lines`` are deprecated. +(``CheckButtons`` now draws itself using `~.Axes.scatter`.) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 0e1045af70ad..d5cbcd5a1c00 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -7,6 +7,8 @@ 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) @@ -1009,8 +1011,7 @@ def test_check_radio_buttons_image(): cb = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), (False, True, True)) with pytest.warns(DeprecationWarning): - # Trigger old-style Rectangle check boxes - cb.rectangles + cb.rectangles # Trigger old-style Rectangle check boxes @check_figures_equal(extensions=["png"]) @@ -1035,6 +1036,55 @@ 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_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): + 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): + 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 26c30a041042..501643bbeefd 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1012,13 +1012,11 @@ def __init__(self, ax, labels, actives=None): self._squares = ax.scatter( [0.15] * len(ys), ys, marker='s', s=text_size**2, - c=["none" for _ in range(len(ys))], - linewidth=1, transform=ax.transAxes, edgecolor="k" + c="none", linewidth=1, transform=ax.transAxes, edgecolor="k" ) - mask = [not x for x in actives] self._crosses = ax.scatter( [0.15] * len(ys), ys, marker='x', linewidth=1, s=text_size**2, - c=["k" if actives[i] else "none" for i in range(len(ys))], + c=["k" if active else "none" for active in actives], transform=ax.transAxes ) @@ -1030,22 +1028,19 @@ 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)) - _, square_inds = self._squares.contains(event) - coords = self._squares.get_offset_transform().transform( - self._squares.get_offsets() - ) 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 ( - p.get_x() <= pclicked[0] <= p.get_x() - + p.get_width() - and p.get_y() <= pclicked[1] <= p.get_y() - + p.get_height() - )): + 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: + _, square_inds = self._squares.contains(event) + coords = self._squares.get_offset_transform().transform( + self._squares.get_offsets() + ) for i, t in enumerate(self.labels): if (i in square_inds["ind"] or t.get_window_extent().contains(event.x, event.y)): @@ -1074,12 +1069,12 @@ def set_active(self, index): raise ValueError(f'Invalid CheckButton index: {index}') cross_facecolors = self._crosses.get_facecolor() - cross_facecolors[index] = ( - colors.to_rgba("black") + cross_facecolors[index] = colors.to_rgba( + "black" if colors.same_color( cross_facecolors[index], colors.to_rgba("none") ) - else colors.to_rgba("none") + else "none" ) self._crosses.set_facecolor(cross_facecolors) @@ -1098,9 +1093,8 @@ def get_status(self): """ Return a tuple of the status (True/False) of all of the check buttons. """ - return [False if colors.same_color( - self._crosses.get_facecolors()[i], colors.to_rgba("none")) - else True for i in range(len(self.labels))] + return [not colors.same_color(color, colors.to_rgba("none")) + for color in self._crosses.get_facecolors()] def on_clicked(self, func): """ @@ -1124,7 +1118,7 @@ def rectangles(self): rectangles = self._rectangles = [ Rectangle(xy=(0.05, ys[i] - h / 2), width=w, height=h, edgecolor="black", - facecolor=self._squares.get_facecolor()[i], + facecolor="none", transform=self.ax.transAxes ) for i, y in enumerate(ys)