How to plot tiles obtained from deldir tesselation using ggplot in R?

226 Views Asked by At

Now that ggvoronoi package is not working anymore in R, I am using deldir to plot voronoi cells clipped by the convex hull for a bunch of points in the following way:

library(deldir)
set.seed(1)
x <- runif(50)
y <- runif(50)

tesselation <- deldir(x, y)
tiles <- tile.list(tesselation)

s <- seq(0, 2 * pi, length.out = 3000)
circle <- list(x = 0.5 * (1 + cos(s)),
               y = 0.5 * (1 + sin(s)))

plot(tiles, pch = 19,
     col.pts = "white",
     border = "white",
     fillcol = hcl.colors(50, "viridis"),
     clipp = circle)

enter image description here

I want to create a gradient plot for the tiles according to the area of the tiles. Or in some way to import tiles in ggplot and then use the functionalities like scale_fill_gradientn to achieve my goal. Can you please help me?

1

There are 1 best solutions below

2
On

Thanks for your nice reproducible example.

As far as I know, ggplot2 doesn't support clipping polygons directly. There are some workarounds: drawing a big polygon on top of them to obscure the parts you don't want to see, or clipping the polygons before you draw them using some other package, or working "down a level" with the grid package to modify the final plot. This question has answers that explore a couple of those methods. Here, I'm just going to concentrate on drawing the Voronoi cells and not attempt the clipping.

The main issue is extracting the cells and their color from the tiles object. It's a list, with one entry per cell. If you look at str(tiles[[1]]) you'll see something like this:

List of 6
 $ ptNum: int 1
 $ pt   : Named num [1:2] 0.266 0.478
  ..- attr(*, "names")= chr [1:2] "x" "y"
 $ x    : num [1:8] 0.386 0.365 0.317 0.128 0.141 ...
 $ y    : num [1:8] 0.638 0.656 0.67 0.642 0.509 ...
 $ bp   : logi [1:8] FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ area : num 0.0402
 - attr(*, "ncomp")= num 1

The boundary of the cell is in the polygon defined by the x and y components.

So here's one way to do what you want (except for the clipping):

library(ggplot2)
library(deldir)

set.seed(1)
x <- runif(50)
y <- runif(50)

tesselation <- deldir(x, y)
tiles <- tile.list(tesselation)

s <- seq(0, 2 * pi, length.out = 3000)
circle <- list(x = 0.5 * (1 + cos(s)),
               y = 0.5 * (1 + sin(s)))

points <- data.frame(x=x, y=y)
colors <- hcl.colors(50, "viridis")

g <- ggplot(points, aes(x = x, y = y))
for (i in 1:50)
  g <- g + geom_polygon(data = data.frame(x = tiles[[i]]$x,
                                          y = tiles[[i]]$y,
                                          density = 1/tiles[[i]]$area), 
                        aes(fill = density))
g <- g + geom_point(col = "white")
g

Created on 2022-08-22 with reprex v2.0.2