{"spec_id":"hexbin-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nhexbin-basic: Basic Hexbin Plot\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 87/100 | Created: 2026-05-29\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-shadowing: remove this script's directory from sys.path so\n# 'import plotly' resolves to the installed package, not this file.\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p and os.path.abspath(p) != _here]\n\nimport numpy as np\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint sequential colorscale for continuous density data\nimprint_seq = [[0.0, \"#009E73\"], [1.0, \"#4467A3\"]]\n\n# Data - ride-share pickup density across a metro area\nnp.random.seed(42)\n\n# Three pickup hotspots: Downtown (dense hub), Airport (tight cluster), University (diffuse)\nclusters = [(-4, 1.0, 1.3, 4000), (1.5, 3.5, 0.9, 3500), (6, 1.5, 1.1, 2500)]\n\nx_all, y_all = [], []\nfor cx, cy, spread, n in clusters:\n    x_all.extend(np.random.randn(n) * spread + cx)\n    y_all.extend(np.random.randn(n) * spread + cy)\n\nx = np.array(x_all)\ny = np.array(y_all)\n\n# Hexagonal binning (manual — plotly lacks a native hexbin trace)\ngridsize = 25\nx_min, x_max = x.min() - 0.5, x.max() + 0.5\ny_min, y_max = y.min() - 0.5, y.max() + 0.5\n\nhex_size = (x_max - x_min) / (gridsize * 2)\nhex_w = hex_size * np.sqrt(3)\nhex_h = hex_size * 2\nvert_spacing = hex_h * 0.75\n\nhex_bins = {}\nfor xi, yi in zip(x, y, strict=True):\n    row = int((yi - y_min) / vert_spacing)\n    offset = (row % 2) * hex_w * 0.5\n    col = int((xi - x_min - offset) / hex_w)\n    hx = x_min + col * hex_w + offset + hex_w / 2\n    hy = y_min + row * vert_spacing + hex_h / 2\n    key = (col, row)\n    if key not in hex_bins:\n        hex_bins[key] = [hx, hy, 0]\n    hex_bins[key][2] += 1\n\nhex_x = np.array([v[0] for v in hex_bins.values()])\nhex_y = np.array([v[1] for v in hex_bins.values()])\ncounts = np.array([v[2] for v in hex_bins.values()])\n\n# Sort by count so dense hexagons render on top at overlaps\norder = np.argsort(counts)\nhex_x, hex_y, counts = hex_x[order], hex_y[order], counts[order]\n\n# Log-scale color mapping for wide density range — spec recommendation\nlog_counts = np.log1p(counts)\nlog_max = float(log_counts.max())\n\n# Colorbar ticks at meaningful count thresholds, displayed in raw count space\ntick_vals_counts = [v for v in [1, 5, 10, 25, 50, 100, 200] if v <= int(counts.max())]\ntick_vals_counts.append(int(counts.max()))\ntick_vals_log = [float(np.log1p(v)) for v in tick_vals_counts]\ntick_text = [str(v) for v in tick_vals_counts]\n\n# Marker size calibrated to logical canvas (800×450) for seamless tessellation\nmargins = {\"l\": 80, \"r\": 125, \"t\": 80, \"b\": 60}\nplot_w = 800 - margins[\"l\"] - margins[\"r\"]\nplot_h = 450 - margins[\"t\"] - margins[\"b\"]\nax_x_range = (hex_x.max() + hex_w) - (hex_x.min() - hex_w)\nax_y_range = (hex_y.max() + hex_h) - (hex_y.min() - hex_h)\npx_per_unit = min(plot_w / ax_x_range, plot_h / ax_y_range)\nmarker_size = 2 * hex_size * px_per_unit * 1.85\n\ntitle = \"hexbin-basic · python · plotly · anyplot.ai\"\n\nfig = go.Figure(\n    go.Scatter(\n        x=hex_x,\n        y=hex_y,\n        mode=\"markers\",\n        marker={\n            \"symbol\": \"hexagon2\",\n            \"size\": marker_size,\n            \"color\": log_counts,\n            \"colorscale\": imprint_seq,\n            \"cmin\": 0,\n            \"cmax\": log_max,\n            \"colorbar\": {\n                \"title\": {\"text\": \"Pickups\", \"font\": {\"size\": 12, \"color\": INK_SOFT}},\n                \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n                \"tickcolor\": INK_SOFT,\n                \"tickvals\": tick_vals_log,\n                \"ticktext\": tick_text,\n                \"outlinewidth\": 0,\n                \"thickness\": 16,\n                \"len\": 0.7,\n                \"x\": 1.01,\n                \"bgcolor\": ELEVATED_BG,\n            },\n            \"line\": {\"width\": 1, \"color\": log_counts, \"colorscale\": imprint_seq, \"cmin\": 0, \"cmax\": log_max},\n        },\n        customdata=counts,\n        hovertemplate=\"East: %{x:.1f} km<br>North: %{y:.1f} km<br>Pickups: %{customdata}<extra></extra>\",\n        showlegend=False,\n    )\n)\n\nfig.update_layout(\n    autosize=False,\n    width=800,\n    height=450,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    margin=margins,\n    font={\"color\": INK},\n    title={\"text\": title, \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"Distance East (km)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"range\": [hex_x.min() - hex_w, hex_x.max() + hex_w],\n    },\n    yaxis={\n        \"title\": {\"text\": \"Distance North (km)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"scaleanchor\": \"x\",\n        \"scaleratio\": 1,\n        \"range\": [hex_y.min() - hex_h, hex_y.max() + hex_h],\n    },\n    hoverlabel={\"bgcolor\": ELEVATED_BG, \"font\": {\"size\": 10, \"color\": INK}, \"bordercolor\": INK_SOFT},\n)\n\n# Annotate cluster hotspots for spatial narrative\nfor label, cx, cy, ax_offset, ay_offset in [\n    (\"Downtown\", -4, 1.0, -45, 55),\n    (\"Airport\", 1.5, 3.5, 35, -50),\n    (\"University\", 6, 1.5, 45, 55),\n]:\n    fig.add_annotation(\n        x=cx,\n        y=cy,\n        text=f\"<b>{label}</b>\",\n        showarrow=True,\n        arrowhead=0,\n        arrowwidth=1.5,\n        arrowcolor=INK_MUTED,\n        ax=ax_offset,\n        ay=ay_offset,\n        font={\"size\": 11, \"color\": INK},\n        bgcolor=ELEVATED_BG,\n        borderpad=8,\n        bordercolor=INK_SOFT,\n        borderwidth=0.5,\n    )\n\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}