Waterfall Chart

Headcount Changes Analysis

Workforce planning waterfall showing hiring, attrition, and restructuring impacts.

Output
Headcount Changes Analysis
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

# Headcount changes
categories = ['Start\nof Year', 'New\nHires', 'Internal\nTransfers', 'Promotions', 
              'Voluntary\nAttrition', 'Involuntary\nSeparations', 'Contractor\nConversions', 'End\nof Year']
values = [0, 145, 0, 0, -68, -35, 22, 0]

# Calculate running total
initial = 520
running_total = initial
bottoms = []
heights = []
colors = []

for i, (cat, val) in enumerate(zip(categories, values)):
    if cat == 'Start\nof Year':
        bottoms.append(0)
        heights.append(initial)
        colors.append('#27D3F5')
    elif cat == 'End\nof Year':
        bottoms.append(0)
        heights.append(running_total)
        colors.append('#6CF527')
    elif 'Transfer' in cat or 'Promotion' in cat:
        bottoms.append(running_total - 10)
        heights.append(20)
        colors.append('#F5B027')
    elif val > 0:
        bottoms.append(running_total)
        heights.append(val)
        colors.append('#27F5B0')
        running_total += val
    else:
        bottoms.append(running_total + val)
        heights.append(abs(val))
        colors.append('#F5276C')
        running_total += val

# Create figure with light theme
fig, ax = plt.subplots(figsize=(14, 8), facecolor='#ffffff')
ax.set_facecolor('#ffffff')

x = np.arange(len(categories))
bars = ax.bar(x, heights, bottom=bottoms, color=colors, width=0.65, edgecolor='#e5e7eb', linewidth=1)

# Add value labels
for i, (bar, val, bot, height) in enumerate(zip(bars, values, bottoms, heights)):
    y_pos = bot + height / 2
    if categories[i] in ['Start\nof Year', 'End\nof Year']:
        label = f"{height}"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, 
                ha='center', va='center', fontsize=12, fontweight='bold', color='white')
    elif 'Transfer' in categories[i] or 'Promotion' in categories[i]:
        label = "0"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, 
                ha='center', va='center', fontsize=10, fontweight='bold', color='#0a0a0f')
    else:
        label = f"+{val}" if val > 0 else f"{val}"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, 
                ha='center', va='center', fontsize=10, fontweight='bold', color='white')

# Styling
ax.set_xlim(-0.6, len(categories) - 0.4)
ax.set_ylim(0, max(bottoms[i] + heights[i] for i in range(len(heights))) * 1.1)
ax.set_xticks(x)
ax.set_xticklabels(categories, fontsize=9, color='#374151')
ax.set_ylabel('Headcount', fontsize=12, color='#374151', fontweight='500')
ax.set_title('Annual Workforce Planning Analysis', fontsize=16, color='#111827', fontweight='bold', pad=20)

ax.tick_params(axis='y', colors='#374151', labelsize=10)
ax.yaxis.grid(True, linestyle='--', alpha=0.4, color='#e5e7eb')
ax.set_axisbelow(True)

for spine in ax.spines.values():
    spine.set_color('#d1d5db')

# Legend outside plot
legend_elements = [
    Patch(facecolor='#27D3F5', label='Starting Headcount'),
    Patch(facecolor='#27F5B0', label='Additions'),
    Patch(facecolor='#F5B027', label='Neutral Movements'),
    Patch(facecolor='#F5276C', label='Separations'),
    Patch(facecolor='#6CF527', label='Ending Headcount')
]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1), 
          ncol=5, fontsize=8, facecolor='white', edgecolor='#d1d5db', labelcolor='#374151')

plt.tight_layout()
plt.subplots_adjust(bottom=0.15)
plt.show()
Library

Matplotlib

Category

Financial

Did this help you?

Support PyLucid to keep it free & growing

Support