Home > Net >  Draw something outside of the plot, between the axis and plot area
Draw something outside of the plot, between the axis and plot area

Time:05-04

I have some generic plot with continuous X and categorical Y axis, for example:

data(iris)
p1 <-
  ggplot(iris, aes(y=Species, x=Sepal.Length))  
  geom_violin()

p1

Now I want to annotate it with some labels that should be positioned right next to the Y axis, with a quick-and-dirty solution like this:

df.labels <- data.frame(Species=c('setosa','versicolor','virginica'), Labels=c('labA','labB','labC'))

p2 <- p1   geom_text(data = df.labels, aes(x=4, y=Species, label=Labels))

p2

Note that I had to indicate x=4 explicitly; for more general case I made this function:

add_label <- function(inpPlot, inpData, colLabel){
  plotX1 <- ggplot_build(inpPlot)$layout$panel_params[[1]]$x.range[1]
  plotX2 <- ggplot_build(inpPlot)$layout$panel_params[[1]]$x.range[2]
  plotW <- plotX2 - plotX1
  posX <- plotX1 - 0.05*plotW

  colGrp <- as_label(inpPlot$mapping$y)
    
  inpPlot   
    coord_cartesian(clip='off')  
    geom_text(data = inpData, aes_string(x='posX', y=colGrp, label=colLabel))  
}

p3 <- add_label(p1, df.labels, 'Labels')

p3

What I don't like in this solution:

  1. Offset calculated as "5% of plot width" doesn't work very well when I resize plot window (I would like to have a fixed absolute distance between the axis and the labels)
  2. It doesn't work if my initial plot has coord_cartesian() applied, for example:
p1c <- p1   coord_cartesian(xlim = c(5,NA))
p4 <- add_label(p1c, df.labels, 'Labels')

p4

  1. Labels are shown on the plot area itself, I want them to be outside of the plot area (but "inside" from the Y axis, i.e. to the right from the axis labels).

  2. What I would like to have ideally, is something like this:

    p5 <- add_label_good(p1c, df.labels, 'Labels')

p5 i.e. it should draw labels between the axis and plot area, and this space should be of fixed width.
Is there any way at all to achieve this?

CodePudding user response:

You might consider patchwork, with two aligned plots:

library(patchwork)

plot_labels <-  ggplot(df.labels, aes(x = 0, y = Species, label = Labels))  
  geom_text(hjust = 0)  
  theme_void()  
  theme(axis.text.y = element_text(),
        axis.title.y = element_text(angle = 90, margin = margin(0,12,0,0, unit = "pt")))

plot_labels  
  (p1   theme(axis.title.y = element_blank(),
              axis.text.y = element_blank()))  
  plot_layout(widths = c(1,8))

enter image description here

CodePudding user response:

One option is to use facet strips:

p1   facet_grid(Species~., scales = 'free_y', switch = "y",
                labeller = labeller(Species = function(x) {
                  df.labels$Labels[match(x, df.labels$Species)]}))  
  theme(text = element_text(size = 16),
        strip.text.y.left = element_text(angle = 0, face =  "bold",
                                         margin = margin(10, 10, 10, 10)),
        strip.background = element_blank(),
        panel.spacing = unit(0, "mm"))

enter image description here

Another option is to use ggtext::element_markdown() and use the axis text itself as a label:

p1  
  scale_y_discrete(labels = function(x) {
    paste(x, df.labels$Labels[match(x, df.labels$Species)],
          sep = paste0(rep("&nbsp; ", 5), "<b>", collapse = ""))
  })  
  theme(text = element_text(size = 16),
        axis.text.y = ggtext::element_markdown())

enter image description here

For a truly fixed-width text area on the far left of the plot, you could use annotation_custom

p1   
  annotation_custom(grid::rectGrob(x = unit(10, "mm"), y = 0.5,
                                   width = unit(20, "mm"), height = 1,
                                   gp = grid::gpar(col = NA)))  
  annotation_custom(grid::textGrob(df.labels$Labels,
                    x = unit(10, "mm"),
                    y = c(0.81, 0.5, 0.19)))

enter image description here

  • Related