Waterfall Chart

Customer Acquisition Cost Breakdown

CAC waterfall decomposing total acquisition cost by marketing and sales channels.

Output
Customer Acquisition Cost Breakdown
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

Did this help you?

Support PyLucid to keep it free & growing

Support