Income Statement Sankey
revenue decomposition to net income
Example from the compendium of canonical charts
Python Code
"""Income Statement Sankey — revenue decomposition to net income."""
from pathlib import Path
# ── Palette + theme (matching the Plotly Studio gallery these charts ship in) ──
VIOLET, TEAL, GREEN, PINK, ORANGE = "#845EEE", "#52B3D0", "#55B685", "#DA5597", "#E9A23B"
PRIMARY, SECONDARY = VIOLET, TEAL
COLORWAY = [VIOLET, TEAL, GREEN, PINK, ORANGE]
BG, TEXT, GRID, MUTED = "#ffffff", "#1c2024", "#d9d9e0", "#60646c"
FONT = "Inter, -apple-system, BlinkMacSystemFont, sans-serif"
COLORSCALE = [[0, "rgba(132, 94, 238, 0.05)"], [1, "rgba(132, 94, 238, 0.9)"]]
def apply_theme(fig):
"""Light gallery theme: white background, Inter font, soft gridlines."""
fig.update_layout(
paper_bgcolor=BG, plot_bgcolor=BG, colorway=COLORWAY,
font=dict(family=FONT, color=TEXT, size=12),
legend=dict(font=dict(color=TEXT)),
hoverlabel=dict(bgcolor="#f0f0f3", font=dict(color=TEXT, family=FONT), bordercolor=GRID),
)
fig.update_xaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
fig.update_yaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
def fetch_csv(url, **kwargs):
import io
import pandas as pd
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return pd.read_csv(io.StringIO(r.text), **kwargs)
def fetch_json(url):
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return r.json()
import plotly.graph_objects as go
# Nodes
# 0=Revenue, 1=COGS, 2=Gross Profit, 3=R&D, 4=S&M, 5=G&A, 6=Operating Income,
# 7=Interest/Other, 8=Tax, 9=Net Income
NODES = [
"Revenue", # 0
"COGS", # 1
"Gross Profit", # 2
"R&D", # 3
"Sales & Mktg", # 4
"G&A", # 5
"Operating Income", # 6
"Interest/Other", # 7
"Tax", # 8
"Net Income", # 9
]
# ($M) — illustrative tech-company P&L
FLOWS = [
(0, 1, 380), # Revenue → COGS
(0, 2, 620), # Revenue → Gross Profit
(2, 3, 180), # Gross Profit → R&D
(2, 4, 140), # Gross Profit → Sales & Mktg
(2, 5, 60), # Gross Profit → G&A
(2, 6, 240), # Gross Profit → Operating Income
(6, 7, 20), # Operating Income → Interest/Other
(6, 8, 50), # Operating Income → Tax
(6, 9, 170), # Operating Income → Net Income
]
NODE_COLORS = [
"#845EEE", # Revenue
"#DA5597", # COGS
"#52B3D0", # Gross Profit
"#E9A23B", # R&D
"#E9A23B", # S&M
"#E9A23B", # G&A
"#55B685", # Operating Income
"#DA5597", # Interest
"#DA5597", # Tax
"#55B685", # Net Income
]
def generate():
print("building Income Statement Sankey …")
sources = [f[0] for f in FLOWS]
targets = [f[1] for f in FLOWS]
values = [f[2] for f in FLOWS]
fig = go.Figure(go.Sankey(
node=dict(
label=NODES,
color=NODE_COLORS,
pad=20, thickness=24,
line=dict(color="rgba(0,0,0,0.08)", width=0.5),
),
link=dict(
source=sources,
target=targets,
value=values,
color="rgba(150,150,150,0.2)",
),
))
fig.update_layout(
title=dict(text="Income Statement Flow ($M)", x=0.5),
margin=dict(t=60, b=40, l=20, r=20),
height=480,
font=dict(size=11),
)
apply_theme(fig)
return fig
fig = generate()
fig.show()
Made with Plotly