{"spec_id":"voronoi-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nvoronoi-basic: Voronoi Diagram for Spatial Partitioning\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove current directory from path BEFORE importing plotly\ncur_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != cur_dir]\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito palette (position 1 is brand color #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Data - Generate seed points (retail stores) within city bounds\nnp.random.seed(42)\nn_stores = 20\nx = np.random.uniform(10, 90, n_stores)\ny = np.random.uniform(10, 90, n_stores)\npoints = np.column_stack([x, y])\n\n# Bounding box for city area\nx_min, x_max = 0, 100\ny_min, y_max = 0, 100\n\n# Add far-away boundary points to ensure finite regions within bounding box\nboundary_offset = 200\nboundary_points = np.array(\n    [\n        [x_min - boundary_offset, y_min - boundary_offset],\n        [x_max + boundary_offset, y_min - boundary_offset],\n        [x_min - boundary_offset, y_max + boundary_offset],\n        [x_max + boundary_offset, y_max + boundary_offset],\n        [(x_min + x_max) / 2, y_min - boundary_offset],\n        [(x_min + x_max) / 2, y_max + boundary_offset],\n        [x_min - boundary_offset, (y_min + y_max) / 2],\n        [x_max + boundary_offset, (y_min + y_max) / 2],\n    ]\n)\nall_points = np.vstack([points, boundary_points])\n\n# Compute Voronoi diagram\nvor = Voronoi(all_points)\n\n# Extended color palette (cycle through Okabe-Ito + supplementary colors)\nextended_colors = IMPRINT + [\n    \"#FF9AA2\",\n    \"#FFB7B2\",\n    \"#E0BBE4\",\n    \"#957DAD\",\n    \"#D291BC\",\n    \"#C9B1FF\",\n    \"#A1C4FD\",\n    \"#B5EAD7\",\n    \"#C7CEEA\",\n    \"#FFDAC1\",\n    \"#E2F0CB\",\n]\n\n# Create figure\nfig = go.Figure()\n\n# Draw Voronoi cells (service areas for retail stores)\nfor idx in range(n_stores):\n    region_idx = vor.point_region[idx]\n    region = vor.regions[region_idx]\n\n    if not region or -1 in region:\n        continue\n\n    vertices = [list(vor.vertices[v]) for v in region]\n\n    # Clip polygon to bounding box using Sutherland-Hodgman algorithm\n    polygon = vertices\n    for edge, bounds in [(\"left\", x_min), (\"right\", x_max), (\"bottom\", y_min), (\"top\", y_max)]:\n        if len(polygon) == 0:\n            break\n        clipped = []\n        for i in range(len(polygon)):\n            curr = polygon[i]\n            next_v = polygon[(i + 1) % len(polygon)]\n\n            # Check if points are inside edge\n            if edge == \"left\":\n                curr_in, next_in = curr[0] >= bounds, next_v[0] >= bounds\n            elif edge == \"right\":\n                curr_in, next_in = curr[0] <= bounds, next_v[0] <= bounds\n            elif edge == \"bottom\":\n                curr_in, next_in = curr[1] >= bounds, next_v[1] >= bounds\n            else:  # top\n                curr_in, next_in = curr[1] <= bounds, next_v[1] <= bounds\n\n            # Compute intersection if needed\n            if curr_in != next_in:\n                dx, dy = next_v[0] - curr[0], next_v[1] - curr[1]\n                if edge in (\"left\", \"right\"):\n                    t = (bounds - curr[0]) / dx if dx != 0 else 0\n                    intersect = [bounds, curr[1] + t * dy]\n                else:\n                    t = (bounds - curr[1]) / dy if dy != 0 else 0\n                    intersect = [curr[0] + t * dx, bounds]\n\n            if curr_in:\n                clipped.append(curr)\n                if not next_in:\n                    clipped.append(intersect)\n            elif next_in:\n                clipped.append(intersect)\n\n        polygon = clipped\n\n    if len(polygon) >= 3:\n        polygon_x = [p[0] for p in polygon] + [polygon[0][0]]\n        polygon_y = [p[1] for p in polygon] + [polygon[0][1]]\n\n        fig.add_trace(\n            go.Scatter(\n                x=polygon_x,\n                y=polygon_y,\n                fill=\"toself\",\n                fillcolor=extended_colors[idx % len(extended_colors)],\n                opacity=0.6,\n                line={\"color\": INK_SOFT, \"width\": 2},\n                mode=\"lines\",\n                hoverinfo=\"skip\",\n                showlegend=False,\n            )\n        )\n\n# Draw seed points (store locations) on top\nfig.add_trace(\n    go.Scatter(\n        x=x,\n        y=y,\n        mode=\"markers\",\n        marker={\"size\": 18, \"color\": IMPRINT[0], \"line\": {\"color\": ELEVATED_BG, \"width\": 3}, \"symbol\": \"circle\"},\n        name=\"Store Location\",\n        hovertemplate=\"Store %{pointNumber}<br>Latitude: %{y:.1f}<br>Longitude: %{x:.1f}<extra></extra>\",\n    )\n)\n\n# Update layout with theme-adaptive colors\nfig.update_layout(\n    title={\n        \"text\": \"voronoi-basic · plotly · pyplots.ai\",\n        \"font\": {\"size\": 28, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Longitude (°)\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"range\": [-5, 105],\n        \"showgrid\": True,\n        \"gridwidth\": 1,\n        \"gridcolor\": GRID,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Latitude (°)\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"range\": [-5, 105],\n        \"showgrid\": True,\n        \"gridwidth\": 1,\n        \"gridcolor\": GRID,\n        \"zeroline\": False,\n        \"scaleanchor\": \"x\",\n        \"scaleratio\": 1,\n        \"linecolor\": INK_SOFT,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    showlegend=True,\n    legend={\n        \"font\": {\"size\": 16, \"color\": INK_SOFT},\n        \"x\": 0.02,\n        \"y\": 0.98,\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 100, \"b\": 80},\n)\n\n# Save as PNG and HTML (square format for Voronoi diagram)\nfig.write_image(f\"plot-{THEME}.png\", width=1200, height=1200, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}