{"spec_id":"bubble-map-geographic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nbubble-map-geographic: Bubble Map with Sized Geographic Markers\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nimport plotly.graph_objects as go\n\n\n# Theme tokens\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\nLAND_COLOR = \"#E8E4D8\" if THEME == \"light\" else \"#2E2E28\"\nOCEAN_COLOR = \"#D0E4F0\" if THEME == \"light\" else \"#1A2535\"\nCOAST_COLOR = \"#B0A890\" if THEME == \"light\" else \"#4A4A40\"\nCOUNTRY_COLOR = \"#C8C4B4\" if THEME == \"light\" else \"#3A3A34\"\n\n# Okabe-Ito palette for regions (canonical order, first = #009E73)\nREGION_COLORS = {\n    \"Asia\": \"#009E73\",\n    \"Europe\": \"#C475FD\",\n    \"North America\": \"#4467A3\",\n    \"South America\": \"#BD8233\",\n    \"Africa\": \"#AE3030\",\n    \"Oceania\": \"#2ABCCD\",\n}\nREGION_ORDER = [\"Asia\", \"Europe\", \"North America\", \"South America\", \"Africa\", \"Oceania\"]\n\n# Data: Major world cities with population (in millions)\nnp.random.seed(42)\n\ncities = [\n    {\"city\": \"Tokyo\", \"lat\": 35.6762, \"lon\": 139.6503, \"pop\": 37.4, \"region\": \"Asia\"},\n    {\"city\": \"Delhi\", \"lat\": 28.7041, \"lon\": 77.1025, \"pop\": 31.2, \"region\": \"Asia\"},\n    {\"city\": \"Shanghai\", \"lat\": 31.2304, \"lon\": 121.4737, \"pop\": 27.8, \"region\": \"Asia\"},\n    {\"city\": \"São Paulo\", \"lat\": -23.5505, \"lon\": -46.6333, \"pop\": 22.4, \"region\": \"South America\"},\n    {\"city\": \"Mexico City\", \"lat\": 19.4326, \"lon\": -99.1332, \"pop\": 21.9, \"region\": \"North America\"},\n    {\"city\": \"Cairo\", \"lat\": 30.0444, \"lon\": 31.2357, \"pop\": 21.3, \"region\": \"Africa\"},\n    {\"city\": \"Mumbai\", \"lat\": 19.0760, \"lon\": 72.8777, \"pop\": 20.7, \"region\": \"Asia\"},\n    {\"city\": \"Beijing\", \"lat\": 39.9042, \"lon\": 116.4074, \"pop\": 20.5, \"region\": \"Asia\"},\n    {\"city\": \"Dhaka\", \"lat\": 23.8103, \"lon\": 90.4125, \"pop\": 22.5, \"region\": \"Asia\"},\n    {\"city\": \"Osaka\", \"lat\": 34.6937, \"lon\": 135.5023, \"pop\": 19.2, \"region\": \"Asia\"},\n    {\"city\": \"New York\", \"lat\": 40.7128, \"lon\": -74.0060, \"pop\": 18.8, \"region\": \"North America\"},\n    {\"city\": \"Karachi\", \"lat\": 24.8607, \"lon\": 67.0011, \"pop\": 16.5, \"region\": \"Asia\"},\n    {\"city\": \"Buenos Aires\", \"lat\": -34.6037, \"lon\": -58.3816, \"pop\": 15.2, \"region\": \"South America\"},\n    {\"city\": \"Istanbul\", \"lat\": 41.0082, \"lon\": 28.9784, \"pop\": 15.4, \"region\": \"Europe\"},\n    {\"city\": \"Lagos\", \"lat\": 6.5244, \"lon\": 3.3792, \"pop\": 14.9, \"region\": \"Africa\"},\n    {\"city\": \"Manila\", \"lat\": 14.5995, \"lon\": 120.9842, \"pop\": 14.4, \"region\": \"Asia\"},\n    {\"city\": \"Rio de Janeiro\", \"lat\": -22.9068, \"lon\": -43.1729, \"pop\": 13.5, \"region\": \"South America\"},\n    {\"city\": \"Los Angeles\", \"lat\": 34.0522, \"lon\": -118.2437, \"pop\": 12.5, \"region\": \"North America\"},\n    {\"city\": \"Moscow\", \"lat\": 55.7558, \"lon\": 37.6173, \"pop\": 12.5, \"region\": \"Europe\"},\n    {\"city\": \"Paris\", \"lat\": 48.8566, \"lon\": 2.3522, \"pop\": 11.1, \"region\": \"Europe\"},\n    {\"city\": \"London\", \"lat\": 51.5074, \"lon\": -0.1278, \"pop\": 9.5, \"region\": \"Europe\"},\n    {\"city\": \"Lima\", \"lat\": -12.0464, \"lon\": -77.0428, \"pop\": 10.9, \"region\": \"South America\"},\n    {\"city\": \"Bangkok\", \"lat\": 13.7563, \"lon\": 100.5018, \"pop\": 10.7, \"region\": \"Asia\"},\n    {\"city\": \"Jakarta\", \"lat\": -6.2088, \"lon\": 106.8456, \"pop\": 10.6, \"region\": \"Asia\"},\n    {\"city\": \"Seoul\", \"lat\": 37.5665, \"lon\": 126.9780, \"pop\": 9.9, \"region\": \"Asia\"},\n    {\"city\": \"Sydney\", \"lat\": -33.8688, \"lon\": 151.2093, \"pop\": 5.4, \"region\": \"Oceania\"},\n    {\"city\": \"Melbourne\", \"lat\": -37.8136, \"lon\": 144.9631, \"pop\": 5.0, \"region\": \"Oceania\"},\n    {\"city\": \"Toronto\", \"lat\": 43.6532, \"lon\": -79.3832, \"pop\": 6.3, \"region\": \"North America\"},\n    {\"city\": \"Chicago\", \"lat\": 41.8781, \"lon\": -87.6298, \"pop\": 8.9, \"region\": \"North America\"},\n    {\"city\": \"Singapore\", \"lat\": 1.3521, \"lon\": 103.8198, \"pop\": 5.9, \"region\": \"Asia\"},\n]\n\ndf = pd.DataFrame(cities)\n\n# Bubble sizing: scale area proportional to population (sqrt of normalized value)\nmin_size, max_size = 15, 70\npop_min, pop_max = df[\"pop\"].min(), df[\"pop\"].max()\ndf[\"marker_size\"] = min_size + (max_size - min_size) * np.sqrt((df[\"pop\"] - pop_min) / (pop_max - pop_min))\n\n# Plot\nfig = go.Figure()\n\nfor region in REGION_ORDER:\n    rdf = df[df[\"region\"] == region]\n    if rdf.empty:\n        continue\n    fig.add_trace(\n        go.Scattergeo(\n            lon=rdf[\"lon\"],\n            lat=rdf[\"lat\"],\n            text=rdf.apply(lambda r: f\"{r['city']}<br>Population: {r['pop']:.1f}M\", axis=1),\n            marker={\n                \"size\": rdf[\"marker_size\"],\n                \"color\": REGION_COLORS[region],\n                \"opacity\": 0.65,\n                \"line\": {\"width\": 1.5, \"color\": \"white\"},\n                \"sizemode\": \"diameter\",\n            },\n            name=region,\n            hovertemplate=\"%{text}<extra></extra>\",\n        )\n    )\n\n# Style\nfig.update_layout(\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    title={\n        \"text\": \"World City Populations · bubble-map-geographic · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 28, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    geo={\n        \"showland\": True,\n        \"landcolor\": LAND_COLOR,\n        \"showocean\": True,\n        \"oceancolor\": OCEAN_COLOR,\n        \"showcoastlines\": True,\n        \"coastlinecolor\": COAST_COLOR,\n        \"coastlinewidth\": 1,\n        \"showframe\": True,\n        \"framecolor\": INK_SOFT,\n        \"framewidth\": 1,\n        \"showcountries\": True,\n        \"countrycolor\": COUNTRY_COLOR,\n        \"countrywidth\": 0.5,\n        \"projection_type\": \"natural earth\",\n        \"lataxis\": {\"range\": [-60, 75]},\n        \"lonaxis\": {\"range\": [-140, 180]},\n        \"bgcolor\": PAGE_BG,\n    },\n    legend={\n        \"title\": {\"text\": \"Region\", \"font\": {\"size\": 20, \"color\": INK}},\n        \"font\": {\"size\": 18, \"color\": INK_SOFT},\n        \"itemsizing\": \"constant\",\n        \"x\": 0.02,\n        \"y\": 0.40,\n        \"xanchor\": \"left\",\n        \"yanchor\": \"bottom\",\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 20, \"r\": 20, \"t\": 80, \"b\": 20},\n)\n\n# Visual size legend: circle shapes in paper coordinates\nFIG_W, FIG_H = 1600, 900\nref_pops = [35, 20, 5]\nref_sizes_px = [min_size + (max_size - min_size) * np.sqrt((p - pop_min) / (pop_max - pop_min)) for p in ref_pops]\n\nfig.add_shape(\n    type=\"rect\",\n    xref=\"paper\",\n    yref=\"paper\",\n    x0=0.005,\n    y0=0.005,\n    x1=0.195,\n    y1=0.270,\n    fillcolor=ELEVATED_BG,\n    line={\"color\": INK_SOFT, \"width\": 1},\n    opacity=0.92,\n)\n\nfig.add_annotation(\n    text=\"<b>Population scale</b>\",\n    xref=\"paper\",\n    yref=\"paper\",\n    x=0.100,\n    y=0.252,\n    showarrow=False,\n    font={\"size\": 15, \"color\": INK},\n    align=\"center\",\n    xanchor=\"center\",\n    yanchor=\"top\",\n)\n\ncx = 0.062\ny_centers = [0.075, 0.160, 0.220]\nlabels = [\"35M\", \"20M\", \"5M\"]\n\nfor size_px, y_c, label in zip(ref_sizes_px, y_centers, labels, strict=False):\n    rx = (size_px / 2) / FIG_W\n    ry = (size_px / 2) / FIG_H\n    fig.add_shape(\n        type=\"circle\",\n        xref=\"paper\",\n        yref=\"paper\",\n        x0=cx - rx,\n        y0=y_c - ry,\n        x1=cx + rx,\n        y1=y_c + ry,\n        fillcolor=INK_SOFT,\n        line={\"color\": PAGE_BG, \"width\": 1},\n        opacity=0.55,\n    )\n    fig.add_annotation(\n        text=label,\n        xref=\"paper\",\n        yref=\"paper\",\n        x=cx + rx + 0.012,\n        y=y_c,\n        showarrow=False,\n        font={\"size\": 15, \"color\": INK_SOFT},\n        align=\"left\",\n        xanchor=\"left\",\n        yanchor=\"middle\",\n    )\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=FIG_W, height=FIG_H, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}