For space reasons, I sometimes make plots in the following style:
fig, ax = plt.subplots(figsize=(3, 3))
ax.plot([0,1], [1000, 1001])
ax.set_xticks([0, 1])
ax.set_yticks([1000, 1001])
ax.set_xlabel("x", labelpad=-8)
ax.set_ylabel("y", labelpad=-18)

Here, I've kept just ticks marking the boundaries of the X/Y domains, and I'm manually aligning the xlabel and ylabel using the labelpad keyword argument so that the x and y axis labels visually align with the tick labels.
Note, I've had to use different amounts of padding for the different axes, since the length of the y tick labels 1000 and 1001 extends farther away from the axis than the height of the x tick labels 0 and 1, and since the vertical position of the x axis label and the horizontal position of the y axis label are relative to their usual position, which would be just past the extent of the tick labels.
I'm wondering, is there a way to automate this procedure, and to do it exactly rather than visually? For example, if labelpad were relative to the spines, that would be very nice, or if there were a way to determine the extent of the ticks and tick labels away from the spines, that number could be used to automate this as well.
A similar effect can be obtained using ax.yaxis.set_label_coords, but this transforms the position relative to the axes' transform, and thus depends on the size of the axes, while the ticks are positioned absolutely relative to the spines.
The path you were going down with ax.{x,y}axis.set_label_coords was pretty much there! All you need to do is wrap the transAxes transform in an offset_copy and then provide an offset that is a combination of the current length of the ticks + any space around the tick bbox.
import matplotlib.pyplot as plt
from matplotlib.transforms import offset_copy
fig, ax = plt.subplots(figsize=(3, 3))
fig.set_facecolor('white')
ax.plot([0,1], [1000, 1001])
ax.set_xticks([0, 1])
ax.set_yticks([1000, 1001])
# Create a transform that vertically offsets the label
# starting at the edge of the Axes and moving downwards
# according to the total length of the bounding box of a major tick
t = offset_copy(
ax.transAxes, y=-(ax.xaxis.get_tick_padding() + ax.xaxis.majorTicks[0].get_pad()),
fig=fig, units='dots'
)
ax.xaxis.set_label_coords(.5, 0, transform=t)
ax.set_xlabel('x', va='top')
# Repeat the above, but on the y-axis
t = offset_copy(
ax.transAxes, x=-(ax.yaxis.get_tick_padding() + ax.yaxis.majorTicks[0].get_pad()),
fig=fig, units='dots'
)
ax.yaxis.set_label_coords(0, .5, transform=t)
ax.set_ylabel('y', va='bottom')

import matplotlib.pyplot as plt
from matplotlib.transforms import offset_copy
fig, ax = plt.subplots(figsize=(3, 3))
fig.set_facecolor('white')
ax.plot([0,1], [1000, 1001])
ax.set_xticks([0, 1])
ax.set_yticks([1000, 1001])
ax.xaxis.set_tick_params(length=10)
ax.yaxis.set_tick_params(length=15)
t = offset_copy(
ax.transAxes, y=-(ax.xaxis.get_tick_padding() + ax.xaxis.majorTicks[0].get_pad()),
fig=fig, units='points'
)
ax.xaxis.set_label_coords(.5, 0, transform=t)
ax.set_xlabel('x', va='top')
t = offset_copy(
ax.transAxes, x=-(ax.yaxis.get_tick_padding() + ax.yaxis.majorTicks[0].get_pad()),
fig=fig, units='points'
)
ax.yaxis.set_label_coords(0, .5, transform=t)
ax.set_ylabel('y', va='bottom')

import matplotlib.pyplot as plt
from matplotlib.transforms import offset_copy
fig, ax = plt.subplots(figsize=(3, 3), dpi=150)
fig.set_facecolor('white')
ax.plot([0,1], [1000, 1001])
ax.set_xticks([0, 1])
ax.set_yticks([1000, 1001])
ax.xaxis.set_tick_params(length=10)
ax.yaxis.set_tick_params(length=15)
t = offset_copy(
ax.transAxes, y=-(ax.xaxis.get_tick_padding() + ax.xaxis.majorTicks[0].get_pad()),
fig=fig, units='points'
)
ax.xaxis.set_label_coords(.5, 0, transform=t)
ax.set_xlabel('x', va='top')
t = offset_copy(
ax.transAxes, x=-(ax.yaxis.get_tick_padding() + ax.yaxis.majorTicks[0].get_pad()),
fig=fig, units='points'
)
ax.yaxis.set_label_coords(0, .5, transform=t)
ax.set_ylabel("y", va='bottom')

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With