Waterfall Chart
SaaS Unit Economics Waterfall
Unit economics breakdown from gross revenue to contribution margin per customer.
Output
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch
# Unit economics (monthly $ per customer)
categories = ['Gross\nRevenue', 'Payment\nFees', 'Hosting\nCosts', 'Support\nCosts',
'Gross\nMargin', 'CAC\nPayback', 'Retention\nCosts', 'Contribution\nMargin']
values = [0, -8.50, -12.00, -15.50, 0, -18.00, -6.00, 0]
# Calculate through the waterfall
initial = 89.00
running_total = initial
bottoms = []
heights = []
colors = []
for i, (cat, val) in enumerate(zip(categories, values)):
if cat == 'Gross\nRevenue':
bottoms.append(0)
heights.append(initial)
colors.append('#27D3F5')
elif cat == 'Gross\nMargin':
bottoms.append(0)
heights.append(running_total)
colors.append('#F5B027')
elif cat == 'Contribution\nMargin':
bottoms.append(0)
heights.append(running_total)
colors.append('#6CF527')
elif val < 0:
bottoms.append(running_total + val)
heights.append(abs(val))
colors.append('#F5276C')
running_total += val
# Create figure - 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
labels = [89.00, 8.50, 12.00, 15.50, 53.00, 18.00, 6.00, 29.00]
for i, (bar, label, bot, height) in enumerate(zip(bars, labels, bottoms, heights)):
y_pos = bot + height / 2
if categories[i] in ['Gross\nRevenue', 'Gross\nMargin', 'Contribution\nMargin']:
text = f"${label:.2f}"
color = 'white' if height > 35 else '#374151'
else:
text = f"-${label:.2f}"
color = 'white'
ax.text(bar.get_x() + bar.get_width()/2, y_pos, text,
ha='center', va='center', fontsize=10, fontweight='bold', color=color)
# Styling
ax.set_xlim(-0.6, len(categories) - 0.4)
ax.set_ylim(0, initial * 1.1)
ax.set_xticks(x)
ax.set_xticklabels(categories, fontsize=9, color='#374151')
ax.set_ylabel('Monthly $ per Customer', fontsize=12, color='#374151', fontweight='500')
ax.set_title('SaaS Unit Economics: Revenue to Contribution Margin', 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')
# Margin percentages
gross_margin_pct = 53.00 / 89.00 * 100
contrib_margin_pct = 29.00 / 89.00 * 100
ax.annotate(f'Gross Margin: {gross_margin_pct:.1f}% | Contribution Margin: {contrib_margin_pct:.1f}%',
xy=(0.5, 0.95), xycoords='axes fraction',
fontsize=11, color='#374151', ha='center', fontweight='bold',
bbox=dict(boxstyle='round,pad=0.4', facecolor='#f3f4f6', edgecolor='#d1d5db'))
# Legend outside plot
legend_elements = [
Patch(facecolor='#27D3F5', label='Revenue'),
Patch(facecolor='#F5276C', label='Costs'),
Patch(facecolor='#F5B027', label='Gross Margin'),
Patch(facecolor='#6CF527', label='Contribution Margin')
]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1),
ncol=4, fontsize=9, facecolor='white', edgecolor='#d1d5db', labelcolor='#374151')
plt.tight_layout()
plt.subplots_adjust(bottom=0.15)
plt.show()
Library
Matplotlib
Category
Financial
More Waterfall Chart examples
☕