-
-
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
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?
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. |
lib/matplotlib/widgets.py
Outdated
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)
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! Just went through the discussion too. Don't know how I missed the setter. Will update this!
PS: @anntzer do you mind if I also update the radio button implementation? Or should that be a different PR?
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.
Sure, please do it here too.
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). |
Fair enough. I missed that we did this for radio buttons and I am biased towards the minimal changes to solve the problem. The consensus is clearly in favor of this so I take back my comment. |
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 would rather not change the test image, see how I ensured that it still uses the old radiobuttons. We can always reconsider and change both at the same time once the deprecation elapses, but the better way to test the new version is probably to use check_figures_equal like I did in test_radio_buttons.
Ah gotcha. I'll take a look at your PR and do that. Thanks. |
The failing check is unhappy because I changed an image and reverted the test image for |
Docs test is because we fail on warnings and you got this warning: which is a poor interaction with an old changelog entry which didn't use the full name of a reference. This line should probably be updated to simply be anywho, because you are defining a function called "lines" and that reference was not fully specified, it had worked because there was only one target for "lines" previously, but now there is more than one. |
As for the PR cleanliness check, are you familiar with here is an article that goes over the basics, probably better than I could in an off hand github comment (though admittedly I only skimmed this article to make sure it wasn't outright incorrect): https://thoughtbot.com/blog/git-interactive-rebase-squash-amend-rewriting-history Feel free to ask any additional clarifying questions, its a relatively complicated (and powerful) edge case of Essentially rather than committing, changing and then reverting that image in separate commits, squashing out the changes to the image entirely will make it so that there is not an extra version of the image in the canonical git history. |
Ah okay. That makes sense. Though, should I do anything in this PR to resolve that? That's not close to anything I'm working on here. |
Yes, I'm familiar with rebase, just did not want to go rebase stuff without knowing if that was fine since some people prefer not to do that. I'll rebase the commits to remove the image issue. Thanks! |
since its a one line change that while it is unrelated to what you were directly working on, it is elevated to a warning by what you were doing, I'd just go ahead and fix it here to keep CI happy rather than opening a separate PR |
31e9a1f
to
d2b175f
Compare
PR matplotlib#24474 introduces a (to be deprecated) property called `lines`. Because of that, the documentation in a previous api change doc which just used `.lines` instead of :mod:`matplotlib.lines` couldn't be built properly as it was unable to resolve the names. This changes the old doc to the correct module link.
lib/matplotlib/tests/test_widgets.py
Outdated
with pytest.warns(DeprecationWarning): | ||
# Trigger old-style Rectangle check boxes | ||
cb.rectangles | ||
cb.lines |
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 line is never reached. You need to put in in a separate warns()
context.
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 cb.lines
line?
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.
Yes.
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.
Ah okay, thanks. I realized a funny thing then. Accessing rectangles is all that's required since that's all that changes from the older reference image with the rectangles. The line crosses and the scatter crosses are the exact same. Anyway, I've updated it, thanks!
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 think you'd need to create a new CheckButtons
object to check that the cb.lines
is falling back correctly.
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.
We should still check both, I think. Also, does accessing the other object trigger a second warning unnecessarily?
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.
No yeah, I agree about checking both, just that they should be different independent tests and not on this one. About the second warning, yeah, that's a good point. Do you know of a straight-forward way to call the lines
property from inside rectangles
without triggering the API deprecation decorator warning?
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.
You can wrap it in a _api.suppress_matplotlib_deprecation_warning
context, or put the code in a separate private function that's called from both.
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'll take a look at that. Thanks, did not know about the context!
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.
@QuLogic I've updated and added 2 tests for checking the fallbacks. Let me know if this makes sense now!
@QuLogic let me know when you get a chance to look at this. Thanks! |
Running this example: https://github.com/matplotlib/matplotlib/blob/main/examples/widgets/check_buttons.py The |
I'm going to push this to 3.8, but if it is indeed ready by the end of the year I am 👍🏻 on it getting merged and being re-milestoned to 3.7. |
That issue was not supposed to be there anymore....taking a look right away. Sorry this is taking too long, I keep missing small things with this one for some reason. |
@tacaswell corrected that. Everything seems to be working locally. Can you check once? Thanks! |
No changes to test images are left.
@chahak13 Could you squash this to a smaller number of commits? |
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.
42b4a89
to
ba30bdf
Compare
That should do it. I've kept the major, important commits and squashed all changes into them. |
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 think I'm okay with this now, but you may want to squash it down again?
dbd882b
to
788a0d1
Compare
@QuLogic done. Should be good. |
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
pytest
passes)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.rst
ornext_api_changes/README.rst