pythonmatplotlibplotbar-charttwinx

Ordering plots so that twin axis plot is behind axis plot (matplotlib)


Im plotting graphs (nrows=2), p1 bars on top chart y-axis, p2 bars on bottom y axis, and a line/area chart on the twin yaxis (pidx on top and bottom).

I cant seem to get the twin axis plot (pidx) to go behind the p1 and p2 bars being drawn.

Whats happening is that where the p1 and p2 bars go below the pidx line, and into the lightgrey area Im plotting (with alpha=0.3) the bars start to fade, as if they are behind pidx. Also, the pidx line always passes on top of the bars.

Ive tried various combinations of zorder: p1=1, pidx=0, pidx=-1, p1 default etc, and put the axs code below the axs-twin code, to make sure its plotted after, but nothing seems to have any effect at all.

Anyone have any suggestions? Thanks. Here the code:

#############################################################################
# PLOTTING
#############################################################################

p = breadth_df_summed.reset_index().rename(columns={'index': 'Date'})
p1 = p[['Date', '>4%1d', '>25%Q', '>25%M', '>50%M', '>13%34d']].tail(lookback)
date_labels = p1['Date'].dt.strftime("%d/%m/%Y").tolist()

p2 = p[['Date', '<4%1d', '<25%Q', '<25%M', '<50%M', '<13%34d']].tail(lookback)

pidx = df_idx.tail(lookback)

# Create subplots
fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(17, 9))

#############################################################################
# Top subplot POSITIVE MOVERS
#############################################################################

# Create a stacked bar chart
bottom = None
for col in p1.columns[1:]:
    axs[0].bar(p1.index, p1[col], label=col, bottom=bottom)
    if bottom is None:
        bottom = p1[col]
    else:
        bottom += p1[col]

axs[0].set_xticks(p1.index[::10])
axs[0].set_xticklabels(date_labels[::10], rotation=45)
# Adding the x-axis with dates
axs[0].set_xlabel('Date')
# Adding labels for both y-axes
axs[0].set_ylabel('Plus % movers')
# Add title for plot
axs[0].set_title(f"{idx} - Positive Movers")

# Creating the second y-axis on the right
axs0_twin = axs[0].twinx()
axs0_twin.fill_between(p1.index, 0, pidx, color='lightgrey', alpha=0.3, label=idx, zorder=-1)  # Light grey area
axs0_twin.plot(p1.index, pidx, 'black', label=idx, linewidth=1, zorder=-1)
axs0_twin.set_ylim(bottom=min(pidx), top=max(pidx))
axs0_twin.set_ylabel(idx, color='black')

# Combine legends for ax and ax_twin into a single legend
lines, labels = axs[0].get_legend_handles_labels()
lines_twin, labels_twin = axs0_twin.get_legend_handles_labels()
axs[0].legend(lines + lines_twin, labels + labels_twin, loc='upper left')

#############################################################################
# Bottom subplot NEGATIVE MOVERS
#############################################################################

# Create a stacked bar chart
bottom = None
for col in p2.columns[1:]:
    axs[1].bar(p2.index, -p2[col], label=col, bottom=bottom)
    if bottom is None:
        bottom = -p2[col]
    else:
        bottom += -p2[col]

axs[1].set_xticks(p1.index[::10])
axs[1].set_xticklabels(date_labels[::10], rotation=45)
# Adding the x-axis with dates
axs[1].set_xlabel('Date')
# Adding labels for both y-axes
axs[1].set_ylabel('Negative % Movers')
# Add title for plot
axs[1].set_title(f"{idx} - Negative Movers")

# Creating the second y-axis on the right
axs1_twin = axs[1].twinx()
axs1_twin.fill_between(p1.index, 0, pidx, color='lightgrey', alpha=0.3, label=idx, zorder=-1)  # Light grey area
axs1_twin.plot(p2.index, pidx, 'black', label=idx, linewidth=1, zorder=-1)
axs1_twin.set_ylim(bottom=min(pidx), top=max(pidx))
axs1_twin.set_ylabel(idx, color='black')

# Combine legends for ax and ax_twin into a single legend
lines, labels = axs[1].get_legend_handles_labels()
lines_twin, labels_twin = axs0_twin.get_legend_handles_labels()
axs[1].legend(lines + lines_twin, labels + labels_twin, loc='upper left')

plt.tight_layout()
plt.show()

Solution

  • Z-order cannot be respected between twin axes, and whatever is on the twin is drawn on top. If I have understood your problem, you could swap what you plot with the main axes and what you plot with the twin:

    Current:

    import matplotlib.pyplot as plt
    
    fig, ax = plt.subplots()
    ax.bar([1, 2, 3, 4], [2, 4, 1, 7], alpha=0.5)
    
    twin_ax = ax.twinx()
    twin_ax.fill_between([1, 2, 3, 4], [60, 67, 54, 60], color='gray')
    

    enter image description here

    New:

    fig, ax = plt.subplots()
    ax.fill_between([1, 2, 3, 4], [60, 67, 54, 60], color='gray')
    
    twin_ax = ax.twinx()
    twin_ax.bar([1, 2, 3, 4], [2, 4, 1, 7], alpha=0.5)
    
    # Swap the left and right ticks.
    ax.yaxis.tick_right()
    twin_ax.yaxis.tick_left()
    

    enter image description here