{"spec_id":"sankey-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nsankey-basic: Basic Sankey Diagram\nLibrary: matplotlib 3.11.1 | Python 3.13.14\nQuality: 76/100 | Updated: 2026-07-25\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nfrom matplotlib.sankey import Sankey\n\n\n# Theme tokens (Imprint palette; see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — chosen semantic mapping is stage-based rather than\n# per-source: position 1 (brand) for the source/generation stage, position 2\n# for the downstream end-use distribution stage. This keeps the two-stage\n# flow structure (inputs -> hub -> sectors) visually legible as two families\n# of ribbons instead of splintering it into eight near-identical hues.\nBRAND = \"#009E73\"\nSECONDARY = \"#C475FD\"\n\n# Data - Energy flow example (in TWh - Terawatt-hours)\n# This shows how energy from primary sources flows through generation\n# to end-use sectors, demonstrating the typical Sankey flow pattern\n\n# Primary energy sources (inputs)\ncoal = 120\nnatural_gas = 90\nnuclear = 60\nrenewables = 30\ntotal_primary = coal + natural_gas + nuclear + renewables  # 300 TWh\n\n# Energy lost in generation/transmission\nlosses = 100\n\n# Net energy delivered to sectors\nresidential = 55\ncommercial = 45\nindustrial = 80\ntransportation = 20\nnet_delivered = residential + commercial + industrial + transportation  # 200 TWh\n\n# Verify balance: inputs = outputs + losses\nassert total_primary == net_delivered + losses, \"Energy balance must be maintained\"\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nsankey = Sankey(\n    ax=ax,\n    scale=0.0025,\n    offset=0.25,\n    head_angle=120,\n    format=\"\",\n    unit=\"\",\n    gap=1.2,\n    radius=0.15,\n    shoulder=0.04,\n    margin=0.6,\n)\n\n# Add primary sources to generation hub (first diagram)\n# Positive flows = inputs (from sources)\n# Negative flows = outputs (to next stage or losses)\n# gap/pathlengths tuned so the label pairs that end up spatially close\n# (Nuclear/Renewables both pointing up, Coal/Losses both pointing down)\n# get enough separation to stay legible at this fontsize\nsankey.add(\n    flows=[coal, natural_gas, nuclear, renewables, -losses, -net_delivered],\n    labels=[\"Coal\\n120 TWh\", \"Natural Gas\\n90 TWh\", \"Nuclear\\n60 TWh\", \"Renewables\\n30 TWh\", \"Losses\\n100 TWh\", \"\"],\n    orientations=[-1, 0, 1, 1, -1, 0],\n    pathlengths=[0.5, 0.3, 0.9, 0.75, 0.55, 0.5],\n    facecolor=BRAND,\n    edgecolor=\"none\",\n    alpha=0.85,\n)\n\n# Add distribution to end-use sectors (second diagram connected to first)\nsankey.add(\n    flows=[net_delivered, -residential, -commercial, -industrial, -transportation],\n    labels=[\"\", \"Residential\\n55 TWh\", \"Commercial\\n45 TWh\", \"Industrial\\n80 TWh\", \"Transport\\n20 TWh\"],\n    orientations=[0, -1, 0, 1, 1],\n    pathlengths=[0.3, 1.0, 0.95, 0.9, 0.6],\n    prior=0,\n    connect=(5, 0),\n    facecolor=SECONDARY,\n    edgecolor=\"none\",\n    alpha=0.85,\n)\n\n# Finish and get diagram objects\ndiagrams = sankey.finish()\n\n# Style all labels with larger, theme-adaptive text for visibility\n# A few default label positions sit on top of their own ribbon's curved bend\n# or too close to a neighboring label — nudge those clear by hand (offsets\n# tuned against the rendered PNG, same technique for every entry below).\nLABEL_OFFSETS = {\n    # Residential's default position sits close to Losses (both hubs break\n    # away near the same seam) — nudge it clear to avoid a text collision\n    \"Residential\": (0.35, -0.45),\n    # Coal's label sits on the ribbon's downward bend into the hub\n    \"Coal\": (-0.95, -0.25),\n    # Losses' label sits on the ribbon's downward bend into the hub\n    \"Losses\": (0.15, -0.05),\n    # Renewables crowds edge-to-edge against the Nuclear/\"60 TWh\" label\n    \"Renewables\": (-0.4, 0.15),\n    # Commercial's straight horizontal flow passes directly through the\n    # label at the same height — lift the label clear of the arrow tip\n    \"Commercial\": (0.2, 0.35),\n    # Natural Gas's \"90 TWh\" line sits on the Nuclear/Renewables ribbons'\n    # downward bend into the hub — lift it into the gap between its own\n    # flow-start arrow and that bend (kept within the label's existing\n    # leftward extent so it doesn't push the figure's bounding box further\n    # left and reflow every other label)\n    \"Natural Gas\": (0.0, 0.3),\n    # Industrial's \"80 TWh\" line touches Transport's label with no gap —\n    # drop Transport down and slightly left to clear Industrial without\n    # crowding into Commercial's label further right\n    \"Transport\": (-0.3, -0.25),\n}\nfor diagram in diagrams:\n    for text in diagram.texts:\n        text.set_fontsize(18)\n        text.set_fontweight(\"bold\")\n        text.set_color(INK)\n        label_name = text.get_text().split(\"\\n\")[0]\n        if label_name in LABEL_OFFSETS:\n            dx, dy = LABEL_OFFSETS[label_name]\n            x, y = text.get_position()\n            text.set_position((x + dx, y + dy))\n\n# Title (mandated format; length is under the 67-char baseline, so default fontsize applies)\ntitle = \"sankey-basic · python · matplotlib · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=30)\n\n# Remove axes for cleaner look\nax.axis(\"off\")\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)  # bbox_inches MUST stay default (None)\n"}