1

In case of where ticks are dynamically set (like the following example), how can I change the tick label properties (like size, rotation, etc)? I tried ax.set_xticklabels(ax.get_xticklabels(), rotation=90, size=7, ha='center'), it doesn't work. In fact, ax.get_xticklabels() doesn't even return the correct number of tick labels of 5 as set in MaxNLocator - is this a bug?

import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter, MaxNLocator
fig, ax = plt.subplots()
xs = range(26)
ys = range(26)
labels = list('abcdefghijklmnopqrstuvwxyz')


def format_fn(tick_val, tick_pos):
    if int(tick_val) in xs:
        return labels[int(tick_val)]
    else:
        return ''


ax.xaxis.set_major_formatter(FuncFormatter(format_fn))
ax.xaxis.set_major_locator(MaxNLocator(5, integer=True))
ax.set_xticklabels(ax.get_xticklabels(), rotation=90, size=7, ha='center')
ax.plot(xs, ys)

ax.get_xticklabels() get empty tick labels: enter image description here

import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter, MaxNLocator
fig, ax = plt.subplots()
xs = range(26)
ys = range(26)
labels = list('abcdefghijklmnopqrstuvwxyz')


def format_fn(tick_val, tick_pos):
    if int(tick_val) in xs:
        return labels[int(tick_val)]
    else:
        return ''


ax.xaxis.set_major_formatter(FuncFormatter(format_fn))
ax.xaxis.set_major_locator(MaxNLocator(5, integer=True))
ax.set_xticklabels(labels, rotation=90, size=7, ha='center')
ax.plot(xs, ys)

This offsets the labels by 1. It should start with a, not b: enter image description here

In the answer here, plt.xticks can do the work. However, I am wondering if there is any way to use the object-oriented interface? because I don't want to globally set those properties with plt.xticks.

RNA
  • 146,987
  • 15
  • 52
  • 70
  • The object-oriented approach would be `for t in ax.get_xticklabels(): t.set_rotation(90)`. Since this answer is already given in [this question](https://stackoverflow.com/questions/10998621/rotate-axis-text-in-python-matplotlib) I would actually consider it to be a duplicate. Maybe you want to edit the question to say in how far that would not solve the issue?! – ImportanceOfBeingErnest Oct 09 '17 at 13:50
  • @ImportanceOfBeingErnest It most definitely is not a duplicate, as has been pointed out in multiple places on this page. `ax.get_xticklabels()` does not work if there are no ticklabels set. Your alternative solution from referenced question DOES work .. – Uvar Oct 10 '17 at 07:44

2 Answers2

2

EDIT: found the, in retrospect quite logical, solution.

ax.set_xticklabels(ax.get_xticklabels(), rotation=90, size=7, ha='center') is intended to rotate labels a g m s y 90 degrees and makes them a slight bit smaller. The two extra tick labels which you get from ax.get_xticklabels() are empty ticks '' at the plot axis limits. And they are exactly what you told your plot to do with the MaxNLocator, as this selects N intervals, not N ticks.

Only when the script is completed and I afterwards make the call to ax am I actually able to access tick labels. This indicates that at plotting time, the tick labels are not set, only the formatter and locator are set, from which ticks are generated later. This means that ax.get_xticklabels() returns an empty list at specified locations.

So, to have this work from a reference to ax, you will have to set the ticks manually.

import matplotlib.pyplot as plt
import matplotlib
import numpy as np
from matplotlib.ticker import FuncFormatter, MaxNLocator
from matplotlib.text import Text
fig, ax = plt.subplots()
xs = range(26)
ys = range(26)
labels = list('abcdefghijklmnopqrstuvwxyz')

def format_fn(tick_val, tick_pos):
    if int(tick_val) in xs:
        return labels[int(tick_val)]
    else:
        return ''

ax.set_xticklabels(labels, rotation=90, size=7, ha='center')
ax.xaxis.set_major_formatter(FuncFormatter(format_fn))
ax.xaxis.set_major_locator(MaxNLocator(5, integer=True))

ax.plot(xs, ys)
plt.show()

As credit to ImportanceOfBeingErnest, as he found a working solution to this problem, before it even became a problem:

fig.autofmt_xdate(bottom=0.1, rotation=90, ha='center')

would work as well, as the figure object is accessible at plot time, thereby foregoing the need to 'manually' set the ticklabels. However, please be advised that this has a potentially nasty interaction with the set_major_formatter and set_major_locator functions when zooming.

enter image description here

Uvar
  • 3,372
  • 12
  • 25
  • good to know about the empty ticks at both of axis limits. `ax.set_xticklabels` definitely does not work properly. If possible, I would avoid explicitly typing out all tick labels as in your last example. – RNA Oct 09 '17 at 17:11
  • 1
    Answer updated, all what was wrong before was a call to `ax.get_xticklabels()` which were not set yet, instead of to the actual labels. – Uvar Oct 10 '17 at 07:42
1

You can use plt.xticks(rotation=90) just before calling plot (works in interactive mode), or using plt.setp(ax.xaxis.get_majorticklabels(), rotation=90) which works only in non-interactive mode.

See this post for more info.

Gerges
  • 6,269
  • 2
  • 22
  • 44