Skip to content

MaxNLocator changes the scientific notation exponent with different number of tick labels #12072

Closed

Description

@joelostblom

Bug report

Bug summary

MaxNLocator changes the scientific notation with different number of tick labels.

Code for reproduction

I encountered this unexpected behavior when I changed the y-ticklabel size. Going from size 13 to 14 changes the scientific notation from 1e9 to 1e10, and adds an decimal offset to all the ticklabels.

import matplotlib.pyplot as plt

sci_nums = [10**9, 8*10**9]
plt.rcParams['ytick.labelsize'] = 13
fig, ax = plt.subplots()
ax.scatter(range(len(sci_nums)), sci_nums)

image

plt.rcParams['ytick.labelsize'] = 14
fig, ax = plt.subplots()
ax.scatter(range(len(sci_nums)), sci_nums)

image

This is related to that the larger font size reduces the number of tickslabels. MaxNLocator apparently adjusts the scientific notation depending on the number of ticklabels.

plt.rcParams['ytick.labelsize'] = 13
fig, ax = plt.subplots()
ax.scatter(range(len(sci_nums)), sci_nums)

import matplotlib.ticker as ticker
ax.yaxis.set_major_locator(ticker.MaxNLocator(4))

image

I dont' quite understand the rule that MaxNLocator uses. Setting 1, 2, 4 or 5 ticklabels results in 1e10. Setting 3, 6, 7, 8, or 9 ticklabels results in 1e9.

Expected outcome
I would expect that changing the number of ticklabels (either directly or via another parameter such as the font size) would not result in a change in the formatting of the scientific notation. In this case, I would expect it to remain 1e9 for all the examples above.

Matplotlib version

  • Operating system: Linux
  • Matplotlib version: 2.2.2
  • Matplotlib backend (print(matplotlib.get_backend())): module://ipykernel.pylab.backend_inline
  • Python version: 3.6.6 (conda-forge)
  • Jupyter version (if applicable): jupyterlab=0.34.3, jupyter_core=4.4.0
  • Other libraries:

Activity

  1. dabana commented on Sep 10, 2018

    @dabana
    Contributor

    The integer keyword in MaxNlocator should do what you want. Have you tried it?

    import matplotlib.pyplot as plt
    import matplotlib.ticker as ticker
    
    sci_nums = [10**9, 8*10**9]
    fig, ax = plt.subplots()
    ax.scatter(range(len(sci_nums)), sci_nums)
    ax.yaxis.set_major_locator(ticker.MaxNLocator(4, integer = True))
    

    I tried it, for my part it does not seem to work in the example you provided (Matplotlib version: 3.0.0rc1.post186+g5e01393)

  2. jklymak commented on Sep 10, 2018

    @jklymak
    Member

    The issue is actually in ScalarFormatter and its interaction with the extra ticks made by MaxNLocator. MaxNLocator(4) actually makes six ticks that are used for the orderOfMagnitude calculation, and the order of magnitiude of that larger span changes when you use 4 ticks instead of 6.

    I don't actually think there is any reason to not just set the order of magnitude by visible ticks. Fix on its way...

  3. jklymak commented on Sep 10, 2018

    @jklymak
    Member

    Cross ref #11004

  4. dabana commented on Sep 11, 2018

    @dabana
    Contributor

    I am a little concerned by the handling of the integer keyword.

       *integer*
         If True, ticks will take only integer values, provided
         at least `min_n_ticks` integers are found within the
         view limits.
    

    How can we be sure that only integer ticks are displayed if the formatter method axis.major.formatter does not know about the integer keyword provided to the tick locator, in that case maxNLocator?

  5. added this to the v3.1 milestone on Sep 11, 2018
  6. jklymak commented on Sep 11, 2018

    @jklymak
    Member

    @dabana the ticks being chosen by the locator are integers in both cases. It’s the formatting of those integers that needs fixing.

  7. dabana commented on Sep 11, 2018

    @dabana
    Contributor

    I guess I am confused with what the integer keyword is supposed to do. I thought all the tick labels would end-up greater than 1 without decimals (like 1, 2, 3 ... or 3, 6, 9, etc...).

    By reading the code, I understand than only the tick labels greater than 1 will be integer.

            if self._integer:
                # For steps > 1, keep only integer values.
                igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001)
                steps = steps[igood]
    

    If this is the correct behavior it might be worth rewording the doc_string to make this clear. For exemple:

    integer
    If True, ticks greater than 1 will take only integer values, provided
    at least min_n_ticks integers are found within the
    view limits.

  8. jklymak commented on Sep 11, 2018

    @jklymak
    Member

    Integer on the locator will simply make sure the tick locations are integers. You are conflating the scientific notation of the formatter with the job of the locator.

  9. joelostblom commented on Sep 14, 2018

    @joelostblom
    ContributorAuthor

    Thank you for fixing this @jklymak !!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions