← Back to list

Journal Figure Replication | Plotting a Horizontal Percent Stacked Bar Chart with Python

Horizontal Percent Stacked Bar Chart

ZHEMING XU in Top Python Libraries · 2026-06-05 13:24 · 50 claps · 5.6 min read paywalled
#python #data-science #data-visualization #machine-learning #artificial-intelligence
Open on Medium ↗
Wiki topics: ML · Machine Learning AI · AI · General VIS · Visual & Graphic Design EDU · Education & Learning 🔬 · Science · General

Journal Figure Replication | Plotting a Horizontal Percent Stacked Bar Chart with Python

Result

Paper: A land–water–energy–greenhouse gas nexus framework informs climate change mitigation in agriculture: A case study in the North China Plain

Imitation:

Code Explanation

  1. Importing libraries and configuring fonts
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = ['Times New Roman']
plt.rcParams['axes.unicode_minus'] = False
  1. Color library setup and color scheme selection
COLOR_SCHEMES = {
    1: ['#5D2126', '#A6302E', '#CD7D4E', '#EFE68A', '#DBEFF9'],
}

SCHEME_INDEX = 1
current_colors = COLOR_SCHEMES[SCHEME_INDEX]
  1. Plotting function initialization and figure setup
def draw_and_save_chart(categories, labels, data, colors):
    fig, ax = plt.subplots(figsize=(10, 6))  # Create the canvas/figure
    bar_height = 0.75  # Width (thickness) of the horizontal bar chart
    y_pos = np.arange(len(categories))  # Generate position indices for the Y-axis ticks
    # Initialize the left margin to track the starting X-coordinate for each row's stacked bars
    left_bottom = np.zeros(len(categories))
    # Save the right-edge coordinates of each segment to draw connecting lines later
    segments_right_edges = np.zeros((len(categories), len(labels)))
  1. Loop to plot the stacked bar chart
        bars = ax.barh(y_pos,
                       values,
                       height=bar_height,
                       left=left_bottom,
                       color=color,
                       edgecolor='white',
                       linewidth=0.5,
                       label=label.replace('\n', ' '),
                       zorder=0)
        #Track the current segment's right edge
        segments_right_edges[:, i] = left_bottom + values
            ax.text(bar.get_x() + bar.get_width() / 2,
                    bar.get_y() + bar.get_height() / 2,
                    f'{val:.2f}',
                    va='center',
                    ha='center',
                    color=text_color,
                    fontsize=11,
                    zorder=15)
  1. Draw hierarchical connecting lines
    # Draw connecting lines
    for row in range(len(categories) - 1):  # Iterate through each row of categories
        for col in range(len(labels)):  # Iterate through columns
            x1 = segments_right_edges[row, col]  # Get the right-edge X-coordinate of the bar segment in the current row
            y1 = y_pos[row] + bar_height / 2  # Upper-edge Y-coordinate of the current row's bar

            ax.plot(
                [x1, x2],
                [y1, y2],
                color='black',
                linewidth=1,
                zorder=10,
                clip_on=False)
  1. Chart styling and details configuration
    ax.set_yticks(y_pos)  # Set the primary ticks on the Y-axis
    ax.set_yticklabels(categories, fontsize=12)  # Set the tick labels text for the Y-axis
    ax.set_xlabel('Proportion of GHG emissions (%)', fontsize=12)  # Set the X-axis title
    ax.set_xlim(0, 100)  # Display range for the X-axis
    ax.tick_params(axis='x', labelsize=12)  # Set tick label font size for the X-axis

    # Configure horizontal background dashed lines
    minor_locs = np.arange(len(categories) - 1) + 0.5  # Position of minor ticks on the Y-axis
    ax.set_yticks(minor_locs, minor=True)  # Set the minor ticks on the Y-axis
    # Draw horizontal background dashed grid lines at the minor tick positions
    ax.grid(which='minor', axis='y', linestyle='--', alpha=0.7, color='gray', zorder=0)  

    # Hide specified spines (borders)
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)

    # Configure tick parameters
    ax.tick_params(axis='y', which='major', left=True, right=False, length=5, width=1, direction='out')
    ax.tick_params(axis='x', direction='out')
  1. Legend Setup

handles, plot_labels = ax.get_legend_handles_labels() 
    #Add legends
    ax.legend(handles,
              labels,
              loc='upper left',
              bbox_to_anchor=(1, 1),
              frameon=False,
              fontsize=12,
              handlelength=1.0,
              handleheight=1.0)

    plt.tight_layout()
  1. Data preparation & plotting function execution
