{"spec_id":"voronoi-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nvoronoi-basic: Voronoi Diagram for Spatial Partitioning\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_polygon,\n    ggplot,\n    labs,\n    scale_fill_manual,\n    theme,\n)\nfrom scipy.spatial import Voronoi\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# Seed for reproducibility\nnp.random.seed(42)\n\n# Generate seed points (20 points for clear visualization)\nn_points = 20\nx_points = np.random.uniform(1, 9, n_points)\ny_points = np.random.uniform(1, 9, n_points)\npoints = np.column_stack([x_points, y_points])\n\n# Define bounding box for clipping\nx_min, x_max = 0, 10\ny_min, y_max = 0, 10\n\n# Add mirror points outside bounds to ensure all cells are bounded\nmargin = 20\nmirror_points = []\nfor px, py in points:\n    mirror_points.append([2 * x_min - margin - px, py])\n    mirror_points.append([2 * x_max + margin - px, py])\n    mirror_points.append([px, 2 * y_min - margin - py])\n    mirror_points.append([px, 2 * y_max + margin - py])\n\nall_points = np.vstack([points, mirror_points])\n\n# Compute Voronoi diagram with mirrored points\nvor = Voronoi(all_points)\n\n# Build polygon dataframe for Voronoi cells\npolygon_data = []\n\nfor point_idx in range(n_points):\n    region_idx = vor.point_region[point_idx]\n    region = vor.regions[region_idx]\n\n    if not region or -1 in region:\n        continue\n\n    vertices = [tuple(vor.vertices[v]) for v in region]\n\n    # Sutherland-Hodgman polygon clipping algorithm (inline)\n    polygon = list(vertices)\n\n    # Clip left edge\n    clipped = []\n    for i in range(len(polygon)):\n        curr = polygon[i]\n        prev = polygon[i - 1]\n        curr_in = curr[0] >= x_min\n        prev_in = prev[0] >= x_min\n        if curr_in:\n            if not prev_in:\n                x1, y1 = prev\n                x2, y2 = curr\n                t = (x_min - x1) / (x2 - x1) if x2 != x1 else 0\n                clipped.append((x_min, y1 + t * (y2 - y1)))\n            clipped.append(curr)\n        elif prev_in:\n            x1, y1 = prev\n            x2, y2 = curr\n            t = (x_min - x1) / (x2 - x1) if x2 != x1 else 0\n            clipped.append((x_min, y1 + t * (y2 - y1)))\n    polygon = clipped\n\n    # Clip right edge\n    clipped = []\n    for i in range(len(polygon)):\n        curr = polygon[i]\n        prev = polygon[i - 1]\n        curr_in = curr[0] <= x_max\n        prev_in = prev[0] <= x_max\n        if curr_in:\n            if not prev_in:\n                x1, y1 = prev\n                x2, y2 = curr\n                t = (x_max - x1) / (x2 - x1) if x2 != x1 else 0\n                clipped.append((x_max, y1 + t * (y2 - y1)))\n            clipped.append(curr)\n        elif prev_in:\n            x1, y1 = prev\n            x2, y2 = curr\n            t = (x_max - x1) / (x2 - x1) if x2 != x1 else 0\n            clipped.append((x_max, y1 + t * (y2 - y1)))\n    polygon = clipped\n\n    # Clip bottom edge\n    clipped = []\n    for i in range(len(polygon)):\n        curr = polygon[i]\n        prev = polygon[i - 1]\n        curr_in = curr[1] >= y_min\n        prev_in = prev[1] >= y_min\n        if curr_in:\n            if not prev_in:\n                x1, y1 = prev\n                x2, y2 = curr\n                t = (y_min - y1) / (y2 - y1) if y2 != y1 else 0\n                clipped.append((x1 + t * (x2 - x1), y_min))\n            clipped.append(curr)\n        elif prev_in:\n            x1, y1 = prev\n            x2, y2 = curr\n            t = (y_min - y1) / (y2 - y1) if y2 != y1 else 0\n            clipped.append((x1 + t * (x2 - x1), y_min))\n    polygon = clipped\n\n    # Clip top edge\n    clipped = []\n    for i in range(len(polygon)):\n        curr = polygon[i]\n        prev = polygon[i - 1]\n        curr_in = curr[1] <= y_max\n        prev_in = prev[1] <= y_max\n        if curr_in:\n            if not prev_in:\n                x1, y1 = prev\n                x2, y2 = curr\n                t = (y_max - y1) / (y2 - y1) if y2 != y1 else 0\n                clipped.append((x1 + t * (x2 - x1), y_max))\n            clipped.append(curr)\n        elif prev_in:\n            x1, y1 = prev\n            x2, y2 = curr\n            t = (y_max - y1) / (y2 - y1) if y2 != y1 else 0\n            clipped.append((x1 + t * (x2 - x1), y_max))\n    polygon = clipped\n\n    if len(polygon) < 3:\n        continue\n\n    for order, (vx, vy) in enumerate(polygon):\n        polygon_data.append({\"cell_id\": str(point_idx), \"x\": vx, \"y\": vy, \"order\": order})\n\ndf_polygons = pd.DataFrame(polygon_data)\n\n# Create dataframe for seed points\ndf_points = pd.DataFrame({\"x\": x_points, \"y\": y_points})\n\n# Cell colors - diverse palette starting with Okabe-Ito positions\ncolors_20 = [\n    \"#009E73\",\n    \"#C475FD\",\n    \"#4467A3\",\n    \"#BD8233\",\n    \"#AE3030\",\n    \"#2ABCCD\",\n    \"#954477\",\n    \"#306998\",\n    \"#8DD3C7\",\n    \"#BEBADA\",\n    \"#FB8072\",\n    \"#80B1D3\",\n    \"#FDB462\",\n    \"#B3DE69\",\n    \"#FCCDE5\",\n    \"#BC80BD\",\n    \"#CCEBC5\",\n    \"#FFED6F\",\n    \"#A6CEE3\",\n    \"#B2DF8A\",\n]\n\n# Create the Voronoi diagram plot\nplot = (\n    ggplot()\n    + geom_polygon(df_polygons, aes(x=\"x\", y=\"y\", group=\"cell_id\", fill=\"cell_id\"), color=INK_SOFT, size=1.0, alpha=0.7)\n    + geom_point(df_points, aes(x=\"x\", y=\"y\"), color=\"#009E73\", fill=\"#009E73\", size=6, stroke=1.5, shape=\"o\")\n    + scale_fill_manual(values=colors_20)\n    + coord_fixed(ratio=1.0, xlim=(x_min, x_max), ylim=(y_min, y_max))\n    + labs(title=\"voronoi-basic · plotnine · anyplot.ai\", x=\"X Coordinate\", y=\"Y Coordinate\")\n    + theme(\n        figure_size=(12, 12),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5),\n        axis_line=element_line(color=INK_SOFT, size=0.5),\n        plot_title=element_text(size=24, color=INK, weight=\"bold\", margin={\"b\": 20}),\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        legend_position=\"none\",\n    )\n)\n\n# Save the plot\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}