Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/_static/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Rounded borders on the plots/figures rendered in the page content.
Scoped to the main article so the sidebar logo is left untouched. */
.content img {
border-radius: 12px;
border: 1px solid var(--color-background-border);
}
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
html_theme = "furo"
html_title = "compute-geometry"
html_static_path = ["_static"]
html_css_files = ["custom.css"]
html_logo = "../public/logo.svg"
html_favicon = "../public/logo.svg"

Expand Down
172 changes: 172 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Examples

One complete, self-contained example for each algorithm in the library. Every
snippet runs as-is: it builds the input, runs the algorithm, inspects the
result, and draws it with the matching visualization helper. The `# ->` comments
show the actual output, and each example is followed by the figure it produces.

## Convex hull

The smallest convex polygon enclosing a set of points (Gift Wrapping / Jarvis
march).

```python
from cgeom.algorithms import ConvexHull
from cgeom.visualization import plot_convex_hull

points = [(326, 237), (373, 209), (378, 265), (443, 241),
(396, 231), (416, 270), (361, 335), (324, 297)]

hull = ConvexHull(points)

hull.convex_hull() # ordered hull vertices, [[x, y], ...]
hull.get_indexes() # -> [1, 3, 6, 7, 0] (indices into `points`)

plot_convex_hull(hull)
```

```{image} ../public/examples/convex_hull.png
:alt: Convex hull of a point set
:align: center
:width: 70%
```

## Minimum enclosing circle

The smallest circle that contains every point.

```python
import numpy as np
from cgeom import Circle
from cgeom.algorithms import MinimumCircle
from cgeom.visualization import plot_min_circle

points = [(0, 0), (1, 0), (0, 1), (1, 1), (0.5, 0.5)]

mc = MinimumCircle()
center, radius = mc.minimum_circle(points) # -> [0.5, 0.5], 0.70710678...

# the raw [[cx, cy], radius] result drops straight into a Circle primitive
circle = Circle(mc.minimum_circle(points))
circle.area # 1.5707...

plot_min_circle(mc, np.array(points))
```

```{image} ../public/examples/min_circle.png
:alt: Exact and heuristic minimum enclosing circle
:align: center
:width: 100%
```

## Delaunay triangulation

A triangulation that maximizes the minimum angle — no point lies inside any
triangle's circumcircle.

```python
from cgeom.algorithms import DelaunayTriangulation
from cgeom.visualization import plot_delaunay

points = [(0, 0), (4, 0), (4, 4), (0, 4), (2, 2)]

dt = DelaunayTriangulation(points)

dt.triangulate() # -> [[0, 1, 4], [0, 3, 4], [1, 2, 4], [2, 3, 4]]
len(dt.get_edges()) # -> 8
len(dt.get_circumcircles()) # -> 4

plot_delaunay(dt, show_circumcircles=True)
```

```{image} ../public/examples/delaunay.png
:alt: Delaunay triangulation with circumcircles
:align: center
:width: 70%
```

## Voronoi diagram

Partitions the plane into cells, one per site, containing everything closest to
that site — the dual of the Delaunay triangulation.

```python
import numpy as np
from cgeom.algorithms import VoronoiDiagram
from cgeom.visualization import plot_voronoi

points = np.loadtxt("examples/points1.txt") # 27 sites

voronoi = VoronoiDiagram(points)
cells = voronoi.build_voronoi_diagram() # one cell per site
len(cells) # -> 27

plot_voronoi(voronoi, cells)
```

```{image} ../public/examples/voronoi.png
:alt: Voronoi diagram of 27 sites
:align: center
:width: 70%
```

## Polygon triangulation

Decomposes a simple polygon into triangles by ear clipping. The polygon is
triangulated on construction; the diagonals are available immediately.

```python
from cgeom.algorithms import PolygonTriangulation
from cgeom.visualization import plot_triangulation

# a non-convex pentagon, vertices counter-clockwise
poly = [[0, 0], [4, 0], [4, 4], [2, 2], [0, 4]]

pt = PolygonTriangulation(poly)

pt.diagonals # the added diagonals, [[[x1, y1], [x2, y2]], ...]
pt.get_diag_vertexes() # -> [[4, 1], [1, 3]] (vertex-index pairs)

plot_triangulation(pt)
```

```{image} ../public/examples/triangulation.png
:alt: Ear-clipping triangulation of a non-convex polygon
:align: center
:width: 70%
```

## Segment intersection

Reports every pairwise crossing of a set of segments using the Bentley–Ottmann
sweep line, with a brute-force method for verification.

