0

I would like to use the "west" anchor for my tick labels for a twinx (right-side) axis. Looking at the plot below, for example, I would like the left side of the tick labels to be aligned with the right axis.

I attempted a few things below, to no avail.

import matplotlib.pyplot as plt
X = [1,2,3]
fig, ax = plt.subplots()
ax.plot(X)
ax.set_ylim([1,3])
ax.set_yticks(X)
axR = ax.twinx()
axR.set_ylim(ax.get_ylim())
axR.set_yticks(ax.get_yticks())
axR.set_yticklabels(['-1.00', '2.00', '3.00'], ha='right')
# axR.set_yticklabels(['-1.00', '2.00', '3.00'], ha='right', bbox_to_anchor='W')
# axR.set_yticklabels(['-1.00', '2.00', '3.00'], ha='right', bbox=dict(bbox_to_anchor='W'))
# bbox can have args from: https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.FancyBboxPatch.html#matplotlib.patches.FancyBboxPatch
fig.show()

enter image description here

L0tad
  • 574
  • 3
  • 15
likethevegetable
  • 264
  • 1
  • 4
  • 17
  • try this:`axR.set_yticklabels(['-1.00', '2.00', '3.00'], ha='left')` – r-beginners Aug 30 '21 at 14:01
  • I specifically want `ha='right'`, but to "pin" the left side of the tick label boxes to the axes. – likethevegetable Aug 30 '21 at 14:05
  • Add this. `axR.tick_params(axis='y', which='major', pad=-5)` Is this what you want to achieve? – r-beginners Aug 30 '21 at 14:42
  • That's a bit hacky because the padding will have to change depending on the number of digits of the y-ticks. There's a clear solution here and that's changing the bounding box anchor point. It's clearly achievable because somewhere internally `matplotlib` likely uses `east` for left x-axis and `north` for the bottom x-axis. – likethevegetable Aug 30 '21 at 14:48

1 Answers1

2

So I had the same problem and stumbled on this question. I tried quite a bit and I basically decided I would need to find the right side of the labels when they are left aligned on the right side and then right align them from this point.

I tried a few things but don't have a lot of experience, so it's not perfect, but it seems to work by finding the coordinates as a bbox. I converted that back and forth to get it as an array (probably a shorter way that I don't know). I then took the gap of the largest one and added that to the spacing.

A few notes: I'm doing this on a subplot, hence ax2. I've also already moved the axis tick labels to the right side with ax2.yaxis.tick_right()

r = plt.gcf().canvas.get_renderer()
coord = ax2.yaxis.get_tightbbox(r)

ytickcoord = [yticks.get_window_extent() for yticks in ax2.get_yticklabels()]

inv = ax2.transData.inverted()

ytickdata = [inv.transform(a) for a in ytickcoord]
ytickdatadisplay = [ax2.transData.transform(a) for a in ytickdata]
                
gap = [a[1][0]-a[0][0] for a in ytickdatadisplay]

for tick in ax2.yaxis.get_majorticklabels():
    tick.set_horizontalalignment("right")

ax2.yaxis.set_tick_params(pad=max(gap)+1)}

Update: I have recently been sent the solution to a similar problem with left alignment on the left side. From this solution, I believe this can be simplified to:

import matplotlib as mpl
import matplotlib.pyplot as plt

fig = plt.figure(figsize =(5,3))
ax = fig.add_axes([0,0,1,1])
plt.plot([0,100,200])
ax.yaxis.tick_right()

# Draw plot to have current tick label positions
plt.draw()
# Read max width of tick labels
ytickcoord = max([yticks.get_window_extent(renderer = plt.gcf().canvas.get_renderer()).width for yticks in ax.get_yticklabels()])
# Change ticks to right aligned
ax.axes.set_yticklabels(ax.yaxis.get_majorticklabels(),ha = "right")
# Add max width of tick labels
ax.yaxis.set_tick_params(pad=ytickcoord+1)

plt.show()
plt.close("all")
Craig
  • 2,248
  • 1
  • 19
  • 23
cmay
  • 153
  • 6
  • Thanks for the answer. I was thinking the exact thing--simply pad based on the max characters to make it fit. I use the `pgf` backend so could likely use some LaTeX hackery to make this work as well. – likethevegetable Sep 09 '21 at 20:21
  • No problem, mine was definitely done in frustration with about 30 tabs open, so I'm sure there are far cleaner ways of doing it. And another note I just had a renderer error come up on one case of using this, which was remedied by putting in the kwarg: renderer = r for get_window_extent – cmay Sep 09 '21 at 23:05