-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Use scatter for check boxes and set facecolors correctly in check boxes and radio buttons #24474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
723cd86
805f365
61db294
04d8fda
0f09fa1
143cb9a
788a0d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
``CheckButtons.rectangles`` and ``CheckButtons.lines`` | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
``CheckButtons.rectangles`` and ``CheckButtons.lines`` are deprecated. | ||
(``CheckButtons`` now draws itself using `~.Axes.scatter`.) |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1002,43 +1002,23 @@ 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', 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 | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
self.connect_event('button_press_event', self._clicked) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
@@ -1047,11 +1027,27 @@ 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)) | ||||||||||||||||||||||||
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: | ||||||||||||||||||||||||
_, 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)): | ||||||||||||||||||||||||
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 +1068,20 @@ 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()) | ||||||||||||||||||||||||
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" | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
self._crosses.set_facecolor(cross_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: | ||||||||||||||||||||||||
self.ax.figure.canvas.draw() | ||||||||||||||||||||||||
Comment on lines
1086
to
1087
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @anntzer I have a preliminary implementation for square check boxes but I'm running into one small issue. When I try running this, everything works as intended when we change the figure size (the check boxes remain square), but the unit test fails. For some reason, calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like I'm missing something but can't see it right now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you do some print-debugging and grep for places that could touch facecolors, you'll see that Collection.update_scalarmappable gets called at some point which causes the facecolors to be reset, but you'll need to figure out by yourself why (because I don't actually know immediately). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That helps, thanks! I'll try figuring out why this is happening. The main reason why I was getting confused was that it happened only when set_active was explicitly called but worked as intended with the actual figure. Should be able to figure this out now though, hopefully. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @anntzer this is actually true for the radio buttons too! Small code to reproduce (based on the checkbox example) radio = RadioButtons(rax, labels, active=1, activecolor="k")
print(radio.value_selected)
print(radio._buttons.get_facecolor())
print("Changing active button from 1 to 0")
radio.set_active(0)
print(radio.value_selected)
print(radio._buttons.get_facecolor()) Output:
This shows that the facecolors for the buttons don't change even after matplotlib/lib/matplotlib/collections.py Lines 842 to 845 in a9c9620
Since it sets the flags such that it returns True the first time, matplotlib/lib/matplotlib/collections.py Lines 894 to 900 in a9c9620
set the original colors again. Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the investigation, I have opened #24479 to track this. Sorry for sending you down this rabbit hole, perhaps this was not so easy after all... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, it was fun. But now with the question on hand, this bug stops me to run |
||||||||||||||||||||||||
|
@@ -1086,7 +1093,8 @@ 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 [not colors.same_color(color, colors.to_rgba("none")) | ||||||||||||||||||||||||
for color in self._crosses.get_facecolors()] | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def on_clicked(self, func): | ||||||||||||||||||||||||
""" | ||||||||||||||||||||||||
|
@@ -1100,6 +1108,57 @@ def disconnect(self, cid): | |||||||||||||||||||||||
"""Remove the observer with connection id *cid*.""" | ||||||||||||||||||||||||
self._observers.disconnect(cid) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@_api.deprecated("3.7") | ||||||||||||||||||||||||
@property | ||||||||||||||||||||||||
def rectangles(self): | ||||||||||||||||||||||||
tacaswell marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
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._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") | ||||||||||||||||||||||||
@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) | ||||||||||||||||||||||||
if not hasattr(self, "_rectangles"): | ||||||||||||||||||||||||
with _api.suppress_matplotlib_deprecation_warning(): | ||||||||||||||||||||||||
_ = self.rectangles | ||||||||||||||||||||||||
return self._lines | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
class TextBox(AxesWidget): | ||||||||||||||||||||||||
""" | ||||||||||||||||||||||||
|
@@ -1457,8 +1516,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") | ||||||||||||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.