Waterfall Chart

Insurance Premium Breakdown

Insurance premium waterfall showing base rate adjustments and final premium calculation.

Output
Insurance Premium Breakdown
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

categories = ['Base\nPremium', 'Age\nFactor', 'Driving\nHistory', 'Vehicle\nType', 
              'Coverage\nLevel', 'Multi-Policy\nDiscount', 'Loyalty\nDiscount', 'Final\nPremium']
values = [0, 180, 95, 120, 85, -145, -65, 0]

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

for i, (cat, val) in enumerate(zip(categories, values)):
    if 'Base' in cat:
        bottoms.append(0)
        heights.append(initial)
        colors.append('#4927F5')
    elif 'Final' in cat:
        bottoms.append(0)
        heights.append(running_total)
        colors.append('#6CF527')
    elif val > 0:
        bottoms.append(running_total)
        heights.append(val)
        colors.append('#F5276C')
        running_total += val
    else:
        bottoms.append(running_total + val)
        heights.append(abs(val))
        colors.append('#27F5B0')
        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)

for i, (bar, val, bot, height) in enumerate(zip(bars, values, bottoms, heights)):
    y_pos = bot + height / 2
    if 'Base' in categories[i] or 'Final' in categories[i]:
        label = str(height)
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, ha='center', va='center', 
                fontsize=11, fontweight='bold', color='white')
    else:
        label = '+' + str(val) if val > 0 else str(val)
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, ha='center', va='center', 
                fontsize=10, fontweight='bold', color='white')

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

ax.set_xlim(-0.6, len(categories) - 0.4)
ax.set_ylim(0, max(b + h for b, h in zip(bottoms, heights)) * 1.1)
ax.set_xticks(x)
ax.set_xticklabels(categories, fontsize=9, color='#e2e8f0')
ax.set_ylabel('Annual Premium', fontsize=12, color='#e2e8f0', fontweight='500')
ax.set_title('Auto Insurance Premium Calculation', 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')

change = running_total - initial
pct = (change / initial) * 100
ax.annotate('Net Adjustment: ' + ('+' if change > 0 else '') + str(change) + ' (' + str(round(pct, 1)) + ' pct)', 
            xy=(0.98, 0.95), xycoords='axes fraction',
            fontsize=11, color='#6CF527' if change < initial * 0.5 else '#F5B027', ha='right', fontweight='bold',
            bbox=dict(boxstyle='round,pad=0.4', facecolor='#1e293b', edgecolor='#6CF527', alpha=0.9))

legend_elements = [Patch(facecolor='#4927F5', label='Base Premium'), Patch(facecolor='#F5276C', label='Surcharges'),
                   Patch(facecolor='#27F5B0', label='Discounts'), Patch(facecolor='#6CF527', label='Final Premium')]
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

Did this help you?

Support PyLucid to keep it free & growing

Support