Skip to content

Commit 93abce6

Browse files
authored
Merge pull request #13 from quant-sci/enhance-docs
feat: enhance docs
2 parents e687350 + 7fb16f4 commit 93abce6

12 files changed

Lines changed: 350 additions & 10 deletions

File tree

docs/_static/custom.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* Rounded borders on the plots/figures rendered in the page content.
2+
Scoped to the main article so the sidebar logo is left untouched. */
3+
.content img {
4+
border-radius: 12px;
5+
border: 1px solid var(--color-background-border);
6+
}

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
html_theme = "furo"
8282
html_title = "compute-geometry"
8383
html_static_path = ["_static"]
84+
html_css_files = ["custom.css"]
8485
html_logo = "../public/logo.svg"
8586
html_favicon = "../public/logo.svg"
8687

docs/examples.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Examples
2+
3+
One complete, self-contained example for each algorithm in the library. Every
4+
snippet runs as-is: it builds the input, runs the algorithm, inspects the
5+
result, and draws it with the matching visualization helper. The `# ->` comments
6+
show the actual output, and each example is followed by the figure it produces.
7+
8+
## Convex hull
9+
10+
The smallest convex polygon enclosing a set of points (Gift Wrapping / Jarvis
11+
march).
12+
13+
```python
14+
from cgeom.algorithms import ConvexHull
15+
from cgeom.visualization import plot_convex_hull
16+
17+
points = [(326, 237), (373, 209), (378, 265), (443, 241),
18+
(396, 231), (416, 270), (361, 335), (324, 297)]
19+
20+
hull = ConvexHull(points)
21+
22+
hull.convex_hull() # ordered hull vertices, [[x, y], ...]
23+
hull.get_indexes() # -> [1, 3, 6, 7, 0] (indices into `points`)
24+
25+
plot_convex_hull(hull)
26+
```
27+
28+
```{image} ../public/examples/convex_hull.png
29+
:alt: Convex hull of a point set
30+
:align: center
31+
:width: 70%
32+
```
33+
34+
## Minimum enclosing circle
35+
36+
The smallest circle that contains every point.
37+
38+
```python
39+
import numpy as np
40+
from cgeom import Circle
41+
from cgeom.algorithms import MinimumCircle
42+
from cgeom.visualization import plot_min_circle
43+
44+
points = [(0, 0), (1, 0), (0, 1), (1, 1), (0.5, 0.5)]
45+
46+
mc = MinimumCircle()
47+
center, radius = mc.minimum_circle(points) # -> [0.5, 0.5], 0.70710678...
48+
49+
# the raw [[cx, cy], radius] result drops straight into a Circle primitive
50+
circle = Circle(mc.minimum_circle(points))
51+
circle.area # 1.5707...
52+
53+
plot_min_circle(mc, np.array(points))
54+
```
55+
56+
```{image} ../public/examples/min_circle.png
57+
:alt: Exact and heuristic minimum enclosing circle
58+
:align: center
59+
:width: 100%
60+
```
61+
62+
## Delaunay triangulation
63+
64+
A triangulation that maximizes the minimum angle — no point lies inside any
65+
triangle's circumcircle.
66+
67+
```python
68+
from cgeom.algorithms import DelaunayTriangulation
69+
from cgeom.visualization import plot_delaunay
70+
71+
points = [(0, 0), (4, 0), (4, 4), (0, 4), (2, 2)]
72+
73+
dt = DelaunayTriangulation(points)
74+
75+
dt.triangulate() # -> [[0, 1, 4], [0, 3, 4], [1, 2, 4], [2, 3, 4]]
76+
len(dt.get_edges()) # -> 8
77+
len(dt.get_circumcircles()) # -> 4
78+
79+
plot_delaunay(dt, show_circumcircles=True)
80+
```
81+
82+
```{image} ../public/examples/delaunay.png
83+
:alt: Delaunay triangulation with circumcircles
84+
:align: center
85+
:width: 70%
86+
```
87+
88+
## Voronoi diagram
89+
90+
Partitions the plane into cells, one per site, containing everything closest to
91+
that site — the dual of the Delaunay triangulation.
92+
93+
```python
94+
import numpy as np
95+
from cgeom.algorithms import VoronoiDiagram
96+
from cgeom.visualization import plot_voronoi
97+
98+
points = np.loadtxt("examples/points1.txt") # 27 sites
99+
100+
voronoi = VoronoiDiagram(points)
101+
cells = voronoi.build_voronoi_diagram() # one cell per site
102+
len(cells) # -> 27
103+
104+
plot_voronoi(voronoi, cells)
105+
```
106+
107+
```{image} ../public/examples/voronoi.png
108+
:alt: Voronoi diagram of 27 sites
109+
:align: center
110+
:width: 70%
111+
```
112+
113+
## Polygon triangulation
114+
115+
Decomposes a simple polygon into triangles by ear clipping. The polygon is
116+
triangulated on construction; the diagonals are available immediately.
117+
118+
```python
119+
from cgeom.algorithms import PolygonTriangulation
120+
from cgeom.visualization import plot_triangulation
121+
122+
# a non-convex pentagon, vertices counter-clockwise
123+
poly = [[0, 0], [4, 0], [4, 4], [2, 2], [0, 4]]
124+
125+
pt = PolygonTriangulation(poly)
126+
127+
pt.diagonals # the added diagonals, [[[x1, y1], [x2, y2]], ...]
128+
pt.get_diag_vertexes() # -> [[4, 1], [1, 3]] (vertex-index pairs)
129+
130+
plot_triangulation(pt)
131+
```
132+
133+
```{image} ../public/examples/triangulation.png
134+
:alt: Ear-clipping triangulation of a non-convex polygon
135+
:align: center
136+
:width: 70%
137+
```
138+
139+
## Segment intersection
140+
141+
Reports every pairwise crossing of a set of segments using the Bentley–Ottmann
142+
sweep line, with a brute-force method for verification.
143+
144+
```python
145+
from cgeom.algorithms import SegmentIntersection
146+
from cgeom.visualization import plot_intersections
147+
148+
segments = [
149+
[[0, 0], [4, 4]], # diagonal /
150+
[[0, 4], [4, 0]], # diagonal \
151+
[[0, 2], [4, 2]], # horizontal
152+
[[2, 0], [2, 4]], # vertical
153+
]
154+
155+
si = SegmentIntersection(segments)
156+
157+
si.find_intersections() # sweep line -> [[2.0, 2.0]]
158+
si.find_intersections_brute_force() # cross-check -> [[2.0, 2.0]]
159+
si.get_intersection_pairs() # (i, j, point) for each crossing pair
160+
161+
plot_intersections(si)
162+
```
163+
164+
```{image} ../public/examples/intersection.png
165+
:alt: Segment intersections found by the sweep line
166+
:align: center
167+
:width: 70%
168+
```
169+
170+
See the [User guide](guide/algorithms) for the full method-by-method reference,
171+
or the [API reference](api/index) for complete signatures. The figures above are
172+
regenerated by `docs/generate_example_figures.py`.

