The above solutions didnt work for me as I had data with multiple intersections, this is what helped me.
This solution introduces a function that interpolates the dataset slightly, namely the intersections are interpolated with the fill_data_gaps()
function:
library(tidyverse)
# finds the intercept between two lines.
# note that C and D are fixed to the same x coords as A and B
find_intercept <- function(x1, x2, y1, y2, l1, l2) {
d <- (x1 - x2) * ((l1 - l2) - (y1 - y2))
a <- (x1*y2 - x2*y1)
b <- (x1*l2 - x2*l1)
px <- (a*(x1 - x2) - (x1 - x2)*b) / d
py <- (a*(l1 - l2) - (y1 - y2)*b) / d
list(x = px, y = py)
}
fill_data_gaps <- function(data, xvar, yvar, levelvar) {
xv <- deparse(substitute(xvar))
yv <- deparse(substitute(yvar))
lv <- deparse(substitute(levelvar))
data <- data %>% arrange({{xvar}}) # not needed?
grp <- ifelse(data[[yv]] >= data[[lv]], "up", "down")
sp <- split(data, cumsum(grp != lag(grp, default = "")))
# calculate the intersections
its <- lapply(seq_len(length(sp) - 1), function(i) {
lst <- sp[[i]] %>% slice(n())
nxt <- sp[[i + 1]] %>% slice(1)
it <- find_intercept(x1 = lst[[xv]], x2 = nxt[[xv]],
y1 = lst[[yv]], y2 = nxt[[yv]],
l1 = lst[[lv]], l2 = nxt[[lv]])
it[[lv]] <- it[["y"]]
setNames(as_tibble(it), c(xv, yv, lv))
})
# insert the intersections at the correct values
for (i in seq_len(length(sp))) {
dir <- ifelse(mean(sp[[i]][[yv]]) > mean(sp[[i]][[lv]]), "up", "down")
if (i > 1) sp[[i]] <- bind_rows(its[[i - 1]], sp[[i]]) # earlier interpolation
if (i < length(sp)) sp[[i]] <- bind_rows(sp[[i]], its[[i]]) # next interpolation
sp[[i]] <- sp[[i]] %>% mutate(.dir = dir)
}
# combine the values again
bind_rows(sp)
}
Create some fake data
N <- 10
set.seed(1235)
data <- tibble(
year = 2000:(2000 + N),
value = c(100, 100 + cumsum(rnorm(N))),
level = c(100, 100 + cumsum(rnorm(N)))
)
data
#> # A tibble: 11 x 3
#> year value level
#> <int> <dbl> <dbl>
#> 1 2000 100 100
#> 2 2001 99.3 99.1
#> 3 2002 98.0 100.
#> 4 2003 99.0 99.4
#> 5 2004 99.1 99.0
#> 6 2005 99.2 98.1
#> 7 2006 101. 98.6
#> 8 2007 101. 99.2
#> 9 2008 102. 98.7
#> 10 2009 103. 98.1
#> 11 2010 103. 98.4
data2 <- fill_data_gaps(data, year, value, level)
data2
#> # A tibble: 15 x 4
#> year value level .dir
#> <dbl> <dbl> <dbl> <chr>
#> 1 2000 100 100 up
#> 2 2001 99.3 99.1 up
#> 3 2001. 99.2 99.2 up
#> 4 2001. 99.2 99.2 down
#> 5 2002 98.0 100. down
#> 6 2003 99.0 99.4 down
#> 7 2004. 99.1 99.1 down
#> 8 2004. 99.1 99.1 up
#> 9 2004 99.1 99.0 up
#> 10 2005 99.2 98.1 up
#> 11 2006 101. 98.6 up
#> 12 2007 101. 99.2 up
#> 13 2008 102. 98.7 up
#> 14 2009 103. 98.1 up
#> 15 2010 103. 98.4 up
Note that we have more rows with interpolated values (eg rows 3, 4, 7, 8).
Then we can use ggplot2::geom_ribbon()
as usual/expected.
ggplot(data2, aes(x = year)) +
geom_ribbon(aes(ymin = level, ymax = value, fill = .dir)) +
geom_line(aes(y = value)) +
geom_line(aes(y = level), linetype = "dashed") +
scale_fill_manual(name = "Dir", values = c("up" = "green", "down" = "red"))
