{"spec_id":"cartogram-area-distortion","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncartogram-area-distortion: Cartogram with Area Distortion by Data Value\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-06-16\n\"\"\"\n\nimport os\n\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — canonical order, regions take positions 1..4\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: US states with population (millions) on an approximate tile grid\nnp.random.seed(42)\n\nstates_data = {\n    # state: (row, col, population_millions, region)\n    \"WA\": (0, 1, 7.7, \"West\"),\n    \"MT\": (0, 3, 1.1, \"West\"),\n    \"ND\": (0, 5, 0.8, \"Midwest\"),\n    \"MN\": (0, 6, 5.7, \"Midwest\"),\n    \"WI\": (0, 7, 5.9, \"Midwest\"),\n    \"MI\": (0, 8, 10.0, \"Midwest\"),\n    \"NY\": (0, 10, 19.5, \"Northeast\"),\n    \"VT\": (0, 11, 0.6, \"Northeast\"),\n    \"ME\": (0, 12, 1.4, \"Northeast\"),\n    \"OR\": (1, 1, 4.2, \"West\"),\n    \"ID\": (1, 2, 1.9, \"West\"),\n    \"WY\": (1, 3, 0.6, \"West\"),\n    \"SD\": (1, 5, 0.9, \"Midwest\"),\n    \"IA\": (1, 6, 3.2, \"Midwest\"),\n    \"IL\": (1, 7, 12.6, \"Midwest\"),\n    \"IN\": (1, 8, 6.8, \"Midwest\"),\n    \"OH\": (1, 9, 11.8, \"Midwest\"),\n    \"PA\": (1, 10, 13.0, \"Northeast\"),\n    \"MA\": (1, 11, 7.0, \"Northeast\"),\n    \"NH\": (1, 12, 1.4, \"Northeast\"),\n    \"NV\": (2, 1, 3.1, \"West\"),\n    \"UT\": (2, 2, 3.3, \"West\"),\n    \"CO\": (2, 3, 5.8, \"West\"),\n    \"NE\": (2, 5, 2.0, \"Midwest\"),\n    \"KS\": (2, 6, 2.9, \"Midwest\"),\n    \"MO\": (2, 7, 6.2, \"Midwest\"),\n    \"KY\": (2, 8, 4.5, \"South\"),\n    \"WV\": (2, 9, 1.8, \"South\"),\n    \"VA\": (2, 10, 8.6, \"South\"),\n    \"MD\": (2, 11, 6.2, \"South\"),\n    \"NJ\": (2, 12, 9.3, \"Northeast\"),\n    \"CA\": (3, 1, 39.0, \"West\"),\n    \"AZ\": (3, 2, 7.3, \"West\"),\n    \"NM\": (3, 3, 2.1, \"West\"),\n    \"OK\": (3, 5, 4.0, \"South\"),\n    \"AR\": (3, 6, 3.0, \"South\"),\n    \"TN\": (3, 7, 7.0, \"South\"),\n    \"NC\": (3, 9, 10.6, \"South\"),\n    \"SC\": (3, 10, 5.2, \"South\"),\n    \"DE\": (3, 11, 1.0, \"Northeast\"),\n    \"CT\": (3, 12, 3.6, \"Northeast\"),\n    \"TX\": (4, 3, 29.5, \"South\"),\n    \"LA\": (4, 5, 4.6, \"South\"),\n    \"MS\": (4, 6, 3.0, \"South\"),\n    \"AL\": (4, 7, 5.0, \"South\"),\n    \"GA\": (4, 8, 10.8, \"South\"),\n    \"FL\": (4, 10, 22.2, \"South\"),\n    \"RI\": (4, 12, 1.1, \"Northeast\"),\n    \"AK\": (5, 0, 0.7, \"West\"),\n    \"HI\": (5, 2, 1.4, \"West\"),\n}\n\nrows = []\nfor state, (r, c, pop, region) in states_data.items():\n    rows.append({\"state\": state, \"row\": r, \"col\": c, \"population\": pop, \"region\": region})\ndf = pd.DataFrame(rows)\n\n# Region ordering and Imprint color mapping\nregion_order = [\"West\", \"Midwest\", \"South\", \"Northeast\"]\nregion_palette = dict(zip(region_order, IMPRINT_PALETTE, strict=True))\n\n# Marker area range — wide enough to read area ∝ population, floored so small tiles stay legible\nsize_min = 70\nsize_max = 1500\n\n# Theme — seaborn drives the chrome via rc tokens\nsns.set_theme(\n    style=\"white\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"font.family\": \"sans-serif\",\n    },\n)\n\n# Plot — 8 × 4.5 in @ dpi 400 → 3200 × 1800 px (hard canvas contract)\nfig = plt.figure(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\ngs = fig.add_gridspec(\n    2, 2, width_ratios=[3.2, 1], height_ratios=[1, 1], wspace=0.06, hspace=0.32, bottom=0.14, top=0.92\n)\nax_main = fig.add_subplot(gs[:, 0])\nax_ref = fig.add_subplot(gs[0, 1])\nax_bar = fig.add_subplot(gs[1, 1])\nfor ax in (ax_main, ax_ref, ax_bar):\n    ax.set_facecolor(PAGE_BG)\n\n# Main cartogram — square tiles sized by population, colored by region\nsns.scatterplot(\n    data=df,\n    x=\"col\",\n    y=\"row\",\n    size=\"population\",\n    sizes=(size_min, size_max),\n    hue=\"region\",\n    hue_order=region_order,\n    palette=region_palette,\n    style=\"region\",\n    style_order=region_order,\n    markers=dict.fromkeys(region_order, \"s\"),\n    alpha=0.9,\n    edgecolor=PAGE_BG,\n    linewidth=1.0,\n    ax=ax_main,\n)\n\n# Keep only the region (hue) handles for a compact horizontal legend\nhandles, labels = ax_main.get_legend_handles_labels()\nregion_handles, region_labels = [], []\nfor handle, lbl in zip(handles, labels, strict=False):\n    if lbl in region_order:\n        handle.set_markersize(9)\n        handle.set_markeredgecolor(PAGE_BG)\n        handle.set_markeredgewidth(0.8)\n        region_handles.append(handle)\n        region_labels.append(lbl)\n\nax_main.get_legend().remove()\nlegend = ax_main.legend(\n    handles=region_handles,\n    labels=region_labels,\n    loc=\"lower center\",\n    fontsize=8,\n    title=\"Region\",\n    title_fontsize=8,\n    framealpha=0.95,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    ncol=4,\n    bbox_to_anchor=(0.5, -0.13),\n    borderpad=0.5,\n    columnspacing=1.2,\n    handletextpad=0.3,\n)\nlegend.get_title().set_color(INK)\nfor txt in legend.get_texts():\n    txt.set_color(INK_SOFT)\n\n# State abbreviations — white fill with dark stroke stays legible on any tile color\nlabel_stroke = [pe.withStroke(linewidth=1.1, foreground=\"#1A1A17\")]\npop_max = df[\"population\"].max()\nfor _, row in df.iterrows():\n    pop_frac = row[\"population\"] / pop_max\n    fontsize = 4.5 + pop_frac * 3.5\n    ax_main.text(\n        row[\"col\"],\n        row[\"row\"] - 0.02,\n        row[\"state\"],\n        ha=\"center\",\n        va=\"center\",\n        fontsize=fontsize,\n        fontweight=\"bold\",\n        color=\"white\",\n        path_effects=label_stroke,\n        zorder=5,\n    )\n    # Population callout for the very largest states only\n    if row[\"population\"] >= 18.0:\n        ax_main.text(\n            row[\"col\"],\n            row[\"row\"] + 0.28,\n            f\"{row['population']:.0f}M\",\n            ha=\"center\",\n            va=\"center\",\n            fontsize=fontsize * 0.62,\n            color=\"white\",\n            path_effects=label_stroke,\n            zorder=5,\n        )\n\n# Style main axes — geographic tile grid, no axes chrome\nax_main.invert_yaxis()\nax_main.set_aspect(\"equal\")\nax_main.set_xlim(-0.9, 13.4)\nax_main.set_ylim(5.9, -1.5)\nax_main.set_xlabel(\"\")\nax_main.set_ylabel(\"\")\nax_main.set_xticks([])\nax_main.set_yticks([])\nsns.despine(ax=ax_main, left=True, bottom=True)\n\nax_main.set_title(\n    \"cartogram-area-distortion · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK, pad=14\n)\n\nax_main.text(\n    0.01,\n    0.97,\n    \"Tile area ∝ state population (millions)\",\n    ha=\"left\",\n    va=\"top\",\n    fontsize=7.5,\n    color=INK_MUTED,\n    fontstyle=\"italic\",\n    transform=ax_main.transAxes,\n)\n\n# Reference inset — equal-area tile map, no labels so the size contrast reads cleanly\nsns.scatterplot(\n    data=df,\n    x=\"col\",\n    y=\"row\",\n    hue=\"region\",\n    hue_order=region_order,\n    palette=region_palette,\n    style=\"region\",\n    style_order=region_order,\n    markers=dict.fromkeys(region_order, \"s\"),\n    s=80,\n    alpha=0.85,\n    edgecolor=PAGE_BG,\n    linewidth=0.6,\n    legend=False,\n    ax=ax_ref,\n)\n\nax_ref.invert_yaxis()\nax_ref.set_aspect(\"equal\")\nax_ref.set_xlim(-0.6, 13.0)\nax_ref.set_ylim(5.9, -0.6)\nax_ref.set_xlabel(\"\")\nax_ref.set_ylabel(\"\")\nax_ref.set_xticks([])\nax_ref.set_yticks([])\nsns.despine(ax=ax_ref, left=True, bottom=True)\nax_ref.set_title(\"Equal-area reference\", fontsize=9, fontweight=\"medium\", color=INK, pad=6)\n\n# Subtle divider between the main cartogram and the side panels\nfig.add_artist(\n    plt.Line2D(\n        [0.72, 0.72],\n        [0.08, 0.9],\n        transform=fig.transFigure,\n        color=INK_SOFT,\n        linewidth=0.8,\n        linestyle=(0, (4, 4)),\n        alpha=0.4,\n    )\n)\n\n# Regional totals — seaborn barplot with statistical aggregation\nregion_totals = df.groupby(\"region\", observed=True)[\"population\"].sum().reset_index()\nregion_totals.columns = [\"region\", \"total_pop\"]\nregion_totals = region_totals.set_index(\"region\").reindex(region_order).reset_index()\nregion_totals[\"total_pop\"] = region_totals[\"total_pop\"].round(1)\n\nsns.barplot(\n    data=region_totals,\n    x=\"total_pop\",\n    y=\"region\",\n    hue=\"region\",\n    hue_order=region_order,\n    order=region_order,\n    palette=region_palette,\n    edgecolor=PAGE_BG,\n    linewidth=1.0,\n    legend=False,\n    ax=ax_bar,\n    saturation=0.9,\n)\n\nfor i, rrow in region_totals.iterrows():\n    ax_bar.text(\n        rrow[\"total_pop\"] + 1.5,\n        i,\n        f\"{rrow['total_pop']:.0f}M\",\n        ha=\"left\",\n        va=\"center\",\n        fontsize=7.5,\n        fontweight=\"bold\",\n        color=INK,\n    )\n\nax_bar.set_xlabel(\"Total population (M)\", fontsize=8.5, color=INK)\nax_bar.set_ylabel(\"\")\nax_bar.set_title(\"Regional totals\", fontsize=9, fontweight=\"medium\", color=INK, pad=6)\nax_bar.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\nax_bar.tick_params(axis=\"x\", labelsize=7, colors=INK_SOFT)\nax_bar.set_xlim(0, region_totals[\"total_pop\"].max() * 1.28)\nsns.despine(ax=ax_bar, left=True)\nax_bar.yaxis.grid(False)\nax_bar.xaxis.grid(True, alpha=0.15, linewidth=0.8)\n\n# Save — bbox_inches stays default (None) to keep the exact 3200×1800 canvas\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}