rggplot2ggdist

Extract the coordinates of a rendered dot plot


Say I have a variable and create a dot plot of the distribution:

library(ggplot2)
library(ggdist)

data.frame(x = rchisq(500, df = 10)) |> 
  ggplot(aes(x = x)) +
  geom_dots(layout = "hex")

Plot rendered by included code showing dots in the shape of a chi-square distribution

The points are automatically arranged along the x- and y-axes. Is is possible to extract the x and y coordinates for each point, after they have been arranged?

I have tried using ggplot_build(), but when I extract the data, the value of y is always 0.

my_plot <- data.frame(x = rchisq(500, df = 10)) |> 
  ggplot(aes(x = x)) +
  geom_dots(layout = "hex")

head(ggplot_build(my_plot)$data[[1]])
#>           x PANEL group flipped_aes y datatype height ymin ymax thickness
#> 1  8.712773     1    -1        TRUE 0     slab      1    0    1         1
#> 2  4.223199     1    -1        TRUE 0     slab      1    0    1         1
#> 3  7.535543     1    -1        TRUE 0     slab      1    0    1         1
#> 4  9.253599     1    -1        TRUE 0     slab      1    0    1         1
#> 5 20.435195     1    -1        TRUE 0     slab      1    0    1         1
#> 6  9.940869     1    -1        TRUE 0     slab      1    0    1         1
#>   family     side scale
#> 1        topright   0.9
#> 2        topright   0.9
#> 3        topright   0.9
#> 4        topright   0.9
#> 5        topright   0.9
#> 6        topright   0.9

Created on 2025-06-17 with reprex v2.1.1


Solution

  • Stefan's answer may be all you need, but I thought it would be worthwhile posting an answer to the question as asked, since someone searching for a method to extract point data from a geom_dot for reasons other than drawing hexagons is likely to come across this Q&A.

    It's quite tricky to extract the dot locations, because they are recalculated every time the plotting window is resized. This means that they have to be drawn by a special grob with its own makeContent method - this is where the actual dot arrangement must be taking place.

    The following function will take a ggplot and extract the dot positions by hijacking the makeContent method:

    extract_dot_locations <- function(p) {
      p2 <- p |> ggplot2::ggplot_build() |> ggplot2::ggplot_gtable()
      
      pg <- p2$grobs[[6]]$children[[3]]$children[[1]]
      
      xvals <- numeric()
      yvals <- numeric()
      pg$make_points_grob <- function (x, y, pch, col, fill, fontfamily, 
                                       fontsize, lwd, lty,  ...) {
        xvals <<- x
        yvals <<- y
        grid::pointsGrob(x = x, y = y, pch = pch, 
                   gp = grid::gpar(col = col, 
                             fill = fill, fontfamily = fontfamily, fontsize = fontsize, 
                             lwd = lwd, lty = lty))
      }
      
      p2$grobs[[6]]$children[[3]]$children[[1]] <- pg
      grid::grid.newpage()
      grid::grid.draw(p2)
      data.frame(x = xvals, y = yvals)
    }
    

    Now we can do:

    library(ggplot2)
    library(ggdist)
    
    set.seed(1)
    
    p <- data.frame(x = rchisq(500, df = 10)) |> 
      ggplot(aes(x = x)) +
      geom_dots(layout = "hex") 
    
    point_data <- extract_dot_locations(p)
    

    enter image description here

    And we have our point data frame:

     head(point_data)
    #>            x          y
    #> 1 0.04939082 0.05674432
    #> 2 0.05750770 0.07932387
    #> 3 0.06916483 0.05674432
    #> 4 0.06916483 0.10190342
    #> 5 0.07728171 0.07932387
    #> 6 0.09077930 0.05674432
    

    So you can use the hexagons from ggstaras planned:

    point_data |> 
      ggplot(aes(x = x, y = y)) +
      ggstar::geom_star(aes(fill = y), starshape = 6, size = 6) +
      scale_fill_viridis_c()
    

    enter image description here