Waterfall Chart

Manufacturing Variance Analysis

Production cost variance waterfall showing material, labor, and overhead variances.

Output
Manufacturing Variance Analysis
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

categories = ['Standard\nCost', 'Material\nPrice Var', 'Material\nUsage Var', 'Labor\nRate Var', 
              'Labor\nEfficiency', 'OH Volume\nVar', 'OH Spending\nVar', 'Actual\nCost']
values = [0, -18, -12, 8, 15, -22, -8, 0]

initial = 850
running_total = initial
bottoms, heights, colors = [], [], []

for i, (cat, val) in enumerate(zip(categories, values)):
    if 'Standard' in cat:
        bottoms.append(0)
        heights.append(initial)
        colors.append('#4927F5')
    elif 'Actual' in cat:
        bottoms.append(0)
        heights.append(running_total)
        colors.append('#F5276C' if running_total > initial else '#6CF527')
    elif val > 0:
        bottoms.append(running_total)
        heights.append(val)
        colors.append('#6CF527')  # Favorable
        running_total += val
    else:
        bottoms.append(running_total + val)
        heights.append(abs(val))
        colors.append('#F5276C')  # Unfavorable
        running_total += val

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)

for i, (bar, val, bot, height) in enumerate(zip(bars, values, bottoms, heights)):
    y_pos = bot + height / 2
    if 'Standard' in categories[i] or 'Actual' in categories[i]:
        label = f"${height}K"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, ha='center', va='center', 
                fontsize=11, fontweight='bold', color='#374151')
    else:
        label = f"+${val}K (F)" if val > 0 else f"-${abs(val)}K (U)"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, ha='center', va='center', 
                fontsize=9, fontweight='bold', color='#374151')

for i in range(len(x) - 1):
    y = initial if i == 0 else bottoms[i] + heights[i]
    ax.plot([x[i] + 0.35, x[i+1] - 0.35], [y, y], color='#9ca3af', linestyle='--', linewidth=1.5, alpha=0.7)

ax.set_xlim(-0.6, len(categories) - 0.4)
ax.set_ylim(0, max(b + h for b, h in zip(bottoms, heights)) * 1.1)
ax.set_xticks(x)
ax.set_xticklabels(categories, fontsize=9, color='#374151')
ax.set_ylabel('Cost ($ Thousands)', fontsize=12, color='#374151', fontweight='500')
ax.set_title('Manufacturing Cost Variance Analysis', fontsize=16, color='#374151', fontweight='bold', pad=20)
ax.tick_params(axis='y', colors='#e2e8f0', labelsize=10)
ax.yaxis.grid(True, linestyle='--', alpha=0.3, color='#e5e7eb')
ax.set_axisbelow(True)
for spine in ax.spines.values():
    spine.set_color('#334155')

net_var = running_total - initial
status = "Unfavorable" if net_var > 0 else "Favorable"
ax.annotate(f'Net Variance: ${abs(net_var)}K {status}', xy=(0.98, 0.95), xycoords='axes fraction',
            fontsize=11, color='#F5276C' if net_var > 0 else '#6CF527', ha='right', fontweight='bold',
            bbox=dict(boxstyle='round,pad=0.4', facecolor='white', edgecolor='#F5276C' if net_var > 0 else '#6CF527', alpha=0.9))

legend_elements = [Patch(facecolor='#4927F5', label='Standard Cost'), Patch(facecolor='#6CF527', label='Favorable (F)'),
                   Patch(facecolor='#F5276C', label='Unfavorable (U)')]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1), ncol=3, fontsize=9,
          facecolor='white', edgecolor='#e5e7eb', 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