Skip to content

Minor ticklabels are missing at positions of major ticks. #13618

Closed

Description

@ImportanceOfBeingErnest

Bug report

Bug summary

Minor ticklabels are missing at positions of major ticks.

Code for reproduction

import numpy as np
import matplotlib.dates as mdates
import matplotlib.pyplot as plt

t = np.arange("2018-11-03", "2018-11-06", dtype="datetime64")
x = np.random.rand(len(t))

fig, ax = plt.subplots()
ax.plot(t,x)

ax.xaxis.set_major_locator(mdates.DayLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('\n%a'))

ax.xaxis.set_minor_locator(mdates.HourLocator((0,6,12,18)))
ax.xaxis.set_minor_formatter(mdates.DateFormatter('%H:%M'))

plt.show()

Actual outcome

The above code run with current master produces

image

The minor ticklabels showing the 00:00 hours are missing.

Expected outcome

The expected outcome would be the same as when running the code with matplotlib 3.0.2 or below:

image

I would expect to see the hours throughout.

Matplotlib version

  • Operating system: Win8
  • Matplotlib version: master
  • Matplotlib backend (print(matplotlib.get_backend())): any
  • Python version: 3.6

Activity

  1. added this to the v3.1.0 milestone on Mar 7, 2019
  2. jklymak commented on Mar 7, 2019

    @jklymak
    Member

    There is no minor tick there anymore so there won’t be a label. What’s wrong w putting the HH:MM in the major label?

  3. ImportanceOfBeingErnest commented on Mar 7, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    Actually, I don't think there is anything wrong with that. It's more that the previous code suddenly broke. Was this an intentional change?

  4. jklymak commented on Mar 7, 2019

    @jklymak
    Member

    Yes though I’m on my phone and can’t look up the PRs. Recent ones by @anntzer and or myself. Basically minor ticks no longer include major ticks. So no more over strike on the ticking and no more heuristic guessing if a labeled minor tick is really a major tick.

  5. anntzer commented on Mar 7, 2019

    @anntzer
    Contributor

    Yes, that comes from #13314. I guess this could have been better documented; on the other hand the issue that #13314 fixed did keep coming up again and again, so trying to play whack-a-mole by fixing it one locator at a time is a bit an endless task.

    Note that in the example here, your formatters are actually not really independent from one another (you need to embed the newline in the major formatter), so I think the solution with the new API (ax.xaxis.set_major_formatter(mdates.DateFormatter('%H%M\n%a')) looks just fine. (But yes, I acknowledge it's an API break.)

  6. ImportanceOfBeingErnest commented on Mar 7, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    I see. Now reading the API change note, "Minor Locator no longer try to avoid overstriking major Locators", it seems to tell me the opposite, because obviously the minor locator does avoid the major locations.

    May I suggest to write an additional what's new entry that is understandable by normal people and shows what is changed and why that is?

  7. anntzer commented on Mar 7, 2019

    @anntzer
    Contributor

    Do you want to give it a try? You are obviously more aware of the cases that have been broken. (If not I'll do it, that's fine too.)

  8. ImportanceOfBeingErnest commented on Mar 8, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    Is there any way to revert back to the old behaviour?

  9. anntzer commented on Mar 8, 2019

    @anntzer
    Contributor

    Right now, no. Could perhaps be switched with a new flag (with the note that in that case, even loglocators don't try to avoid crashing minor and major ticks).

  10. ImportanceOfBeingErnest commented on Mar 8, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    For a what's new entry maybe show the effect as follows:

    ax.xaxis.set_major_locator(mticker.MultipleLocator(10))
    ax.xaxis.set_minor_locator(mticker.MultipleLocator(2))
    ax.xaxis.set_minor_formatter(mticker.ScalarFormatter())
    ax.grid(which="both", axis="x")
    

    previously:
    majorminorchange_3 0 2

    now:
    majorminorchange_3 0 2 post1846 gfd40d7d74

    I mean this really looks like a great improvement, but maybe someone relies on the major and minor ticks/grids overlapping?

  11. ImportanceOfBeingErnest commented on Mar 8, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    I think a what's new entry would still be useful, since noone reads API change notes. (Reading through the recent API changes actually a lot of them should have been mentionned in the what's new section?! Or maybe I don't quite understand the difference between what's new and API change notes?)

    Also, how do you revert this change? Previously you could still write your own ticker in order not to tick some locations. Arguably, the new behaviour is much better for most standard cases. However for special cases, with this change, you cannot write any ticker to force a tick at a specific location if it happens to be part of the major ticks. Not even a FixedLocator will work, right?

    Concrete example:

    ax.set_xticks([0.2], minor=True)
    ax.grid(which="minor", axis="x")
    

    previously:
    image

    now:

    image

    Question: How to get the gridline back?

  12. jklymak commented on Mar 8, 2019

    @jklymak
    Member

    I’m not opposed to having a way to get all the ticks back, but I’m not clear on what the practical problem is versus a theoretical one. If you need a bunch of vertical lines at arbitrary locations axvline does that for you. This makes all the practical cases much better at the cost of a few obscure cases being a bit harder. I’d need a bit more to convince me that adding API to toggle this behaviour is worth the fuss.

    I think what’s new is for new features. API changes is for changes to existing features. At least in my mind. OTOH Id support merging these two under what’s new and just labelling the API changes as such.

  13. ImportanceOfBeingErnest commented on Mar 8, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    I’m not clear on what the practical problem is versus a theoretical one.

    That is a theoretical problem indeed. You type something in (ax.set_xticks(..)) and don't get out what you asked for, like

    you > Please give me a tick at position 0.2
    interpreter > Na, I don't feel like doing that is a good idea; I will ignore your command.
    

    If you need a bunch of vertical lines at arbitrary locations axvline does that for you.

    Sure, there is no need for .grid at all, given that there is a Line2D object available.

    I think what’s new is for new features. API changes is for changes to existing features.

    I think I would argue that things like "Hey look, we've fixed this long standing bug." or "If you use good old command x your plot will now look like y." are still somehow news people are interested in reading the What's new section.

  14. jklymak commented on Mar 8, 2019

    @jklymak
    Member

    interpreter > Na, I don't feel like doing that is a good idea; I will ignore your command.

    Thats correct - #13314 says that minor ticks are exclusive of major ticks by definition, so if you ask to put a minor tick where a major tick is, you won't get it.

    I'm still not clear what the use-case is, but if you need to hack around this definition:

    import matplotlib.pyplot as plt
    
    fig, ax =plt.subplots()
    ax.set_xticks([0.2001], minor=True)
    ax.grid(which="minor", axis="x")
    plt.show()
    

    though I note that going more decimal places (0.20001) excludes the tick, which seems a bit too much slop... (well, its rtol=1e-5)

  15. 2 remaining items

  16. anntzer commented on Mar 12, 2019

    @anntzer
    Contributor

    I don't mind moving/duplicating the api_changes to the whatsnew.
    If you want to put up an alternate PR to fix issue #11575 and the other similar issues, and revert #13314, I won't block it either.
    Having a different behavior for default and nondefault locators (what's even a "default" locator?) seems strange, though.

  17. ImportanceOfBeingErnest commented on Mar 12, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    By "default" I meant all those cases where the user does not type in .set_minor_locator or .set_xticks; that would in addition to normal plots be e.g. plt.semilogy, plt.plot(<list of datetimes>) etc.
    But I fully agree that different behaviour is in general undesired. I also acknowledge that this change is useful for all but a few edge cases.
    It's really more a principle thing: major and minor locators are not independent of each other any more. (A use case would be the original issue where in addition you use a different color or font(size) for the major and minor labels.)

    The best would be an opt-out option for this behaviour. (But I currently wouldn't know where to put that. In the locators? In the axes?)
    If people really think, that is not necessary, adding a note in the what's new/Api change that says something like "We feel this change best reflects how people would use major and minor locators; however if you have a usecase where this is causes problems, please do file a report on the issue tracker." might be the way to go.

  18. anntzer commented on Mar 12, 2019

    @anntzer
    Contributor

    By "default" I meant all those cases where the user does not type in .set_minor_locator or .set_xticks;

    But all #11575 is a case where the user uses set_xticks but wants collision suppression...

    A use case would be the original issue where in addition you use a different color or font(size) for the major and minor labels.

    The real fix would be to allow text objects with variable color or size (I mean, here you can have two different colors (major/minor) but not three, so that's clearly a hack).


    Can you open a PR to add whatever note you want to the api_changes and possibly move it to the whatsnew? I think we should try to keep this as is, and, if there's too much pushback against it, we can consider adding the opt-out in a future release.

  19. ImportanceOfBeingErnest commented on Mar 23, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    Can you open a PR [...] ?

    No sorry, I can't. I did try and it came out too sarcastic to be publishable.

  20. anntzer commented on Mar 24, 2019

    @anntzer
    Contributor

    Do you want to block 3.1 over that? (That's fine with me, but you need to ask for it :))

  21. ImportanceOfBeingErnest commented on Mar 24, 2019

    @ImportanceOfBeingErnest
    MemberAuthor

    No, I don't want to block 3.1 over this. I gave some arguments above, and if they are not shared by others, I might simply be wrong in my analysis.

  22. anntzer commented on Mar 24, 2019

    @anntzer
    Contributor

    OK, let's just ping @tacaswell to get his opinion as well then, if he wants to chime in before the 3.1 release.

  23. jklymak commented on Mar 24, 2019

    @jklymak
    Member

    Suggest we add to tomorrow’s agenda.

  24. added
    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
    on Mar 25, 2019
  25. tacaswell commented on Mar 25, 2019

    @tacaswell
    Member

    Discussed on call

    https://paper.dropbox.com/doc/Matplotlib-2019-meeting-agenda--AaCmZlKDONJlV5crSSBPDIBjAg-aAmENlkgepgsMeDZtlsYu#:h2=13618:-Minor-tick-supression-w

    Primary plan is to try to add a public API for controlling the de-confliction
    Backup plan is to revert this and try again for 3.2

  26. added 2 commits that reference this issue on Apr 9, 2019
    d3b9484
    e3a8393
  27. Hoops01 commented on Nov 18, 2023

    @Hoops01

    Sorry to bring up a long closed thread, but is there a way for the user to reverse this change?
    I would actually like the minor ticklabel overstrike to occur so I can set a shifted minor tick labels as a "sub category" for example calendar months, and a shifted major tick label as category say calendar years;
    date_ticklabel_shifted

    I think this is a much more natural way of displaying time series data, since when people refer something happening in "2023" or in "March 2023", they mean over a time period. Plotting it this way makes that super easy to find (imo).
    FYI, I asked this question here https://stackoverflow.com/questions/77505618/place-tick-labels-between-ticks-for-time-series-charts-matplotlib

    My code for doing this is;

    import datetime as dt
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    
    fig, ax = plt.subplots(figsize=(16,4))
    
    # set plot date range
    startdate = dt.date(2023,1,1)
    enddate = dt.date(2025,7,1)
    ax.set_xlim([startdate, enddate])
    
    # set grid and x ticks
    ax.grid(which='major', axis='x', color='b')
    ax.tick_params(axis='x', which='major', length=16, width=1, colors='b')
    ax.grid(which='minor', axis='x', color='y')
    ax.tick_params(axis='x', which='minor', length=8, width=1, colors='y')
    
    # Set major x ticks
    ax.xaxis.set_major_locator(mdates.YearLocator())
    ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
    
    # offset major x tick labels
    for label in ax.xaxis.get_majorticklabels():
        label.set_transform(transforms.Affine2D().translate(365/2, 0) +label.get_transform())
    
    # Set minor x ticks
    ax.xaxis.set_minor_locator(mdates.MonthLocator())
    ax.xaxis.set_minor_formatter(mdates.DateFormatter('%b'))
    # offset minor x tick labels
    for label in ax.xaxis.get_minorticklabels():
        label.set_transform(transforms.Affine2D().translate(15, 0) +label.get_transform())
        
    plt.setp(ax.get_xticklabels(which='minor')[-1], visible=False)
    

    Apologies if this is not the right forum for this, but if I can get Matplotlib to show exactly what @ImportanceOfBeingErnest did in the first post, I can get this to work.

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

    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions