{"spec_id":"cartogram-area-distortion","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\ncartogram-area-distortion: Cartogram with Area Distortion by Data Value\nLibrary: plotnine 0.15.5 | Python 3.13.13\nQuality: 82/100 | Updated: 2026-06-08\n\"\"\"\n\nimport os\nimport sys\n\n\n# Work around naming conflict between this file (plotnine.py) and the plotnine package\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nfor _p in (_script_dir, \"\", \".\"):\n    if _p in sys.path:\n        sys.path.remove(_p)\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_label,\n    geom_path,\n    geom_polygon,\n    ggplot,\n    guide_colorbar,\n    labs,\n    scale_fill_gradient,\n    theme,\n)\n\n\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# African countries: (name, abbrev, centroid_x, centroid_y, population_M, urbanization_pct)\n# Schematic grid positions preserving rough geographic adjacency; area ∝ population\nCOUNTRIES = [\n    (\"Morocco\", \"MA\", -3.0, 10.0, 38.0, 65),\n    (\"Algeria\", \"DZ\", 4.0, 10.0, 46.0, 75),\n    (\"Egypt\", \"EG\", 14.0, 10.0, 105.0, 43),\n    (\"Mali\", \"ML\", 0.0, 5.0, 23.0, 44),\n    (\"Niger\", \"NE\", 8.0, 5.0, 27.0, 17),\n    (\"Sudan\", \"SD\", 14.0, 5.0, 46.0, 36),\n    (\"Ethiopia\", \"ET\", 20.0, 3.0, 126.0, 24),\n    (\"Nigeria\", \"NG\", 2.0, -1.0, 220.0, 54),\n    (\"Ghana\", \"GH\", -3.0, -4.5, 33.0, 58),\n    (\"Cameroon\", \"CM\", 9.0, -1.0, 28.0, 59),\n    (\"Kenya\", \"KE\", 18.0, -3.0, 55.0, 29),\n    (\"DR Congo\", \"CD\", 11.0, -7.0, 100.0, 46),\n    (\"Tanzania\", \"TZ\", 17.5, -8.5, 65.0, 38),\n    (\"Angola\", \"AO\", 8.0, -13.0, 36.0, 68),\n    (\"Zambia\", \"ZM\", 14.0, -13.0, 20.0, 46),\n    (\"Mozambique\", \"MZ\", 20.5, -13.0, 33.0, 38),\n]\n\npopulations = [c[4] for c in COUNTRIES]\nmedian_pop = float(np.median(populations))\ntotal_pop = sum(populations)\nBASE_R = 1.6  # base hexagon radius in data units\n\n\ndef hex_poly(cx, cy, r, rotation=np.pi / 6):\n    \"\"\"Return closed hexagon vertices as list of (x, y).\"\"\"\n    angles = np.linspace(0, 2 * np.pi, 6, endpoint=False) + rotation\n    xs = cx + r * np.cos(angles)\n    ys = cy + r * np.sin(angles)\n    return list(zip(np.append(xs, xs[0]), np.append(ys, ys[0]), strict=False))\n\n\n# Reference outlines — fixed size for all countries (shows original territory extent)\nref_rows = []\nfor name, _abbrev, cx, cy, _pop, _urb in COUNTRIES:\n    for i, (x, y) in enumerate(hex_poly(cx, cy, BASE_R * 0.88)):\n        ref_rows.append({\"country\": name, \"x\": x, \"y\": y, \"order\": i})\ndf_ref = pd.DataFrame(ref_rows)\n\n# Cartogram polygons: radius scales with sqrt(population / median_population)\npoly_rows, cent_rows = [], []\nfor name, abbrev, cx, cy, pop, urb in COUNTRIES:\n    r = min(BASE_R * np.sqrt(pop / median_pop), BASE_R * 2.65)\n    for i, (x, y) in enumerate(hex_poly(cx, cy, r)):\n        poly_rows.append({\"country\": name, \"x\": x, \"y\": y, \"order\": i, \"urb\": urb, \"pop\": pop})\n    cent_rows.append({\"country\": name, \"abbrev\": abbrev, \"x\": cx, \"y\": cy, \"urb\": urb, \"pop\": pop})\n\ndf_poly = pd.DataFrame(poly_rows)\ndf_cent = pd.DataFrame(cent_rows)\n\ntotal_str = f\"{total_pop:.0f}M total · {len(COUNTRIES)} African countries\"\n\nplot = (\n    ggplot()\n    # Dashed reference outlines — original territory borders\n    + geom_path(df_ref, aes(x=\"x\", y=\"y\", group=\"country\"), color=INK_SOFT, size=0.35, linetype=\"dashed\", alpha=0.45)\n    # Cartogram polygons filled by urbanization rate (Imprint sequential: green=rural → blue=urban)\n    + geom_polygon(df_poly, aes(x=\"x\", y=\"y\", group=\"country\", fill=\"urb\"), color=INK, size=0.35, alpha=0.88)\n    # Imprint sequential colormap — single-polarity continuous data\n    + scale_fill_gradient(low=\"#009E73\", high=\"#4467A3\", name=\"Urbanization\\nRate (%)\", guide=guide_colorbar(nbin=100))\n    # Country abbreviation labels\n    + geom_label(\n        df_cent,\n        aes(x=\"x\", y=\"y\", label=\"abbrev\"),\n        color=INK,\n        fill=ELEVATED_BG,\n        size=3.3,\n        fontweight=\"bold\",\n        label_padding=0.18,\n        label_size=0.2,\n    )\n    + coord_fixed(ratio=1.0, xlim=(-7.5, 25.5), ylim=(-17.5, 14.5))\n    + labs(\n        title=\"cartogram-area-distortion · python · plotnine · anyplot.ai\",\n        subtitle=f\"Area ∝ Population — {total_str}  |  Dashed outlines = original region borders\",\n    )\n    + annotate(\n        \"text\",\n        x=-7.0,\n        y=-16.5,\n        label=\"Larger polygon = larger population\",\n        size=3.1,\n        color=INK_MUTED,\n        fontstyle=\"italic\",\n        ha=\"left\",\n    )\n    + annotate(\n        \"text\", x=25.0, y=-16.5, label=\"Color = % urban population (2024)\", size=3.1, color=INK_MUTED, ha=\"right\"\n    )\n    + theme(\n        figure_size=(8, 4.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        plot_title=element_text(size=12, ha=\"center\", weight=\"bold\", color=INK, margin={\"b\": 4}),\n        plot_subtitle=element_text(size=8, ha=\"center\", color=INK_SOFT, margin={\"b\": 6}),\n        legend_title=element_text(size=9, weight=\"bold\", color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_position=\"right\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.3),\n        axis_text=element_blank(),\n        axis_title=element_blank(),\n        axis_ticks=element_blank(),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_border=element_blank(),\n        plot_margin=0.02,\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}