5 minor divisions when major ticks are 2.5 units apart#13069
5 minor divisions when major ticks are 2.5 units apart#13069efiring merged 1 commit intomatplotlib:masterfrom
Conversation
|
As a side note, I personally feel that major ticks spaced 1 unit apart are more naturally divided into 4 minor divisions (spaced 0.25 units apart) instead of the current 5 minor divisions (spaced 0.2 units apart). If others agree, I can adjust that in this PR. |
|
I‘m also positive on on changing the subdivision of 1 unit from 5 to 4. |
|
Thanks @timhoffm. I'll wait a bit to see if there's anyone opposed. |
|
I'm not necessarily "opposed", but this seems to be special-casing a major tick interval you had to specify by hand. I don't know that its reasonable to special case all the possible manual tick intervals folks might give us in the minor tick code. If you specify the major ticks by hand, I think its reasonable to specify the minor ticks by hand. But maybe I'm missing a subtlety whereby this is more useful in general. |
|
Ah, sorry, I see that example "masks" the fact that 2.5 is one of the default major tick intervals. It's just more "crowded" so I tried to make it clearer, sacrificing the point that 2.5 major tick interval is one of the defaults. |
|
Ah, OK, thats different. Thanks! |
lib/matplotlib/ticker.py
Outdated
| majorlocs_no_exponent = 10 ** (np.log10(majorstep) % 1) | ||
|
|
||
| # round to closest 0.5 | ||
| majorlocs_no_exponent = round(majorlocs_no_exponent * 2) / 2 |
There was a problem hiding this comment.
I'm not sure if we want to round 2.4 (for instance) to 2.5 do we?
There was a problem hiding this comment.
majorlocs_no_exponent is only used to decide if there should be 4 or 5 minor intervals. The rounding is there to take care of floating point inaccuracies so the major tick step interval can be searched for in the following line (number 2627).
There should be no effect on the major ticks (if that's your concern).
P.S. I just realized that the name of that variable is wrong - it should be majorstep_no_exponent. I'll fix that.
There was a problem hiding this comment.
No it’s that you are rounding 2.4 to 2.5 so if that was the major tick the minor ticks will now be different. I think you want to use np.isclose or some such to check and allow some float inaccuracies.
There was a problem hiding this comment.
Ah, ok. Thanks. Replaced the round with np.isclose.
lib/matplotlib/tests/test_ticker.py
Outdated
|
|
||
| # make sure we're checking all major steps in ticker.AutoLocator | ||
| majorsteps = [x[0] for x in majorstep_minordivisions] | ||
| assert np.allclose(majorsteps, mticker.AutoLocator()._steps) |
There was a problem hiding this comment.
I wanted to make sure that the major steps in majorstep_minordivisions are the ones in AutoLocator()._steps with the goal that test_number_of_minor_ticks will fail if that's not true. So I added this assert outside any test function.
But if this assert fails pytest emits an error in the collection phase and doesn't run any tests.
Any suggestions how to improve this?
There was a problem hiding this comment.
Just move it to its own test with the comment that it's really intended to test that the next test is correctly parametrized.
There was a problem hiding this comment.
Done - thanks.
I created a staticmethod, because it's the only way I found for the parameters to be accessible both in a test and by the @pytest.mark.parametrize decorator.
There was a problem hiding this comment.
Just make it a class variable like params just above?
55135d6 to
d079d67
Compare
lib/matplotlib/ticker.py
Outdated
|
|
||
| majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1) | ||
|
|
||
| # The part after the 'or' is for #13069 to make minimal changes to |
There was a problem hiding this comment.
Please make the comment understandable without refering to github (perhaps just describe the desired behavior instead of refering to the behavior change).
There was a problem hiding this comment.
Removed comment as it's no longer relevant.
lib/matplotlib/ticker.py
Outdated
| # behaviour | ||
| if np.isclose(majorstep_no_exponent, 2.5) or ( | ||
| (not np.isclose(majorstep_no_exponent, [1, 10]).any() and | ||
| int(np.round(majorstep_no_exponent)) in [1, 5, 10])): |
There was a problem hiding this comment.
The two conditions (not close to 1 or 10, but possibly in [1 or 10]) seem contradictory.
Given that you are already going to use round(), I would just use its ability to round to one decimal point:
majorstep_no_exponent = round(10**(np.log10(majorstep) % 1), 1)
# (in fact the following looks more natural to me, but heh)
majorstep_no_exponent = round(majorstep / 10**int(np.log(majorstep)), 1)
if majorstep_no_exponent in [2.5, 5]: ...
else: ...
There was a problem hiding this comment.
Thanks @anntzer for the comments.
- The conditions are not close to 1 or 10 and when rounded are in [1 or 10], so not always contradictory (for example 1.1 satisfies both).
I was trying to make as minimal a change to the number of minor ticks calculation.
Rounding to 1 decimal place:
if round(majorstep_no_exponent, 1) in [2.5, 5]:
ndivs = 5
else:
ndivs = 4
will change the number of minor divisions from 5 to 4 for major ticks spaced 1.1 units apart (for example), compared to the situation before this PR. @jklymak indicated this is undesirable. The code is a bit ugly because of this, though.
- I think the calculation used
%in order to guarantee 1 <= majorstep_no_exponent <= 10 because that's the range of the steps in AutoLocator.majorstep / 10**int(np.log10(majorstep))can return values outside this range (for example if marjostep is 0.5).
There was a problem hiding this comment.
Re 1: I personally think if we're already changing the minorstep mapping for the common case of 1/5/10-spaced ticks (which appear by default), it's really not a big leap to also change the case of 1.1-spaced ticks (which don't even appear by default). This just needs to be documented... (plus, I'm pretty sure there's a lot of other places in the code which probably don't work so well with "weird" tick spacings like 1.1)
Re 2: Sorry, that should have been m/10**np.floor(np.log10(m)) (int rounds towards zero, but I want to round down), which works for m<1 too. Anyways, the formulas are equivalent, no big deal.
There was a problem hiding this comment.
Simplified condition according to @anntzer's suggestion.
|
I went ahead and adjusted the number of minor ticks from 5 to 4 when the major ticks are spaced 1 or 10 units apart. |
|
Needs a changelog entry too. |
a769bef to
cfa5393
Compare
|
I added an entry in doc/users/next_whats_new/. I hope that's the right place for it. I think that addresses all current comments. |
anntzer
left a comment
There was a problem hiding this comment.
Leaving this open for comments for a bit longer as this is a behavior change, but looks reasonable to me.
lib/matplotlib/ticker.py
Outdated
|
|
||
| majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1) | ||
|
|
||
| if np.isclose(majorstep_no_exponent, [2.5, 5.0]).any(): |
There was a problem hiding this comment.
I do not think that removing 1, 10 from this list is a good idea.
There was a problem hiding this comment.
Changed to the original 5 divisions for 1, 10.
efiring
left a comment
There was a problem hiding this comment.
Please put 1, 10 back in the list for 5 minor ticks.
|
Agreed during the call that this could be merged after restoring the old behavior for 1 & 10. |
|
My preference for 4 divisions for 1 and 10 is not strong (and might be just because that's what I'm used to). But I'd be interested to know why the consensus is that 5 divisions is better for 1 and 10. |
|
cfa5393 to
67b22e4
Compare
|
Thanks for the explanation @efiring. |
Split and expand related test.
67b22e4 to
4e227bd
Compare
|
Anything else I can do to make this PR more mergeable? |
|
The test failure was unrelated, so I went ahead with the merge. Thank you for the contribution. |

PR Summary
Currently when the major ticks are spaced 2.5 units apart there are 3 minor ticks spaced 0.625 units apart.
This PR increases the number of minor ticks to 4, spaced 0.5 units apart. To me, this is a much more natural spacing.
Before:

After:

PR Checklist