Search code examples
rggplot2rgbspatial

ggplot: Plot point data as RGB colors and add legend


I want to plot the share of three variables of each geographical point.

I thought this would be easily done with RGB values and I managed to do it simply like this:

data <- data.frame(
  Variable = c("Point1", "Point2", "Point3"),
  R = c(0.4, 0.6, 0.3),
  G = c(0.3, 0.2, 0.4),
  B = c(0.3, 0.2, 0.3),
  x = c(1, 2, 4),
  y = c(5, 6, 7)
)
color_plot <- rgb(data$R, data$G, data$B)

ggplot() +
  geom_point(data = data, aes(x = x, y = y, color = color_plot))  

The problem is that the color values are discrete, making the legend that ggplot produces useless. Is there an elegant way to add a meaningful (continuous) color scale to this plot? Ideally, the names of R, G and B would be on the scale.


Solution

  • Since the three variable's values all add to one, you can use a ternary RGB as your legend. Whilst this technically works, it is very difficult for the viewer to cross reference the colors, and in reality I would probably use a different method, depending on how many points you have on the plot.

    However, if you have a ternary RGB as a grob called my_legend, you can add it as a custom legend to your plot like this:

    ggplot(data) +
      geom_point(aes(x = x, y = y, color = color_plot), size = 4) +
      scale_color_identity() +
      guides(custom = guide_custom(title = "Relative share", grob = my_legend,
                                   width = unit(5, "cm"), 
                                   height = unit(5 * sin(pi/3), "cm"))) +
      theme_bw(16) +
      theme(legend.title = element_text(hjust = 0.5))
    

    enter image description here

    The difficult part is creating my_legend. You could do this using ggtern or create one from scratch, as I have done here:

    cols <- do.call("rbind", lapply(seq(0, 1, 0.01), function(r) {
      do.call("rbind", lapply(seq(0, 1 - r, 0.01), function(g) {
            data.frame(red = r, green = g, blue = 1 - r - g)
        }))
    }))
    
    cols$rgb <- rgb(cols)
    cols$x <- cols$red * 0.5 - cols$green * 0.5 + 0.5
    cols$y <- sin(pi/3) / 2 * (1 + cols$blue - (cols$red + cols$green))
    
    my_legend <- (ggplot(cols, aes(x, y, colour = rgb)) +
      geom_point(size = 1) +
      annotate("text", x = c(-0.2, 1.2, 0.5), y = c(-0.1, -0.1, sin(pi/3) + 0.2),
               label = c("G", "R", "B"), size = 5) +
      annotate("segment", 
               x = c(0, 1, 0.5) + c(0, 0.1, -0.1) * sin(pi/3), 
               xend = c(1, 0.5, 0) + c(0, 0.1, -0.1) * sin(pi/3),
               y = c(-0.1, 0.05, sin(pi/3) + 0.05), 
               yend = c(-0.1, sin(pi/3) + 0.05, 0.05),
               arrow = arrow(length = unit(2, "mm"), type = "closed")) +
      scale_color_identity() +
      theme_void()) |>
      ggplotGrob()