Search code examples
rggplot2r-sfvoronoi

Transparent or uniform colour for outer-most Voronoi polygons in a figure


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()

main pic

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)

[limited pic

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?


Solution

  • {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