{"spec_id":"sankey-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nsankey-basic: Basic Sankey Diagram\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 92/100 | Updated: 2026-07-25\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\n\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\"]\n\nsns.set_theme(style=\"white\", rc={\"figure.facecolor\": PAGE_BG, \"axes.facecolor\": PAGE_BG, \"text.color\": INK})\n\n# Data — energy flows in TWh (varied magnitudes for clear proportional scaling)\nsource_names = [\"Gas\", \"Coal\", \"Nuclear\"]\ntarget_names = [\"Residential\", \"Industrial\", \"Commercial\"]\nflows = [\n    (\"Gas\", \"Residential\", 50),\n    (\"Gas\", \"Industrial\", 30),\n    (\"Gas\", \"Commercial\", 40),\n    (\"Coal\", \"Industrial\", 45),\n    (\"Coal\", \"Residential\", 20),\n    (\"Coal\", \"Commercial\", 15),\n    (\"Nuclear\", \"Residential\", 25),\n    (\"Nuclear\", \"Industrial\", 10),\n    (\"Nuclear\", \"Commercial\", 10),\n]\ndf = pd.DataFrame(flows, columns=[\"source\", \"target\", \"value\"])\n\nsource_colors = dict(zip(source_names, IMPRINT[:3], strict=True))\ntarget_colors = dict(zip(target_names, IMPRINT[3:6], strict=True))\n\nsources = df.groupby(\"source\")[\"value\"].sum().loc[source_names]\ntargets = df.groupby(\"target\")[\"value\"].sum().loc[target_names]\n\n# Per-source flow shading — seaborn's light_palette blends each source's\n# brand hue into value-ranked tints (larger flow = fuller color), a genuine\n# seaborn palette feature layered on top of the Imprint categorical colors.\ndf[\"_rank\"] = df.groupby(\"source\")[\"value\"].rank(method=\"first\").astype(int) - 1\nflow_shades = {src: sns.light_palette(source_colors[src], n_colors=len(target_names) + 2)[2:] for src in source_names}\n\n# Layout (axes data coordinates, 0-1 logical span)\nNODE_W = 0.055\nX_LEFT, X_RIGHT = 0.13, 0.87\nGAP = 0.022\nTOTAL_H = 0.88\nY_START = 0.94\n\nsource_pos = {}\ny = Y_START\nfor name in source_names:\n    h = (sources[name] / sources.sum()) * TOTAL_H\n    source_pos[name] = {\"y\": y - h, \"h\": h}\n    y -= h + GAP\n\ntarget_pos = {}\ny = Y_START\nfor name in target_names:\n    h = (targets[name] / targets.sum()) * TOTAL_H\n    target_pos[name] = {\"y\": y - h, \"h\": h}\n    y -= h + GAP\n\nsrc_y = {n: source_pos[n][\"y\"] + source_pos[n][\"h\"] for n in source_names}\ntgt_y = {n: target_pos[n][\"y\"] + target_pos[n][\"h\"] for n in target_names}\n\n# Figure — canonical 3200x1800 canvas (figsize x dpi), no bbox_inches=\"tight\"\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\nfig.subplots_adjust(left=0.005, right=0.995, top=0.86, bottom=0.02)\n\nt = [i / 119 for i in range(120)]\ns = [v * v * (3 - 2 * v) for v in t]  # smoothstep: zero tangents at both endpoints\n\n# Sort flows by source order then target order to minimise crossings\nsrc_ord = {n: i for i, n in enumerate(source_names)}\ntgt_ord = {n: i for i, n in enumerate(target_names)}\ndf[\"_si\"] = df[\"source\"].map(src_ord)\ndf[\"_ti\"] = df[\"target\"].map(tgt_ord)\ndf_sorted = df.sort_values([\"_si\", \"_ti\"])\n\n# Draw flows\nfor _, row in df_sorted.iterrows():\n    src, tgt, val, rank = row[\"source\"], row[\"target\"], row[\"value\"], row[\"_rank\"]\n    bh_src = (val / sources[src]) * source_pos[src][\"h\"]\n    bh_tgt = (val / targets[tgt]) * target_pos[tgt][\"h\"]\n\n    y0t, y0b = src_y[src], src_y[src] - bh_src\n    src_y[src] = y0b\n    y1t, y1b = tgt_y[tgt], tgt_y[tgt] - bh_tgt\n    tgt_y[tgt] = y1b\n\n    x0, x1 = X_LEFT + NODE_W, X_RIGHT\n    cx0, cx1 = x0 + (x1 - x0) * 0.35, x0 + (x1 - x0) * 0.65\n    xs = [(1 - v) ** 3 * x0 + 3 * (1 - v) ** 2 * v * cx0 + 3 * (1 - v) * v**2 * cx1 + v**3 * x1 for v in t]\n    ylo = [y0b + (y1b - y0b) * sv for sv in s]\n    yhi = [y0t + (y1t - y0t) * sv for sv in s]\n\n    # Gas (dominant source) rendered with heavier alpha for visual emphasis\n    flow_alpha = 0.72 if src == \"Gas\" else 0.52\n    ax.fill_between(xs, ylo, yhi, color=flow_shades[src][rank], alpha=flow_alpha, linewidth=0)\n\n# Draw source nodes and labels\nfor name in source_names:\n    pos = source_pos[name]\n    ax.add_patch(\n        mpatches.FancyBboxPatch(\n            (X_LEFT, pos[\"y\"]),\n            NODE_W,\n            pos[\"h\"],\n            boxstyle=\"round,pad=0.005,rounding_size=0.015\",\n            facecolor=source_colors[name],\n            edgecolor=PAGE_BG,\n            linewidth=2,\n        )\n    )\n    ax.text(\n        X_LEFT - 0.015,\n        pos[\"y\"] + pos[\"h\"] / 2,\n        f\"{name}\\n{sources[name]:.0f} TWh\",\n        ha=\"right\",\n        va=\"center\",\n        fontsize=15,\n        fontweight=\"bold\",\n        color=INK,\n    )\n\n# Draw target nodes and labels\nfor name in target_names:\n    pos = target_pos[name]\n    ax.add_patch(\n        mpatches.FancyBboxPatch(\n            (X_RIGHT, pos[\"y\"]),\n            NODE_W,\n            pos[\"h\"],\n            boxstyle=\"round,pad=0.005,rounding_size=0.015\",\n            facecolor=target_colors[name],\n            edgecolor=PAGE_BG,\n            linewidth=2,\n        )\n    )\n    ax.text(\n        X_RIGHT + NODE_W + 0.015,\n        pos[\"y\"] + pos[\"h\"] / 2,\n        f\"{name}\\n{targets[name]:.0f} TWh\",\n        ha=\"left\",\n        va=\"center\",\n        fontsize=15,\n        fontweight=\"bold\",\n        color=INK,\n    )\n\nax.set_xlim(-0.20, 1.20)\nax.set_ylim(0, 1)\nax.axis(\"off\")\n\n# Title + subtitle live in the figure's reserved top margin (not axes data\n# space) so they never compete with the diagram for room.\nfig.suptitle(\"sankey-basic · python · seaborn · anyplot.ai\", fontsize=18, fontweight=\"medium\", color=INK, y=0.97)\nfig.text(\n    0.5,\n    0.89,\n    \"Gas supplies 49% of total energy — the dominant source\",\n    ha=\"center\",\n    va=\"center\",\n    fontsize=12,\n    color=source_colors[\"Gas\"],\n    fontstyle=\"italic\",\n)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}