Search code examples
rggplot2cowplottikzdevice

grid_plot + tikzDevice + shared legend with latex mark up


I've been trying to follow this vignette on how to make a shared legend for multiple ggplot2. The given examples work perfectly as is, but in my case, I'm using tikzDevice to export a tikzpicture environment. The main problem seems to be that the widths of the legend keys are not correctly captured by grid_plot.

I came up with a minimal R code that reproduces the problem:

require(ggplot2)
require(grid)
require(gridExtra)
require(cowplot)
require(tikzDevice)

tikz(file = "./tmp.tex", width = 5.6, height = 2.2, standAlone = T )
mpg2 <- mpg

mpg2$cyl = as.factor(mpg2$cyl)
levels(mpg2$cyl) <- c("\\textbf{\\textsc{four}}",
                      "\\textbf{\\textsc{five}}",
                      "\\textbf{\\textsc{six}}",
                      "\\textbf{\\textsc{seven}}",
                      "\\textbf{\\textsc{eight}}")

plot.mpg <- ggplot(mpg2, aes(x=cty, colour=cyl, y = hwy)) + 
     geom_point() +
     theme(legend.position='none')

legend <- get_legend(plot.mpg + theme(legend.position = "top"))

print(plot_grid(legend, 
                 plot.mpg, nrow=2, ncol=1,align='h', 
                 rel_heights = c(.1, 1)))

dev.off()

The generated PDF file (after compiling tmp.tex) looks like this:

enter image description here

As we can observe, first legend key (four) is only partially displayed and legend key (eight) is completely invisible. I tried changing tikz command width to no avail.

Also, I suspect that the reason behind the problem is that grid_plot command incorrectly measures the length of the legend keys if they contain latex mark up. To show that this is the cause of the problem, consider changing the levels of the mpg2$cyl to the following:

levels(mpg2$cyl) <- c("four",
                      "five",
                      "six",
                      "seven",
                      "eight")

This should result in the following plot with a perfect legend:

enter image description here

Please note that the example above is just meant to reproduce the problem and is not what I'm trying to do. Instead, I have four plots that I'm trying to use a shared common legend for them.

Anyone please can tell me how to fix the legend problem when it contains latex mark up?

By the way, here is my sessionInfo():

R version 3.3.2 (2016-10-31)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X Yosemite 10.10.5

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] tikzDevice_0.10-1 dplyr_0.5.0       gdata_2.17.0      cowplot_0.7.0    
[5] gridExtra_2.2.1   ggplot2_2.2.0    

loaded via a namespace (and not attached):
 [1] gtools_3.5.0       colorspace_1.2-6   DBI_0.5            RColorBrewer_1.1-2
 [5] plyr_1.8.4         munsell_0.4.3      gtable_0.2.0       labeling_0.3      
 [9] Rcpp_0.12.6        scales_0.4.1       filehash_2.3       digest_0.6.10     
[13] tools_3.3.2        magrittr_1.5       lazyeval_0.2.0     tibble_1.1        
[17] assertthat_0.1     R6_2.1.3         

Thank you all.


Solution

  • ggplot2 appears to calculate the string widths based on the raw string as opposed to box sizes that should be returned by TeX. I'm guessing it's a calculation done too early (ie not at drawing time) in the guides code. As a workaround you could edit the relevant widths manually in the gtable, by calling getLatexStrWidth explicitly. Note I also added a package in the preamble otherwise the default font doesn't show bold small caps.

    require(ggplot2)
    require(grid)
    require(gridExtra)
    require(tikzDevice)
    
    setTikzDefaults(overwrite = TRUE)
    preamble <- options("tikzLatexPackages") 
    options("tikzLatexPackages" = c(preamble$tikzLatexPackages, "\\usepackage{bold-extra}"))
    
    tikz(file = "./tmp.tex", width = 5.6, height = 2.2, standAlone = TRUE )
    mpg2 <- mpg
    
    mpg2$cyl = as.factor(mpg2$cyl)
    
    levels(mpg2$cyl) <- c("\\textbf{\\textsc{four}}",
                          "\\textbf{\\textsc{five}}",
                          "\\textbf{\\textsc{six}}",
                          "\\textbf{\\textsc{seven}}",
                          "\\textbf{\\textsc{eight}}")
    
    p <- ggplot(mpg2, aes(x=cty, colour=cyl, y = hwy)) + 
      geom_point() +
      theme(legend.position='none')
    
    
    
    leg <- cowplot::get_legend(p + theme(legend.position = "top"))
    
    ids <- grep("label",leg$grobs[[1]]$layout$name)
    pos <- leg$grobs[[1]]$layout$l[ids]
    wl <- sapply(leg$grobs[[1]][["grobs"]][ids], function(g) getLatexStrWidth(g[["label"]]))
    
    leg$grobs[[1]][["widths"]][pos] <- unit(wl, "pt")
    
    grid.arrange(p, top=leg)
    
    dev.off()
    

    enter image description here