Search code examples
rggplot2

ggplot2 x-axis with many hours for each of many days. Is there a way to span the dates in the x-axis over the hours for that day?


I have a dataset which runs over many days. For each day, I have multiple hours. In this toy example, I am looking for 3 days and, for each of the three days, I am looking at the data for 13 individual hours (from 6am to 6pm).

I want to capture both the date and the hour in the x-axis. However, rather than repeat the date for each of the 13 individuals hours, I'd like to span the date across those hours, something like tab_spanner in gt.

For example, consider this sample code:

library(ggplot2)

exp<-data.frame(
  date = c(rep("2025-02-01", 13), rep("2025-02-02", 13),rep("2025-02-03", 13)),
  hour = rep(seq(6, 18, 1),3),
  obs = runif(13*3, min=1, max=50)
)

exp |>
  mutate(
    date.hour = factor(paste0(date, " ", hour, " hour"), levels=unique(paste0(date, " ", hour, " hour")), ordered=TRUE) 
  ) |>
  ggplot(aes(x=date.hour, y=obs, group=1)) +
  geom_line() + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

This will generate the following graph:

date+hour in x-axis

However, what I am wondering is if it is possible to span the date across the hours, to create a graph that looks something like this:

date spanned for hours in x-axis


Solution

  • Using the recently released legendry package you can achieve your desired result easily using guide_axis_nested like so, i.e. map the interaction of date and hour on x, set the delimiter to split the variables using key_range_auto and set the style for each level of axis labels using levels_text= which expects a list of element_text elements.

    library(ggplot2)
    library(legendry)
    
    exp |>
      ggplot(
        aes(
          x = interaction(hour, date),
          y = obs, group = 1
        )
      ) +
      geom_line() +
      guides(x = guide_axis_nested(
        key = key_range_auto(sep = "\\."),
        levels_text = list(
          element_text(angle = 90, vjust = 0.5, hjust = 1),
          element_text(angle = 0, hjust = .5)
        )
      ))
    

    enter image description here