1

I wrote a Python script based on matplotlib that generates curves based on a common timeline. The number of curves sharing the same x axis in my plot can vary from 1 to 6 depending on user options. Each of the data plotted use different y scales and require a different axis for drawing. As a result, I may need to draw up to 5 different Y axes on the right of my plot. I found the way in some other post to offset the position of the axes as I add new ones, but I still have two issues:

  1. How to control the position of the multiple axes so that the tick labels don't overlap?
  2. How to control the position of each axis label so that it is placed vertically at the bottom of each axis? And how to preserve this alignment as the display window is resized, zoomed-in etc... I probably need to write some code that will first query the position of the axis and then a directive that will place the label relative to that position but I really have no idea how to do that.

I cannot share my entire code because it is too big, but I derived it from the code in this example. I modified that example by adding one extra plot and one extra axis to more closely match what intend to do in my script.

import matplotlib.pyplot as plt


def make_patch_spines_invisible(ax):
    ax.set_frame_on(True)
    ax.patch.set_visible(False)
    for sp in ax.spines.values():
        sp.set_visible(False)


fig, host = plt.subplots()
fig.subplots_adjust(right=0.75)

par1 = host.twinx()
par2 = host.twinx()
par3 = host.twinx()

# Offset the right spine of par2.  The ticks and label have already been
# placed on the right by twinx above.
par2.spines["right"].set_position(("axes", 1.2))
# Having been created by twinx, par2 has its frame off, so the line of its
# detached spine is invisible.  First, activate the frame but make the patch
# and spines invisible.
make_patch_spines_invisible(par2)
# Second, show the right spine.
par2.spines["right"].set_visible(True)

par3.spines["right"].set_position(("axes", 1.4))
make_patch_spines_invisible(par3)
par3.spines["right"].set_visible(True)

p1, = host.plot([0, 1, 2], [0, 1, 2], "b-", label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], "r-", label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], "g-", label="Velocity")
p4, = par3.plot([0,0.5,1,1.44,2],[100, 102, 104, 108, 110], "m-", label="Acceleration")

host.set_xlim(0, 2)
host.set_ylim(0, 2)
par1.set_ylim(0, 4)
par2.set_ylim(1, 65)

host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")
par3.set_ylabel("Acceleration")

host.yaxis.label.set_color(p1.get_color())
par1.yaxis.label.set_color(p2.get_color())
par2.yaxis.label.set_color(p3.get_color())
par3.yaxis.label.set_color(p4.get_color())

tkw = dict(size=4, width=1.5)
host.tick_params(axis='y', colors=p1.get_color(), **tkw)
par1.tick_params(axis='y', colors=p2.get_color(), **tkw)
par2.tick_params(axis='y', colors=p3.get_color(), **tkw)
par3.tick_params(axis='y', colors=p4.get_color(), **tkw)
host.tick_params(axis='x', **tkw)

lines = [p1, p2, p3, p4]

host.legend(lines, [l.get_label() for l in lines])

# fourth y axis is not shown unless I add this line
plt.tight_layout()
plt.show()

When I run this, I obtain the following plot: output from above script

In this image, question 2 above means that I would want the y-axis labels 'Temperature', 'Velocity', 'Acceleration' to be drawn directly below each of the corresponding axis.

Thanks in advance for any help.

Regards,

L.

LMNCA
  • 21
  • 6
  • There is an example of this: https://matplotlib.org/gallery/ticks_and_spines/multiple_yaxis_with_spines.html – ImportanceOfBeingErnest Feb 15 '19 at 20:07
  • Yes, I saw that example. I just cannot modify it to show my labels lined up with each axis at its bottom. – LMNCA Feb 15 '19 at 20:17
  • Which labels are those? Can you position the label of a single axes at the position you desire? If not, forget about multiple axes, ask for single axes. Keep things simple and advance step by step. When asking a question, make your problem reproducible (the code above serves no purpose). – ImportanceOfBeingErnest Feb 15 '19 at 20:20
  • The labels I set when calling .set_y_label('some_string'). in the example you pointed me to, they are displayed next to each extra axis and on its right. I want them to be displayed below and in the alignment of the axis. – LMNCA Feb 15 '19 at 21:06
  • @ImportanceOfBeingErnest: I modified the script and my question by modifying the example that you pointed me to in order to showcase my problem in a clearer fashion, and I rephrased my question in light of that example. Hopefully, it will be more useful to others with this new description. – LMNCA Feb 15 '19 at 21:34
  • Would `par1.yaxis.label.set_position((0,0))` be what you're after? – ImportanceOfBeingErnest Feb 15 '19 at 21:55
  • Well, that moves the label down, but not enough (I want it below the axis), and it is still offset to the right a bit. I would want it to be in the alignment of the axis, e.g. printed verfically at the place where the first tick is printed, or even further down. I tried to tinker with the values of x, using both negative or positive value, but I am not able to make it move where I want it to be. Also, it does not seem to follow the axis when I change the window size. – LMNCA Feb 15 '19 at 22:12
  • It seems to insist on putting it to the right of the bounding box of the y-ticks of the related axis. When I reduce the window, the y-ticks tend to overlap, so I also see overlapping labels even if there is enough space to plot them below the axis themselves. – LMNCA Feb 15 '19 at 22:22
  • I think after all you don't want to have a `ylabel` at all, but just some text at the desired position, what about `host.text(1.2, 0, "Velocity" , ha="left", va="top", rotation=90, transform=host.transAxes)`? – ImportanceOfBeingErnest Feb 15 '19 at 22:41
  • @ImportanceOfBeingErnest : Thank you: your suggestion works and solves my issue with the labels. I have another issue with the 'tight_layout' directive which seems to cause extra empty padding to be added to my plot. It is another issue, however, so I will open a separate question. – LMNCA Feb 18 '19 at 14:43

1 Answers1

0

What worked for me was ImportanceOfBeingErnest's suggestion of using text (with a line like

host.text(1.2, 0, "Velocity" , ha="left", va="top", rotation=90, transform=host.transAxes))

instead of trying to control the label position.

LMNCA
  • 21
  • 6