Waterfall Chart
Customer Acquisition Cost Breakdown
CAC waterfall decomposing total acquisition cost by marketing and sales channels.
Output
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch
# CAC breakdown (in dollars per customer)
categories = ['Total\nCAC', 'Paid\nSearch', 'Social\nAds', 'Content\nMarketing',
'Sales\nTeam', 'Onboarding', 'Tech\nStack', 'Attribution\nEfficiency']
values = [0, -85, -62, -28, -145, -35, -25, 0]
# Calculate running total (deconstructing CAC)
initial = 380
running_total = initial
bottoms = []
heights = []
colors = []
palette = ['#F5276C', '#F54927', '#F5B027', '#4927F5', '#27D3F5', '#27F5B0']
for i, (cat, val) in enumerate(zip(categories, values)):
if cat == 'Total\nCAC':
bottoms.append(0)
heights.append(initial)
colors.append('#F5276C')
elif 'Efficiency' in cat:
bottoms.append(0)
heights.append(running_total)
colors.append('#6CF527')
else:
bottoms.append(running_total + val)
heights.append(abs(val))
colors.append(palette[(i-1) % len(palette)])
running_total += val
# Create figure
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)
# Add value labels
for i, (bar, val, bot, height) in enumerate(zip(bars, values, bottoms, heights)):
y_pos = bot + height / 2
if categories[i] == 'Total\nCAC':
label = f"${height}"
ax.text(bar.get_x() + bar.get_width()/2, y_pos, label,
ha='center', va='center', fontsize=11, fontweight='bold', color='white')
elif 'Efficiency' in categories[i]:
label = f"${height}"
ax.text(bar.get_x() + bar.get_width()/2, y_pos, label,
ha='center', va='center', fontsize=11, fontweight='bold', color='#0a0a0f')
else:
label = f"${abs(val)}"
ax.text(bar.get_x() + bar.get_width()/2, y_pos, label,
ha='center', va='center', fontsize=10, fontweight='bold', color='white')
# Connect bars
for i in range(len(x) - 1):
if i == 0:
y = initial
else:
y = bottoms[i]
ax.plot([x[i] + 0.35, x[i+1] - 0.35], [y, y],
color='#475569', linestyle='--', linewidth=1.5, alpha=0.7)
# 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='#e2e8f0')
ax.set_ylabel('Cost per Customer ($)', fontsize=12, color='#e2e8f0', fontweight='500')
ax.set_title('Customer Acquisition Cost Component Analysis', 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')
# LTV:CAC ratio annotation
ltv = 1520
cac = initial
ratio = ltv / cac
ax.annotate(f'LTV:CAC Ratio = {ratio:.1f}x', 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 outside plot
legend_elements = [
Patch(facecolor='#F5276C', label='Total CAC'),
Patch(facecolor='#F5B027', label='Cost Components'),
Patch(facecolor='#6CF527', label='Remaining')
]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1),
ncol=3, 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
☕