{"spec_id":"hexbin-map-geographic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nhexbin-map-geographic: Hexagonal Binning Map\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-16\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    coord_fixed,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hex,\n    geom_polygon,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_fill_gradient,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBASEMAP_FILL = \"#E4E4DC\" if THEME == \"light\" else \"#2E2E2A\"\nBASEMAP_COLOR = \"#AAAAAA\" if THEME == \"light\" else \"#5A5A54\"\n\n# Data: Simulated taxi pickup locations in New York City area\nnp.random.seed(42)\nn_points = 15000\n\nmanhattan_lat = np.random.normal(40.758, 0.025, n_points // 2)\nmanhattan_lon = np.random.normal(-73.985, 0.015, n_points // 2)\n\nmidtown_lat = np.random.normal(40.752, 0.018, n_points // 4)\nmidtown_lon = np.random.normal(-73.972, 0.012, n_points // 4)\n\njfk_lat = np.random.normal(40.641, 0.012, n_points // 6)\njfk_lon = np.random.normal(-73.778, 0.015, n_points // 6)\n\nlga_lat = np.random.normal(40.773, 0.008, n_points // 12)\nlga_lon = np.random.normal(-73.872, 0.010, n_points // 12)\n\nlatitude = np.concatenate([manhattan_lat, midtown_lat, jfk_lat, lga_lat])\nlongitude = np.concatenate([manhattan_lon, midtown_lon, jfk_lon, lga_lon])\n\ndf = pd.DataFrame({\"lat\": latitude, \"lon\": longitude})\n\n# Simplified basemap: faithful NYC borough outlines for geographic context.\n# Vertices trace the real coastlines of each borough (narrow Manhattan island,\n# Brooklyn's south/west waterfront, Queens' large NE landmass) so the clusters\n# sit plausibly inside their nominal borough.\nmanhattan_lon_outline = [\n    -74.015,\n    -74.013,\n    -74.009,\n    -73.993,\n    -73.972,\n    -73.948,\n    -73.934,\n    -73.910,\n    -73.920,\n    -73.929,\n    -73.937,\n    -73.958,\n    -73.971,\n    -73.973,\n    -73.978,\n    -74.000,\n    -74.015,\n]\nmanhattan_lat_outline = [\n    40.701,\n    40.731,\n    40.756,\n    40.782,\n    40.800,\n    40.829,\n    40.850,\n    40.872,\n    40.866,\n    40.834,\n    40.806,\n    40.776,\n    40.752,\n    40.733,\n    40.711,\n    40.703,\n    40.701,\n]\nmanhattan_outline = pd.DataFrame(\n    {\n        \"lon\": manhattan_lon_outline,\n        \"lat\": manhattan_lat_outline,\n        \"borough\": [\"Manhattan\"] * len(manhattan_lon_outline),\n        \"order\": list(range(len(manhattan_lon_outline))),\n    }\n)\n\nbrooklyn_lon_outline = [\n    -74.025,\n    -74.012,\n    -73.998,\n    -73.972,\n    -73.934,\n    -73.866,\n    -73.858,\n    -73.866,\n    -73.926,\n    -73.978,\n    -74.010,\n    -74.025,\n]\nbrooklyn_lat_outline = [40.633, 40.640, 40.700, 40.704, 40.739, 40.694, 40.668, 40.629, 40.575, 40.574, 40.602, 40.633]\nbrooklyn_outline = pd.DataFrame(\n    {\n        \"lon\": brooklyn_lon_outline,\n        \"lat\": brooklyn_lat_outline,\n        \"borough\": [\"Brooklyn\"] * len(brooklyn_lon_outline),\n        \"order\": list(range(len(brooklyn_lon_outline))),\n    }\n)\n\nqueens_lon_outline = [\n    -73.962,\n    -73.910,\n    -73.840,\n    -73.765,\n    -73.700,\n    -73.736,\n    -73.760,\n    -73.823,\n    -73.866,\n    -73.866,\n    -73.934,\n    -73.962,\n]\nqueens_lat_outline = [40.741, 40.779, 40.792, 40.789, 40.745, 40.660, 40.605, 40.583, 40.629, 40.694, 40.739, 40.741]\nqueens_outline = pd.DataFrame(\n    {\n        \"lon\": queens_lon_outline,\n        \"lat\": queens_lat_outline,\n        \"borough\": [\"Queens\"] * len(queens_lon_outline),\n        \"order\": list(range(len(queens_lon_outline))),\n    }\n)\n\ndf_boroughs = pd.concat([manhattan_outline, brooklyn_outline, queens_outline], ignore_index=True)\n\n# Title — scale fontsize for length; kept compact so the full string (incl.\n# \"anyplot.ai\") sits inside the panel width and renders at uniform contrast.\ntitle = \"NYC Taxi Pickups · hexbin-map-geographic · python · letsplot · anyplot.ai\"\ntitle_size = max(11, round(13 * 60 / len(title)))\n\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK_MUTED, size=0.2),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=12),\n    axis_text=element_text(color=INK_SOFT, size=10),\n    plot_title=element_text(color=INK, size=title_size),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=10),\n    legend_title=element_text(color=INK, size=11),\n    legend_position=\"right\",\n)\n\nplot = (\n    ggplot()\n    + geom_polygon(\n        aes(x=\"lon\", y=\"lat\", group=\"borough\"),\n        data=df_boroughs,\n        fill=BASEMAP_FILL,\n        color=BASEMAP_COLOR,\n        size=0.6,\n        alpha=0.7,\n    )\n    + geom_hex(\n        aes(x=\"lon\", y=\"lat\"), data=df, bins=[40, 40], alpha=0.85, tooltips=layer_tooltips().line(\"Pickups|@..count..\")\n    )\n    + scale_fill_gradient(low=\"#009E73\", high=\"#4467A3\", name=\"Pickup\\nCount\", trans=\"log10\")\n    + labs(x=\"Longitude (°)\", y=\"Latitude (°)\", title=title)\n    + coord_fixed(ratio=1.0, xlim=[-74.05, -73.68], ylim=[40.55, 40.90])\n    + ggsize(800, 450)\n    + theme_minimal()\n    + anyplot_theme\n)\n\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}