Skip to content

FIX: minor log ticks overwrite #13126

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
Jan 11, 2019
Merged

Conversation

jklymak
Copy link
Member

@jklymak jklymak commented Jan 7, 2019

PR Summary

  • needs tests...

attempting to fix log overprint issue (closes #13112)

import matplotlib.pyplot as plt

fig, axs = plt.subplots(3,3, figsize=(8, 8), constrained_layout=True)

axs[0,0].semilogy([10000, 2500], [10000, 10010])
axs[0,1].semilogy([10000, 2500], [10000, 10100])
axs[0,2].semilogy([10000, 2500], [10000, 11000])

axs[1,0].semilogy([10000, 2500], [10000, 25000])
axs[1,1].semilogy([10000, 2500], [10000, 55000])
axs[1,2].semilogy([10000, 2500], [10000, 85000])

axs[2,0].semilogy([10000, 2500], [10000, 100000])
axs[2,1].semilogy([10000, 2500], [10000, 120000])
axs[2,2].semilogy([10000, 2500], [10000, 850000])

#ax.set_yticks([10000, 20000, 25000])
plt.show()

Before:

baras

After

booooo

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@anntzer
Copy link
Contributor

anntzer commented Jan 7, 2019

I think the change on ticker.py's side should look something like

diff --git i/lib/matplotlib/ticker.py w/lib/matplotlib/ticker.py
index 89e077a04..84bd9a2b4 100644
--- i/lib/matplotlib/ticker.py
+++ w/lib/matplotlib/ticker.py
@@ -2093,9 +2093,7 @@ def is_decade(x, base=10):
 
 
 def is_close_to_int(x):
-    if not np.isfinite(x):
-        return False
-    return abs(x - round(x)) < 1e-10
+    return abs(x - np.round(x)) < 1e-10
 
 
 class LogLocator(Locator):
@@ -2256,7 +2254,11 @@ class LogLocator(Locator):
             # If we're a minor locator *that expects at least two ticks per
             # decade* and the major locator stride is 1 and there's no more
             # than one minor tick, switch to AutoLocator.
-            return AutoLocator().tick_values(vmin, vmax)
+            ticklocs = AutoLocator().tick_values(vmin, vmax)
+            # Don't overstrike the major labels.
+            ticklocs = ticklocs[
+                ~is_close_to_int(np.log(ticklocs) / np.log(b))]
+            return ticklocs
         return self.raise_if_exceeds(ticklocs)
 
     def view_limits(self, vmin, vmax):

after which I don't know if the other changes (touching labelOnlyBase) are needed?

@jklymak
Copy link
Member Author

jklymak commented Jan 7, 2019

Yeah that might be better.

Not sure we should be implying that a locator is a minor locator. My preference would be that we just explicitly pass the major locator instance to anything we want to be a minor locator and then do a set diff between the two locators Or simply doing this in the axis code after the fact where we know about both locators.

@anntzer
Copy link
Contributor

anntzer commented Jan 7, 2019

The log locator code already has "magic" to guess whether it's a minor or a major locator; given that it's already assuming that it works we may as well rely on it.
As argued in a bunch of other places it is indeed suboptimal that tickers don't really know whether they are major or minor, but adding more logic to clean that up on the axis side seems worse.

@jklymak
Copy link
Member Author

jklymak commented Jan 7, 2019

I'd argue that the "magic" is probably a bad idea because it relies on the user knowing the magic when they specify the Locator, and as we have seen, even the developers don't get the "magic" right.

I think there are two approaches - 1) tell the locator explicitly what ticks to avoid with an optional major_ticks=None kwarg, or 2) get the ticks from both locators, and then exclude any overlaps in the minor ticks.

I guess I think 1) is the best because I can think of cases where the minorLocator might want to exclude a tick because it is close but not equal to a major tick. I don't see why we can't impliment this as a Locator._remove_duplicates method that defaults on all Locators to removing all the duplicates in the __call__. If child classes want to ignore all this they can, so I don't think it'd be a backwards incompatibility issue.

@jklymak
Copy link
Member Author

jklymak commented Jan 7, 2019

(BTW implimenting your change to squash the bug, the other discussion is more long-term)

