Alessandro Jacopson
Alessandro Jacopson

Reputation: 18593

In ggplot2, how can avoid geom_rect to extend the limits of the plot?

enter image description here

With the attached code snippet, geom_rect extends the limit of the plot, i.e., the vertical band for year 2016 is plotted even if no data are contained in that interval.

I would like the rectangles be drawn only if they contain the data points.

I read the help for annotate and it explicitly says that annotate has exactly the behavior I see with geom_rect and that I would like to avoid.

Details

Note that all position aesthetics are scaled (i.e. they will expand the limits of the plot so they are visible), but all other aesthetics are set. This means that layers created with this function will never affect the legend.

library(ggplot2)
days<-rep(Sys.Date(),100)+seq(1,100)
v<-sin(as.numeric(days))
df<-data.frame(days=c(days,days),v=c(v,cos(v)+.1),n=c(rep('a',100),rep('b',100)))

shade <- data.frame(x1=c(as.Date('2016-10-15'),as.Date('2017-11-11')), 
                    x2=c(as.Date('2016-10-20'),as.Date('2017-11-13')), 
                    y1=c(-Inf,-Inf), y2=c(Inf,Inf))

plot(ggplot(df, aes(x=days, y=v, colour=n)) +
  geom_line() +
  geom_rect(data=shade, inherit.aes = F,
            aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), 
            color = 'grey', alpha=0.2) +
  geom_point())

Upvotes: 1

Views: 2149

Answers (3)

Nate
Nate

Reputation: 10671

Would setting limits based on range(df$days) work for your situation?

my_limits <- range(df$days)

ggplot(df, aes(x=days, y=v, colour=n)) +
    geom_line() +
    geom_rect(data=shade, inherit.aes = F,
           aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), 
           color = 'grey', alpha=0.2) +
    geom_point() +
    scale_x_date(limits = my_limits) # See scale_x_datetime in case the data has time too.

enter image description here

Upvotes: 2

MrGumble
MrGumble

Reputation: 5766

Just filter shade before giving it to ggplot:

library(dplyr)
library(ggplot2)
days<-rep(Sys.Date(),100)+seq(1,100)
v<-sin(as.numeric(days))
df<-data.frame(days=c(days,days),v=c(v,cos(v)+.1),n=c(rep('a',100),rep('b',100)))

shade <- data.frame(x1=c(as.Date('2016-10-15'),as.Date('2017-11-11')), 
                    x2=c(as.Date('2016-10-20'),as.Date('2017-11-13')), 
                    y1=c(-Inf,-Inf), y2=c(Inf,Inf)) %>%
  filter(between(x1, min(df$days), max(df$days)), between(x2, min(df$days), max(df$days)))

plot(ggplot(df, aes(x=days, y=v, colour=n)) +
       geom_line() +
       geom_rect(data=shade, inherit.aes = F,
                 aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), 
                 color = 'grey', alpha=0.2) +
       geom_point())

Upvotes: 1

pogibas
pogibas

Reputation: 28309

My solution creates rectangle plot and adds it to the main plot if days are within interval of shade. Date manipulation is performed using lubridate package.

# Main plot that doesn't change depending on time interval
pMain <- ggplot(df, aes(days, v, colour = n)) +
    geom_line() +
    geom_point()

# Time manipulation part
library(lubridate)
# Turn character string to dates
df$days <- ymd(df$days)
shade$x1 <- ymd(shade$x1)
shade$x2 <- ymd(shade$x2)

# Check which shade entries contain days 
foo <- c()
for(i in nrow(shade)) {
    bar <- any(df$days %within% interval(shade[i, ]$x1, shade[i, ]$x2))
    if (bar) {
        foo <- c(foo, i)
    }
}

# If any interval contained days
# Create rectangle plot and add it to the main plot
CONTAINS <- length(foo) > 0
if (CONTAINS) {
    pRect <-  geom_rect(data=shade[foo, ], inherit.aes = F,
                aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), 
                color = 'grey', alpha=0.2)
    pMain <- pMain + pRect
}
plot(pMain)

enter image description here

Upvotes: 1

Related Questions