Search code examples
rgraphicslatticedirect-labels

directlabels: using different positioning methods for different panels in the same plot


I have a two-panel lattice lineplot. I want to use the directlabels package to automatically label the lines in each plot. But I want to use a different positioning method for each plot. Specifically, I want to use the first.bumpup method for the first panel, and the last.bumpup method for the second panel. Here is a minimal example:

library(directlabels)
library(lattice)
myDF <- data.frame(
  y     = rep(1:4,         2),
  x     = rep(rep(1:2, 2), 2),
  group = rep(c('a', 'b'), each = 2),
  panel = rep(1:2,         each = 4))
myPlot <- xyplot(y ~ x | panel, groups = group, data = myDF, type = 'l')
direct.label(
  p      = myPlot,
  method = 'first.bumpup')

This code produces a plot in which labels appear on the left-hand side of each panel:

figure produced by example code

I want labels on the left-hand side of the left-hand panel (as in this example), but on the right-hand side of the right-hand panel. What is the simplest way to produce that sort of figure?

I've checked the advanced examples in the directlabels documentation, and they make me think that it may be possible to use different methods for different panels by creating a custom positioning method or a custom panel. But I cannot quite see how to do it.


Solution

  • I took a crack at this with ggplot2 (only because I know ggplot2 much better than I know lattice). Let me know what you think. Below are two approaches. The first actually doesn't use directlabels. The placement rule is relatively simple, so I just used geom_text for label placement. The second method does use directlabels, but is more complicated.

    Place labels using geom_text

    library(dplyr)   # For chaining operator (%>%)
    library(ggplot2)
    library(cowplot) # For cowplot theme
    
    ggplot(myDF, aes(x, y, colour=group)) + 
      geom_line() +
      geom_text(data=myDF %>% group_by(panel) %>%
                  filter(ifelse(panel==1, x==min(x), x==max(x))),
                aes(x + 0.07*(panel-mean(panel)), y, label=group)) +
      facet_grid(~panel) +
      scale_x_continuous(breaks=seq(1,2,0.2)) +
      theme_cowplot() +
      guides(colour=FALSE)
    

    In the code above, inside geom_text we keep only the lowest x value for the first panel and the highest x value in the second panel and then place the group labels at the y values that pair with the x values. The x + 0.07*(panel-mean(panel)) is just to move the labels slightly away from the ends of the lines.

    enter image description here

    Place labels using mapply and directlabels

    Here's a more complicated method using directlabels. My approach was to plot each "facet" separately using mapply, so that I could use a different directlabels method for each panel, but then lay the two plots out together as if they were two facets of the same overall plot. If you like the result, maybe you can adapt it to a lattice plot if none of the ggplot2 versions meet your needs.

    library(directlabels)
    library(ggplot2)
    library(gridExtra)
    library(cowplot)
    
    pl = mapply(function(pnl, m) {
    
      # Create plot for each level of panel
      p = ggplot(myDF[myDF$panel==pnl, ], aes(x, y, colour=group)) + 
        geom_line() +
        facet_grid(~panel) +
        scale_x_continuous(breaks=seq(1,2,0.2)) +
        theme_cowplot()
    
      # # Tweak margins of panel 1
      # if(pnl==1) p = p + theme(plot.margin=unit(rep(0,4),"lines"))
    
      # Remove y-axis title, labels and ticks for panel 2 and tweak margins
      if(pnl==2) p = p + theme(axis.title.y=element_blank(), 
                               axis.text.y=element_blank(),
                               axis.ticks.y=element_blank())
    
      # Add directlabels with different method for each panel
      direct.label(p, method=m)
      }, 
      pnl=unique(myDF$panel), m=c("first.bumpup", "last.bumpup"), SIMPLIFY=FALSE)
    

    Because I removed the y-axis title, labels, and ticks in panel 2, that panel is wider than panel 1. plot_grid has an align argument that allows us to align the two plots so that they have the same width, but I don't know of a way to get rid of the space between the plots. grid.arrange will also lay out that plots, but we have to adjust the widths manually (you can adjust the widths manually with plot_grid as well).

    # Lay out each panel using plot_grid from cowplot package
    plot_grid(plotlist=pl, ncol=2, align="v")
    

    enter image description here

    # Lay out each panel using grid.arrange from gridExtra package
    grid.arrange(grobs=pl, ncol=2, widths=c(10,9))
    

    enter image description here