return AutoLocator().tick_values(vmin, vmax)
ticklocs = AutoLocator().tick_values(vmin, vmax)
# Don't overstrike the major labels.
ticklocs = [t for t in ticklocs if
Copy link
Contributor

Choose a reason for hiding this comment

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

eh, that needs to use boolean indexing, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what you mean? Your version didn't work because ticklocs is an array and is_close_to_int only works on a single value. This seems to work...

Copy link
Contributor

Choose a reason for hiding this comment

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

My patch also fixed is_close_to_int to be vectorized: it deleted the lines

-    if not np.isfinite(x):
-        return False

you may have missed that?
(if x is not finite, the expression below will also be false).

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, my fault.

@timhoffm
Copy link
Member

timhoffm commented Jan 7, 2019

I think there are two approaches - 1) tell the locator explicitly what ticks to avoid with an optional major_ticks=None kwarg, or 2) get the ticks from both locators, and then exclude any overlaps in the minor ticks.

I would name this exclude_ticks=None, which is more general. It may take a list of values and/or (possibly also later implemented) a locator.

@jklymak jklymak changed the title WIP/FIX: minor log ticks overwrite FIX: minor log ticks overwrite Jan 7, 2019
@jklymak jklymak force-pushed the fix-minor-log branch 2 times, most recently from 6e15b71 to 5c9d3bd Compare January 9, 2019 18:57
@jklymak jklymak added this to the v3.0.3 milestone Jan 9, 2019
# Don't overstrike the major labels.
ticklocs = ticklocs[
~is_close_to_int(np.log(ticklocs) / np.log(b))]
print('ticklocs', ticklocs)
Copy link
Contributor

Choose a reason for hiding this comment

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

remove this line

@@ -2250,13 +2248,19 @@ def tick_values(self, vmin, vmax):
ticklocs = b ** decades

_log.debug('ticklocs %r', ticklocs)
print('subs', subs, have_subs, ticklocs)
Copy link
Contributor

Choose a reason for hiding this comment

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

remove this line

Copy link
Member Author

Choose a reason for hiding this comment

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

Oooops, sorry

Copy link
Contributor

@anntzer anntzer left a comment

Choose a reason for hiding this comment

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

I guess that can count as Jody's approval of my patch :)

ticklocs = AutoLocator().tick_values(vmin, vmax)
# Don't overstrike the major labels.
ticklocs = ticklocs[
~is_close_to_int(np.log(ticklocs) / np.log(b))]
Copy link
Member

Choose a reason for hiding this comment

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

This looks like a hard-coded assumption on the position of the major ticks. Will this fail if I’ve set non-default values no the major locator?

Copy link
Member Author

Choose a reason for hiding this comment

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

Absolutely. See discussion above for fixing that. But that assumption is already in the code, this just makes that assumption work properly under all conditions. The next step is to get rid of the assumption, but thats a bigger project.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, that's reasonable for now. However, it would be good to explicitly document this in the code with a comment (unless you're immediately following up with the full fix).

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure thing

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.

Anybody can merge after CI pass.

@anntzer anntzer merged commit 71dd3ae into matplotlib:master Jan 11, 2019
@lumberbot-app
Copy link

lumberbot-app bot commented Jan 11, 2019

Owee, I'm MrMeeseeks, Look at me.

There seem to be a conflict, please backport manually. Here are approximate instructions:

  1. Checkout backport branch and update it.
$ git checkout v3.0.x
$ git pull
  1. Cherry pick the first parent branch of the this PR on top of the older branch:
$ git cherry-pick -m1 71dd3ae125d060bc08193abb93cca643245e3de0
  1. You will likely have some merge/cherry-pick conflict here, fix them and commit:
$ git commit -am 'Backport PR #13126: FIX: minor log ticks overwrite'
  1. Push to a named branch :
git push YOURFORK v3.0.x:auto-backport-of-pr-13126-on-v3.0.x
  1. Create a PR against branch v3.0.x, I would have named this PR:

"Backport PR #13126 on branch v3.0.x"

And apply the correct labels and milestones.

Congratulation you did some good work ! Hopefully your backport PR will be tested by the continuous integration and merged soon!

If these instruction are inaccurate, feel free to suggest an improvement.

@timhoffm
Copy link
Member

Automatic backport fails due to #12865 not being backported. We should either backport both or none.

@tacaswell tacaswell modified the milestones: v3.0.3, v3.1 Jan 12, 2019
@tacaswell
Copy link
Member

Moved both to 3.1. The logic around the log ticks is subtle and probably better to keep changes to it for minor release (not patch releases).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LogNorm colorbar prints double tick labels after set_ticks()
5 participants