docs/generate_example_figures.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Regenerate the figures embedded in ``docs/examples.md``.
2+
3+
Each visualization helper ends in ``plt.show()``; here we patch ``show`` to save
4+
the current figure instead, so the rendered output matches the documented code.
5+
6+
Run from the repository root::
7+
8+
.venv/bin/python docs/generate_example_figures.py
9+
"""
10+
11+
import os
12+
13+
import matplotlib
14+
15+
matplotlib.use("Agg")
16+
import matplotlib.pyplot as plt # noqa: E402
17+
import numpy as np # noqa: E402
18+
19+
from cgeom.algorithms import ( # noqa: E402
20+
ConvexHull,
21+
DelaunayTriangulation,
22+
MinimumCircle,
23+
PolygonTriangulation,
24+
SegmentIntersection,
25+
VoronoiDiagram,
26+
)
27+
from cgeom.visualization import ( # noqa: E402
28+
plot_convex_hull,
29+
plot_delaunay,
30+
plot_intersections,
31+
plot_min_circle,
32+
plot_triangulation,
33+
plot_voronoi,
34+
)
35+
36+
OUT_DIR = "public/examples"
37+
os.makedirs(OUT_DIR, exist_ok=True)
38+
39+
_saved = set()
40+
_current = {"name": None}
41+
42+
43+
def _save_show():
44+
"""Stand-in for ``plt.show`` that saves the first figure per example."""
45+
name = _current["name"]
46+
if name and name not in _saved:
47+
fig = plt.gcf()
48+
fig.savefig(
49+
os.path.join(OUT_DIR, f"{name}.png"),
50+
dpi=150,
51+
facecolor=fig.get_facecolor(),
52+
bbox_inches="tight",
53+
)
54+
_saved.add(name)
55+
plt.close("all")
56+
57+
58+
plt.show = _save_show
59+
60+
61+
def render(name, fn):
62+
_current["name"] = name
63+
fn()
64+
print(f"saved {OUT_DIR}/{name}.png")
65+
66+
67+
# --- Convex hull -----------------------------------------------------------
68+
hull = ConvexHull(
69+
[
70+
(326, 237),
71+
(373, 209),
72+
(378, 265),
73+
(443, 241),
74+
(396, 231),
75+
(416, 270),
76+
(361, 335),
77+
(324, 297),
78+
]
79+
)
80+
render("convex_hull", lambda: plot_convex_hull(hull))
81+
82+
# --- Minimum enclosing circle ---------------------------------------------
83+
mc_points = np.array([(0, 0), (1, 0), (0, 1), (1, 1), (0.5, 0.5)])
84+
render("min_circle", lambda: plot_min_circle(MinimumCircle(), mc_points, show=True))
85+
86+
# --- Delaunay triangulation ------------------------------------------------
87+
dt = DelaunayTriangulation([(0, 0), (4, 0), (4, 4), (0, 4), (2, 2)])
88+
dt.triangulate()
89+
render("delaunay", lambda: plot_delaunay(dt, show_circumcircles=True))
90+
91+
# --- Voronoi diagram -------------------------------------------------------
92+
voronoi = VoronoiDiagram(np.loadtxt("examples/points1.txt"))
93+
cells = voronoi.build_voronoi_diagram()
94+
render("voronoi", lambda: plot_voronoi(voronoi, cells))
95+
96+
# --- Polygon triangulation -------------------------------------------------
97+
pt = PolygonTriangulation([[0, 0], [4, 0], [4, 4], [2, 2], [0, 4]])
98+
render("triangulation", lambda: plot_triangulation(pt))
99+
100+
# --- Segment intersection --------------------------------------------------
101+
si = SegmentIntersection(
102+
[[[0, 0], [4, 4]], [[0, 4], [4, 0]], [[0, 2], [4, 2]], [[2, 0], [2, 4]]]
103+
)
104+
render("intersection", lambda: plot_intersections(si))

docs/guide/algorithms.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,14 @@ si.get_intersection_pairs() # (i, j, point) tuples
8585

8686
## PolygonTriangulation
8787

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

9092
```python
91-
pt = PolygonTriangulation([[0, 0], [4, 0], [4, 4], [0, 4]])
92-
pt.triangulate()
93+
pt = PolygonTriangulation([[0, 0], [4, 0], [4, 4], [2, 2], [0, 4]])
94+
pt.diagonals # the diagonals added, [[[x1, y1], [x2, y2]], ...]
95+
pt.get_diag_vertexes() # the same diagonals as vertex-index pairs
9396
```
9497

9598
## Input validation

0 commit comments

Comments
 (0)