Home > database >  Add unique letters per facet with scales=free
Add unique letters per facet with scales=free

Time:10-19

Similar to this question, I'm trying to add a, b, c, ... to a grid of facets so they can be referenced in individually elsewhere. With scales = 'fixed', this is relatively easy, as you can even hardcode the x,y coordinates for a geom_text label. However, with scales = 'free', it's a pain to compute all the x,y coordinates for each facet so they labels end up in the same location, visually. Can this be done automatically?

CodePudding user response:

A pure vanilla ggplot2 solution would be to use Inf, -Inf to snap text to the limits of each panel. To automatically get the labels you can use after_stat() to grab the PANEL internal variable. If you plot it as a label, you can have control over the offset from the panel edges by hiding the label itself and setting label.padding.

library(ggplot2)

x = data.frame(a=c('a','b','a','b'),b=c(1,1,2,2),v=runif(8))
ggplot(x,aes(x=v,y=v))  
  geom_point()  
  facet_grid('a~b',scales='free')  
  geom_label(
    aes(x = -Inf, y = Inf, label = after_stat(ifelse(
      duplicated(PANEL), "", letters[as.numeric(PANEL)]
    ))),
    vjust = 1, hjust = 0, 
    fill = NA, label.size = 0, # Don't show box
    label.padding = unit(5, "mm") # Control margins to panel bounds
  )

If that spacing mechanism seems to finnicky to you, you can use the ggpp::geom_text_npc() function to directly set relative coordinates for the text labels.

x = data.frame(a=c('a','b','a','b'),b=c(1,1,2,2),v=runif(8))
ggplot(x,aes(x=v,y=v))  
  geom_point()  
  facet_grid('a~b',scales='free')  
  ggpp::geom_text_npc(
    aes(npcx = 0.05, npcy = 0.95,
        label = after_stat(
          ifelse(duplicated(PANEL), "", letters[as.numeric(PANEL)])
        ))
  )

Created on 2022-10-18 by the reprex package (v2.0.1)

CodePudding user response:

Here is a solution with letters as default, but allowing other labels too, and ... is also passed to geom_text.

library(ggplot2)

# main solution
add.letters = function(g,px,py,lab=NULL,...){
  data = ggplot_build(g)$layout$layout
  if (is.null(lab)){ data$lab = letters[1:nrow(data)] } else { data$lab = lab }
  p.range = function(r,p){ r[1]   (r[2]-r[1])*p }
  data = do.call(rbind,lapply(1:nrow(data),function(i){
    ls = layer_scales(g,data[i,]$ROW,data[i,]$COL)
    data.i = cbind(data[i,],
      x = p.range(ls$x$range$range,px),
      y = p.range(ls$y$range$range,py))
  }))
  g = g   geom_text(data=data,aes(label=lab,x=x,y=y),...)
}

# mwe
x = data.frame(a=c('a','b','a','b'),b=c(1,1,2,2),v=runif(8))
g = ggplot(x,aes(x=v,y=v))  
  geom_point()  
  facet_grid('a~b',scales='free')
g = add.letters(g,.05,.95,lab=c('a','bb','ccc','dddd'),hjust='left')
print(g)
  • Related