How to Create a Waterfall Chart in Plotly
Waterfall charts visualize how an initial value increases and decreases through a series of intermediate steps to reach a final value. Unlike traditional bar charts that show independent values,...
Key Insights
- Waterfall charts excel at visualizing cumulative effects of sequential positive and negative values, making them essential for financial statements, budget variance analysis, and inventory flow tracking
- Plotly’s
go.Waterfall()provides a declarative API with built-in connector lines and automatic color coding, eliminating the need for manual bar stacking calculations required in other libraries - The
measureparameter is critical—use ‘relative’ for incremental changes, ’total’ for checkpoints, and ‘absolute’ for starting points to ensure proper chart rendering
Introduction to Waterfall Charts
Waterfall charts visualize how an initial value increases and decreases through a series of intermediate steps to reach a final value. Unlike traditional bar charts that show independent values, waterfall charts display cumulative impact, with floating bars connected by lines that show the flow from one value to the next.
These charts are indispensable in finance for breaking down profit and loss statements, showing how revenue and various expense categories combine to produce net income. They’re equally valuable for analyzing budget variances, tracking inventory changes, or explaining any sequential process where you need to show how you got from point A to point B.
Plotly stands out as the best choice for waterfall charts because it handles the complex positioning calculations automatically, provides excellent interactivity out of the box, and renders beautifully in both Jupyter notebooks and web applications. While you could manually construct waterfall charts using stacked bars in matplotlib, Plotly’s dedicated waterfall object saves hours of calculation and positioning headaches.
Setting Up Your Environment
Install Plotly using pip if you haven’t already:
pip install plotly pandas numpy
For this tutorial, we’ll create a sample dataset representing a company’s quarterly financial performance. This gives us a realistic scenario with both positive and negative changes:
import plotly.graph_objects as go
import pandas as pd
import numpy as np
# Sample financial data for Q1 performance
data = {
'category': ['Starting Revenue', 'Product Sales', 'Service Revenue',
'Cost of Goods Sold', 'Operating Expenses', 'Marketing Costs',
'Net Income'],
'amount': [0, 250000, 180000, -120000, -95000, -65000, 0],
'measure': ['absolute', 'relative', 'relative', 'relative',
'relative', 'relative', 'total']
}
df = pd.DataFrame(data)
print(df)
The measure parameter is crucial here. Set it to ‘absolute’ for starting points, ‘relative’ for changes (positive or negative), and ’total’ for cumulative checkpoints where you want to show the running total.
Creating a Basic Waterfall Chart
Creating a waterfall chart in Plotly is straightforward with the go.Waterfall() object. Here’s a basic implementation:
fig = go.Figure(go.Waterfall(
name = "Financial Performance",
orientation = "v",
measure = df['measure'],
x = df['category'],
y = df['amount'],
text = df['amount'],
textposition = "outside",
connector = {"line": {"color": "rgb(63, 63, 63)"}},
))
fig.update_layout(
title = "Q1 2024 Financial Breakdown",
showlegend = False,
height = 500,
width = 900
)
fig.show()
This code produces a functional waterfall chart where positive changes float upward from the previous total, negative changes float downward, and total markers show cumulative values. The connector lines automatically link each bar to the next, creating the characteristic “waterfall” appearance.
The chart automatically colors increasing values differently from decreasing ones, and the ’total’ measure creates a bar that extends from zero to the cumulative sum, making it easy to spot checkpoints.
Customizing Chart Appearance
Plotly offers extensive customization options. Here’s how to create a professional-looking chart with custom colors and formatting:
fig = go.Figure(go.Waterfall(
name = "Cash Flow",
orientation = "v",
measure = df['measure'],
x = df['category'],
y = df['amount'],
textposition = "outside",
text = ["$" + "{:,.0f}".format(val) if val != 0 else "" for val in df['amount']],
# Custom colors
increasing = {"marker": {"color": "#2ecc71"}}, # Green
decreasing = {"marker": {"color": "#e74c3c"}}, # Red
totals = {"marker": {"color": "#3498db"}}, # Blue
# Connector styling
connector = {
"line": {
"color": "rgb(100, 100, 100)",
"width": 2,
"dash": "dot"
}
},
# Hover customization
hovertemplate = "<b>%{x}</b><br>Amount: $%{y:,.0f}<extra></extra>"
))
fig.update_layout(
title = {
'text': "Q1 2024 Financial Performance",
'x': 0.5,
'xanchor': 'center',
'font': {'size': 20, 'color': '#2c3e50'}
},
xaxis = {
'title': '',
'tickangle': -45
},
yaxis = {
'title': 'Amount (USD)',
'tickformat': '$,.0f'
},
plot_bgcolor = 'rgba(240, 240, 240, 0.5)',
showlegend = False,
height = 600,
width = 1000,
margin = dict(l=80, r=80, t=100, b=120)
)
fig.show()
This enhanced version uses semantic colors (green for gains, red for losses, blue for totals), formats currency properly with dollar signs and thousand separators, and includes a custom hover template that displays clean information when users interact with the chart.
Advanced Features
For more complex analyses, you often need subtotals at intervals. Here’s a comprehensive example showing annual revenue with quarterly subtotals:
# More complex dataset with quarterly subtotals
advanced_data = {
'category': [
'Starting Balance',
'Q1 Sales', 'Q1 Expenses', 'Q1 Total',
'Q2 Sales', 'Q2 Expenses', 'Q2 Total',
'Q3 Sales', 'Q3 Expenses', 'Q3 Total',
'Q4 Sales', 'Q4 Expenses', 'Q4 Total',
'Annual Total'
],
'amount': [
0,
450000, -280000, 0,
520000, -310000, 0,
490000, -295000, 0,
580000, -340000, 0,
0
],
'measure': [
'absolute',
'relative', 'relative', 'total',
'relative', 'relative', 'total',
'relative', 'relative', 'total',
'relative', 'relative', 'total',
'total'
]
}
df_advanced = pd.DataFrame(advanced_data)
fig = go.Figure(go.Waterfall(
orientation = "v",
measure = df_advanced['measure'],
x = df_advanced['category'],
y = df_advanced['amount'],
textposition = "outside",
text = ["$" + "{:,.0f}".format(val) if val != 0 else ""
for val in df_advanced['amount']],
increasing = {"marker": {"color": "#27ae60"}},
decreasing = {"marker": {"color": "#c0392b"}},
totals = {"marker": {"color": "#2980b9", "line": {"color": "#1a5490", "width": 2}}},
connector = {"line": {"color": "rgba(63, 63, 63, 0.5)", "width": 1}}
))
# Add annotations for quarterly totals
quarterly_totals = [3, 6, 9, 12]
for idx in quarterly_totals:
if idx < len(df_advanced):
cumsum = df_advanced['amount'][:idx+1].sum()
fig.add_annotation(
x = df_advanced['category'][idx],
y = cumsum,
text = f"${cumsum:,.0f}",
showarrow = True,
arrowhead = 2,
arrowsize = 1,
arrowwidth = 2,
arrowcolor = "#2980b9",
ax = 0,
ay = -40,
font = dict(size=12, color="#2c3e50", family="Arial Black")
)
fig.update_layout(
title = "Annual Financial Performance with Quarterly Breakdown",
xaxis = {'tickangle': -45},
yaxis = {'title': 'Amount (USD)', 'tickformat': '$,.0f'},
height = 600,
width = 1200,
showlegend = False
)
fig.show()
This advanced example demonstrates subtotals at regular intervals, allowing viewers to see both quarterly performance and cumulative progress throughout the year. The annotations highlight key checkpoints, drawing attention to quarterly results.
Making Charts Interactive
Plotly charts are interactive by default, but you can enhance them further with custom hover templates and dynamic controls:
# Create multiple datasets for different views
q1_data = df_advanced[df_advanced['category'].str.contains('Q1|Starting|Annual')]
q2_data = df_advanced[df_advanced['category'].str.contains('Q2|Starting|Annual')]
# Enhanced interactive chart
fig = go.Figure()
fig.add_trace(go.Waterfall(
name = "Full Year",
orientation = "v",
measure = df_advanced['measure'],
x = df_advanced['category'],
y = df_advanced['amount'],
text = ["$" + "{:,.0f}".format(val) if val != 0 else ""
for val in df_advanced['amount']],
textposition = "outside",
increasing = {"marker": {"color": "#27ae60"}},
decreasing = {"marker": {"color": "#c0392b"}},
totals = {"marker": {"color": "#2980b9"}},
connector = {"line": {"color": "rgba(63, 63, 63, 0.5)"}},
hovertemplate = "<b>%{x}</b><br>" +
"Amount: $%{y:,.0f}<br>" +
"<extra></extra>"
))
fig.update_layout(
title = "Interactive Financial Waterfall",
updatemenus = [
dict(
type = "buttons",
direction = "left",
buttons = list([
dict(
args = [{"visible": [True]}],
label = "Full Year",
method = "update"
)
]),
pad = {"r": 10, "t": 10},
showactive = True,
x = 0.11,
xanchor = "left",
y = 1.15,
yanchor = "top"
)
],
xaxis = {'tickangle': -45},
yaxis = {'title': 'Amount (USD)', 'tickformat': '$,.0f'},
height = 600,
width = 1200
)
fig.show()
Best Practices and Common Pitfalls
Data Preparation: Always ensure your data is in the correct format before creating the chart. Your ‘measure’ array must match the length of your data, and you need at least one ’total’ or ‘absolute’ measure to anchor the chart properly.
Measure Type Selection: The most common mistake is incorrectly assigning measure types. Use ‘absolute’ only for true starting points (usually zero), ‘relative’ for all incremental changes, and ’total’ for cumulative checkpoints. Mixing these up will produce incorrect visualizations.
Label Readability: With many categories, labels can overlap. Rotate x-axis labels with tickangle = -45, increase chart width, or abbreviate category names. For dense data, consider creating multiple charts or using drill-down interactions.
Color Consistency: Stick to conventional color schemes—green for positive, red for negative. This aids quick comprehension and aligns with user expectations from financial contexts.
Performance: For datasets with hundreds of points, waterfall charts become cluttered and slow. Consider aggregating data, showing top contributors, or implementing filtering mechanisms. Plotly handles up to 50-100 points well, but beyond that, user experience degrades.
Context Matters: Always include a clear title, axis labels, and consider adding a subtitle explaining what the chart shows. Waterfall charts can be unfamiliar to some audiences, so providing context helps interpretation.
Waterfall charts in Plotly transform complex sequential data into clear visual narratives. With these techniques, you can create professional, interactive visualizations that communicate financial and operational insights effectively.