Say I have this simple dotplot:
ggplot(mtcars, aes(hp)) +
geom_dotplot(binwidth = 10, stackdir = 'center')
And I want to label (some of) the points. This doesn't work:
ggplot(mtcars, aes(hp)) +
geom_dotplot(binwidth = 10, stackdir = 'center') +
geom_text(aes(label = rownames(mtcars)))
# Error: geom_text requires the following missing aesthetics: y
So how do I get access to the y values computed for the geom_dotplot, in so that I can place the labels at the correct location?
If I set y = 0 and use geom_text_repel I get:
ggplot(mtcars, aes(hp)) +
geom_dotplot(binwidth = 10, stackdir = 'center') +
geom_text_repel(aes(label = rownames(mtcars)), box.padding = unit(2, 'lines'), y = 0)
This is close to what I want, except all of the line segments are pointing to y = 0.
EDIT
I got this to work using a modification of the accepted answer which tries to infer the y-scaling amount from the device dimensions:
library(ggplot2)
library(ggrepel)
bw <- 10
p <- ggplot(mtcars, aes(hp)) +
geom_dotplot(binwidth = bw, stackdir = 'center')
built <- ggplot_build(p)
point.pos <- built$data[[1]]
# Order rows of mtcars by hp
idx <- order(mtcars$hp)
mtcars2 <- mtcars[idx,]
# Get the dimensions of the target device in pixels
size <- dev.size(units = 'px')
# Get the range of x and y domain values
extent <- with(built$layout$panel_params[[1]], abs(c(diff(x.range), diff(y.range))))
mtcars2$ytext <- (size[1] / size[2]) * (extent[2] / extent[1]) * point.pos$stackpos * bw
mtcars2$xtext <- point.pos$x
ggplot(mtcars2, aes(hp)) +
geom_dotplot(binwidth = bw, stackdir = 'center') +
geom_text_repel(
aes(xtext, ytext, label = rownames(mtcars2)),
box.padding = unit(.5 * size[1] * bw / extent[1], 'points'),
color = 'red'
)
Which produces
It's not perfect--the segments don't point to the exact centers of the dots because the aspect ratio of the entire image is not the same as the aspect ratio of just the panel, but it's pretty close.


