2

I want to create a discrete legend (not continuous) in pheatmap. So for this code,

m=as.matrix(c(1:100))
breaks=c(1,5,10,50,80,100)
color=c("red","blue","green","yellow","orange")
pheatmap(m,cluster_rows=FALSE, cluster_cols=FALSE,breaks=breaks,color=color)

the legend looks like this:

default pheatmap legend

But I want it to look like this where the size of each rectangle is the same:

discrete pheatmap legend

Can you point me to the options in pheatmap that will make this possible? I cannot figure it out. Thank you muchly,

pcantalupo
  • 2,212
  • 17
  • 27

1 Answers1

4

Well, the function itself really does not want to accommodate such a legend. There is no way to pass in any combination of arguments to make it discrete as far as I can tell and all the plotting functions it relies on seem to be locked so you can't really adjust their behavior.

But, the good news is that the function uses grid graphics to make the output. We can hack at the grid objects left on the grid tree to remove the legend they drew and draw our own. I've created a function to do this.

changeLegend<-function(breaks, color) {
    tree <- grid.ls(viewport=T, print=F)
    #find legend
    legendvp <- tail( grep("GRID.VP", tree$name), 1)
    #get rid of grobs in this viewport
    drop <- tree$name[grepl(tree$vpPath[legendvp],tree$vpPath) & 
        grepl("grob",tree$type)]
    sapply(drop, grid.remove)

    #calculate size/position of labels
    legend_pos = seq(0,to=1,length.out=length(breaks))
    brat = seq(0,to=1,length.out=length(breaks))
    h = 1/(length(breaks)-1)

    #render legend
    seekViewport(tree$name[legendvp])    
    grid.rect(x = 0, y = brat[-length(brat)], 
        width = unit(10, "bigpts"), height = h, hjust = 0, 
        vjust = 0, gp = gpar(fill = color, col = "#FFFFFF00"))
    grid.text(breaks, x = unit(12, "bigpts"), 
        y = legend_pos, hjust = 0,)
}

Since they didn't really name any of their viewports, I had to make some guesses about which viewport contained which objects. I'm assuming the legend will always be the last viewport and that it will contain two globs, one for the box of color and one for the text in the legend. I remove those items, and then re-draw a new legend using the breaks and colors passed in. Here's how you would use that function with your sample

library(pheatmap)
library(grid)

mm <- as.matrix(c(1:100))
breaks <- c(1,5,10,50,80,100)
colors <- c("red","blue","green","yellow","orange")

pp<-pheatmap(mm,cluster_rows=FALSE, cluster_cols=FALSE,
   breaks=breaks, color=colors, legend=T)
changeLegend(breaks, colors)

And that produces

discrete legend

Because we are hacking at undocumented grid objects, this might not be the most robust method, but it shows off how flexible grid graphics are

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Thank you for your help...I'm glad it wasn't something obvious. I was researching how to do this all morning long. – pcantalupo May 29 '14 at 20:40
  • Great solution. I can't get it to print to a file though (only to the viewer). Any idea? – user1701545 Aug 15 '14 at 04:53
  • @user1701545 What type of file are you trying to print to? How are you doing it? What exactly is the error? – MrFlick Aug 15 '14 at 05:01
  • I was trying to first define the file where the plot is to be saved pdf("my.pdf"), then run the plotting commands and then dev.off(). But I found that running the plotting commands, then dev.copy(pdf, "my.pdf") and then dev.off() does the trick. BTW, can I bother you with asking how would I add a title to be located above the legend? – user1701545 Aug 15 '14 at 05:08
  • @user1701545 Since it's a grid plot, you should be able to add additional text with `grid.text()` – MrFlick Aug 15 '14 at 05:12