{"spec_id":"hexbin-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nhexbin-basic: Basic Hexbin Plot\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 88/100 | Created: 2026-05-29\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_polygon,\n    ggplot,\n    guide_colorbar,\n    labs,\n    scale_fill_gradient,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme-adaptive chrome tokens (Imprint palette)\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\nnp.random.seed(42)\n\n# Data — seismic epicenters with multi-cluster distribution (Eastern Mediterranean)\nlon = np.concatenate(\n    [\n        np.random.normal(35.5, 0.8, 2500),  # Primary fault zone\n        np.random.normal(37.5, 0.5, 1200),  # Aftershock region\n        np.random.normal(33.5, 0.4, 600),  # Background cluster\n        np.random.uniform(32.0, 39.0, 700),  # Diffuse regional activity\n    ]\n)\nlat = np.concatenate(\n    [\n        np.random.normal(37.0, 0.7, 2500),\n        np.random.normal(38.5, 0.5, 1200),\n        np.random.normal(36.5, 0.4, 600),\n        np.random.uniform(35.0, 40.0, 700),\n    ]\n)\n\n# Vectorized hexagonal binning\ngridsize = 30\nhex_w = (lon.max() - lon.min() + 1.0) / gridsize\nhex_h = hex_w * np.sqrt(3) / 2\n\nrow_idx = np.round(lat / hex_h).astype(int)\noffset = (row_idx % 2) * (hex_w / 2)\ncol_idx = np.round((lon - offset) / hex_w).astype(int)\n\nbin_df = pd.DataFrame({\"cx\": np.round(col_idx * hex_w + offset, 6), \"cy\": np.round(row_idx * hex_h, 6)})\ncounts = bin_df.groupby([\"cx\", \"cy\"]).size().reset_index(name=\"count\")\n\n# Build hex polygon vertices — oversize 1.08 minimises gaps in sparse regions\nr = hex_w / np.sqrt(3) * 1.08\nangles = np.linspace(0, 2 * np.pi, 7)[:-1] + np.pi / 6\nn = len(counts)\n\nhex_df = pd.DataFrame(\n    {\n        \"x\": np.repeat(counts[\"cx\"].values, 6) + r * np.cos(np.tile(angles, n)),\n        \"y\": np.repeat(counts[\"cy\"].values, 6) + r * np.sin(np.tile(angles, n)),\n        \"hex_id\": np.repeat(np.arange(n), 6),\n        \"count\": np.repeat(counts[\"count\"].values, 6),\n    }\n)\n\ntitle = \"Seismic Event Density · hexbin-basic · python · plotnine · anyplot.ai\"\ntitle_fontsize = max(8, round(12 * 67 / len(title)))\n\nplot = (\n    ggplot(hex_df, aes(x=\"x\", y=\"y\", group=\"hex_id\", fill=\"count\"))\n    + geom_polygon(color=PAGE_BG, size=0.15)\n    + scale_fill_gradient(low=\"#009E73\", high=\"#4467A3\", name=\"Event Count\", guide=guide_colorbar(nbin=200))\n    # Focal annotations identifying the two main seismic clusters\n    + annotate(\"text\", x=32.2, y=37.1, label=\"Primary\\nFault Zone →\", size=3, color=INK, ha=\"left\")\n    + annotate(\"text\", x=38.8, y=38.6, label=\"← Aftershock\\n   Region\", size=3, color=INK, ha=\"right\")\n    + coord_fixed(ratio=1)\n    + labs(x=\"Longitude (°E)\", y=\"Latitude (°N)\", title=title)\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=8, color=INK_SOFT),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        plot_title=element_text(size=title_fontsize, color=INK),\n        legend_title=element_text(size=8, color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_background=element_rect(fill=PAGE_BG, color=\"none\"),\n        plot_background=element_rect(fill=PAGE_BG, color=\"none\"),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        panel_border=element_rect(color=INK_SOFT, fill=None),\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}