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")
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
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)
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 ggstar
as planned:
point_data |>
ggplot(aes(x = x, y = y)) +
ggstar::geom_star(aes(fill = y), starshape = 6, size = 6) +
scale_fill_viridis_c()