Skip to content

FIX: improve symlog ticker #21096

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

Closed
wants to merge 145 commits into from
Closed

Conversation

jklymak
Copy link
Member

@jklymak jklymak commented Sep 16, 2021

PR Summary

SymLogLocator is supposed to not show any ticks in the range [-linthres, linthresh], except for zero. However, the floor call can leave a tick inside the lin threshold region, and this usually looks quite bad.

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(3, 3))
ax.plot(np.linspace(-1, 1, 1000))
ax.set_yscale('symlog', linthresh=0.03)
plt.show()

Before

Before

After:

After

PR Checklist

  • 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 run flake8 --docstring-convention=all).
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).

story645 and others added 30 commits August 26, 2021 23:06
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Fixes matplotlib#20516

PGF's `random steps` decoration seems to be the most similar,
but does not exactly match the behaviour described in matplotlib's docs.
Therefore I repurposed the `randomness` argument as a seed to give
control on how the line looks afterwards.
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Switch documented deprecations in mathtext by `__getattr__` deprecations
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
…_output

API: rename draw_no_output to draw_without_rendering
Make warning for no-handles legend more explicit.
Remove unused HostAxes._get_legend_handles.
- parse() already normalizes prop=None to prop=FontProperties().
- We don't need to explicitly attach a canvas to a plain Figure() before
  calling savefig() anymore; FigureCanvasBase is always attached and
  handles the dispatching to the correct concrete canvas class.
According to the docs, this function was added in GTK 2.2, so probably
was only needed when we supported GTK2.

It also no longer exists in GTK4.
Since these examples don't need to support the very oldest GTK3, I took
the opportunity to rewrite them using the recommended Application-styled
model.
The `Gtk.Window.destroy` doesn't work, but `.close` correctly triggers
our cleanup.
@dstansby
Copy link
Member

dstansby commented Sep 16, 2021

I think the better fix is to change floor to ceil on line 2519 above? (which is what I did at the end of #14309, which never got merged)

@jklymak
Copy link
Member Author

jklymak commented Sep 16, 2021

Maybe? We often return out of bounds ticks so I wasn't sure if that was desired here.

@dstansby
Copy link
Member

What do you mean by out of bounds ticks? I think changing floor to ceil will have exactly the same effect as the current diff, and is makes more sense (in my opinion) when reading the code.

@jklymak
Copy link
Member Author

jklymak commented Sep 17, 2021

Consider

import matplotlib.pyplot as plt 
import matplotlib.ticker as mticker

loc = mticker.LogLocator()
print(loc.tick_values(1e-3, 1e3))

This returns

[1.e-04 1.e-03 1.e-02 1.e-01 1.e+00 1.e+01 1.e+02 1.e+03 1.e+04]

and note that the first and last ticks are (well) outside the vmin and vmax.

@jklymak jklymak marked this pull request as ready for review September 17, 2021 11:09
@dstansby
Copy link
Member

Ah I think that's because the hi lims should be using np.floor, not np.ceil? Essentially it looks like both calls should be switched in lines 2519-2520

@jklymak
Copy link
Member Author

jklymak commented Sep 18, 2021

No, I mean the hi lim uses ceil because we typically want one tick above vmax.

@jklymak jklymak added this to the v3.5.1 milestone Sep 24, 2021
@dstansby
Copy link
Member

Sorry, I got a bit confused. to go back to my original suggestion, changing

lo = np.floor(np.log(lo) / np.log(base))

from floor to ceil should fix the original issue. Using floor currently gives the first tick outside the log range; changing to ceil will give the first tick inside the log range.

@jklymak
Copy link
Member Author

jklymak commented Sep 30, 2021

@dstansby We want one tick below vmin and one tick above vmax, and then we clip them when we draw the axis.

@dstansby
Copy link
Member

In general yes, but that's not what this PR is about - the vmin/vmax of the Axis are distinct from the linear threshold of the ticker. As you say right at the top:

SymLogLocator is supposed to not show any ticks in the range [-linthres, linthresh], except for zero. However, the floor call can leave a tick inside the lin threshold region, and this usually looks quite bad.

Changing the

lo = np.floor(np.log(lo) / np.log(base))

to ceil solves the issue, and is a much simpler and easy to follow piece of code than what is currently proposed.

@jklymak
Copy link
Member Author

jklymak commented Sep 30, 2021

That same code is used above and below the threshold...

@dstansby
Copy link
Member

I don't think we're doing a very good job of convincing each other 😄 - I won't block this, but leave for others to review.

@jklymak
Copy link
Member Author

jklymak commented Sep 30, 2021

Well let me try again: get_log_range is used for both range_a and range_c So the hi part of range_a is thresh, but the hi part of range_c is vmax. So you would want to use floor and for range_a you'd want to use ceil for range_c. We could just get rid of get_log_range and write it all out, but that seemed more invasive than clipping the existing result at thresh.

@dstansby
Copy link
Member

For the a and c ranges the calls are:

get_log_range(abs(a_upper_lim), abs(vmin) + 1)
get_log_range(c_lower_lim, vmax + 1)

The first argument for both the a and c ranges is linthresh (if linthresh is within the Axis limits), so applying ceil to the first argument will have the correct effect for both the a and c ranges, ie. returning the first tick outside the linear range.

@jklymak
Copy link
Member Author

jklymak commented Sep 30, 2021

Oh duh, my apologies, you are absolutely correct. I've fixed it. Sorry for being so dense...

@dstansby
Copy link
Member

Don't worry! Sorry for not explaining very well!

@jklymak
Copy link
Member Author

jklymak commented Sep 30, 2021

Hmm, except that fails symlog2 test. Which I agree I've updated, but I think the update is better than before...

Master

Oldway

Suggested by @dstansby

dstansby

My suggestion

Myway

I guess not of these are quite right (i.e. the first axes should have more than just a zero tick!)

@jklymak jklymak marked this pull request as draft September 30, 2021 11:32
@dstansby
Copy link
Member

Ahh I think this is because of the floating point issues using np.log instead of np.log10 when linthresh lies exactly on a decade. So I think changing log to log10 will fix it for base 10 (in #21177 ut seemed to work for other bases too) , but given this maybe it's better to go back to floor and clipping the ticks after all...

I guess not of these are quite right (i.e. the first axes should have more than just a zero tick!)

From memory, in this case the locator should just end up using the default linear locator?

@tacaswell
Copy link
Member

This PR is affected by a re-writing of our history to remove a large number of accidentally committed files see discourse for details.

To recover this PR it will need be rebased onto the new default branch (main). There are several ways to accomplish this, but we recommend (assuming that you call the matplotlib/matplotlib remote "upstream"

git remote update
git checkout main
git merge --ff-only upstream/main
git checkout YOUR_BRANCH
git rebase --onto=main upstream/old_master
# git rebase -i main # if you prefer
git push --force-with-lease   # assuming you are tracking your branch

If you do not feel comfortable doing this or need any help please reach out to any of the Matplotlib developers. We can either help you with the process or do it for you.

Thank you for your contributions to Matplotlib and sorry for the inconvenience.

@QuLogic QuLogic modified the milestones: v3.5.1, v3.5.2 Dec 10, 2021
@jklymak jklymak modified the milestones: v3.5.2, v3.7.0 Mar 31, 2022
@jklymak jklymak closed this Dec 11, 2022
@jklymak jklymak deleted the fix-symlogscale branch December 11, 2022 21:41
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.