Position the labels at the middle of the bin in a binned / step colorbar guide?

Viewed 203

Reasoning: I would like to easily use readily available continuous scales (from any package that offers scale_..._continuous etc), for ordinal factor-like data, e.g. mtcars$cyl. Because those data do only contain few sort of discrete values, I'd like to label the legend keys directly, and not the bin limits. How to do that?

library(ggplot2)

ggplot(mtcars, aes(mpg, disp, color = cyl))+
  geom_point() +
  scale_color_continuous(limits = range(mtcars$cyl),
                         guide = guide_colorsteps(ticks.colour = "black"))

Not desired:

Hacky way, which actually are four (!) hacks, and is not desired.

# Hack 1 - create the colors manually from the palette. This is already annoying. 
cont_col <- colorRampPalette(c("#132B43","#56B1F7"))(length(unique(mtcars$cyl)))

# Hack 2- you need to modify the underlying draw_key function 
# from https://github.com/tidyverse/ggplot2/issues/2844
draw_key_polygon2 <- function(data, params, size) {
  lwd <- min(data$size, min(size) / 4)
  grid::rectGrob(
    width = grid::unit(1, "npc"),
    height = grid::unit(1, "npc"),
    gp = grid::gpar(
      col = data$colour,
      fill = alpha(data$fill, data$alpha),
      lty = data$linetype,
      lwd = lwd * .pt,
      linejoin = "mitre"
    ))
}

# Hack 3 discretise the variable
ggplot(mtcars, aes(mpg, disp, color = as.character(cyl))) +
  geom_point(key_glyph = "polygon2") +
  scale_color_manual(values = cont_col) +
# Hack 4 override aes to change the fill of the key glyph polygons
  guides(color = guide_legend(override.aes = list(fill = cont_col))) 

But it results in the desired output

Created on 2021-05-02 by the reprex package (v2.0.0)

3 Answers

This is the least hacky way I could think of.

  1. Set the fill to after_scale(colour) in conjunction with a rectangle key.
  2. Set the breaks to the literal values and use a legend guide.
library(ggplot2)

ggplot(mtcars, aes(mpg, disp, color = cyl)) +
  geom_point(aes(fill = after_scale(colour)), # set fill for rect key
             key_glyph = draw_key_rect) + # rect key
  scale_colour_continuous(
    breaks = sort(unique(mtcars$cyl)),
    guide = guide_legend()
  )

Created on 2021-05-02 by the reprex package (v1.0.0)

You already mentioned the colorRampPalette hack and you don't really like it but I found it really useful. Here is some tweak to your code hope it provide some alternative perspective to this.

library(ggplot2)

# Predefine a colr pallete with steps which later take number of color
# you want and generate proper palletes
color_code <- c("#56B1F7", "#3B7BAF", "#132B43")
color_code_func <- colorRampPalette(color_code)

# Cut the data into prefer bracket - I find this really useful when using
# gradient color on continuos data, you can easily define the cut off for 
# which amke it easier to track which values for which color
mtcars$cyl_factor <- cut(mtcars$cyl, breaks = c(4, 5, 6, 7, 8),
  include.lowest = TRUE, right = TRUE)

# using the color pallete defined earlier and assign the levels of 
# factor to the genereated pallete
color_code_genereated <- color_code_func(length(levels(mtcars$cyl_factor)))
names(color_code_genereated) <- levels(mtcars$cyl_factor)

# then using your code just a bit revised the guides overide here
ggplot(mtcars, aes(mpg, disp, fill = cyl_factor, color = cyl_factor)) +
  geom_point() +
  scale_color_manual(values = color_code_genereated) +
  # only need to provide the shape and size you want to override instead
  guides(col = guide_legend(override.aes = list(shape = 15, size = 10)))

A practical example with some detail infor blurred out. Where it is easier to identified which color on map is which bracklet. Where using a continous scales with gradient would make it a bit tricker.

enter image description here

Created on 2021-05-02 by the reprex package (v2.0.0)

You can add the breaks points in ggplot2

library(ggplot2)

ggplot(mtcars, aes(mpg, disp, color = cyl))+
  geom_point() +
  scale_color_continuous(limits = range(mtcars$cyl),
                         guide = guide_colorsteps(ticks.colour = "black"), 
                         breaks = seq(4, 8, by = 2)) 

    
Related