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
Use scatter for check boxes instead of Rectangle #24474
base: main
Are you sure you want to change the base?
Conversation
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.
| if self.drawon: | ||
| self.ax.figure.canvas.draw() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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 set_active(0) in code doesn't change the status of the checkboxes. The issue is arising in these lines. The status changes correctly based on the code that I added but after executing self.ax.figure.canvas.draw(), the colors for all the scatter markers change back to what they were initially set to be, and not the changed ones. This is happening only in the unit tests and not while actually checking it using a GUI backend. Any ideas/suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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 comment
The 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 comment
The 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 comment
The 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:
4 Hz
[[0. 0. 0. 0.]
[0. 0. 0. 1.]
[0. 0. 0. 0.]]
Changing active button from 1 to 0
2 Hz
[[0. 0. 0. 0.]
[0. 0. 0. 1.]
[0. 0. 0. 0.]]
This shows that the facecolors for the buttons don't change even after set_active(0) is called, even though value_selected is correct. The problem arises because of these lines
matplotlib/lib/matplotlib/collections.py
Lines 842 to 845 in a9c9620
| # The flags are initialized to None to ensure this returns True | |
| # the first time it is called. | |
| edge0 = self._edge_is_mapped | |
| face0 = self._face_is_mapped |
Since it sets the flags such that it returns True the first time,
matplotlib/lib/matplotlib/collections.py
Lines 894 to 900 in a9c9620
| else: | |
| self._set_facecolor(self._original_facecolor) | |
| if self._edge_is_mapped: | |
| self._edgecolors = self._mapped_colors | |
| else: | |
| self._set_edgecolor(self._original_edgecolor) | |
| self.stale = True |
set the original colors again. Since update_scalarmappable gets called the first time they're drawn, when creating a figure, all functionalities work as everything works as expected after the second call. Even in this case, if there are any set_active calls after the first one, they work fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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 comment
The 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 get_status for the check boxes correctly. I guess one workaround could be that I maintain an active array similar to value_active in radio buttons but that'll be a hack and I'm on the fence about having an extra array to handle that when it can be done with the colors itself. That'll pass the tests though, and everything works in a figure anyway. Suggestions?
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems unused? (Otherwise I would have asked why it is halved?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a mistake. It's supposed to be used for marker size. As to why it is halved, it's because radio buttons are also half the text size, that's why. I think it's arbitrary.
|
I do not think that going with scatter is the correct approach here. Looking at the internals of matplotlib/lib/matplotlib/patches.py Lines 720 to 726 in a9c9620
I suspect that making sure this gets set on |
|
@tacaswell Just making sure that I'm understanding correctly, but if we do that then we're kinda working on the current implementation itself, right? In that case, we'll also have to make a call on should the checkbox size increase, while maintaining the square shape or not? |
@tacaswell why? Using markers instead of cobbling the box together from basic artists has some appeal. You don't have to bother with aspect. You get pixel snapping. You have only a single artist to change for updating the check state. And you can switch to more complex markers like a check symbol relatively easy. Note also that @anntzer did the same thing in #24455 with radio buttons. |
| 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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point made in #24479 is that editing the output of the getter function is not guaranteed to keep those changes (it works, sometimes, because it is not a copy that is returned, but there are other mechanisms at play)
more properly this would be:
facecolors = self._crosses.get_faceolor()
facecolors[index] = colors.to_rgba("k") # or "none"
self._crosses.set_facecolor(facecolors)This calls the setter which updates the internal state properly
(This also does need to be corrected in the radio button implementation)
|
From a quick look, _aspect_ratio_correction, introduced in #20839 for the rectangle selector widget, seems actually brittle, as 1) it will basically never work for polar plots (not a concern here, but one for rectangle selectors in screen space), and 2) it makes one of the two axises the "main" one relative to the other (I think for circles the radius will effectively be the radius in relative units along the x axis, not along the y axis), whereas the proper thing to do would be to have a radius in pixels (basically proportional to the font size, to make the radio button close to the size of the label). |
ref: #24471
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.
Documentation and Tests
pytestpasses)Release Notes
.. versionadded::directive in the docstring and documented indoc/users/next_whats_new/.. versionchanged::directive in the docstring and documented indoc/api/next_api_changes/next_whats_new/README.rstornext_api_changes/README.rst