I'm trying to remove the outer polygons in a Voronoi diagram. The main plot looks like this:
require(ggplot2)
require(ggvoronoi)
set.seed(2023)
N <- 80
x <- runif(N)
y <- runif(N)
df <- data.frame(x, y)
df$dist <- rnorm(N)
ggplot(df, aes(x, y, fill = dist)) +
geom_voronoi() +
stat_voronoi(geom = "path") +
theme_void()
Something that's close to what I would like occurs if I set a limit on the axes. However, there's still fill in some of the outer polygons:
ggplot(df, aes(x, y, fill = dist)) +
geom_voronoi() +
stat_voronoi(geom = "path") +
theme_void() +
xlim(0.1, 0.9) +
ylim(0.1, 0.9)
[
What is the best approach to isolate/identify the outer-most polygons around the edge of the figure and make them transparent or a uniform colour?
{sf}
-based approach (GEOS implementation of st_voronoi
) might look something like this:
library(sf)
#> Linking to GEOS 3.11.2, GDAL 3.6.2, PROJ 9.2.0; sf_use_s2() is TRUE
library(ggplot2)
set.seed(2023)
N <- 80
x <- runif(N)
y <- runif(N)
df <- data.frame(x, y)
df$dist <- rnorm(N)
# to sf object
points_sf <- st_as_sf(df, coords = c("x", "y"))
# for st_voronoi we need to go though st_union to create a MULTIPOINT,
# as `dist` will be lost, we'll do a spatial join as a final step
# to add that attribute back;
# st_voronoi returns GEOMETRYCOLLECTION,
# st_collection_extract() to extract POLYGONs and st_sf() to convert sfc to sf
voronoi_sf <-
points_sf |>
st_union() |>
st_voronoi() |>
st_collection_extract() |>
st_sf(geometry = _) |>
st_join(points_sf) |>
# crop to the extent of points_sf to align with other implementations
st_crop(points_sf)
#> Warning: attribute variables are assumed to be spatially constant throughout
#> all geometries
# boundary polygon (currently matches points_sf bbox)
boundary_sf <- st_union(voronoi_sf) |> st_boundary()
# use st_filter with st_disjoint predicate to keep only polygons that
# have no common points with the boundary of union:
st_filter(voronoi_sf, boundary_sf, .predicate = st_disjoint) |>
ggplot(aes(fill = dist)) +
geom_sf() +
theme_void()
# or flag outer polygons with st_disjoint(),
# sparse = FALSE: returns dense logical matrix instead of sparse index list
# [,1]: selects 1st matrix column
voronoi_sf$inner <- st_disjoint(voronoi_sf, boundary_sf, sparse = FALSE)[,1]
voronoi_sf
#> Simple feature collection with 80 features and 2 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 0.03039173 ymin: 0.01943989 xmax: 0.9992733 ymax: 0.9911794
#> CRS: NA
#> First 10 features:
#> dist geometry inner
#> 1 0.1413685 POLYGON ((0.089608 0.837129... FALSE
#> 2 0.9269828 POLYGON ((0.09466375 0.6231... FALSE
#> 3 0.1516984 POLYGON ((0.05469083 0.3648... FALSE
#> 4 0.3005414 POLYGON ((0.05469083 0.3648... TRUE
#> 5 -0.1206981 POLYGON ((0.08540603 0.4952... FALSE
#> 6 1.3876944 POLYGON ((0.1273276 0.32198... FALSE
#> 7 2.7046756 POLYGON ((0.2191197 0.09137... FALSE
#> 8 0.2651664 POLYGON ((0.6434843 0.04838... FALSE
#> 9 0.9245641 POLYGON ((0.04950097 0.1988... FALSE
#> 10 -0.3157660 POLYGON ((0.1273276 0.32198... TRUE
ggplot() +
geom_sf(data = voronoi_sf, aes(fill = ifelse(inner, dist, NA)), color = "grey10") +
geom_sf(data = points_sf, aes(fill = dist), size = 2, show.legend = FALSE, shape = 21) +
scale_fill_continuous(na.value = "grey80", name = "dist") +
theme_void()
Added st_crop(points_sf)
to deal with a larger-than-optimal envelope size of st_voronoi()
; this should probably be reconsidered depending on the actual dataset and application.
First revision - https://stackoverflow.com/revisions/77569621/1