if __name__ == "__main__":
    categories = ['BS', 'S1', 'S2', 'S3', 'S4', 'S5']  # List of categories for the Y-axis
    labels = ['Rice', 'Agricultural\nland', 'Diesel use', 'Irrigation', 'Indirect']  # List of labels for the stacked segments

    # Define the data matrix where each row corresponds to a category and each column corresponds to a label
    data = np.array([
        [42.96, 9.57, 16.38, 16.60, 14.49],
        [38.28, 10.21, 17.49, 18.55, 15.47],
        [43.07, 9.35, 16.42, 16.64, 14.53],
        [44.19, 9.85, 16.85, 14.21, 14.90],
        [45.65, 10.17, 13.92, 17.63, 12.63],
        [42.36, 11.02, 15.48, 17.10, 14.04]
    ])

    draw_and_save_chart(categories, labels, data, current_colors)

Complete Code

# =========================================================================================
# ====================================== 1. Environment Setup =======================================
# =========================================================================================
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = ['Times New Roman']
plt.rcParams['axes.unicode_minus'] = False

# =========================================================================================
# ====================================== 2. Color Library ==========================
# =========================================================================================
COLOR_SCHEMES = {
    1: ['#5D2126', '#A6302E', '#CD7D4E', '#EFE68A', '#DBEFF9'],
    2: ['#264653', '#2A9D8F', '#E9C46A', '#F4A261', '#E76F51'],
    3: ['#003f5c', '#58508d', '#bc5090', '#ff6361', '#ffa600'],
    4: ['#335c67', '#fff3b0', '#e09f3e', '#9e2a2b', '#540b0e'],
    5: ['#d72631', '#a2d5c6', '#077b8a', '#5c3c92', '#e2d810'],
    6: ['#ef476f', '#ffd166', '#06d6a0', '#118ab2', '#073b4c'],
    7: ['#f94144', '#f3722c', '#f8961e', '#f9c74f', '#90be6d'],
    8: ['#54478c', '#2c699a', '#048ba8', '#0db39e', '#16db93'],
    9: ['#0d3b66', '#faf0ca', '#f4d35e', '#ee964b', '#f95738'],
    10: ['#5f0f40', '#9a031e', '#fb8b24', '#e36414', '#0f4c5c'],
    11: ['#22223b', '#4a4e69', '#9a8c98', '#c9ada7', '#f2e9e4'],
    12: ['#606c38', '#283618', '#fefae0', '#dda15e', '#bc6c25'],
    13: ['#1d3557', '#457b9d', '#a8dadc', '#f1faee', '#e63946'],
    14: ['#8ecae6', '#219ebc', '#023047', '#ffb703', '#fb8500'],
    15: ['#cdb4db', '#ffc8dd', '#ffafcc', '#bde0fe', '#a2d2ff'],
    16: ['#000000', '#14213d', '#fca311', '#e5e5e5', '#ffffff'],
    17: ['#50514f', '#f25f5c', '#ffe066', '#247ba0', '#70c1b3'],
    18: ['#7400b8', '#6930c3', '#5e60ce', '#5390d9', '#4ea8de'],
    19: ['#386641', '#6a994e', '#a7c957', '#f2e8cf', '#bc4749'],
    20: ['#355070', '#6d597a', '#b56576', '#e56b6f', '#eaac8b'],
}

SCHEME_INDEX = 1
current_colors = COLOR_SCHEMES[SCHEME_INDEX]

