Waterfall Chart

Revenue Recognition Timing Analysis

ASC 606 revenue recognition waterfall showing timing of revenue recognition.

Output
Revenue Recognition Timing Analysis
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

categories = ['Contract\nValue', 'Upfront\nRecognition', 'Q1\nRecognition', 'Q2\nRecognition', 
              'Q3\nRecognition', 'Q4\nRecognition', 'Deferred\nRevenue']
values = [0, -180, -95, -88, -75, -62, 0]

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

palette = ['#3b82f6', '#8b5cf6', '#06b6d4', '#22c55e', '#f59e0b']

for i, (cat, val) in enumerate(zip(categories, values)):
    if 'Contract' in cat:
        bottoms.append(0)
        heights.append(initial)
        colors.append('#3b82f6')
    elif 'Deferred' in cat:
        bottoms.append(0)
        heights.append(running_total)
        colors.append('#f59e0b')
    else:
        bottoms.append(running_total + val)
        heights.append(abs(val))
        colors.append(palette[(i) % len(palette)])
        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)

for i, (bar, val, bot, height) in enumerate(zip(bars, values, bottoms, heights)):
    y_pos = bot + height / 2
    if 'Contract' in categories[i] or 'Deferred' in categories[i]:
        label = str(height) + "M"
        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(abs(val)) + "M"
        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]
    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.1)
ax.set_xticks(x)
ax.set_xticklabels(categories, fontsize=10, color='#374151')
ax.set_ylabel('Revenue (Millions)', fontsize=12, color='#374151', fontweight='500')
ax.set_title('ASC 606 Revenue Recognition Timing', fontsize=16, color='#111827', fontweight='bold', pad=20)
ax.tick_params(axis='y', colors='#374151', labelsize=10)
ax.yaxis.grid(True, linestyle='--', alpha=0.4, color='#e5e7eb')
ax.set_axisbelow(True)
for spine in ax.spines.values():
    spine.set_color('#d1d5db')

recognized = initial - running_total
pct = (recognized / initial) * 100
label_text = 'Recognized: ' + str(recognized) + 'M (' + str(int(pct)) + ' pct)'
ax.annotate(label_text, xy=(0.5, 0.95), xycoords='axes fraction',
            fontsize=10, color='#374151', ha='center', fontweight='bold',
            bbox=dict(boxstyle='round,pad=0.4', facecolor='#f3f4f6', edgecolor='#d1d5db'))

legend_elements = [Patch(facecolor='#3b82f6', label='Contract Value'), Patch(facecolor='#8b5cf6', label='Revenue Recognized'),
                   Patch(facecolor='#f59e0b', label='Deferred Revenue')]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1), ncol=3, fontsize=9,
          facecolor='white', edgecolor='#d1d5db', 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