How do I label the dots of a geom_dotplot in ggplot2?

Viewed 3100

Say I have this simple dotplot:

ggplot(mtcars, aes(hp)) +
  geom_dotplot(binwidth = 10, stackdir = 'center')

enter image description here

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)

enter image description here

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

enter image description here

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.

1 Answers
Related