Waterfall Chart
Subscription Revenue Lifecycle
SaaS subscription journey from trial to renewal showing conversion and churn.
Output
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch
categories = ['Trial\nSignups', 'Trial\nDropoff', 'Paid\nConversion', 'Month 1\nChurn',
'Month 3\nChurn', 'Upgrades', 'Annual\nRenewal', 'Active\nSubscribers']
values = [0, -4200, 0, -380, -290, 420, 0, 0]
initial = 10000
running_total = initial
bottoms, heights, colors = [], [], []
for i, (cat, val) in enumerate(zip(categories, values)):
if 'Trial\nSignups' in cat:
bottoms.append(0)
heights.append(initial)
colors.append('#27D3F5')
elif 'Paid\nConversion' in cat:
bottoms.append(0)
heights.append(running_total)
colors.append('#F5B027')
elif 'Annual\nRenewal' in cat:
bottoms.append(0)
heights.append(running_total)
colors.append('#4927F5')
elif 'Active' in cat:
bottoms.append(0)
heights.append(running_total)
colors.append('#6CF527')
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
fig, ax = plt.subplots(figsize=(14, 8), facecolor='#0a0a0f')
ax.set_facecolor('#0a0a0f')
x = np.arange(len(categories))
bars = ax.bar(x, heights, bottom=bottoms, color=colors, width=0.65, edgecolor='#1e293b', linewidth=1)
display_vals = [10000, -4200, 5800, -380, -290, 420, 5550, 5550]
for i, (bar, val, bot, height) in enumerate(zip(bars, display_vals, bottoms, heights)):
y_pos = bot + height / 2
if height > 2000:
label = f"{height:,}"
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 i not in [0, 2, 6, 7] else f"{height:,}"
ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, ha='center', va='center',
fontsize=9, fontweight='bold', color='white')
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='#e2e8f0')
ax.set_ylabel('Subscribers', fontsize=12, color='#e2e8f0', fontweight='500')
ax.set_title('Subscription Funnel: Trial to Active Subscriber', fontsize=16, color='white', fontweight='bold', pad=20)
ax.tick_params(axis='y', colors='#e2e8f0', labelsize=10)
ax.yaxis.grid(True, linestyle='--', alpha=0.3, color='#334155')
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_color('#334155')
conversion = (5550 / 10000) * 100
ax.annotate(f'Overall Conversion: {conversion:.1f}%', xy=(0.98, 0.95), xycoords='axes fraction',
fontsize=11, color='#6CF527', ha='right', fontweight='bold',
bbox=dict(boxstyle='round,pad=0.4', facecolor='#1e293b', edgecolor='#6CF527', alpha=0.9))
legend_elements = [Patch(facecolor='#27D3F5', label='Trials'), Patch(facecolor='#F5276C', label='Churn'),
Patch(facecolor='#27F5B0', label='Upgrades'), Patch(facecolor='#6CF527', label='Active')]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1), ncol=4, fontsize=9,
facecolor='#1e293b', edgecolor='#334155', labelcolor='white')
plt.tight_layout()
plt.subplots_adjust(bottom=0.15)
plt.show()
Library
Matplotlib
Category
Financial
More Waterfall Chart examples
☕