Thanks to visit codestin.com
Credit goes to github.com

Skip to content

More generic value snapping for Slider widgets #18569

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

Merged
merged 4 commits into from
Dec 3, 2020

Conversation

ianhi
Copy link
Contributor

@ianhi ianhi commented Sep 25, 2020

PR Summary

Closes: #18562

Adds a valsnap argument to the Slider widget to allow specifying the values the slider should snap to. This overrides the valstep argument.

I also made a new slider demo to show the use of valstep and valsnap as I think they would get lost in the current slider demo. For a point of context I have read through the existing tutorial multiple times, but never managed to encode that valstep existed until I started working on this PR

  • I used searchsorted instead of argmin as it is significantly faster.
  • This works for idx=0 because then the indexing just becomes -1 and we compare the largest and smallest elements of the array.

PR Checklist

I have a few questions here:

  1. Documentation:
    a. There isn't any narrative docs on widgets to add this to?
    b. Does this deserve it's own example (perhaps named slider valstep and valsnap demo) it feel weird to cram into the current
  2. naming. I chose valsnap because it was consistent with the other kwargs but if there are better ideas I'm happy to change it.
  3. Should this get both a whats_new and a next_api_changes?
  4. To get the number for next_api_changes is the best strategy to look through both open and closed PRs right before opening this?
    • Or I guess do what I've done here and wait until after opening the PR
  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (run flake8 on changed files to check).
  • New features are documented, with examples if plot related.
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • Conforms to Matplotlib style conventions (install flake8-docstrings and pydocstyle<4 and run flake8 --docstring-convention=all).
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • [N/A] API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).

@@ -330,6 +334,11 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
self.valmin = valmin
self.valmax = valmax
self.valstep = valstep
self.valsnap = valsnap
if self.valsnap is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a great pattern because now if valsnap is None, we silently drop the user's input for valstep on the floor. This is the sort of thing that causes the greatest anger at libraries, you change the thing AND NOTHING HAPPENS.

We should find a way to either fold valstep and valsnap into one parameter or raise if both are passed.

Folding them into one parameter seems ok (there is a bit of ambiguity else where about plural) and an the code shows, you can generate the snap location from a step. In other cases where we have colliding kwargs we raise (e.g. norm vs density in hist).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah indeed. Are you expressing a preference for a single kwarg here? I'm 55/45 towards two kwargs (appropriately ordered) and raising an error if both are passed. I think there is also an opportunity for user anger when they discover that a feature exists but was not very discoverable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed a change so it is two kwargs and raises an error if both are given. If need be I'm happy to move to a single valstep kwarg

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just do this above, before the assignment, as if valstep is not None and valsnap is not None:...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the ValueError up to be with the other ValueErrors

@@ -255,7 +255,7 @@ class Slider(AxesWidget):
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', **kwargs):
valsnap=None, orientation='horizontal', **kwargs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are adding a new argument it has to go at the end (or better yet be keyword only!). This in an API break as if there is someone (admittedly making a poor coding choice) of passing in 13 positional arguments, valsnap would get the string intended for orientation and likely blow up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yikes. But yes ok, I suppose having millions of users nearly guarantees that that is happening somewhere.

Copy link
Member

@tacaswell tacaswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 in principle, but the API needs some tweaking.

@tacaswell
Copy link
Member

  • Just needs a whats_new (assuming we un-break the signature change) as no existing code should be broken by it.
  • if you are changing the API odds are close to one it will take atleast 1 round of review so open the PR, get number, then add file
  • adding narrative docs about widgets would be great (but maybe in a different PR)

@tacaswell tacaswell added this to the v3.4.0 milestone Sep 25, 2020
@ianhi ianhi force-pushed the slider-snap-values branch 2 times, most recently from 7334e31 to 1055bce Compare September 25, 2020 21:45
@ianhi
Copy link
Contributor Author

ianhi commented Sep 25, 2020

While I'm at it would it be ok to add a kwarg that would allow for eliminating the red line that marks the initial value?
image

