I'm using InsetPosition
and mark_inset
to make a subplot so that I have the lines connecting them. However, I can't get the lines to be on top of the legend in the first plot. Any thoughts on how I can fix this?
I'd also like to get the tick markers on top of the line plots if I can.
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition, mark_inset
plind = 1 # don't want the first plot
### make the main plot
fig, ax = plt.subplots(1, 1, figsize=(3.35,2.5), dpi=150)
### make the first plot
### three bits of data
ax.plot(f[plind:]/1e3, s[plind:], zorder=4, c='C0', label='BES')
ax.plot(ff[plind:]/1e3, ss[plind:], zorder=6, c='C2', label='BES welch, N=2$^3$')
ax.plot(fbeam[plind:]/1e3, sbeam[plind:], lw=0.8, zorder=5, c='C1', label='Beam')
### want this fill between on the right
ax.fill_between([5e2,4e3], [ylim[0],ylim[0]], [ylim[-1],ylim[-1]], color='C3', alpha=0.3, zorder=3)
### set the x and y limits
ax.set_xlim([4e-3, 2e3])
ax.set_ylim([1e-19, 1e-1])
### want it in log scale
ax.set_xscale('log')
ax.set_yscale('log')
### set the ylabel
ax.set_ylabel('Spectra (V$^2$/Hz)', fontsize=9)
### formatting the ticks
ax.tick_params(axis="both", which='both', labelsize=9,
direction='in', left=True, bottom=True, right=True, top=True)
ax.minorticks_on()
### make the legend
leg = ax.legend(handlelength=0, handletextpad=0, fontsize=8, fancybox=1,
framealpha=1, labelcolor='linecolor', loc='lower center').set_zorder(15)
### changing the positions so it fits within my box
X0 = ax.get_position().x0 + 0.07
X1 = ax.get_position().x1 - 0.32
Y0 = ax.get_position().y0 + 0.06
Y1 = ax.get_position().y1 + 0.09
ax.set_position([X0, Y0, X1-X0, Y1-Y0])
### making the inset
ax2 = plt.axes([0, 0, 1, 1])
ip = InsetPosition(ax, [1.05, 0., 1, 1])
ax2.set_axes_locator(ip)
mark_inset(ax, ax2, loc1=2, loc2=3, fc='none', ec='k', lw=0.8, zorder=7)
### plot the data again
ax2.plot(f[plind:]/1e3, s[plind:], zorder=4, c='C0')
ax2.plot(ff[plind:]/1e3, ss[plind:], zorder=6, c='C2')
ax2.plot(fbeam[plind:]/1e3, sbeam[plind:], lw=0.8, zorder=5, c='C1')
### limit it and set the log scale for y only
ax2.set_xlim([2, 7])
ax2.set_ylim([5e-12,2e-5])
ax2.set_yscale('log')
### format the ticks
ax2.yaxis.tick_right()
ax2.tick_params(axis="both", which='both', labelsize=9,
direction='in', left=True, bottom=True, right=True, top=True)
ax2.minorticks_on()
ax2.set_xticks(np.arange(2,7.1,1))
### make a joint axis for the xlabel
ax3=fig.add_subplot(111, frameon=False)
ax3.set_position([ax.get_position().x0, ax.get_position().y0,
ax2.get_position().x1-ax.get_position().x0,
ax.get_position().y1-ax.get_position().y0])
ax3.tick_params(labelcolor='none', which='both', top=False, bottom=False, left=False, right=False)
ax3.set_xlabel('Frequency (kHz)')
The main confusion here is that zorder
only applies to all elements drawn onto the same subplots. First, everything belonging to one subplot is plotted, and only then everything belonging to the other subplot.
Note that the code gives a warning: MatplotlibDeprecationWarning: The InsetPosition class was deprecated in Matplotlib 3.8 and will be removed two minor releases later. Use Axes.inset_axes instead.
I didn't change this, but this will need to be adapted for future matplotlib versions.
As the code didn't provide reproducible test data, I created my own. At the same time I simplified things a bit, just to concentrate on the main issue. (I did change the positions a bit to make space for the right y ticks).
The important changes are:
bbox_to_anchor
is measured in "axes coordinates", where x=0
is the left side of ax2
(x=1
would be the right side), -0.05
counts for the gap between the subplots, and an extra -0.5
would be the center of the main subplot.Some extra fine-tuning might be needed to further position everything.
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition, mark_inset
def plot_common_curves_on_ax(ax):
ax.plot(x, y1, zorder=4, c='C0', label='BES')
ax.plot(x, y2, zorder=6, c='C2', label='BES welch, N=2$^3$')
ax.plot(x, y3, lw=0.8, zorder=5, c='C1', label='Beam')
### three bits of data
x = np.linspace(0, 20, 500)
y1 = np.random.randn(len(x)).cumsum()
y2 = np.random.randn(len(x)).cumsum()
y3 = np.random.randn(len(x)).cumsum()
### make the first plot
fig, ax = plt.subplots(1, 1, figsize=(3.35, 2.5), dpi=150)
plot_common_curves_on_ax(ax=ax)
### changing the positions to make space at the right
X0 = ax.get_position().x0 + 0.07
X1 = ax.get_position().x1 - 0.37
Y0 = ax.get_position().y0 + 0.06
Y1 = ax.get_position().y1 + 0.09
ax.set_position([X0, Y0, X1 - X0, Y1 - Y0])
### making the inset
ax2 = plt.axes()
ip = InsetPosition(ax, [1.05, 0., 1, 1])
ax2.set_axes_locator(ip)
mark_inset(ax, ax2, loc1=2, loc2=3, fc='none', ec='k', lw=0.8, zorder=7)
### plot the data again
plot_common_curves_on_ax(ax=ax2)
### limit the inset
ax2.set_xlim([1, 3])
ax2.set_ylim([-4, 4])
### format the ticks
ax2.yaxis.tick_right()
ax2.tick_params(axis="both", which='both', labelsize=9,
direction='in', left=True, bottom=True, right=True, top=True)
ax2.minorticks_on()
# add the legend to ax2, but located above the main ax
leg2 = ax2.legend(handlelength=0, handletextpad=0, fontsize=8, fancybox=1,
framealpha=1, labelcolor='linecolor',
loc='lower center', bbox_to_anchor=(-0.55, 0))
leg2.set_zorder(15)
plt.show()
PS: About your extra question: To set the tick marks on top of the curves:
ax.set_axisbelow(False)
and ax2.set_axisbelow(False)
. This sets the zorder
of the ticks to 2.5
. I don't see another way to change their zorder
, as these ticks are only created during the plotting process.zorder
of the curves and other elements to be lower than 2.5
. Curves (lines) by default get a zorder=2
. When multiple curves have the same zorder
they get drawn in the order they are encountered.