{"spec_id":"voronoi-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nvoronoi-basic: Voronoi Diagram for Spatial Partitioning\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\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    ggsave,\n    ggsize,\n    labs,\n    scale_fill_manual,\n    theme,\n    theme_minimal,\n)\nfrom scipy.spatial import Voronoi\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Data - Generate seed points for spatial partitioning\nnp.random.seed(42)\nn_points = 20\n\n# Generate clustered seed points representing facility locations\nx = np.concatenate([np.random.normal(25, 8, n_points // 2), np.random.normal(75, 8, n_points // 2)])\ny = np.concatenate([np.random.normal(40, 10, n_points // 2), np.random.normal(60, 10, n_points // 2)])\n\n# Define bounding box for clipping\nx_min, x_max = 0, 100\ny_min, y_max = 0, 100\n\n# Add boundary points to ensure all regions are clipped\nboundary_margin = 200\nboundary_points = np.array(\n    [\n        [x_min - boundary_margin, y_min - boundary_margin],\n        [x_min - boundary_margin, y_max + boundary_margin],\n        [x_max + boundary_margin, y_min - boundary_margin],\n        [x_max + boundary_margin, y_max + boundary_margin],\n    ]\n)\n\n# Combine seed points with boundary points\nall_points = np.column_stack([x, y])\npoints_with_boundary = np.vstack([all_points, boundary_points])\n\n# Compute Voronoi tessellation\nvor = Voronoi(points_with_boundary)\n\n# Build polygon data for each Voronoi region using Sutherland-Hodgman clipping\npolygon_data = []\nfor idx, region_idx in enumerate(vor.point_region[: len(all_points)]):\n    region = vor.regions[region_idx]\n    if not region or -1 in region:\n        continue\n\n    # Get vertices for this region\n    vertices = [list(vor.vertices[i]) for i in region]\n\n    # Sutherland-Hodgman polygon clipping to bounding box\n    output = vertices\n    for edge in [\"left\", \"right\", \"bottom\", \"top\"]:\n        if len(output) == 0:\n            break\n        input_list = output\n        output = []\n        for i in range(len(input_list)):\n            current = input_list[i]\n            previous = input_list[i - 1]\n\n            # Check if point is inside this edge\n            if edge == \"left\":\n                curr_inside = current[0] >= x_min\n                prev_inside = previous[0] >= x_min\n            elif edge == \"right\":\n                curr_inside = current[0] <= x_max\n                prev_inside = previous[0] <= x_max\n            elif edge == \"bottom\":\n                curr_inside = current[1] >= y_min\n                prev_inside = previous[1] >= y_min\n            else:  # top\n                curr_inside = current[1] <= y_max\n                prev_inside = previous[1] <= y_max\n\n            # Compute intersection if crossing edge\n            if curr_inside != prev_inside:\n                x1, y1 = previous\n                x2, y2 = current\n                if edge == \"left\":\n                    t = (x_min - x1) / (x2 - x1) if x2 != x1 else 0\n                    ix, iy = x_min, y1 + t * (y2 - y1)\n                elif edge == \"right\":\n                    t = (x_max - x1) / (x2 - x1) if x2 != x1 else 0\n                    ix, iy = x_max, y1 + t * (y2 - y1)\n                elif edge == \"bottom\":\n                    t = (y_min - y1) / (y2 - y1) if y2 != y1 else 0\n                    ix, iy = x1 + t * (x2 - x1), y_min\n                else:  # top\n                    t = (y_max - y1) / (y2 - y1) if y2 != y1 else 0\n                    ix, iy = x1 + t * (x2 - x1), y_max\n                output.append([ix, iy])\n\n            if curr_inside:\n                output.append(current)\n\n    # Add clipped polygon vertices to data\n    if len(output) >= 3:\n        for vx, vy in output:\n            polygon_data.append({\"x\": vx, \"y\": vy, \"region\": f\"Region {idx + 1}\"})\n\ndf_polygons = pd.DataFrame(polygon_data)\n\n# Create seed points dataframe\ndf_seeds = pd.DataFrame({\"x\": x, \"y\": y, \"label\": [f\"P{i + 1}\" for i in range(len(x))]})\n\n# Color palette for regions - diverse and visually distinct\ncolors = [\n    \"#009E73\",\n    \"#C475FD\",\n    \"#4467A3\",\n    \"#BD8233\",\n    \"#AE3030\",\n    \"#2ABCCD\",\n    \"#954477\",\n    \"#1B9E77\",\n    \"#D95F02\",\n    \"#7570B3\",\n    \"#E7298A\",\n    \"#66A61E\",\n    \"#E6AB02\",\n    \"#A6761D\",\n    \"#666666\",\n    \"#1B9E77\",\n    \"#D95F02\",\n    \"#7570B3\",\n    \"#E7298A\",\n    \"#66A61E\",\n]\n\n# Build plot with Voronoi cells and seed points\nplot = (\n    ggplot()\n    + geom_polygon(data=df_polygons, mapping=aes(x=\"x\", y=\"y\", fill=\"region\"), color=INK_SOFT, size=1.5, alpha=0.7)\n    + geom_point(data=df_seeds, mapping=aes(x=\"x\", y=\"y\"), color=INK, size=8)\n    + geom_point(data=df_seeds, mapping=aes(x=\"x\", y=\"y\"), color=PAGE_BG, size=4)\n    + scale_fill_manual(values=colors)\n    + coord_fixed(xlim=[x_min, x_max], ylim=[y_min, y_max])\n    + labs(x=\"X Coordinate\", y=\"Y Coordinate\", title=\"voronoi-basic · letsplot · anyplot.ai\")\n    + theme_minimal()\n    + theme(\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        plot_title=element_text(size=24, color=INK),\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        axis_line=element_line(color=INK_SOFT),\n        legend_position=\"none\",\n    )\n    + ggsize(1600, 900)\n)\n\n# Save outputs with theme suffix\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}