Something like markinit as a boolean. It could also be nice to be able to change the color of that line so another approach could be to have init_mark_color that accepts a color or None and if None then the line is not drawn?

The color and whether is is drawn is currently hardcoded here:

if orientation == 'vertical':
self.poly = ax.axhspan(valmin, valinit, 0, 1, **kwargs)
self.hline = ax.axhline(valinit, 0, 1, color='r', lw=1)
else:
self.poly = ax.axvspan(valmin, valinit, 0, 1, **kwargs)
self.vline = ax.axvline(valinit, 0, 1, color='r', lw=1)

@ianhi
Copy link
Contributor Author

ianhi commented Oct 23, 2020

I also added another kwarg initcolor that can be use to specify the previous hardcoded color of the line marking the initial slider position. It also accepts None to not draw the line.

@ianhi ianhi force-pushed the slider-snap-values branch 2 times, most recently from 24fdcd4 to 70ff4cd Compare October 30, 2020 19:42
@ianhi ianhi force-pushed the slider-snap-values branch 2 times, most recently from ade813e to fc4acd6 Compare October 31, 2020 21:56
@ianhi
Copy link
Contributor Author

ianhi commented Oct 31, 2020

To keep things more manageable, I propose to defer changing set_val() to a separate PR.

👍 I dropped that commit. Otherwise I can't think of anything else to change here.

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only minor style comments.

@ianhi ianhi force-pushed the slider-snap-values branch from fc4acd6 to 41bdc05 Compare November 1, 2020 00:11
@ianhi
Copy link
Contributor Author

ianhi commented Nov 1, 2020

I also added changing the color of the bar for one of the sliders to increase the discoverability of that option.

@ianhi ianhi force-pushed the slider-snap-values branch from 41bdc05 to caa3f42 Compare November 1, 2020 00:14
@ianhi
Copy link
Contributor Author

ianhi commented Nov 1, 2020

Does this need another review from @tacaswell given their prior changes requested?

@timhoffm
Copy link
Member

timhoffm commented Nov 1, 2020

Every code PR needs 2 positive reviews. Ideally, @tacaswell would do the second review because he has requested changes. But if he doesn't have time any other reviewer is fine. I take the authority to clear the change request if needed.

Comment on lines 305 to 307
initcolor : color or None, default: 'r'
The color of the line at the *valinit* position. If None then
no line will be drawn.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we use None to mean default. We also already have 'none' to mean no colour; do we really need to support None here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find using None to mean "don't draw" and colors otherwise to be the most intuitive api. I'd also worry about the discoverability of using 'none' for example: I'm an advanced user but did not know that you could specify 'none' as a color.

Usually we use None to mean default

I made the default 'r' as that's what was hardcoded, but I think it would make more sense for None to be the default and specifiying a color otherwise. I don't really see the usecase for drawing a line at the starting position, but I guess this edges into personal preference rather than logic and additionally is maybe backwards incompatible. What about switching the default to None?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None means "default" unless we really can't help it. I you want the default to be "none" I guess that could work for you....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha - thanks. It probably is the least friction to as 'r' and not accept None so I'll do that + leave a note in the docstring to use 'none' if you don't want it to be visible.

@ianhi ianhi force-pushed the slider-snap-values branch from caa3f42 to 0baa16b Compare November 8, 2020 03:48
@ianhi
Copy link
Contributor Author

ianhi commented Nov 8, 2020

I add a reference to the new example - and while doing so added the references to the other widget examples except for menu.py which doesn't actually use any widgets and i was less sure what to reference.

plus lint snapping example
@ianhi ianhi force-pushed the slider-snap-values branch from 18b6d1d to bafcca9 Compare November 12, 2020 03:17
@QuLogic QuLogic dismissed tacaswell’s stale review December 3, 2020 05:30

API break is fixed.

@QuLogic QuLogic merged commit c1861b0 into matplotlib:master Dec 3, 2020
@ianhi ianhi deleted the slider-snap-values branch December 3, 2020 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow slider valstep to be arraylike
5 participants