Customize the Cartogram Boundary¶
Two independent boundary settings control the shape and behavior of the outer boundary:
boundary(passed tocreate_voronoi_cartogram) — the outer clipping shape: union, bounding box, convex hull, circle, or a custom shapely geometry.boundary(passed to the backend) — how the boundary and the generators near the boundary behave: fixed (default), adhesive, or elastic.
See also: Tutorial · Choose the Right Backend · Gallery: Elastic Boundary · Gallery: Custom Boundary
import matplotlib.pyplot as plt
import carto_flow.data as examples
import carto_flow.voronoi_cartogram as vor
us_states = examples.load_us_census(population=True)
Boundary Shape¶
The boundary argument to create_voronoi_cartogram controls the outer clipping shape. Built-in options are "union" (default), "bbox", "convex_hull", and "circle". You can also pass any shapely Geometry, as long as the geometry fully contains the input region centroids.
opts = vor.VoronoiOptions(n_iter=30)
shapes = {"union": "union", "bbox": "bbox", "convex_hull": "convex_hull", "circle": "circle"}
results_shape = {
name: vor.create_voronoi_cartogram(us_states, boundary=spec, options=opts) for name, spec in shapes.items()
}
fig, axes = plt.subplots(2, 2, figsize=(14, 8))
for ax, (name, res) in zip(axes.flat, results_shape.items(), strict=False):
res.plot(column="Population (Millions)", ax=ax, cmap="RdYlGn_r", legend=False, show_edges=True)
ax.set(title=f'boundary="{name}"')
ax.axis("off")
plt.suptitle("Boundary shape options", y=1.01)
plt.tight_layout();
result_fixed = vor.create_voronoi_cartogram(
us_states,
weights="Population (Millions)",
backend=vor.RasterBackend(boundary=None),
options=opts,
)
AdhesiveBoundary¶
Voronoi cell generators are first moved by Lloyd relaxation. Next, generators that belong to original geometries that touch the outer boundary are pulled toward the boundary. This keeps peripheral regions from drifting toward the center. The strength parameter determines how strong the pull is: strength=1.0 snaps generators fully to the boundary line; lower values produce a partial pull. Finally, a hard-clip constraint is applied to make sure generators stay inside the boundary. The boundary itself does not move.
result_adhesive = vor.create_voronoi_cartogram(
us_states,
weights="Population (Millions)",
backend=vor.RasterBackend(boundary=vor.AdhesiveBoundary(strength=0.3)),
options=opts,
)
ElasticBoundary¶
Borrowing from how flow cartograms are constructed, ElasticBoundary deforms the outer boundary vertices on the basis of an area-pressure field. Voronoi cell generators are co-advected with the elastic boundary. The strength parameter determines the size of the advection step (as a fraction of the area-derived length scale computed as $\sqrt{\frac{A_{total}}{N}}$, $A_{total}$ is total area and $N$ is the number of cells). Next, generators are additionally moved by standard Lloyd relaxation, and a hard-clip constraint is applied to make sure generators stay inside the (newly deformed) boundary.
Two additional parameters fine-tune the behavior:
min_boundary_points— minimum number of vertices distributed along the boundary for elastic deformation tracking. Simple shapes such as"bbox"or"convex_hull"have only a few corners; set this to roughlybackend.resolutionfor smooth deformation. Leave asNone(default) for complex union boundaries, which already have many vertices.adhesion_strength— snap strength in[0, 1]for boundary-touching centroids. When> 0, centroids whose original geometry touches the outer boundary are pulled toward the current deformed boundary after each elastic step — combining shape change and centroid adhesion in one pass. Equivalent to usingAdhesiveBoundaryalongside elastic deformation, but with the snap target tracking the evolving boundary shape.
result_elastic = vor.create_voronoi_cartogram(
us_states,
weights="Population (Millions)",
backend=vor.RasterBackend(boundary=vor.ElasticBoundary(strength=0.05)),
options=opts,
)
fig, axes = plt.subplots(1, 3, figsize=(16, 4))
for ax, res, title in zip(
axes,
[result_fixed, result_adhesive, result_elastic],
["Fixed (None)", "AdhesiveBoundary", "ElasticBoundary"],
strict=False,
):
res.plot(
column="Population (Millions)",
ax=ax,
cmap="RdYlGn_r", # vmin=-70, vmax=70,
legend=False,
)
ax.set(title=title)
ax.axis("off")
plt.suptitle("Boundary behavior — Population (Millions)", y=1.01)
plt.tight_layout();
Summary¶
| Boundary type | Use when |
|---|---|
None (fixed) |
Default; outer shape unchanged |
AdhesiveBoundary |
Peripheral regions need to remain close to the boundary |
ElasticBoundary |
Provide additional degree of freedom that may improve convergence and reduce topological distortions (RasterBackend only) |
See Also¶
- Tutorial
- Choose the Right Backend — ElasticBoundary requires RasterBackend
- Gallery: Elastic Boundary
- Gallery: Custom Boundary