# =========================================================================================
# ====================================== 3. Plotting Function ==========================
# =========================================================================================
def draw_and_save_chart(categories, labels, data, colors):
    fig, ax = plt.subplots(figsize=(10, 6))  # Create the canvas/figure
    bar_height = 0.75  # Width (thickness) of the horizontal bar chart
    y_pos = np.arange(len(categories))  # Generate position indices for the Y-axis ticks
    # Initialize the left margin to track the starting X-coordinate for each row's stacked bars
    left_bottom = np.zeros(len(categories))
    # Save the right-edge coordinates of each segment to draw connecting lines later
    segments_right_edges = np.zeros((len(categories), len(labels)))

    # Loop to plot each layer of the stacked bar chart
    for i, (label, color) in enumerate(zip(labels, colors)):  # Iterate through each label and color
        values = data[:, i]  # Extract the numerical values of the current sub-item across all categories
        # Plot horizontal bars
        bars = ax.barh(y_pos,
                       values,
                       height=bar_height,
                       left=left_bottom,
                       color=color,
                       edgecolor='white',
                       linewidth=0.5,
                       label=label.replace('\n', ' '),
                       zorder=0)
        # Record the right edge position of the current segment
        segments_right_edges[:, i] = left_bottom + values

        for bar, val in zip(bars, values):  # Iterate through each generated bar object and its value
            text_color = 'white' if i < 3 else 'black'  # Determine text color by layer: white for the first 3 layers, black otherwise
            # Add numerical value label to the center of the bar segment
            ax.text(bar.get_x() + bar.get_width() / 2,
                    bar.get_y() + bar.get_height() / 2,
                    f'{val:.2f}',
                    va='center',
                    ha='center',
                    color=text_color,
                    fontsize=11,
                    zorder=15)
        # Update the left starting point for the next drawing layer
        left_bottom += values

    # Draw hierarchical connecting lines
    for row in range(len(categories) - 1):  # Iterate through each row of categories
        for col in range(len(labels)):  # Iterate through columns
            x1 = segments_right_edges[row, col]  # Get the right-edge X-coordinate of the bar segment in the current row
            y1 = y_pos[row] + bar_height / 2  # Upper-edge Y-coordinate of the current row's bar
            x2 = segments_right_edges[row + 1, col]  # Right-edge X-coordinate of the bar segment in the next row
            y2 = y_pos[row + 1] - bar_height / 2  # Lower-edge Y-coordinate of the next row's bar
            # Draw a straight black line connecting the two points
            ax.plot([x1, x2],
                    [y1, y2],
                    color='black',
                    linewidth=1,
                    zorder=10,
                    clip_on=False)

    ax.set_yticks(y_pos)  # Set the primary ticks on the Y-axis
    ax.set_yticklabels(categories, fontsize=12)  # Set the tick labels text for the Y-axis
    ax.set_xlabel('Proportion of GHG emissions (%)', fontsize=12)  # Set the X-axis title
    ax.set_xlim(0, 100)  # Display range for the X-axis
    ax.tick_params(axis='x', labelsize=12)  # Set tick label font size for the X-axis

    # Configure horizontal background dashed lines
    minor_locs = np.arange(len(categories) - 1) + 0.5  # Position of minor ticks on the Y-axis
    ax.set_yticks(minor_locs, minor=True)  # Set the minor ticks on the Y-axis
    # Draw horizontal background dashed grid lines at the minor tick positions
    ax.grid(which='minor', axis='y', linestyle='--', alpha=0.7, color='gray', zorder=0)  

    # Hide specified spines (borders)
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)

    # Configure tick parameters
    ax.tick_params(axis='y', which='major', left=True, right=False, length=5, width=1, direction='out')
    ax.tick_params(axis='x', direction='out')

    handles, plot_labels = ax.get_legend_handles_labels()  # Get legend handles and labels from the current chart
    # Add the legend
    ax.legend(handles,
              labels,
              loc='upper left',
              bbox_to_anchor=(1, 1),
              frameon=False,
              fontsize=12,
              handlelength=1.0,
              handleheight=1.0)

    plt.tight_layout()  # Automatically adjust the layout

    # Save the chart
    plt.savefig(fr"D:\folder\chart_{SCHEME_INDEX}.png", dpi=300, bbox_inches='tight')
    plt.savefig(fr"D:\folder\chart_{SCHEME_INDEX}.pdf", format='pdf', bbox_inches='tight')

# =========================================================================================
# ====================================== 4. Data Analysis & Execution =====================
# =========================================================================================
if __name__ == "__main__":
    categories = ['BS', 'S1', 'S2', 'S3', 'S4', 'S5']  # List of categories for the Y-axis
    labels = ['Rice', 'Agricultural\nland', 'Diesel use', 'Irrigation', 'Indirect']  # List of labels for the stacked segments

    # Define the data matrix where each row corresponds to a category and each column corresponds to a label
    data = np.array([
        [42.96, 9.57, 16.38, 16.60, 14.49],
        [38.28, 10.21, 17.49, 18.55, 15.47],
        [43.07, 9.35, 16.42, 16.64, 14.53],
        [44.19, 9.85, 16.85, 14.21, 14.90],
        [45.65, 10.17, 13.92, 17.63, 12.63],
        [42.36, 11.02, 15.48, 17.10, 14.04]
    ])

    draw_and_save_chart(categories, labels, data, current_colors)  # Call the function to plot and save the chart

Thank you for reading.


메타데이터
post_id
3f25299e4780
slug
journal-figure-replication-plotting-a-horizontal-percent-stacked-bar-chart-with-python-3f25299e4780
url
https://medium.com/top-python-libraries/journal-figure-replication-plotting-a-horizontal-percent-stacked-bar-chart-with-python-3f25299e4780
canonical_url
https://medium.com/top-python-libraries/journal-figure-replication-plotting-a-horizontal-percent-stacked-bar-chart-with-python-3f25299e4780
author_url
https://medium.com/@benjamin_hui
status
ok
fetched_at
2026-06-14 16:15:44