Waterfall Chart

Project ROI Breakdown

Investment return waterfall showing project costs, benefits, and net ROI.

Output
Project ROI Breakdown
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

categories = ['Initial\nInvestment', 'Implementation\nCosts', 'Training', 'Revenue\nIncrease', 
              'Cost\nSavings', 'Efficiency\nGains', 'Maintenance', 'Net\nROI']
values = [0, -85, -25, 180, 95, 65, -45, 0]

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

for i, (cat, val) in enumerate(zip(categories, values)):
    if 'Initial' in cat:
        bottoms.append(0)
        heights.append(250)  # Initial investment as reference
        colors.append('#F5276C')
        running_total = -250
    elif 'Net' in cat:
        bottoms.append(0 if running_total > 0 else running_total)
        heights.append(abs(running_total))
        colors.append('#6CF527' if running_total > 0 else '#F5276C')
    elif val > 0:
        if running_total < 0:
            bottoms.append(running_total)
        else:
            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('#F54927')
        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)

labels_val = [-250, -85, -25, 180, 95, 65, -45, running_total]
for i, (bar, val, bot, height) in enumerate(zip(bars, labels_val, bottoms, heights)):
    y_pos = bot + height / 2
    if i == 0:
        label = f"-${abs(val)}K"
    elif i == len(categories) - 1:
        label = f"${val}K" if val > 0 else f"-${abs(val)}K"
    else:
        label = f"+${val}K" if val > 0 else f"-${abs(val)}K"
    ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, ha='center', va='center', 
            fontsize=10, fontweight='bold', color='#374151')

ax.axhline(y=0, color='#9ca3af', linestyle='-', linewidth=1)
ax.set_xlim(-0.6, len(categories) - 0.4)
ax.set_ylim(min(bottoms) * 1.1, 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('Value ($ Thousands)', fontsize=12, color='#374151', fontweight='500')
ax.set_title('Project Investment ROI 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')

roi_pct = (running_total / 250) * 100
ax.annotate(f'ROI: {roi_pct:.0f}% | Payback: 14 months', xy=(0.98, 0.95), xycoords='axes fraction',
            fontsize=11, color='#6CF527', ha='right', fontweight='bold',
            bbox=dict(boxstyle='round,pad=0.4', facecolor='white', edgecolor='#6CF527', alpha=0.9))

legend_elements = [Patch(facecolor='#F5276C', label='Investment'), Patch(facecolor='#F54927', label='Costs'),
                   Patch(facecolor='#27F5B0', label='Benefits'), Patch(facecolor='#6CF527', label='Net ROI')]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1), ncol=4, 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