gokhale
gokhale

Reputation: 41

How to highlight a single value on a continuous color scale in ggplot2

A question posted here shows how to declare some of the values missing. I have a similar problem except I wish to highlight a single value with a different color eg. mpg = 20. Ideally, I would like it to show up on the legend as well.

To be clear, I wish to highlight a specific value on the gradient.

I am reusing the code that was used in the other post to seed the effort. This code specifies the lower limit of the data but does not allow for an arbitrarily chosen value.

I was wondering if people know how to do this with our without using something like scale_colour_gradientn.

library(ggplot2)
dat <- head(mtcars)
dat$model <- head(colnames(mtcars))
dat$is_low <- ifelse(dat$mpg < 20, TRUE, FALSE)

ggplot(dat, aes(x = model, y = mpg, fill = mpg)) +
    geom_col() + 
    scale_fill_continuous(limits=c(20,max(dat$mpg)))

Upvotes: 2

Views: 858

Answers (1)

teunbrand
teunbrand

Reputation: 37933

This is adapted from the answer I gave here, but it requires some messing around with the palette.

This is a custom palette function that replaces the values between the target values with the replace_colour, but it requires to know the range of the data first. Note that the function isn't very user friendly, but it does the job.

library(ggplot2)
library(scales)

my_palette <- function(colours, target = c(20.5, 21.5), 
                       range = range(target), values = NULL,
                       replace_colour = "green") {
  target <- (target - range[1]) / diff(range)
  ramp <- scales::colour_ramp(colours)
  force(values)
  function(x) {
    # Decide what values to replace
    replace <- x > target[1] & x < target[2]
    if (length(x) == 0)
      return(character())
    if (!is.null(values)) {
      xs <- seq(0, 1, length.out = length(values))
      f <- stats::approxfun(values, xs)
      x <- f(x)
    }
    out <- ramp(x)
    # Actually replace values
    out[replace] <- replace_colour
    out
  }
}

You can then use that function with a custom scale as follows. I chose to highlight around 21 because 20 doesn't occur in dat$mpg.

dat <- head(mtcars)
dat$model <- head(colnames(mtcars))
dat$is_low <- ifelse(dat$mpg < 20, TRUE, FALSE)

colours <- seq_gradient_pal("#132B43", "#56B1F7")(seq(0, 1, length.out = 12))

ggplot(dat, aes(x = model, y = mpg, fill = mpg)) +
  geom_col() +
  continuous_scale(
    "fill", "my_pal",
    my_palette(colours, range = range(dat$mpg), target = c(20.9, 21.1)),
    guide = guide_colourbar(nbin = 500) # Give guide plenty bins
  )

Created on 2021-04-13 by the reprex package (v1.0.0)

Applying this to log scaled values requires you to log scale all the input data to my_palette too.

dat <- head(mtcars)
dat$model <- head(colnames(mtcars))
dat$mpg <- c(1e-6, 1e-4, 1e-2, 1e0, 1e2, 1e4)

colours <- seq_gradient_pal("#132B43", "#56B1F7")(seq(0, 1, length.out = 12))

ggplot(dat, aes(x = model, y = mpg, fill = mpg)) +
  geom_col() +
  scale_y_log10() +
  continuous_scale(
    "fill", "my_pal", trans = "log10",
    my_palette(colours, range = log10(range(dat$mpg)), 
               target = log10(1e2) * c(0.9, 1.1)),
    guide = guide_colourbar(nbin = 500) # Give guide plenty bins
  )

Upvotes: 1

Related Questions