geom_ribbon colors outside of the lines

This question is somewhat similar to this unanswered older question, but I have a reprex.

The issue is that when I plot a ribbon using geom_ribbon(), in cases where the upper and lower line meet, you can see that the ribbon colors outside of the lines. The only fix that I can think of is to turn each section of ribbon into a polygon and draw each polygon separately. That is a lot of work for this lazy programmer when there exists a geom_ribbon()!

Interestingly, I tried set.seed(123) first, and that produced a good test data set. None of the several other seeds I tried out of curiosity subsequently did.

library(tidyverse)

set.seed(123)

test_data <- tibble(
  date = as_date(as.numeric(as_date("20240101")):as.numeric(as_date("20240131")))
) |> mutate(
  line1 = 10 + cumsum(rnorm(n=31, mean=0, sd=3)),
  line2 = 8 + cumsum(rnorm(n=31, mean=0, sd=3)),
  lower = pmin(line1, line2),
  upper = pmax(line1, line2),
  direction = if_else(line1>line2, "above", "below")
)

# single fill color
ggplot(test_data, aes(x=date)) + 
  geom_ribbon(aes(ymin=lower, ymax=upper), fill="#FF6A6A") +
  geom_line(aes(y=line1), color="darkblue") + 
  geom_line(aes(y=line2), color="darkred")

# fill color based on which line is on top
ggplot(test_data, aes(x=date)) + 
  geom_ribbon(aes(ymin=lower, ymax=upper, fill=direction)) +
  geom_line(aes(y=line1), color="darkblue") + 
  geom_line(aes(y=line2), color="darkred")

# not run because geom_ribbon doesn't support groups, but wouldn't it be nice if it did?
#test_data <- test_data |> mutate(group = cumsum(as.numeric(direction == lag(direction, default="NONE"))))
#ggplot(test_date, aes(x=date)) + 
#  geom_ribbon(aes(ymin=lower, ymax=upper, fill=direction, group=group)) +
#  geom_line(aes(y=line1)) + 
#  geom_line(aes(y=line2))

Plot with switching fill color. The single fill color plot has the same problem, but it is more obvious here.

Sorry, I got confused while writing and accidentally answered a different question. For your original question with two colors, I can't think of any easy way: the change of color has to happen at the crossing points, so these need to be present in the data.

With one color, it's easy if you don't use lower and upper but keep line1 and line2:

ggplot(test_data, aes(x=date)) + 
  geom_ribbon(aes(ymin=line1, ymax=line2), fill="#FF6A6A") +
  geom_line(aes(y=line1), color="darkblue") + 
  geom_line(aes(y=line2), color="darkred")

For two colors, the closest I can get is this to have two ribbons, and plot one when line1 is higher, and the other when line2 is higher. It's close, but it still gets outside the lines around crossing points (since the initial data does not contain the crossing points themselves):

test_data |>
  mutate(line1_up = if_else(line1 > line2, line1, line2),
         line2_up = if_else(line2 > line1, line2, line1)) |>
  ggplot(aes(x=date)) + 
  geom_ribbon(aes(ymin = line2, ymax = line1_up),
              fill = "#F8766D", alpha = .4) + 
  geom_ribbon(aes(ymin = line1, ymax = line2_up),
              fill = "#00BFC4", alpha = .4) +
  geom_line(aes(y=line1), color="darkblue") + 
  geom_line(aes(y=line2), color="darkred")

I've had a similar problem before and I think the information provided in this link will fulfill your needs:
GH

ggh4x::stat_difference OR ggbraid::geom_braid

2 Likes

Awesome! ggh4x::stat_difference and ggbraid::geom_braid do what I want.

Update: ggbraid hasn't been updated in 3 years and is not on CRAN, so I've settled on ggh4x.