{"spec_id":"line-yield-curve","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nline-yield-curve: Yield Curve (Interest Rate Term Structure)\nLibrary: plotnine 0.15.5 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_cartesian,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_point,\n    ggplot,\n    guide_legend,\n    guides,\n    labs,\n    scale_color_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens (see prompts/default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — first series always #009E73; matte red for recession/inversion signal\nPALETTE_NORMAL = \"#009E73\"  # brand green — growth / upward-sloping\nPALETTE_FLAT = \"#C475FD\"  # lavender — neutral / transitional\nPALETTE_INVERTED = \"#AE3030\"  # matte red — recession indicator / inverted\n\n# Data\nmaturity_years = [1 / 12, 0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30]\n\nyields_normal = [1.55, 1.72, 1.95, 2.15, 2.45, 2.68, 2.95, 3.12, 3.35, 3.65, 3.80]\nyields_flat = [4.10, 4.15, 4.18, 4.20, 4.15, 4.12, 4.08, 4.05, 4.02, 3.98, 3.95]\nyields_inverted = [5.45, 5.50, 5.48, 5.35, 5.05, 4.78, 4.42, 4.25, 4.10, 4.35, 4.40]\n\ncurve_labels = [\"2021-06-15 · Normal\", \"2023-01-10 · Flat\", \"2024-07-01 · Inverted\"]\n\ndf = pd.DataFrame(\n    {\n        \"maturity_years\": maturity_years * 3,\n        \"yield_pct\": yields_normal + yields_flat + yields_inverted,\n        \"curve\": [curve_labels[0]] * 11 + [curve_labels[1]] * 11 + [curve_labels[2]] * 11,\n    }\n)\n\n# Ordered categorical for legend ordering (plotnine: pd.Categorical + scale interaction)\ndf[\"curve\"] = pd.Categorical(df[\"curve\"], categories=curve_labels, ordered=True)\n\n# Inversion zone bounds\ninv_short_max = max(yields_inverted[:4])  # 5.50\ninv_long_min = min(yields_inverted[4:])  # 4.10\n\n# Tick positions — well-spaced to avoid label cramping\ntick_positions = [1, 2, 5, 7, 10, 20, 30]\ntick_labels = [\"1Y\", \"2Y\", \"5Y\", \"7Y\", \"10Y\", \"20Y\", \"30Y\"]\n\n# Title with length-scaled fontsize (see prompts/plot-generator.md)\ntitle = \"U.S. Treasury Yield Curves · line-yield-curve · python · plotnine · anyplot.ai\"\nn = len(title)\ntitle_fontsize = max(8, round(12 * 67 / n)) if n > 67 else 12\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"maturity_years\", y=\"yield_pct\", color=\"curve\"))\n    # Inversion zone shading — more prominent than previous (alpha=0.10 vs 0.06)\n    + annotate(\n        \"rect\",\n        xmin=-0.5,\n        xmax=10.5,\n        ymin=inv_long_min - 0.08,\n        ymax=inv_short_max + 0.08,\n        fill=PALETTE_INVERTED,\n        alpha=0.10,\n    )\n    + annotate(\n        \"text\",\n        x=0.5,\n        y=inv_short_max + 0.22,\n        label=\"Inversion zone (short-term > long-term)\",\n        size=9,\n        color=PALETTE_INVERTED,\n        alpha=0.85,\n        fontstyle=\"italic\",\n        ha=\"left\",\n    )\n    # geom layers sized for 3200×1800 canvas\n    + geom_line(size=2.0, alpha=0.85)\n    + geom_point(size=3.5, alpha=0.9)\n    + scale_x_continuous(breaks=tick_positions, labels=tick_labels, limits=(0, 31), expand=(0.02, 0))\n    + scale_y_continuous(\n        breaks=[1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5], labels=lambda b: [f\"{v:.1f}%\" for v in b]\n    )\n    + scale_color_manual(values=[PALETTE_NORMAL, PALETTE_FLAT, PALETTE_INVERTED])\n    # coord_cartesian for view clipping without data removal (plotnine pattern)\n    + coord_cartesian(ylim=(1.3, 5.9))\n    # guide_legend for fine-grained legend control\n    + guides(color=guide_legend(title=\"Curve Date\", override_aes={\"size\": 3, \"alpha\": 1}))\n    + labs(x=\"Maturity\", y=\"Yield (%)\", title=title)\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(family=\"sans-serif\", color=INK),\n        axis_title=element_text(size=10, color=INK, margin={\"t\": 8, \"r\": 8}),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        plot_title=element_text(size=title_fontsize, weight=\"bold\", color=INK, margin={\"b\": 10}),\n        legend_title=element_text(size=9, weight=\"bold\", color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_position=(0.25, 0.30),\n        legend_background=element_rect(fill=ELEVATED_BG, alpha=0.9, color=INK_SOFT),\n        legend_key=element_rect(fill=\"none\", color=\"none\"),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.15),\n        axis_line_x=element_line(color=INK_SOFT, size=0.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\")\n"}