{"spec_id":"bubble-map-geographic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nbubble-map-geographic: Bubble Map with Sized Geographic Markers\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\nimport sys\n\n\n_cwd = sys.path[0] if sys.path and sys.path[0] else None\nif _cwd:\n    sys.path.remove(_cwd)\n\nimport pygal\nfrom pygal.style import Style\n\n\nif _cwd:\n    sys.path.insert(0, _cwd)\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nOCEAN_BG = \"#C8DDF0\" if THEME == \"light\" else \"#0D2535\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nCOAST_COLOR = \"#A0A096\" if THEME == \"light\" else \"#5A5A52\"\nBRAND = \"#009E73\"\n\n# Data — major world cities with population (millions)\ncities = {\n    \"Tokyo\": (35.68, 139.69, 37.4),\n    \"Delhi\": (28.61, 77.21, 32.9),\n    \"Shanghai\": (31.23, 121.47, 28.5),\n    \"Sao Paulo\": (-23.55, -46.63, 22.4),\n    \"Mexico City\": (19.43, -99.13, 21.8),\n    \"Cairo\": (30.04, 31.24, 21.3),\n    \"Mumbai\": (19.08, 72.88, 20.7),\n    \"Beijing\": (39.90, 116.41, 20.5),\n    \"New York\": (40.71, -74.01, 18.8),\n    \"Istanbul\": (41.01, 28.98, 15.4),\n    \"Buenos Aires\": (-34.60, -58.38, 15.4),\n    \"Lagos\": (6.52, 3.38, 14.9),\n    \"Los Angeles\": (34.05, -118.24, 12.5),\n    \"Moscow\": (55.76, 37.62, 12.5),\n    \"Bangkok\": (13.76, 100.50, 10.7),\n    \"Jakarta\": (-6.21, 106.85, 10.6),\n    \"Paris\": (48.86, 2.35, 11.0),\n    \"Seoul\": (37.57, 126.98, 9.8),\n    \"London\": (51.51, -0.13, 9.5),\n    \"Sydney\": (-33.87, 151.21, 5.4),\n}\n\n# Simplified world coastlines (longitude, latitude)\ncoastlines = [\n    # North America\n    [\n        (-125, 50),\n        (-141, 60),\n        (-165, 55),\n        (-168, 52),\n        (-148, 60),\n        (-130, 55),\n        (-120, 49),\n        (-95, 49),\n        (-80, 45),\n        (-67, 45),\n        (-75, 35),\n        (-81, 25),\n        (-90, 30),\n        (-97, 26),\n        (-110, 32),\n        (-125, 50),\n    ],\n    # Mexico / Central America\n    [(-117, 33), (-110, 25), (-97, 20), (-87, 16), (-80, 8), (-90, 22), (-110, 32), (-117, 33)],\n    # South America\n    [(-78, 10), (-60, 8), (-35, -6), (-42, -23), (-66, -55), (-72, -30), (-78, 10)],\n    # Europe / Africa\n    [(-10, 36), (10, 37), (30, 31), (42, 14), (35, -22), (17, -30), (0, 6), (-17, 14), (-10, 36)],\n    # Northern Europe\n    [(-6, 50), (5, 58), (28, 70), (24, 55), (3, 51), (-6, 50)],\n    # Asia\n    [(28, 70), (100, 77), (170, 60), (120, 32), (100, 14), (72, 25), (40, 46), (28, 70)],\n    # India / SE Asia\n    [(78, 33), (72, 8), (88, 22), (104, 2), (78, 33)],\n    # Japan\n    [(130, 32), (145, 44), (130, 32)],\n    # Australia\n    [(113, -22), (150, -23), (140, -38), (113, -22)],\n]\n\n# Continuous bubble sizing: area ∝ population → dots_size ∝ sqrt(population)\npops = {name: data[2] for name, data in cities.items()}\nk_scale = 78.0 / max(pops.values()) ** 0.5\ncity_sizes = {name: round(k_scale * pop**0.5) for name, pop in pops.items()}\n\n# Size legend reference markers placed in south Pacific (open ocean)\nLEGEND_POPS = [5, 15, 25, 37]\nlegend_sizes = [round(k_scale * lpop**0.5) for lpop in LEGEND_POPS]\nLEGEND_LON = -158\nLEGEND_LATS = [-31, -38, -45, -52]\n\nn_coasts = len(coastlines)\nn_cities = len(cities)\nn_legend = len(LEGEND_POPS)\n\n# Color tuple: coast gray × n_coasts, brand green × (cities + legend entries)\ncolors_tuple = (COAST_COLOR,) * n_coasts + (BRAND,) * (n_cities + n_legend)\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=OCEAN_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=colors_tuple,\n    opacity=0.72,\n    opacity_hover=0.9,\n    title_font_size=72,\n    label_font_size=48,\n    major_label_font_size=40,\n    legend_font_size=40,\n    value_font_size=36,\n    tooltip_font_size=36,\n    stroke_width=2,\n)\n\n# Plot\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"bubble-map-geographic · python · pygal · anyplot.ai\",\n    x_title=\"Longitude (°)\",\n    y_title=\"Latitude (°)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=4,\n    legend_box_size=40,\n    stroke=False,\n    dots_size=3,\n    show_x_guides=False,\n    show_y_guides=False,\n    explicit_size=True,\n    print_values=False,\n    xrange=(-180, 180),\n    range=(-60, 80),\n)\n\n# Coastlines (title=None → no legend entry)\nfor coords in coastlines:\n    chart.add(None, coords, stroke=True, dots_size=0, show_dots=False, fill=False)\n\n# Cities: one series per city, dots_size proportional to sqrt(population)\nfor name, (lat, lon, _pop) in cities.items():\n    chart.add(None, [{\"value\": (lon, lat), \"label\": f\"{name}: {_pop}M\"}], stroke=False, dots_size=city_sizes[name])\n\n# Size legend: reference markers in south Pacific showing size scale\nfor lpop, lsize, llat in zip(LEGEND_POPS, legend_sizes, LEGEND_LATS, strict=False):\n    chart.add(\n        f\"{lpop}M pop\", [{\"value\": (LEGEND_LON, llat), \"label\": f\"Reference: {lpop}M\"}], stroke=False, dots_size=lsize\n    )\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}