Skip to content

Cleanup API for setting ticks #15005

Closed
Closed

Description

@timhoffm

Inspired by #14980 (comment).

Current APIs:

pyplot.xticks(ticks=None, labels=None, **kwargs)

Axes.set_xticks(self, ticks, minor=False)
Axes.set_xticklabels(self, labels, fontdict=None, minor=False, **kwargs)

Notes:

  • The Axes methods are just thin wrappers around the Axis methods.
  • The kwargs are Text properties.

Issue

The Axes API feels a bit odd. AFAICS set_xticklabels does not make much sense without set_xticks before; otherwise how do I know that the labels will be at the correct places and that I have the right number of labels for the ticks.

Proposal

  1. Extend the API of set_xticks to be
    Axes.set_xticks(self, ticks, labels=None, minor=False, **kwargs)
    
    maybe even with ticks=None to be compatible with the pyplot API - though I don't know if one needs the ability to set ticks without positions.
  2. Soft-deprecate set_xticklabels. It's too widely used to be deprecated, but we shouldn't have two equal ways here.
  3. Optionally add the minor kwarg to pyplot.xticks; it's not strictly necessary because pyplot may have less functionality than the OOP interface; but it doesn't hurt either.

The proposed signature would be an API-change because the minor argument might have been passed positionally. Thus, we should start with _make_keyword_only on that parameter.

