1

I would like to place each x-axis text/label based on another field. Is there a native way in ggplot2 to achieve this? Presently I am doing it through geom_text. Here are my data and the plot.I have two issues with this approach -

  1. Labels are falling inside the plot area
  2. For a facet the labels should only appear at the bottom-most subplots as below alt text

not in all subplots as is the case below (my plot). (The above image was taken from here)

library(ggplot2)
library(magrittr)    

mydata = data.frame(expand.grid(Tag = c('A','B','C'),
  Year = 2010:2011,PNo = paste0("X-",1:4)),Value = round(runif(24,1,20)))
mydata$dist = ifelse(mydata$Tag == 'A',0,ifelse(mydata$Tag=='B',2,7))

mydata %>% ggplot(aes(x = dist,y = Value,fill = factor(Year))) +
  geom_bar(stat='summary',position = 'dodge',fun.y='mean',width=1) +
  facet_wrap(~PNo,ncol=2) +
  theme(axis.text.x = element_blank(),axis.ticks.x = element_blank()) +
  geom_text(aes(x = dist,label = Tag),color = 'black',size=4,angle = 0,show.legend = F)

I would like to place Tag labels based on dist. enter image description here

Community
  • 1
  • 1
Stat-R
  • 5,040
  • 8
  • 42
  • 68

2 Answers2

1

I notice that you have accepted an answer elsewhere, and that you have answered you own question here. But they don't quite answer your original question. In particular, the labels are still inside the plot panel. I offer two possibilities, but neither being straightforward.

The first uses a version of annotation_custom. The default annotation_custom draws the annotation in all panels. But with a small alteration (taken from here), it can be made to draw annotations in selected panels - for your plot, the lower two panels.

 library(ggplot2)
library(magrittr)    
mydata = data.frame(expand.grid(Tag = c('A', 'B', 'C'), 
             Year = 2010:2011, PNo =  paste0("X-", 1:4)), Value = round(runif(24,1,20)))
mydata$dist = ifelse(mydata$Tag == 'A', 0, ifelse(mydata$Tag == 'B', 2, 7))

# The bar plot. Note extra margin above x-axis title.
# This gives space for the annotations between the panel and the title.
p1 = mydata %>% ggplot() +
  geom_bar(aes(x = dist, y = Value, fill = factor(Year)),
             width = 1, stat = 'identity', position = "dodge") + 
  facet_wrap(~PNo, ncol = 2) +
  theme(axis.text.x = element_blank(), 
        axis.ticks.x = element_blank(),
        axis.title.x = element_text(margin = margin(t = 2, unit = "lines")))   

# Baptiste's modification to annotation_custom
annotation_custom2 = 
function (grob, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf, data) {
  layer(data = data, stat = StatIdentity, position = PositionIdentity, 
        geom = ggplot2:::GeomCustomAnn,
        inherit.aes = TRUE, params = list(grob = grob, 
                                          xmin = xmin, xmax = xmax, 
                                          ymin = ymin, ymax = ymax))
}

# The plot with annotations. (ymin and ymax set to -Inf 
# draws the annotation at the bottom of the panel. 
# vjust = 1.5 drops them below the panel). 
for (i in 1:length(unique(mydata$Tag)))  {
   p1 = p1 + annotation_custom2(
        grob =  textGrob(label = unique(mydata$Tag)[i], vjust = 1.5,
                      gp = gpar(col = 'red', cex = 1)),
         xmin = unique(mydata$dist)[i],
         xmax = unique(mydata$dist)[i],
         ymin = -Inf,
         ymax = -Inf,
         data=data.frame(PNo=c("X-3", "X-4") ))    # The two bottom panels
} 

# The annotations are placed outside the panels. 
# Therefore, have to turn off clipping to the panels.
g1 = ggplotGrob(p1)
g1$layout$clip[grepl("panel", g1$layout$name)] = "off"

# Draw the chart
grid.newpage()
grid.draw(g1)

enter image description here

The second draws two charts: p1 is your bar plot, and p2 contains the labels only. The trick is to get the x-axes in the two charts to be the same. Then, plot panels are extracted from p2, and placed into a p1, but into a new row just below p1's plot panel.

library(ggplot2)
library(magrittr)    
mydata = data.frame(expand.grid(Tag = c('A', 'B', 'C'),
      Year = 2010:2011,PNo = paste0("X-", 1:4)),Value = round(runif(24, 1, 20)))
mydata$dist = ifelse(mydata$Tag == 'A', 0, ifelse(mydata$Tag == 'B', 2, 7))

# The bar plot
p1 = mydata %>% ggplot(aes(x = dist, y = Value, fill = factor(Year))) +
   geom_bar(stat = 'summary', position = 'dodge',fun.y = 'mean', width = 1) +
  facet_wrap(~PNo, ncol = 2) +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank()) 

# To get the range of x values -
# so that the extent of the x-axis in p1 and in the following p2 are the same
gd = ggplot_build(p1)
xrange = gd$layout$panel_params[[1]]$x.range   # xrange used in p2 (see below)

# Plot with labels (A, B, and C) only
p2 = mydata %>% ggplot(aes(x = dist, y = Value)) +
     facet_wrap(~PNo, ncol = 2) +
     geom_label(aes(x = dist, y = 0, label = Tag), size = 6, inherit.aes = F, color = 'red') +
    ### geom_text(aes(x = dist, y = 0, label = Tag), size=6, color = 'red') +  ### Alternative style for labels
     scale_x_continuous(lim = xrange, expand = c(0,0)) +
     theme_bw() +
     theme(panel.grid = element_blank(),
           panel.border = element_rect(colour = NA)) 


# Grab a plot panel from p2
g2 = ggplotGrob(p2)
panels = subset(g2$layout, grepl("panel", g2$layout$name), t:r)
panels = subset(panels, t == min(t))

g2 = g2[unique(panels$t), min(panels$l):max(panels$r)]

# Add a row to p1 to take the plot panels
library(gtable)
library(grid)
g1 <- ggplotGrob(p1)
pos = max(subset(g1$layout, grepl("panel", g1$layout$name), t))
g1 = gtable_add_rows(g1, height = unit(2, "lines"), pos = pos)

# Add the panel (g2) to the new row
g1 = gtable_add_grob(g1,g2, t = pos + 1, l =  min(panels$l), r = max(panels$r))

# Draw the chart
grid.newpage()
grid.draw(g1)

enter image description here

Sandy Muspratt
  • 31,719
  • 12
  • 116
  • 122
0

I tried to solve the problem myself but was facing some issue. I posted another question on SO here. Together the answer and question solves this question to some extent. Here is a possible solution.

p <- mydata %>% ggplot(aes(x = dist,y = Value,fill = factor(Year))) +geom_bar(stat='summary',position = 'dodge',fun.y='mean',width = 1) +
  facet_wrap(~PNo,ncol=2) +
  theme(axis.text.x = element_blank(),axis.ticks.x = element_blank()) +
  geom_label(data  = mydata %>% dplyr::filter(PNo %in% c('X-3','X-4')),aes(x = dist,y=0,label = Tag),size=6,inherit.aes=F,color = 'red') 
library(grid) 
gt <- ggplot_gtable(ggplot_build(p)) 
gt$layout$clip[grep("panel-2-\\d+", gt$layout$name)] <- "off"
grid.draw(gt) 
Community
  • 1
  • 1
Stat-R
  • 5,040
  • 8
  • 42
  • 68