Search code examples
rggplot2

colouring a multilayer plot in ggplot2


I'm trying to recreate the following plot as a ggplot2 multilayered object but I think I'm having a harder time than what I thought. enter image description here

Right now I've been able to sketch out the general idea as a stack of three layers:

  • geom_rect for vertical colours
  • geom_rect for horizontal striped/dotted patterns (- geom_point for what will be plotted on that)

but some big issues appeared:

  1. ggplot2 seems not to interpret my colours stored in a dataframe (neither as names [e.g. 'red'] nor as hexadecimals values). I tried to provide them as a named vector (see first solution here) but with no luck. enter image description here
  2. I tried ggpattern but it applies the same pattern across the whole chart space
  3. being transparent, I tried "00" as a hexadecimal value for the lower geom_rect, but had the same outcome of issue n.1
  4. geom_point does not recognise the field 'wtype' returning object not found

Unfortunately, I haven't been able to solve them after quite a bit of reading&testing at least half of StackOverflow.

I know it's a complex question but looked weird to open 3 different questions for what -to me- relates to a general problem of properly managing multiple layers.

Here the code and some data:

vparam.df = data.frame(
  ymin = 0,
  ymax = 15,
  xmin = c(0, 7, 14, 21, 28),
  xmax = c(7, 14, 21, 28, 35)
  , xlabel = c(3.5, 10.5, 17.5, 24.5, 31.5)
  , ylabel = rep(-1,5)
  , fill = c("#1e8449", "#52be80", "#f4d03f", "#f39c12","#c0392b")
  #, fill = c("red", "orange","yellow","lightgreen","darkgreen")
  , label = c("low","mod. low","medium","pretty strong","strong")
)

hparam.df = data.frame(
  xmin = 0,
  xmax = 35,
  ymin = c(5, 10),
  ymax = c(10, 15)
  , xlabel = rep(-1, 2)
  , ylabel = c(7.5, 12.5)
  #, fill = c("dots", "diagonals") # THIS IS HOW I IMAGINE IT
  , fill = c( "#52be80", "#c0392b")
  , label = c("medium","strong")
)

df = structure(list(xvar = c(13, 20, 16, 11, 21, 13, 13, 7, 9, 16, 
11, 13, 15, 13, 22, 16), yvar = c(0, 2, 2, 4, 2, 4, 7, 5, 5, 
0, 7, 2, 2, 7, 2, 7), wtype = c("Type4", "Type3", 
"Type4", "Type4", "Type1", "Type1", 
"Type4", "Type1", "Type1", "Type1", 
"Type3", "Type1", "Type2", "Type4", 
"Type2", "Type1")), row.names = c(NA, -16L), class = c("tbl_df", 
"tbl", "data.frame"))



ggplot(df, aes(x=xvar, y=yvar )) +
  geom_rect(
    data = vparam.df,
    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill),
    inherit.aes = FALSE
  ) +

  geom_rect(
    data = hparam.df,
    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill), alpha = .2,
    inherit.aes = FALSE
  ) +
  
  geom_point( size=1 ) +

  geom_text(data=vparam.df, aes(x=xlabel, y=ylabel, label=label), size=2) +
  geom_text(data=hparam.df, aes(x=xlabel, y=ylabel, label=label), size=2,angle=90) +
  scale_y_discrete( limits=seq(0,15,5) ) +
  scale_x_discrete( limits=seq(0,35,7) ) +
  theme( legend.position="")

Solution

  • You could use scale_fill_identity and scale_pattern_identity to make your life easier here. Rather than using your data, I have reproduced the plot from scratch here - the following is a full reprex:

    library(ggplot2)
    library(ggpattern)
    
    data.frame(xmin = c(0, 7, 14, 21, 28, 0, 0),
               xmax = c(7, 14, 21, 28, 35, 35, 35),
               ymin = c(0, 0, 0, 0, 0, 5, 10),
               ymax = c(15, 15, 15, 15, 15, 10, 15),
               pat  = c('none', 'none', 'none', 'none', 'none', 'circle', 'stripe'),
               fill = c('#fe0003', '#ff8347', '#ffff01', '#02ff07', '#008203',
                        '#00000000', '#00000000')) |>
      ggplot() +
      geom_rect_pattern(aes(xmin = xmin, ymin = ymin, xmax = xmax, ymax = ymax,
                            fill = fill, pattern = pat), pattern_fill = 'black') +
      annotation_custom(grid::textGrob(x = c(0.1, 0.3, 0.5, 0.7, 0.9, rep(-0.1, 3)),
                                       y = c(rep(-0.1, 5), 1/6, 3/6, 5/6),
                                       gp = grid::gpar(col = 'gray', fontface = 4),
                                       label = c('basso', 'abbastanza\nbasso',
                                                 'medio', 'abbastanza\nmedio',
                                                 'forte', 'basso', 'medio', 'forte'))) +
      geom_point(aes(x = xvar, y = yvar, color = wtype), size = 4, data = df) +
      scale_fill_identity() +
      scale_pattern_identity() +
      scale_color_manual(values = c(Type1 = 'white', Type2 = 'black',
                                    Type3 = 'gray', Type4 = 'violet')) +
      scale_x_continuous(breaks = 0:5 * 7, 
                         labels = ~ ifelse(.x == 0, 0,
                                           paste0(.x, ' (', scales::percent(.x/35), ')'))) +
      scale_y_continuous(breaks = 1:3 * 5, 
                         labels = ~ paste0(.x, ' (', scales::percent(.x/15), ')')) +
      coord_cartesian(expand = FALSE, clip = 'off') +
      theme(axis.text = element_text(color = 'black'),
            plot.margin = margin(10, 30, 30, 30),
            panel.border = element_rect(fill = NA))
    

    enter image description here