rggplot2legend-properties

Symmetric colorbar for values but print colorbar for actual observed values


Let's assume I create a simple heatmap in R ggplot2. I need to use certain pre-defined colors for the positive and negative space where the 0 should be, for example, white. Is it possible to draw the tiles such that the values take the colors according to the same range of values in the positive/negative territory but print the final colorbar only showing the actual range of values?

This and this question seem related, but they are concerned about obtaining asymmetric colorbars around a midpoint.

In the left panel I have the current colorbar, in the center panel the expected colorbar and in the right panel the approach of the linked questions. In the left panel the values are correct in principle but the full colorbar from 1.9 to -1.9 is printed. The problem in the right panel is that it squeezes the full colorbar in the range above/below the midpoind, which implies that, for example, 0.4 and -0.4 (top left) have different colors. The size of the colorbar should be of course the same in principle.

Is there a way to ideally directly or indirectly (e.g., ex-post) achieve this?

example

This is the corresponding code:

library(ggplot2)

df <- expand.grid(
  x = c(1, 2, 3),
  y = c(1, 2, 3),
  g = c("one", "two")
)

one <- c(1.90, 1.40, 1.10, 0.90, 0.80, 0.50, 0.40, 0.30, 0.20)
two <- c(1.90, 1.40, 1.10, 0.90, 0.80, -0.50, -0.40, -0.30, -0.20)
df$v <- c(one, two)

min_value <- min(df$v)
max_value <- max(df$v)
  
min_label <- round(min_value, 2)
max_label <- round(max_value, 2)
  
midpoint = 0

ggplot(df, aes(x = x, y = y, fill = v)) +
  facet_wrap(. ~ g, ncol = 2) +
  geom_tile(color = "black") +
  geom_text(aes(label = round(df$v, 2)), color = "red") +
  scale_y_discrete(expand = c(0,0)) +
  scale_x_discrete(expand = c(0,0)) +
  scale_fill_gradientn(
    colors = c(hcl.colors(5, "Inferno")[1:5], "white", hcl.colors(5, "Inferno")[5:1]),
    #values=c(1, (midpoint-min_value)/(max_value-min_value), 0), # uncomment for right heatmap and comment limits
    limits = c(-max_value, max_value),
    breaks = c(min_value, max_value),
    labels = c(min_label, max_label)
    ) +
  guides(
    fill = guide_colorbar(
      barheight = unit(5, "cm"),
      ticks.colour = "black",
      ticks.linewidth = 0.5,
      frame.colour = "black",
      frame.linewidth = 0.5     
    ))


Solution

  • One option to achieve your desired result would be to map abs(v) on fill and to use an appropriate vector of values= for which I use scales::rescale:

    library(ggplot2)
    
    ggplot(df, aes(x = x, y = y, fill = abs(v))) +
      facet_wrap(. ~ g, ncol = 2) +
      geom_tile(color = "black") +
      geom_text(aes(label = round(v, 2)), color = "red") +
      scale_y_discrete(expand = c(0, 0)) +
      scale_x_discrete(expand = c(0, 0)) +
      scale_fill_gradientn(
        colors = c(hcl.colors(5, "Inferno")[1:5], "white", hcl.colors(5, "Inferno")[5:1]),
        values = scales::rescale(
          max_value * seq(-1, 1, length.out = 11),
          from = c(min_value, max_value)
        ),
        limits = c(min_value, max_value),
        breaks = c(min_value, max_value),
        labels = c(min_label, max_label)
      ) +
      guides(
        fill = guide_colorbar(
          barheight = unit(5, "cm"),
          ticks.colour = "black",
          ticks.linewidth = 0.5,
          frame.colour = "black",
          frame.linewidth = 0.5
        )
      )