{"spec_id":"hexbin-map-geographic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nhexbin-map-geographic: Hexagonal Binning Map\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-27\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove the script's own directory from sys.path to prevent self-import collision\n# (this file is named bokeh.py, which would otherwise shadow the installed bokeh package)\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p != \"\" and os.path.abspath(p) != _script_dir]\n\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColorBar, ColumnDataSource, HoverTool, LinearColorMapper\nfrom bokeh.plotting import figure\nfrom bokeh.transform import transform\nfrom bokeh.util.hex import hexbin\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\n# Data — simulated NYC taxi pickup GPS coordinates\nnp.random.seed(42)\nn_points = 8000\n\ncluster_centers = [\n    (-73.984, 40.754),  # Times Square / Midtown\n    (-73.965, 40.782),  # Upper West Side\n    (-74.005, 40.724),  # SoHo / TriBeCa\n    (-73.991, 40.735),  # Greenwich Village\n    (-73.951, 40.773),  # Carnegie Hill / Museum Mile\n]\n\nlon_list = []\nlat_list = []\nfor cx, cy in cluster_centers:\n    n = n_points // len(cluster_centers) + np.random.randint(-300, 300)\n    lon_list.extend(np.random.normal(cx, 0.018, n))\n    lat_list.extend(np.random.normal(cy, 0.014, n))\n\nlon = np.array(lon_list)\nlat = np.array(lat_list)\n\n# Web Mercator projection (EPSG:3857)\nK = 6378137.0\nmerc_x = lon * (K * np.pi / 180.0)\nmerc_y = np.log(np.tan((90.0 + lat) * np.pi / 360.0)) * K\n\n# Hexagonal binning (size in Web Mercator meters)\nhex_size = 650\nbins = hexbin(merc_x, merc_y, hex_size)\nsource = ColumnDataSource(data={\"q\": np.array(bins.q), \"r\": np.array(bins.r), \"counts\": np.array(bins.counts)})\n\n# imprint_seq colormap: #009E73 (green) → #4467A3 (blue), 256 stops\nc0 = np.array([0x00, 0x9E, 0x73])\nc1 = np.array([0x44, 0x67, 0xA3])\nt_vals = np.linspace(0, 1, 256)\nrgb_ramp = np.round(c0 + np.outer(t_vals, c1 - c0)).astype(int)\nimprint_seq = [f\"#{r:02X}{g:02X}{b:02X}\" for r, g, b in rgb_ramp]\n\nmapper = LinearColorMapper(palette=imprint_seq, low=float(bins.counts.min()), high=float(bins.counts.max()))\n\n# Map viewport with padding\npad = 4000\nx_range = (float(merc_x.min()) - pad, float(merc_x.max()) + pad)\ny_range = (float(merc_y.min()) - pad, float(merc_y.max()) + pad)\n\n# Title — 51 chars, under 67 baseline, no scaling needed\ntitle = \"hexbin-map-geographic · python · bokeh · anyplot.ai\"\n\n# Tile layer for geographic context\ntile_provider = \"CartoDB Dark Matter\" if THEME == \"dark\" else \"CartoDB Positron\"\n\n# Figure — 3200×1800 landscape, toolbar disabled for exact PNG dimensions\np = figure(\n    width=3200,\n    height=1800,\n    title=title,\n    x_axis_label=\"Longitude\",\n    y_axis_label=\"Latitude\",\n    x_axis_type=\"mercator\",\n    y_axis_type=\"mercator\",\n    x_range=x_range,\n    y_range=y_range,\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=200,\n    min_border_top=110,\n    min_border_right=80,\n)\n\n# Geographic base layer\np.add_tile(tile_provider)\n\n# Hex tiles\np.hex_tile(\n    q=\"q\", r=\"r\", size=hex_size, fill_color=transform(\"counts\", mapper), line_color=None, alpha=0.78, source=source\n)\n\n# Hover tooltip\nhover = HoverTool(tooltips=[(\"Taxi Pickups\", \"@counts\"), (\"Hex Cell\", \"(@q, @r)\")], mode=\"mouse\")\np.add_tools(hover)\n\n# Color bar\ncolor_bar = ColorBar(\n    color_mapper=mapper,\n    location=(0, 0),\n    title=\"Pickup Count\",\n    title_text_font_size=\"34pt\",\n    title_text_font_style=\"bold\",\n    title_text_color=INK,\n    major_label_text_font_size=\"28pt\",\n    major_label_text_color=INK_SOFT,\n    width=60,\n    padding=30,\n    margin=40,\n)\np.add_layout(color_bar, \"right\")\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.title.text_font_style = \"bold\"\n\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\np.xgrid.grid_line_color = INK\np.ygrid.grid_line_color = INK\np.xgrid.grid_line_alpha = 0.12\np.ygrid.grid_line_alpha = 0.12\n\n# Save interactive HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome — Selenium Manager resolves the driver automatically\nW, H = 3200, 1800\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n    \"--force-device-scale-factor=1\",\n):\n    opts.add_argument(arg)\n\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\n# Override viewport to exact pixel dimensions (bypasses browser-chrome height overhead)\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(5)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}