rggplot2visualization

Change colour of text that labels a tile in ggplot2


This question follows on from this one (Warning message: Removed 2 rows containing missing values (`geom_tile()`)). Some of the days are weekend days and I would like to communicate this in the chart. At the moment I have coded weekend values with a negative number and changed the scale so they appear blue-ish. I am not sure this is what I want though. Ideally I would want weekends to have the same positive value but maybe a different colour text (white rather than black) and/or a border around the tile? I have a boolean column that tells me if they are a weekend. Maybe there is a better visualisation that can identify weekend days?

hours <- c(16, 17, 0, 1, 2, 21, 22, 9, 19, 20)
days <- c(0, 0, 3, 3, 3, 6, 6, 11, 21, 21)
value <- c(3, 60, -18, -60, -23, 51, 48, 49, 47, 40)
weekend<- c(FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE)

df <- data.frame(hours, days, value, weekend)

h<-1
w<-1

ggplot(df, aes(days, hours)) +
  geom_tile(aes(fill = value, width = w, height = h)) +
  geom_text(aes(label = value), size = 3) +
  scale_fill_continuous(limits=c(-60,60),breaks=seq(-60,60,20),low = "blue", high = "red") +
  xlab("Day") +
  ylab("Hour") +
  scale_x_continuous(
    breaks = seq(0, 41, by = 7),
    expand = c(0.05, 0.05)
  ) +
  scale_y_continuous(
    breaks = seq(0, 23, by = 3),
    expand = c(0.05, 0.05)
  ) +
  coord_cartesian(xlim=c(0,41),ylim=c(0,23))

Answer:

hours <- c(16, 17, 0, 1, 2, 21, 22, 9, 19, 20)
days <- c(0, 0, 3, 3, 3, 6, 6, 11, 21, 21)
value <- c(3, 60, 18, 60, 23, 51, 48, 49, 47, 40)
weekend<- c("black","black","white","white","white","black","black","black","black","black")

df <- data.frame(hours, days, value, weekend)

h<-1
w<-1

ggplot(df, aes(days, hours)) +
    geom_tile(aes(fill = value, width = w, height = h)) +
    geom_text(aes(label = value), size = 4, colour=weekend) +
    scale_fill_continuous(limits=c(0,60),breaks=seq(-0,60,10),low = "white", high = "red") +
    xlab("Day") +
    ylab("Hour") +
    scale_x_continuous(
        breaks = seq(0, 41, by = 7),
        expand = c(0.05, 0.05)
    ) +
    scale_y_continuous(
       breaks = seq(0, 23, by = 3),
       expand = c(0.05, 0.05)
    ) +
    coord_cartesian(xlim=c(0,41),ylim=c(0,23))

Solution

  • I think your second code block ("Answer:") is a good approach, though you may run into problems when a fill is light enough that white lettering on the weekends will be washed out. An alternative is to color the entire day a different background color. This adds both to the weekly flow of the plot and allows you some flexibility with keeping the lettering readable in a variety of colors.

    I'll use the hours, days, and value from the second block (so we have all-positive values) and the logical weekend from the first block. I'll add theme_bw() to simplify the plot, allowing the weekend backgrounds to stand out more appropriately.

    df <- structure(list(hours = c(16, 17, 0, 1, 2, 21, 22, 9, 19, 20), days = c(0, 0, 3, 3, 3, 6, 6, 11, 21, 21), value = c(3, 60, 18, 60, 23, 51, 48, 49, 47, 40), weekend = c(FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE)), class = "data.frame", row.names = c(NA, -10L))
    
    ggplot(df, aes(days, hours)) +
      geom_rect(
        aes(xmin = days-0.5, xmax = days+0.5, ymin = -Inf, ymax = Inf),
        fill = "gray70", data = ~ filter(.x, weekend)) +
      geom_tile(aes(fill = value, width = w, height = h)) +
      geom_text(aes(label = value), size = 3) +
      scale_fill_continuous(limits=c(0,60),breaks=seq(-0,60,10),low = "white", high = "red") +
      scale_x_continuous(
        name = "Day",
        breaks = seq(0, 41, by = 7),
        expand = c(0.05, 0.05)
      ) +
      scale_y_continuous(
        name = "Hour",
        breaks = seq(0, 23, by = 3),
        expand = c(0.05, 0.05)
      ) +
      coord_cartesian(xlim = c(0, 41), ylim = c(0, 23)) +
      theme_bw()
    

    plot with one weekend day highlights

    One thing I don't like there is that only one weekend is highlighted. It might be nice to highlight all weekend days, even without data. I don't know what days are really weekends, I'll assume that since day 3 is a weekend, then day 4 is also a weekend (and weeks before/after those).

    weekends <- data.frame(days=c(3, 4, 10, 11, 17, 18, 24, 25, 31, 32, 38, 39))
    ggplot(df, aes(days, hours)) +
      geom_rect(
        aes(xmin = days-0.5, xmax = days+0.5, ymin = -Inf, ymax = Inf),
        fill = "gray70", data = weekends, inherit.aes = FALSE) +
      geom_tile(aes(fill = value, width = w, height = h)) +
      geom_text(aes(label = value), size = 3) +
      scale_fill_continuous(limits=c(0,60),breaks=seq(-0,60,10),low = "white", high = "red") +
      scale_x_continuous(
        name = "Day",
        breaks = seq(0, 41, by = 7),
        expand = c(0.05, 0.05)
      ) +
      scale_y_continuous(
        name = "Hour",
        breaks = seq(0, 23, by = 3),
        expand = c(0.05, 0.05)
      ) +
      coord_cartesian(xlim = c(0, 41), ylim = c(0, 23)) +
      theme_bw()
    

    same plot with all weekend days highlighted

    If you want to have a legend describing what the background means, we can use ggnewscale::new_scale_fill() to add a second legend:

    ggplot(df, aes(days, hours)) +
      geom_rect(
        aes(xmin = days-0.5, xmax = days+0.5, ymin = -Inf, ymax = Inf, fill = "Weekend"),
        data = weekends, inherit.aes = FALSE) +
      scale_fill_manual(name = NULL, values = c(Weekend = "gray70")) +
      ggnewscale::new_scale_fill() +
      geom_tile(aes(fill = value, width = w, height = h)) +
      geom_text(aes(label = value), size = 3) +
      scale_fill_continuous(limits=c(0,60),breaks=seq(-0,60,10),low = "white", high = "red") +
      scale_x_continuous(
        name = "Day",
        breaks = seq(0, 41, by = 7),
        expand = c(0.05, 0.05)
      ) +
      scale_y_continuous(
        name = "Hour",
        breaks = seq(0, 23, by = 3),
        expand = c(0.05, 0.05)
      ) +
      coord_cartesian(xlim = c(0, 41), ylim = c(0, 23)) +
      theme_bw()
    

    same plot with all weekend days and a simple legend identifying background-gray as a weekend