Waterfall Chart

Loan Amortization Waterfall

Loan payment breakdown showing principal reduction and interest allocation.

Output
Loan Amortization Waterfall
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

categories = ['Beginning\nPrincipal', 'Year 1\nPrincipal', 'Year 2\nPrincipal', 'Year 3\nPrincipal', 
              'Year 4\nPrincipal', 'Year 5\nPrincipal', 'Ending\nBalance']
values = [0, -42, -48, -55, -63, -72, 0]

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

for i, (cat, val) in enumerate(zip(categories, values)):
    if 'Beginning' in cat:
        bottoms.append(0)
        heights.append(initial)
        colors.append('#F5276C')
    elif 'Ending' in cat:
        bottoms.append(0)
        heights.append(running_total)
        colors.append('#6CF527' if running_total == 0 else '#F5B027')
    else:
        bottoms.append(running_total + val)
        heights.append(abs(val))
        colors.append('#27F5B0')
        running_total += val

fig, ax = plt.subplots(figsize=(12, 8), facecolor='#ffffff')
ax.set_facecolor('#ffffff')

x = np.arange(len(categories))
bars = ax.bar(x, heights, bottom=bottoms, color=colors, width=0.6, edgecolor='#e5e7eb', linewidth=1)

for i, (bar, val, bot, height) in enumerate(zip(bars, values, bottoms, heights)):
    y_pos = bot + height / 2
    if 'Beginning' in categories[i]:
        label = f"${height}K"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, ha='center', va='center', 
                fontsize=11, fontweight='bold', color='#374151')
    elif 'Ending' in categories[i]:
        label = f"${height}K"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, ha='center', va='center', 
                fontsize=11, fontweight='bold', color='#0a0a0f' if height > 0 else 'white')
    else:
        label = 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='#0a0a0f')

for i in range(len(x) - 1):
    y = initial if i == 0 else bottoms[i]
    ax.plot([x[i] + 0.35, x[i+1] - 0.35], [y, y], color='#9ca3af', linestyle='--', linewidth=1.5, alpha=0.7)

ax.set_xlim(-0.6, len(categories) - 0.4)
ax.set_ylim(0, initial * 1.15)
ax.set_xticks(x)
ax.set_xticklabels(categories, fontsize=10, color='#374151')
ax.set_ylabel('Principal Balance ($ Thousands)', fontsize=12, color='#374151', fontweight='500')
ax.set_title('5-Year Loan Amortization Schedule', 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')

paid_off = initial - running_total
pct = (paid_off / initial) * 100
ax.annotate(f'Principal Paid: ${paid_off}K ({pct:.0f}%)', 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='Initial Debt'), Patch(facecolor='#27F5B0', label='Principal Payments'),
                   Patch(facecolor='#6CF527', label='Remaining Balance')]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1), ncol=3, 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