Activity

  1. added
    API: consistencyConsistency of the matplotlib API, including naming, behavior, defaults, …
    API: changesChanges to the public API, typically requiring deprecation.
    on Aug 7, 2019
  2. bhuvanshukla commented on Aug 8, 2019

    @bhuvanshukla

    And similarly for from mpl_toolkits.mplot3d import Axes3D these 3d plots too, keeping in mind the z-axis especially. :)

  3. anntzer commented on Aug 8, 2019

    @anntzer
    Contributor

    Alternatively, one could allow the ticks argument to also be a dict (or a list of pairs -- i.e., something that you can call dict() on) mapping values to labels. I think the API may be nicer? ax.set_xticks({1: "a", 2: "b"}); ax.set_xticks({x: to_label(x) for x in positions})
    I thiink this doesn't need a deprecation period (because even now you can't do ax.set_xticks({1: "a", 2: "b"}) -- set_xticks won't convert the dict to a list only using the keys (not having a deprecation period is a minor nicety, but I don't think we should decide on the API just based on that if another one is clearly better).

  4. timhoffm commented on Aug 8, 2019

    @timhoffm
    MemberAuthor

    A mapping is indeed a neat API for this and has a number of advantages.

    My main concern is that we do not have this kind of API in Matplolib so far, AFAIK. We always pass multiple lists; e.g. legend(handles, labels). In particular also the direct analogon pyplot.xticks(ticks, labels). I assume that's an inherited Matlabism.

    So, this is also a question of API consistency and if we want to change the paradigm here and if we plan to do this elsewhere.

  5. anntzer commented on Aug 8, 2019

    @anntzer
    Contributor

    It's quite likely this comes from MATLAB, like many other bad ideas :/

  6. Phlya commented on Aug 9, 2019

    @Phlya
    Contributor

    If ticks=None was the default and allowed to just change tick properties (label rotation!) easily, that would be amazing. I have been using matplotlib for years, but still need to google how to rotate the ticklabels when I need to: something like plt.setp(ax.get_xticklabels(), rotation=45) is not super intuitive imo.

  7. ImportanceOfBeingErnest commented on Aug 9, 2019

    @ImportanceOfBeingErnest
    Member

    Indeed it would be good to first/simultaneously clarify what the desired interface for changing ticklabel properties should be. Currently we have

    • ax.set_{x,y}ticklabels() Does take text properties. Drawback: Sets the formatter to a FixedFormatter.
    • plt.{x,y}ticks() Does take text properties. Drawback: is pyplot-only.
    • ax.tick_params() Looks like the canonical function for this, yet it only takes a subset of required parameters (no alignment, no fontproperties, etc.)
    • fig.autofmt_xdate() takes rotation and alignment, but only applies to the lower subplot's xaxis.
    • plt.setp(ax.get_{x,y}ticklabels(), ...) - General purpose, works with everything (except for rotation on polar plots), but requires pyplot, is not very discoverable, and people seem to not like it for some reason.

    If one goes for making ax.set_{x,y}ticklabels() or ax.set_{x,y}ticks the canonical interfaces for setting ticklabel properties, it might be confusing how it would set or not set the locators and formatters depending on the input.

  8. anntzer commented on Aug 9, 2019

    @anntzer
    Contributor

    I guess this sort of points to the fact that the tick labels should really be a single collection object (a TextCollection -- yes, this doesn't exist (yet)) so that one can have methods on it to set its properties globally. Of course this doesn't really make sense with the current design where each Tick owns its own Text and Line2D, but see also the discussion at #5665 (comment).

  9. y9c commented on Aug 19, 2019

    @y9c
    Contributor

    set_xticklabels and get_xticklabels are also confusing. The arg minor=True works for both, but not which='minor'.

  10. added this to the v3.3.0 milestone on Aug 19, 2019
  11. rscmendes commented on Nov 18, 2019

    @rscmendes

    I want to add something which I'm not sure whether it's a bug or intentional. Let me know if I should open a new issue for this.

    pyplot.xticks has both ticks and labels as optional. I wanted to add just the labels while maintaining the ticks (this is a use case where the OP doubted one would need). However, this returns an error as the code is as follows:

    if ticks is None and labels is None:
        locs = ax.get_xticks()
        labels = ax.get_xticklabels()
    elif labels is None:
        locs = ax.set_xticks(ticks)
        labels = ax.get_xticklabels()
    else:
        locs = ax.set_xticks(ticks)
        labels = ax.set_xticklabels(labels, **kwargs)
    

    Thus, my use-case would go to the else, but the ticks would be None, which can't happen. This could be fixed by change the upper code with something similar to:

    locs = ax.set_xticks(ticks) if ticks else ax.get_xticks()
    labels = ax.set_xticklabels(labels, **kwargs) if labels else ax.get_xticklabels()
    

    Or, if one liners are a no go, one can add an extra elif for this specific case.

    Let me know if you agree with this being an issue and/or if I should create a separate issue in github for this. Thanks.

    PS: The same problem exists in yticks.
    Edit: format

  12. timhoffm commented on Nov 24, 2019

    @timhoffm
    MemberAuthor

    @bluetrickpt In any case, this needs action because plt.xticks(labels=['a', 'b', 'c']) yields a quite cryptic TypeError: object of type 'NoneType' has no len(). The question is if it should work or error out with a better message.

    What is your use case for only changing the label texts without caring about their position? In particular, how do you know that the labels will be at the correct places and that you the right number of labels for the ticks?

  13. rscmendes commented on Nov 24, 2019

    @rscmendes

    @bluetrickpt In any case, this needs action because plt.xticks(labels=['a', 'b', 'c']) yields a quite cryptic TypeError: object of type 'NoneType' has no len(). The question is if it should work or error out with a better message.

    What is your use case for only changing the label texts without caring about their position? In particular, how do you know that the labels will be at the correct places and that you the right number of labels for the ticks?

    My use case is for multiple boxplots or bar plots in the same figure, in where I know exactly how many boxplots or bars I'm plotting. The xticks are always range(n), where n is the number of boxplots or bars to put in the plot.

    Here's an example of such use case:
    image

    But in reality, any case in where you do plt.plot(y), xticks get set as range(len(y)). So in all these cases I would assume that only setting the labels is needed.

  14. timhoffm commented on Nov 24, 2019

    @timhoffm
    MemberAuthor

    Thanks, that's indeed a valid case and should work. Are you willing to provide a PR?

  15. rscmendes commented on Nov 24, 2019

    @rscmendes

    Definitely. I'll do it the next few days.

  16. modified the milestones: v3.3.0, v3.4.0 on May 24, 2020
  17. modified the milestones: v3.4.0, v3.5.0 on Jan 27, 2021
  18. QuLogic commented on Aug 18, 2021

    @QuLogic
    Member

    The first two items were actually duplicated in #19016 and fixed in #20047; I'm not sure what we want to do about the third item.

  19. timhoffm commented on Aug 21, 2021

    @timhoffm
    MemberAuthor

    I have no strong opinion, maybe we should briefly discuss that in the dev call.

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

    API: changesChanges to the public API, typically requiring deprecation.API: consistencyConsistency of the matplotlib API, including naming, behavior, defaults, …

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions