Voronoi Diagram for Spatial Partitioning — plotnine

A Voronoi diagram partitions a plane into regions based on the distance to a set of seed points, where each region contains all points closer to its seed than to any other. This visualization is essential for understanding spatial relationships, proximity analysis, and territorial boundaries. It reveals natural clustering patterns and helps identify areas of influence around data points.

Voronoi Diagram for Spatial Partitioning rendered with plotnine

Python source (plotnine)

""" anyplot.ai
voronoi-basic: Voronoi Diagram for Spatial Partitioning
Library: plotnine 0.15.4 | Python 3.13.13
Quality: 92/100 | Updated: 2026-05-17
"""

import os

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    coord_fixed,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_point,
    geom_polygon,
    ggplot,
    labs,
    scale_fill_manual,
    theme,
)
from scipy.spatial import Voronoi


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# Seed for reproducibility
np.random.seed(42)

# Generate seed points (20 points for clear visualization)
n_points = 20
x_points = np.random.uniform(1, 9, n_points)
y_points = np.random.uniform(1, 9, n_points)
points = np.column_stack([x_points, y_points])

# Define bounding box for clipping
x_min, x_max = 0, 10
y_min, y_max = 0, 10

# Add mirror points outside bounds to ensure all cells are bounded
margin = 20
mirror_points = []
for px, py in points:
    mirror_points.append([2 * x_min - margin - px, py])
    mirror_points.append([2 * x_max + margin - px, py])
    mirror_points.append([px, 2 * y_min - margin - py])
    mirror_points.append([px, 2 * y_max + margin - py])

all_points = np.vstack([points, mirror_points])

# Compute Voronoi diagram with mirrored points
vor = Voronoi(all_points)

# Build polygon dataframe for Voronoi cells
polygon_data = []

for point_idx in range(n_points):
    region_idx = vor.point_region[point_idx]
    region = vor.regions[region_idx]

    if not region or -1 in region:
        continue

    vertices = [tuple(vor.vertices[v]) for v in region]

    # Sutherland-Hodgman polygon clipping algorithm (inline)
    polygon = list(vertices)

    # Clip left edge
    clipped = []
    for i in range(len(polygon)):
        curr = polygon[i]
        prev = polygon[i - 1]
        curr_in = curr[0] >= x_min
        prev_in = prev[0] >= x_min
        if curr_in:
            if not prev_in:
                x1, y1 = prev
                x2, y2 = curr
                t = (x_min - x1) / (x2 - x1) if x2 != x1 else 0
                clipped.append((x_min, y1 + t * (y2 - y1)))
            clipped.append(curr)
        elif prev_in:
            x1, y1 = prev
            x2, y2 = curr
            t = (x_min - x1) / (x2 - x1) if x2 != x1 else 0
            clipped.append((x_min, y1 + t * (y2 - y1)))
    polygon = clipped

    # Clip right edge
    clipped = []
    for i in range(len(polygon)):
        curr = polygon[i]
        prev = polygon[i - 1]
        curr_in = curr[0] <= x_max
        prev_in = prev[0] <= x_max
        if curr_in:
            if not prev_in:
                x1, y1 = prev
                x2, y2 = curr
                t = (x_max - x1) / (x2 - x1) if x2 != x1 else 0
                clipped.append((x_max, y1 + t * (y2 - y1)))
            clipped.append(curr)
        elif prev_in:
            x1, y1 = prev
            x2, y2 = curr
            t = (x_max - x1) / (x2 - x1) if x2 != x1 else 0
            clipped.append((x_max, y1 + t * (y2 - y1)))
    polygon = clipped

    # Clip bottom edge
    clipped = []
    for i in range(len(polygon)):
        curr = polygon[i]
        prev = polygon[i - 1]
        curr_in = curr[1] >= y_min
        prev_in = prev[1] >= y_min
        if curr_in:
            if not prev_in:
                x1, y1 = prev
                x2, y2 = curr
                t = (y_min - y1) / (y2 - y1) if y2 != y1 else 0
                clipped.append((x1 + t * (x2 - x1), y_min))
            clipped.append(curr)
        elif prev_in:
            x1, y1 = prev
            x2, y2 = curr
            t = (y_min - y1) / (y2 - y1) if y2 != y1 else 0
            clipped.append((x1 + t * (x2 - x1), y_min))
    polygon = clipped

    # Clip top edge
    clipped = []
    for i in range(len(polygon)):
        curr = polygon[i]
        prev = polygon[i - 1]
        curr_in = curr[1] <= y_max
        prev_in = prev[1] <= y_max
        if curr_in:
            if not prev_in:
                x1, y1 = prev
                x2, y2 = curr
                t = (y_max - y1) / (y2 - y1) if y2 != y1 else 0
                clipped.append((x1 + t * (x2 - x1), y_max))
            clipped.append(curr)
        elif prev_in:
            x1, y1 = prev
            x2, y2 = curr
            t = (y_max - y1) / (y2 - y1) if y2 != y1 else 0
            clipped.append((x1 + t * (x2 - x1), y_max))
    polygon = clipped

    if len(polygon) < 3:
        continue

    for order, (vx, vy) in enumerate(polygon):
        polygon_data.append({"cell_id": str(point_idx), "x": vx, "y": vy, "order": order})

df_polygons = pd.DataFrame(polygon_data)

# Create dataframe for seed points
df_points = pd.DataFrame({"x": x_points, "y": y_points})

# Cell colors - diverse palette starting with Okabe-Ito positions
colors_20 = [
    "#009E73",
    "#C475FD",
    "#4467A3",
    "#BD8233",
    "#AE3030",
    "#2ABCCD",
    "#954477",
    "#306998",
    "#8DD3C7",
    "#BEBADA",
    "#FB8072",
    "#80B1D3",
    "#FDB462",
    "#B3DE69",
    "#FCCDE5",
    "#BC80BD",
    "#CCEBC5",
    "#FFED6F",
    "#A6CEE3",
    "#B2DF8A",
]

# Create the Voronoi diagram plot
plot = (
    ggplot()
    + geom_polygon(df_polygons, aes(x="x", y="y", group="cell_id", fill="cell_id"), color=INK_SOFT, size=1.0, alpha=0.7)
    + geom_point(df_points, aes(x="x", y="y"), color="#009E73", fill="#009E73", size=6, stroke=1.5, shape="o")
    + scale_fill_manual(values=colors_20)
    + coord_fixed(ratio=1.0, xlim=(x_min, x_max), ylim=(y_min, y_max))
    + labs(title="voronoi-basic · plotnine · anyplot.ai", x="X Coordinate", y="Y Coordinate")
    + theme(
        figure_size=(12, 12),
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG),
        panel_grid_major=element_blank(),
        panel_grid_minor=element_blank(),
        panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5),
        axis_line=element_line(color=INK_SOFT, size=0.5),
        plot_title=element_text(size=24, color=INK, weight="bold", margin={"b": 20}),
        axis_title=element_text(size=20, color=INK),
        axis_text=element_text(size=16, color=INK_SOFT),
        legend_position="none",
    )
)

# Save the plot
plot.save(f"plot-{THEME}.png", dpi=300, verbose=False)

Part of Voronoi Diagram for Spatial Partitioning on anyplot.ai.

Other implementations