rggplot2limitcolorbar

How can I add triangles to a ggplot2 colorbar in R to indicate out of bound values?


I'd like to make a plot using ggplot2 where some of the fill values are clipped, i.e. values above or below the limits of the color scale are displayed as the minimum/maximum color. I can get this to work like this, using a combination of limit and oob (out of bounds):

library(ggplot2)
library(scales)
ggplot() + ... + scale_fill_viridis(na.value="white", limit=c(0, 10), oob=squish)

But there is no information in the colorbar that indicates there are values present outside of the limits. How can I reproduce this matplotlib example in ggplot: https://stackoverflow.com/a/32072348

Specifically, how to get the triangles at the end of the colorbar?


Solution

  • EDIT:

    I've now released a package that has this functionality. You can use it as follows. This is if you've already limited and squished your range at the scale level:

    library(legendry)
    #> Loading required package: ggplot2
    
    ggplot(mtcars, aes(mpg, wt, colour = drat)) +
      geom_point() +
      scale_colour_viridis_c(
        limits = c(3, 4), oob = scales::oob_squish,
        guide = "colbar"
      )
    

    This is if you insist on the triangles, but don't want to limit/squish at the scale level.

    ggplot(mtcars, aes(mpg, wt, colour = drat)) +
      geom_point() +
      scale_colour_viridis_c(
        guide = guide_colbar(
          show = TRUE, oob = "squish"
        )
      )
    

    Created on 2024-11-24 with reprex v2.1.1

    OLD ANSWER:

    As far as I'm aware there is not a package that implements triangle ends for colourbars in ggplot2 (but please let me know if there is!). However, we can implement our own. We'd need a constructor for our custom guide and a way to draw it. Most of the stuff is already implemented in guide_colourbar() and methods for their class, so what we need to do is just tag on our own class and expand the guide_gengrob method. The code below should work for vertically oriented colourbars. You'd need to know some stuff about the grid package and gtable package to follow along.

    library(ggplot2)
    library(gtable)
    library(grid)
    
    my_triangle_colourbar <- function(...) {
      guide <- guide_colourbar(...)
      class(guide) <- c("my_triangle_colourbar", class(guide))
      guide
    }
    
    guide_gengrob.my_triangle_colourbar <- function(...) {
      # First draw normal colourbar
      guide <- NextMethod()
      # Extract bar / colours
      is_bar <- grep("^bar$", guide$layout$name)
      bar <- guide$grobs[[is_bar]]
      extremes <- c(bar$raster[1], bar$raster[length(bar$raster)])
      # Extract size
      width  <- guide$widths[guide$layout$l[is_bar]]
      height <- guide$heights[guide$layout$t[is_bar]]
      short  <- min(convertUnit(width, "cm",  valueOnly = TRUE),
                    convertUnit(height, "cm", valueOnly = TRUE))
      # Make space for triangles
      guide <- gtable_add_rows(guide, unit(short, "cm"),
                               guide$layout$t[is_bar] - 1)
      guide <- gtable_add_rows(guide, unit(short, "cm"),
                               guide$layout$t[is_bar])
      
      # Draw triangles
      top <- polygonGrob(
        x = unit(c(0, 0.5, 1), "npc"),
        y = unit(c(0, 1, 0), "npc"),
        gp = gpar(fill = extremes[1], col = NA)
      )
      bottom <- polygonGrob(
        x = unit(c(0, 0.5, 1), "npc"),
        y = unit(c(1, 0, 1), "npc"),
        gp = gpar(fill = extremes[2], col = NA)
      )
      # Add triangles to guide
      guide <- gtable_add_grob(
        guide, top, 
        t = guide$layout$t[is_bar] - 1,
        l = guide$layout$l[is_bar]
      )
      guide <- gtable_add_grob(
        guide, bottom,
        t = guide$layout$t[is_bar] + 1,
        l = guide$layout$l[is_bar]
      )
      
      return(guide)
    }
    

    You can then use your custom guide as the guide argument in a scale.

    g <- ggplot(mtcars, aes(mpg, wt)) +
      geom_point(aes(colour = drat))
    
    g + scale_colour_viridis_c(
        limits = c(3, 4), oob = scales::oob_squish,
        guide = my_triangle_colourbar()
      )
    

    There isn't really a natural way to colour out-of-bounds values differently, but you can make very small slices near the extremes a different colour.

    g + scale_colour_gradientn(
        colours = c("red", scales::viridis_pal()(255), "hotpink"),
        limits = c(3, 4), oob = scales::oob_squish,
        guide = my_triangle_colourbar()
      )
    

    Created on 2021-07-19 by the reprex package (v1.0.0)