Tom Crockett
Tom Crockett

Reputation: 31619

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

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.

Upvotes: 5

Views: 5214

Answers (1)

Marco Sandri
Marco Sandri

Reputation: 24262

The code proposed below is not elegant.
It works after fine tuning of the scaling factor scale.factor and of the plot dimensions.
I hope that some ideas contained in my answer can be useful for the solution of your problem.

library(ggplot2)

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

# Get y-positions of points plotted by geom_dotplot
# Warning: these positions are not given
point.pos <- ggplot_build(p)$data[[1]]

# Order rows of mtcars by hp
idx <- order(mtcars$hp)
mtcars2 <- mtcars[idx,]

# scale.fact needs fine tuning 
# It is strictly connected to the dimensions of the plot
scale.fact <- 0.105
mtcars2$ytext <- point.pos$stackpos*scale.fact
mtcars2$xtext <- point.pos$x
lbls <- gsub(" ","\n",rownames(mtcars2))

png(file="myplot.png", width=4000, height=1400, res=300)
ggplot(mtcars2, aes(hp)) +
  geom_dotplot(binwidth = 10, stackdir = 'center', fill="#AAAAAA55") +
  geom_text(aes(label=lbls, x=xtext, y=ytext), size=2)
dev.off()

enter image description here

Upvotes: 2

Related Questions