Waterfall Chart

Currency Translation Impact Analysis

FX impact waterfall showing currency effects on international revenue.

Output
Currency Translation Impact Analysis
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

categories = ['Local\nCurrency Rev', 'EUR\nTranslation', 'GBP\nTranslation', 'JPY\nTranslation', 
              'CNY\nTranslation', 'Other\nCurrencies', 'Hedging\nGains', 'USD\nRevenue']
values = [0, -28, -15, 12, -8, -5, 18, 0]

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

for i, (cat, val) in enumerate(zip(categories, values)):
    if 'Local' in cat:
        bottoms.append(0)
        heights.append(initial)
        colors.append('#4927F5')
    elif 'USD' in cat:
        bottoms.append(0)
        heights.append(running_total)
        colors.append('#6CF527')
    elif val > 0:
        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('#F5276C')
        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 'Local' in categories[i] or 'USD' in categories[i]:
        label = f"${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 = f"+${val}M" if val > 0 else f"-${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] + 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('Revenue ($ Millions)', fontsize=12, color='#e2e8f0', fontweight='500')
ax.set_title('Foreign Currency Translation Impact on Revenue', 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')

fx_impact = running_total - initial
pct = (fx_impact / initial) * 100
ax.annotate(f'Net FX Impact: {"+" if fx_impact > 0 else ""}{fx_impact}M ({pct:+.1f}%)', xy=(0.98, 0.95), xycoords='axes fraction',
            fontsize=11, color='#F5276C' if fx_impact < 0 else '#6CF527', ha='right', fontweight='bold',
            bbox=dict(boxstyle='round,pad=0.4', facecolor='#1e293b', edgecolor='#F5276C' if fx_impact < 0 else '#6CF527', alpha=0.9))

legend_elements = [Patch(facecolor='#4927F5', label='Local Currency'), Patch(facecolor='#F5276C', label='FX Loss'),
                   Patch(facecolor='#27F5B0', label='FX Gain'), Patch(facecolor='#6CF527', label='USD Revenue')]
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