Conversation
|
Ah, about the tests: Not all tests ran succesfully on my machine, but all the ones that fail also do so on |
There was a problem hiding this comment.
Thank you for opening your first PR into Matplotlib!
If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks.
You can also join us on gitter for real-time discussion.
For details on testing, writing docs, and our review process, please see the developer guide
We strive to be a welcoming and open project. Please follow our Code of Conduct.
|
Leaving this comment to ping you guys, as it's been more than a week since I opened this PR. :-) |
|
Leaving another ping-comment, as it's been almost six weeks. |
|
Sorry there hasn't been much discussion here. I've put this on the agenda for tomorrow's meeting. |
|
Sorry I meant to comment last week after the meeting, but it seemed to have been left here without going through. In general, we are in favour of this change. However, we would like to see that it be behind a flag of some sort, either a) an argument to the class init, or b) an rcParam, or c) both. Sometime in the future, it may make sense to flip that flag (which I would be in favour of), but it should be tested out a bit first.
I think I've seen an issue related to this somewhere and will try to find it.
It was probably an artifact of whoever implemented things, but then was refactored to share code and now it seems inconsistent. But now it can't easily change for backwards compatibility reasons.
Unfortunately we do, so it will have to be reverted. In some cases, we might want to start making arguments keyword-only, but that does require going through a deprecation cycle.
It's probably fine here, but if you have some easy way to show that difference outside of the other changes, it might be quicker for review as a separate PR.
This is set by the classic style, which is the default for all tests, though new tests should start using
I think this might have moved here. @story645 I guess we need to update the pull request template for some of the recent refactors? |
is something like this clearer? if not or you have a suggestion, please let me know at #27641 |
|
Thanks for your comments! I think I have everything to make final adjustments now. I'm quite busy at the moment, but I will try and get that done soon. |
|
Does this also close #10369? |
|
Hi @schtandard , are you interested in finishing this up? If you need help, let us know. Cheers! |
|
Hi @melissawm, thanks for the ping and sorry for the long delay; a stressfull period of my life dragged on longer than I had expected. It finally concluded last month, though, so I hope I'll now be able to wrap this up. In fact, I already took a quick look recently to see what's left to do. I'll have another crack and try to incorporate all the things mentioned above, hopefully without as much delay this time. |
- Use the same logic to calculate numticks in LogFormatter.set_locs() as later in LogLocator.tick_values(). - Do not hard-code n_request == 9 in minor tick check. - Correct a type in an explanatary comment (< instead of <=). - Actually calculate the largest possible stride, as outlined in the comments. - The check for lonely ticks at the end of LogLocator.tick_values() assumed that major ticks are not also minor ticks. Fix that logic.
The new SymmetricalLogLocator emulates the behavior of LogLocator in the logarithmic region and extends its behavior to the linear part in a (hopefully) reasonable manner. The change is hidden behind a new rcParam (and corresponding class parameters) for now.
- For SymLogLocator, do not use a hard-coded limit preventing minor ticks. Instead, always use the same behavior. - For LogLocator, revert to the old behavior with the hard-coded limit of 10 available major ticks.
9cc5db9 to
11a3079
Compare
|
Alright, I gave it another go. The tick placement for log axes had changed since I first tackled this, so I basically re-wrote the symlog stuff to match. I re-named the old branch to
No documentation changes or release notes are included, yet. Once the API is set, I will have a look at what's necessary there. Code to reproduce.import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
mpl.rcParams['axes.formatter.legacy_symlog_ticker'] = False
def showcase_scales(axs, *plotargs, **plotkwargs):
for ax, scale in zip(axs, ['linear', 'symlog', 'log']):
ax.set_yscale(scale)
ax.yaxis.grid()
ax.plot(*plotargs, **plotkwargs)
values = [
np.linspace(3, 150),
np.linspace(20, 60),
np.linspace(0.5, 12),
np.linspace(0.5, 5),
np.linspace(0.6, 3),
np.linspace(1e-4, 1e-2),
np.linspace(0, 40),
np.linspace(-0.8, 0.9),
np.linspace(-4, 4),
np.linspace(-22, 22),
np.linspace(0, 0),
np.linspace(1, 1),
np.linspace(2, 2),
np.linspace(10, 10),
np.linspace(13, 13),
]
fig, axs = plt.subplots(len(values), 3, squeeze=False, sharex=True,
figsize=(9, 2 * len(values)))
for a, v in zip(axs, values):
showcase_scales(a, v)
axs[0, 0].set_title('linear')
axs[0, 1].set_title('symlog')
axs[0, 2].set_title('log')
fig.savefig('showcase.png', bbox_inches='tight') |
|
I would like to discuss whether we need a flag and the legacy behavior. Do we make / want to make guarantees on tick placement? Does anybody depend on reproducible exact positioning of ticks? #29698 has been merged as a strict improvement without the ability to keep the old behavior. We should consider doing the same here (and probably mention this change here in the #29698 what's new message). |
I'm not sure that I am meant to participate in this discussion, but since there have been no comments so far, let me note that I agree that this could be merged as a strict improvement much like #29698. Removing the relevant class parameters and rcParam is easy and would simplify the code a bit. @QuLogic has your reasoning from back in the day changed or do you still see reasons to hide the change behind a flag for now? |
|
That was the conclusion from the meeting; I do not recall the discussion at the time for why, and unfortunately the meeting notes do not have any detail. I'll note that @timhoffm was not in that meeting; perhaps we should re-discuss this at the next meeting? |
|
Sure, we can discuss. I’ll put my opinion in already here. So far we have done several changes to ticks without backs compatibility options, e.g. #29698, #29054, #18754, #13069 (and possibly more). We haven’t seen any complaints on these improvements/fixes of tick positions so likely nobody relies on them (or they can cope with changes). Therefore, flags just for the sake of keeping old behavior in tick locations seem like unnecessary complexity. |




