Search code examples
matplotlibseabornxticks

Trying to place text in mpl just above the first yticklabel


I am having diffculties to move the text "Rank" exactly one line above the first label and by not using guesswork as I have different chart types with variable sizes, widths and also paddings between the labels and bars.

enter image description here

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from pylab import rcParams

rcParams['figure.figsize'] = 8, 6
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
df = pd.DataFrame.from_records(zip(np.arange(1,30)))
df.plot.barh(width=0.8,ax=ax,legend=False)
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.tick_params(left=False, bottom=False)
ax.tick_params(axis='y', which='major', pad=36)
ax.set_title("Rankings")
ax.text(-5,30,"Rank")

plt.show()

Using transData.transform didn't get me any further. The problem seems to be that ax.text() with the position params of (0,0) aligns with the start of the bars and not the yticklabels which I need, so getting the exact position of yticklabels relative to the axis would be helpful.


Solution

  • The following approach creates an offset_copy transform, using "axes coordinates". The top left corner of the main plot is at position 0, 1 in axes coordinates. The ticks have a "pad" (between label and tick mark) and a "padding" (length of the tick mark), both measured in "points".

    The text can be right aligned, just as the ticks. With "bottom" as vertical alignment, it will be just above the main plot. If that distance is too low, you could try ax.text(0, 1.01, ...) to have it a bit higher.

    import matplotlib.pyplot as plt
    from matplotlib.transforms import offset_copy
    import pandas as pd
    import numpy as np
    from matplotlib import rcParams
    
    rcParams['figure.figsize'] = 8, 6
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    df = pd.DataFrame.from_records(zip(np.arange(1, 30)))
    df.plot.barh(width=0.8, ax=ax, legend=False)
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)
    ax.spines['left'].set_visible(False)
    ax.spines['bottom'].set_visible(False)
    ax.tick_params(left=False, bottom=False)
    ax.tick_params(axis='y', which='major', pad=36)
    ax.set_title("Rankings")
    
    tick = ax.yaxis.get_major_ticks()[-1] # get information of one of the ticks
    padding = tick.get_pad() + tick.get_tick_padding()
    trans_offset = offset_copy(ax.transAxes, fig=fig, x=-padding, y=0, units='points')
    ax.text(0, 1, "Rank", ha='right', va='bottom', transform=trans_offset)
    # optionally also use tick.label.get_fontproperties()
    
    plt.tight_layout()
    plt.show()
    

    aligning a text on top of the y tick labels