feat(chart): hyperscaler AI capex trajectory
This commit is contained in:
74
src/charts/spending_debt.py
Normal file
74
src/charts/spending_debt.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"""Spending and Debt Charts — Hyperscaler Capex, Tech Debt, NVIDIA Revenue"""
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
from src.data.ai_infrastructure import hyperscaler_capex_annual, hyperscaler_capex_meta
|
||||||
|
from src.utils.styling import get_theme, EXPORT_DPI, get_company_colors, GRAY_DARK
|
||||||
|
from src.utils.export import save_chart_tight
|
||||||
|
|
||||||
|
|
||||||
|
def plot_hyperscaler_capex() -> str:
|
||||||
|
plt.rcParams.update(get_theme())
|
||||||
|
fig, ax = plt.subplots(figsize=(14, 8))
|
||||||
|
|
||||||
|
company_colors = get_company_colors()
|
||||||
|
companies = ["Microsoft", "Alphabet", "Meta", "Amazon"]
|
||||||
|
years = list(range(2020, 2027))
|
||||||
|
|
||||||
|
# Organize data by company
|
||||||
|
data = {c: [] for c in companies}
|
||||||
|
for entry in hyperscaler_capex_annual:
|
||||||
|
data[entry["company"]].append(entry["capex_billions"])
|
||||||
|
|
||||||
|
# Fill missing years with 0
|
||||||
|
for c in companies:
|
||||||
|
while len(data[c]) < len(years):
|
||||||
|
data[c].append(0)
|
||||||
|
|
||||||
|
# Stacked area
|
||||||
|
y_offset = np.zeros(len(years))
|
||||||
|
totals = []
|
||||||
|
for c in companies:
|
||||||
|
values = np.array(data[c])
|
||||||
|
ax.fill_between(years, y_offset, y_offset + values,
|
||||||
|
alpha=0.8, color=company_colors.get(c, "#666"), label=c)
|
||||||
|
ax.plot(years, y_offset + values, color=company_colors.get(c, "#666"), linewidth=1)
|
||||||
|
y_offset += values
|
||||||
|
totals.append(sum(values))
|
||||||
|
|
||||||
|
# Total annotations
|
||||||
|
for i, (year, total) in enumerate(zip(years, y_offset)):
|
||||||
|
label = f"${total:.0f}B"
|
||||||
|
if year >= 2026:
|
||||||
|
label += "*"
|
||||||
|
ax.text(year, total + 5, label, ha="center", fontsize=9, fontweight="bold")
|
||||||
|
|
||||||
|
# Dashed vertical line at 2026 to mark guided/projected boundary
|
||||||
|
ax.axvline(x=2025.5, color=GRAY_DARK, linestyle="--", alpha=0.4, linewidth=1)
|
||||||
|
|
||||||
|
ax.set_title("Hyperscaler AI Infrastructure Capex — 2020 to 2026", fontsize=16, fontweight="bold")
|
||||||
|
ax.set_xlabel("Year", fontsize=12)
|
||||||
|
ax.set_ylabel("Capex (Billions USD)", fontsize=12)
|
||||||
|
ax.legend(loc="upper left", fontsize=10)
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
ax.text(0.02, 0.97, "*2026 = guided/projected", transform=ax.transAxes,
|
||||||
|
fontsize=9, style="italic", color="gray", va="top")
|
||||||
|
|
||||||
|
# AI-related capex share note
|
||||||
|
ax.text(0.98, 0.03, "80-90% of 2025/2026 capex is AI-related",
|
||||||
|
transform=ax.transAxes, fontsize=9, style="italic",
|
||||||
|
color="gray", ha="right", va="bottom")
|
||||||
|
|
||||||
|
path = save_chart_tight(fig, "05_hyperscaler_capex.png")
|
||||||
|
plt.close(fig)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
path = plot_hyperscaler_capex()
|
||||||
|
print(f"Chart saved: {path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user