PR summary
In the current implementation,
symlogaxes only get major ticks by default. Depending on the data range, this can lead to too few or even no ticks at all. When "subticks" between decades are specified manually, those are sometimes labeled in an unintended manner (when they are at a decade).This PR re-implements the tick placement and formatting for
symlogaxes to remedy these problems. If I am not mistaken, this closes #17402, closes #18757 and closes #25994.I am submitting this as a PR now to get some general feedback on the approach and pointers if I need to consider anything regarding style or conventions that I missed. If I get a thumbs up, I would polish some things off (like making
_SymmetricalLogUtil's methods work withnp.arrays and have.firstdec()cache results) before merging.My approach for the tick locator
I was aiming for
SymmetricalLogLocatorto behave identically toLogLocatoras long as the data range is outside of the linear part of the scale and extend this reasonably to the linear part if it's present. What I came up with is this (I'm describing the positive half axis here for simplicity, everything works the same on the negative side):LogLocator, the only caveat being that 0 should always receive a tick if it is in the range.LogFormatter(which is also used forsymlogaxes) is adapted accordingly._firstsublabelsfor placing sublabels below the first decade.logaxes it can sometimes happen that there is only one major tick with unlabeled minor ticks around it. This is fine (one can still identify the unlabeled positions) as long as the major tick is at some decade and not at 0, which can happen withsymlogaxes. In that case, we need to label at least one of the minor ticks to allow reading values off the axis.Since both the locator and the formatter need the calculation of the first decade I placed the code regarding this into a helper class
_SymmetricalLogUtil.Finally,
SymmetricalLogScaleis adapted to actually use the new functionality by default.I checked all parameter ranges I could think of and this seems to work out quite nicely.
This is how it looks.
For comparison, this is how it used to look.
Code to reproduce.
Requested feedback
There are some details regarding which I would like feedback/guidance from you.
stride(i.e. the number of decades between major ticks) a possible forced tick at 0 is not considered. This may lead to an off-by-one error regardingnumticks(i.e. astridevalue smaller by one would have been possible) but I feel like preventing this is not worth the complication of taking it into account. Do you agree?subs=Noneis equivalent tosubs='auto'inLogLocatorbut tosubs=(1.0,)inSymmetricalLogLocator. Is that on purpose? I left it like that.basecame afterlinthreshinSymmetricalLogLocator's signature, contrary to most other occurences. In addinglinscaleI reordered those. While this is strictly a backwards incompatible change (whereas I just appendedlinscaleeverywhere else), I don't expect it to have ever been used in an incompatible way: providingbaseonly make sense whentransform(the first argument) is not given, so it's most probably always been don by keyword argument, though it is in principle possible to saySymmetricalLogLocator(None, None, 2, 10)to getSymmetricalLogLocator(linthresh=2, base=10). Should I switch it back for backwards compatibility or do we care more about consistent argument order?LogFormatterthrew away the sign of any value, even though it is used for negative values withsymlogaxes. This was no problem becauseLogFormatterMathtextoverwrites__call__and preserves the sign, so it didn't actually show up on any axis (with the default configuration). I fixed this. Is it fine to just leave it in this PR or should it be a separate one?test_ticker.pythe testTestSymmetricalLogLocator.test_extending()relies onrcParams['axes.autolimit_mode']being set toround_numbersbut this is not set up in the test itself. I assume that this configuration is left in place by some previous test, though I can't tell where. Is this by design or should anrc_contextbe used, like in some other tests?linscaleargument to the locator and the formatter as well as the changed default value ofSymmetricalLogScale'ssubsargument qualify as, I think) with a directive and release note, but the linked section does not seem to exist. What do I need to do?PR checklist