Anchoring an Annotation to two artists, where one is an axis label

Hi, all. I’m creating a figure where I have a couple of vertically stacked plots for which I don’t want to use subplots because they share an x-axis. As such, I’d like to add multiple labels to the y-axis at certain y-coordinates so as to delineate which part of the graph is depicting what. Here’s a super silly example of the kind of plot I’d like to be able to make:

download

Here’s the basis for the plot:

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
axis = plt.gca()

axis.plot(x, y1)
axis.plot(x, y2+4)
axis.set_yticks([-1, 0, 1, 3, 4, 5])
axis.set_yticklabels([-1, 0, 1, -1, 0, 1])
axis.set_xlabel('X Value')

…and then, to add the y-axis labels, I’ve been doing something like this to get the left-most bounding box coordinate for the tick labels and then create text artists in the right place:

import matplotlib.axis as ax

transform1 = next(i.label1.get_transform() for i in axis.yaxis.get_children() if isinstance(i, ax.YTick))
ylabel_x = min(t.label1.get_window_extent(fig.canvas.renderer).transformed(transform1.inverted()).get_points()[0, 0] for t in axis.yaxis.get_children() if isinstance(t, ax.YTick))-0.05
ylabel1 = fig.text(ylabel_x, 0, 'Sine', transform=transform1, rotation='vertical', verticalalignment='center')
ylabel2 = fig.text(ylabel_x, 4, 'Cosine', transform=transform1, rotation='vertical', verticalalignment='center')

If I try to do this instead with the transform used to draw the y-axis label (which I have not set here), I run into this issue, whereby the coordinates of the axis label change sometime after I get them:

import matplotlib.transforms as tr

axis.set_ylabel('ylabel')
transform2 = tr.blended_transform_factory(axis.yaxis.label.get_transform(), axis.transData)
ylabel_x = axis.yaxis.label.get_position()[0]
ylabel1 = fig.text(ylabel_x, 0, 'Sine', transform=transform2, rotation='vertical', verticalalignment='center')
ylabel2 = fig.text(ylabel_x, 4, 'Cosine', transform=transform2, rotation='vertical', verticalalignment='center')

download

In this example, “Sine” and “Cosine” end up set too far to the left. The answer in the linked question suggests using an Annotation to achieve this, but I’m not sure how to specify that the x-offset from the label should be 0 while also specifying the y-coordinate in data coordinates (the solution in the linked question passes axis.yaxis.label to the kwarg xycoords of axis.annotate). Maybe I need to anchor the x-coordinate to the y-label’s artist and the y-coordinate to the tick label’s artist? Any suggestions here would be welcome, and thanks!

I guess I should have read the docs for annotate more closely. The kwargs xycoords and textcoords can take a tuple, where the first element is the x-coordinate specification and the second element is the y-coordinate specification. For my requirements, xycoords=(axis.yaxis.label, 'data') works perfectly.

You were on the right track, you simply needed to call fig.draw(fig.canvas.get_renderer()) before getting the ylabel position to make sure the auto-positioning was done.

I know you ruled this out, but I’m not sure that’s necessary. You can use sharex to link the x-axis of both Axes, and then disable the corresponding spines:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 4*np.pi, 1000)
y1 = np.sin(x)
y2 = np.cos(x)

fig, axs = plt.subplots(2, 1, sharex=True)

axs[1].plot(x, y1)
axs[1].set_ylabel('Sine')
axs[0].plot(x, y2)
axs[0].set_ylabel('Cosine')
axs[1].set_xlabel('X Value')

# Squash everything together and hide in between spines.
fig.subplots_adjust(hspace=0)
axs[0].spines['bottom'].set_visible(False)
axs[1].spines['top'].set_visible(False)

plt.show()

Figure_2