pythonmatplotlibsubplotinsets

Setting the legend on top of the lines connecting mark_inset plot


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)')

Image of the plot. Notice how the lines connecting the left subplot with the right subplots are above the legend, this is what I want to change. Any thoughts on how to move the markers above the lines would be helpful too.


Solution

  • 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:

    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()
    

    legend on top of connectors for inset plot

    PS: About your extra question: To set the tick marks on top of the curves: