{"spec_id":"hexbin-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nhexbin-basic: Basic Hexbin Plot\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\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 colormap for continuous density data\nIMPRINT_SEQ = [\"#009E73\", \"#4467A3\"]\n\n# Data - GPS coordinates showing traffic density in Seattle\nnp.random.seed(42)\nn_points = 5000\n\n# Downtown core - highest density (tight cluster)\ndowntown_lon = np.random.randn(n_points // 2) * 0.006 + (-122.335)\ndowntown_lat = np.random.randn(n_points // 2) * 0.005 + 47.608\n\n# Shopping district - secondary hotspot\nshopping_lon = np.random.randn(n_points // 3) * 0.005 + (-122.315)\nshopping_lat = np.random.randn(n_points // 3) * 0.004 + 47.622\n\n# Industrial zone - increased spread to avoid misleading bright center\nindustrial_lon = np.random.randn(n_points // 6) * 0.007 + (-122.355)\nindustrial_lat = np.random.randn(n_points // 6) * 0.005 + 47.635\n\nlongitude = np.concatenate([downtown_lon, shopping_lon, industrial_lon])\nlatitude = np.concatenate([downtown_lat, shopping_lat, industrial_lat])\n\n# Hexagonal binning - compute hex grid positions and counts\nhex_radius = 0.002\ndx = hex_radius * np.sqrt(3)\ndy = hex_radius * 1.5\n\nrow_idx = np.round(latitude / dy).astype(int)\nshift = (row_idx % 2) * 0.5\ncol_adj = np.round((longitude / dx) - shift).astype(int)\n\nhex_cx = (col_adj + shift) * dx\nhex_cy = row_idx * dy\n\nhexbins = pd.DataFrame({\"lon\": hex_cx, \"lat\": hex_cy}).groupby([\"lon\", \"lat\"]).size().reset_index(name=\"count\")\n\n# Pixel area for hexagons, calibrated to inner view dimensions (620×320)\nchart_width, chart_height = 620, 320\nlon_range = hexbins[\"lon\"].max() - hexbins[\"lon\"].min()\nlat_range = hexbins[\"lat\"].max() - hexbins[\"lat\"].min()\nhex_px_w = dx * (chart_width / lon_range) if lon_range > 0 else 1\nhex_px_h = 2 * hex_radius * (chart_height / lat_range) if lat_range > 0 else 1\nhex_area = hex_px_w * hex_px_h\n\n# Pointy-top hexagon SVG path\nhex_path = \"M0,-1L0.866,-0.5L0.866,0.5L0,1L-0.866,0.5L-0.866,-0.5Z\"\n\n# Hover interaction — Altair's interactive selection\nhover = alt.selection_point(on=\"pointerover\", nearest=True, empty=False)\n\n# Hexbin layer\nhexbin_layer = (\n    alt.Chart(hexbins)\n    .transform_calculate(density=\"datum.count > 60 ? 'High' : datum.count > 25 ? 'Medium' : 'Low'\")\n    .mark_point(shape=hex_path, filled=True, stroke=PAGE_BG)\n    .encode(\n        x=alt.X(\n            \"lon:Q\",\n            title=\"Longitude (°W)\",\n            scale=alt.Scale(zero=False),\n            axis=alt.Axis(format=\".2f\", values=[-122.36, -122.34, -122.32, -122.30], grid=True),\n        ),\n        y=alt.Y(\n            \"lat:Q\",\n            title=\"Latitude (°N)\",\n            scale=alt.Scale(zero=False),\n            axis=alt.Axis(format=\".2f\", values=[47.59, 47.60, 47.61, 47.62, 47.63, 47.64], grid=True),\n        ),\n        color=alt.Color(\n            \"count:Q\",\n            scale=alt.Scale(range=IMPRINT_SEQ, type=\"symlog\"),\n            legend=alt.Legend(\n                title=\"Vehicle Count\",\n                titleFontSize=10,\n                labelFontSize=10,\n                gradientLength=120,\n                gradientThickness=15,\n                orient=\"right\",\n                offset=10,\n                titlePadding=6,\n            ),\n        ),\n        size=alt.value(hex_area),\n        strokeWidth=alt.condition(hover, alt.value(1.5), alt.value(0.1)),\n        tooltip=[\n            alt.Tooltip(\"lon:Q\", title=\"Longitude\", format=\".4f\"),\n            alt.Tooltip(\"lat:Q\", title=\"Latitude\", format=\".4f\"),\n            alt.Tooltip(\"count:Q\", title=\"Vehicles\"),\n            alt.Tooltip(\"density:N\", title=\"Density Level\"),\n        ],\n    )\n    .add_params(hover)\n)\n\n# Cluster annotation labels for geographic context\nannotations = pd.DataFrame(\n    {\n        \"lon\": [-122.335, -122.322, -122.360],\n        \"lat\": [47.587, 47.626, 47.648],\n        \"label\": [\"Downtown Core\", \"Shopping District\", \"Industrial Zone\"],\n    }\n)\n\ntext_bg = (\n    alt.Chart(annotations)\n    .mark_text(fontSize=10, fontWeight=\"bold\", color=PAGE_BG, strokeWidth=3, stroke=PAGE_BG)\n    .encode(x=\"lon:Q\", y=\"lat:Q\", text=\"label:N\")\n)\n\ntext_fg = (\n    alt.Chart(annotations)\n    .mark_text(fontSize=10, fontWeight=\"bold\", color=INK)\n    .encode(x=\"lon:Q\", y=\"lat:Q\", text=\"label:N\")\n)\n\ntitle_str = \"hexbin-basic · python · altair · anyplot.ai\"\n\n# Chart composition with theme-adaptive chrome\nchart = (\n    alt.layer(hexbin_layer, text_bg, text_fg)\n    .properties(\n        width=620,\n        height=320,\n        title=alt.Title(\n            title_str,\n            fontSize=16,\n            anchor=\"middle\",\n            color=INK,\n            subtitle=\"Seattle metropolitan traffic density — 5,000 GPS vehicle observations\",\n            subtitleFontSize=11,\n            subtitleColor=INK_SOFT,\n            subtitlePadding=6,\n        ),\n        padding={\"left\": 0, \"right\": 0, \"top\": 0, \"bottom\": 0},\n        background=PAGE_BG,\n    )\n    .configure_view(continuousWidth=620, continuousHeight=320, fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.12,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n    )\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n    .configure_title(color=INK)\n)\n\n# Save PNG then pad to exact 3200×1800 target (see prompts/library/altair.md \"Canvas\")\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}