```python
from cgeom.algorithms import SegmentIntersection
from cgeom.visualization import plot_intersections

segments = [
[[0, 0], [4, 4]], # diagonal /
[[0, 4], [4, 0]], # diagonal \
[[0, 2], [4, 2]], # horizontal
[[2, 0], [2, 4]], # vertical
]

si = SegmentIntersection(segments)

si.find_intersections() # sweep line -> [[2.0, 2.0]]
si.find_intersections_brute_force() # cross-check -> [[2.0, 2.0]]
si.get_intersection_pairs() # (i, j, point) for each crossing pair

plot_intersections(si)
```

```{image} ../public/examples/intersection.png
:alt: Segment intersections found by the sweep line
:align: center
:width: 70%
```

See the [User guide](guide/algorithms) for the full method-by-method reference,
or the [API reference](api/index) for complete signatures. The figures above are
regenerated by `docs/generate_example_figures.py`.
104 changes: 104 additions & 0 deletions docs/generate_example_figures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Regenerate the figures embedded in ``docs/examples.md``.

Each visualization helper ends in ``plt.show()``; here we patch ``show`` to save
the current figure instead, so the rendered output matches the documented code.

Run from the repository root::

.venv/bin/python docs/generate_example_figures.py
"""

import os

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt # noqa: E402
import numpy as np # noqa: E402

from cgeom.algorithms import ( # noqa: E402
ConvexHull,
DelaunayTriangulation,
MinimumCircle,
PolygonTriangulation,
SegmentIntersection,
VoronoiDiagram,
)
from cgeom.visualization import ( # noqa: E402
plot_convex_hull,
plot_delaunay,
plot_intersections,
plot_min_circle,
plot_triangulation,
plot_voronoi,
)

OUT_DIR = "public/examples"
os.makedirs(OUT_DIR, exist_ok=True)

_saved = set()
_current = {"name": None}


def _save_show():
"""Stand-in for ``plt.show`` that saves the first figure per example."""
name = _current["name"]
if name and name not in _saved:
fig = plt.gcf()
fig.savefig(
os.path.join(OUT_DIR, f"{name}.png"),
dpi=150,
facecolor=fig.get_facecolor(),
bbox_inches="tight",
)
_saved.add(name)
plt.close("all")


plt.show = _save_show


def render(name, fn):
_current["name"] = name
fn()
print(f"saved {OUT_DIR}/{name}.png")


# --- Convex hull -----------------------------------------------------------
hull = ConvexHull(
[
(326, 237),
(373, 209),
(378, 265),
(443, 241),
(396, 231),
(416, 270),
(361, 335),
(324, 297),
]
)
render("convex_hull", lambda: plot_convex_hull(hull))

# --- Minimum enclosing circle ---------------------------------------------
mc_points = np.array([(0, 0), (1, 0), (0, 1), (1, 1), (0.5, 0.5)])
render("min_circle", lambda: plot_min_circle(MinimumCircle(), mc_points, show=True))

# --- Delaunay triangulation ------------------------------------------------
dt = DelaunayTriangulation([(0, 0), (4, 0), (4, 4), (0, 4), (2, 2)])
dt.triangulate()
render("delaunay", lambda: plot_delaunay(dt, show_circumcircles=True))

# --- Voronoi diagram -------------------------------------------------------
voronoi = VoronoiDiagram(np.loadtxt("examples/points1.txt"))
cells = voronoi.build_voronoi_diagram()
render("voronoi", lambda: plot_voronoi(voronoi, cells))

# --- Polygon triangulation -------------------------------------------------
pt = PolygonTriangulation([[0, 0], [4, 0], [4, 4], [2, 2], [0, 4]])
render("triangulation", lambda: plot_triangulation(pt))

# --- Segment intersection --------------------------------------------------
si = SegmentIntersection(
[[[0, 0], [4, 4]], [[0, 4], [4, 0]], [[0, 2], [4, 2]], [[2, 0], [2, 4]]]
)
render("intersection", lambda: plot_intersections(si))
9 changes: 6 additions & 3 deletions docs/guide/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,14 @@ si.get_intersection_pairs() # (i, j, point) tuples

## PolygonTriangulation

Triangulates a simple polygon.
Triangulates a simple polygon by ear clipping. The polygon is triangulated on
construction, so the diagonals are available immediately — there is no separate
`triangulate()` call.

```python
pt = PolygonTriangulation([[0, 0], [4, 0], [4, 4], [0, 4]])
pt.triangulate()
pt = PolygonTriangulation([[0, 0], [4, 0], [4, 4], [2, 2], [0, 4]])
pt.diagonals # the diagonals added, [[[x1, y1], [x2, y2]], ...]
pt.get_diag_vertexes() # the same diagonals as vertex-index pairs
```

## Input validation
Expand Down
Loading
Loading