Ignacio
Ignacio

Reputation: 7948

add point and text to ggplot annotation_grob?

I would like to add some more annotations to this graph i generated in my previous question.

set.seed(40816)
library(ggplot2)
library(grid)
df.plot <- data.frame(x = rnorm(100, 0, 1))

strainerGrob <- function(pos=unit(4,"mm"), gp=gpar(lty=2, lwd=2))
segmentsGrob(0, unit(1,"npc") - pos, 1, unit(1,"npc") - pos, gp=gp)
ggplot(df.plot, aes(x = x)) + geom_density() +
  annotation_custom(strainerGrob(), xmin = -1, xmax = 1, ymin=-Inf, ymax=0)

enter image description here

I would like to add -1 to the left of the segment, 1 to the right, a dot at 0 and 0 above the dot. All this while not having to use absolute distance for y. Is this possible? Right now I can do it hard coding y

ggplot(df.plot, aes(x = x)) + geom_density() +
  annotation_custom(strainerGrob(), xmin = -1, xmax = 1, ymin=-Inf, ymax=0) +
  geom_point(aes(x=0, y=-0.01)) +
  annotate("text", x = -1, y = -0.01, label = -1, hjust = 1.5) +
  annotate("text", x = 1, y = -0.01, label = 1, hjust = -1) +
  annotate("text", x = 0, y = 0, label = 0, vjust = 2.75)

enter image description here

But if I change the data the point and other annotations end up in the wrong place.

df.plot <- data.frame(x = rnorm(100, 0, 4))
ggplot(df.plot, aes(x = x)) + geom_density() +
  annotation_custom(strainerGrob(), xmin = -1, xmax = 1, ymin=-Inf, ymax=0) +
  geom_point(aes(x=0, y=-0.01)) +
  annotate("text", x = -1, y = -0.01, label = -1, hjust = 1.5) +
  annotate("text", x = 1, y = -0.01, label = 1, hjust = -1) +
  annotate("text", x = 0, y = 0, label = 0, vjust = 2.75)

enter image description here

Upvotes: 0

Views: 613

Answers (1)

baptiste
baptiste

Reputation: 77116

the grob can be a gTree with multiple children, e.g

enter image description here

set.seed(40816)
library(ggplot2)
df.plot <- data.frame(x = rnorm(100, 0, 1))

strainerGrob <- function(range = c(-1,2), midpoint=0.35, 
                         vpos=unit(5,"mm"), 
                         pad = unit(1.5,"mm"), 
                         gp=gpar(lty=2, lwd=2)){

  labels <- as.character(c(range[1], midpoint, range[2]))
  xpos <- c(0, scales::rescale(midpoint, from=range, to=c(0,1)), 1)
  sg <- segmentsGrob(0, unit(1,"npc") - vpos, 1, unit(1,"npc") - vpos, gp=gp)
  tg <- textGrob(labels, x = unit(xpos, "npc") + c(-1,0,1)*pad,
                 hjust = c(1,0.5,0),
                 vjust=c(0.5,0,0.5), y=unit(1,"npc") - vpos + c(0,1,0)*pad)
  pg <- pointsGrob(x=xpos[2], y=unit(1,"npc") - vpos, pch = 19, gp = gpar(cex=0.5))

  grobTree(sg, pg, tg)
}


# wrapper to ensure that both geom and grob are in sync with x values
custom_range <- function(range = c(-1,2), midpoint=0.35, ...){
  sg <- strainerGrob(range=range, midpoint=midpoint, ...)
  annotation_custom(sg, xmin = range[1], xmax = range[2], ymin=-Inf, ymax=0)
}

ggplot(df.plot, aes(x = x)) + geom_density() +
  custom_range(c(-1, 2), 0.35) +
  expand_limits(y=-0.1)

If it's something you're going to use in a variety of graphs, e.g. with facets, or at multiple locations, I would recommend you write a custom geom instead. For a one-off, annotation_custom is probably OK.

Upvotes: 1

Related Questions