Search code examples
pythonmatplotlibstacked-bar-chart

Plotting a stacked bar chart with line plot in front - zorder and twin axis not working well together


This is my code. The issue is that the stacked bar plot is 'in front of' the line plot. I tried setting the zorder of the stacked bar plot to negative and the one of the line plot to positive, but it didn't work.

How can I get the line to be in front of the bars?

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# Example DataFrame
data = {
    'month_year': ['Jan-2023', 'Feb-2023', 'Mar-2023', 'Apr-2023'],
    'gas_share': [30, 25, 20, 35],
    'lignite_share': [50, 40, 45, 30],
    'coal_share': [20, 35, 35, 35],
    'final_residual': [500, 600, 550, 650]
}

df = pd.DataFrame(data)

# Create figure and primary axis
fig, ax1 = plt.subplots(figsize=(10, 6))

# Create a twin axis for the shares (secondary y-axis on the right)
ax2 = ax1.twinx()

# Create a stacked bar plot for the share columns on the secondary y-axis
width = 0.4  # width of bars
bar1 = np.arange(len(df['month_year']))

# Plot stacked bars (secondary y-axis)
ax2.bar(bar1, df['gas_share'], width, label='Gas Share', color='#FF9999', zorder=-1)
ax2.bar(bar1, df['lignite_share'], width, bottom=df['gas_share'], label='Lignite Share', color='#66B2FF', zorder=-1)
ax2.bar(bar1, df['coal_share'], width, bottom=df['gas_share'] + df['lignite_share'], label='Coal Share', color='#99FF99', zorder=-1)

# Set labels and ticks for the secondary axis
ax2.set_ylabel('Share (%)')
ax2.set_xticks(bar1)
ax2.set_xticklabels(df['month_year'])
ax2.tick_params(axis='y')

# Plot 'final_residual' as a line on the primary y-axis (line plot should appear on top)
ax1.plot(df['month_year'], df['final_residual'], color='black', label='Final Residual', marker='o', linewidth=2, zorder=20)
ax1.set_xlabel('Month-Year')
ax1.set_ylabel('Final Residual')
ax1.tick_params(axis='y')

# Add grid to the primary axis
ax1.grid(True)

# Add a legend and move it outside the plot area
fig.legend(loc="upper center", bbox_to_anchor=(0.5, -0), ncol=4)

# Show plot
plt.title("Final Residual and Share % Over Time")
plt.xticks(rotation=45)  # Rotate x-axis labels for better readability
plt.tight_layout()
plt.show()

Solution

  • You can also add the following before plt.show()

    ax1.set_zorder(1)
    ax1.patch.set_visible(False)
    

    If you only need the y axis grid use

    ax1.yaxis.grid(True)
    

    Here is the full solution.

    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    
    # Example DataFrame
    data = {
        'month_year': ['Jan-2023', 'Feb-2023', 'Mar-2023', 'Apr-2023'],
        'gas_share': [30, 25, 20, 35],
        'lignite_share': [50, 40, 45, 30],
        'coal_share': [20, 35, 35, 35],
        'final_residual': [500, 600, 550, 650]
    }
    
    df = pd.DataFrame(data)
    
    # Create figure and primary axis
    fig, ax1 = plt.subplots(figsize=(10, 6))
    
    # Create a twin axis for the shares (secondary y-axis on the right)
    ax2 = ax1.twinx()
    
    # Create a stacked bar plot for the share columns on the secondary y-axis
    width = 0.4  # width of bars
    bar1 = np.arange(len(df['month_year']))
    
    # Plot stacked bars (secondary y-axis)
    ax2.bar(bar1, df['gas_share'], width, label='Gas Share', color='#FF9999', zorder=-1)
    ax2.bar(bar1, df['lignite_share'], width, bottom=df['gas_share'], label='Lignite Share', color='#66B2FF', zorder=-1)
    ax2.bar(bar1, df['coal_share'], width, bottom=df['gas_share'] + df['lignite_share'], label='Coal Share', color='#99FF99', zorder=-1)
    
    # Set labels and ticks for the secondary axis
    ax2.set_ylabel('Share (%)')
    ax2.set_xticks(bar1)
    ax2.set_xticklabels(df['month_year'])
    ax2.tick_params(axis='y')
    
    # Plot 'final_residual' as a line on the primary y-axis (line plot should appear on top)
    ax1.plot(df['month_year'], df['final_residual'], color='black', label='Final Residual', marker='o', linewidth=2, zorder=20)
    ax1.set_xlabel('Month-Year')
    ax1.set_ylabel('Final Residual')
    ax1.tick_params(axis='y')
    
    # Add grid to the primary axis
    ax1.yaxis.grid(True)
    
    # Set Z of the axis
    ax1.set_zorder(1)
    ax1.patch.set_visible(False)
    
    # Add a legend and move it outside the plot area
    fig.legend(loc="upper center", bbox_to_anchor=(0.5, -0), ncol=4)
    
    # Show plot
    plt.title("Final Residual and Share % Over Time")
    plt.xticks(rotation=45)  # Rotate x-axis labels for better readability
    plt.tight_layout()
    plt.show()
    

    Figure