0

take the following data

df <- data.frame(ID = c(1, 1, 2, 2, 3, 3),
                 cond = c(2, 2, 2, 2, 1, 1),
                 Time = c(1,2,1,2,1,2),
                 State  = c(0, 0, 0, 1, 1, 0),
                 eyes = c(2, -3, -2, 1, 0, -2),
                 combination = c("1", "1","2", "2", "3", "3"))
df$cond <- factor(df$cond,levels = c("1", "2"))
ggplot(df, aes(x = Time, y = eyes)) +
  ggforce::geom_link2(aes(group=ID, color = cond, alpha = State), size = 5, n = 500, lineend = "round") +
  scale_color_manual(values=c("#CC6666", "#9999CC")) +
  scale_alpha_continuous(range = c(0.01, 0.9)) +
  scale_shape_manual(values=c(13,15)) +
  
    theme(legend.title=element_blank(),
        axis.title.x=element_text(size = 12),
        axis.title.y=element_text(size = 12),
        plot.title = element_text(hjust = 0.5, size = 15),
        axis.text.x= element_text(color = "black"),
        axis.text.y= element_text(color = "black"),
        axis.line = element_line(colour = "black"),
        plot.background = element_rect(fill = "white"),
        panel.background = element_rect(fill = "white"))

This produces the following picture: enter image description here

I am trying to make line 2 and 3 appear / disappear across the x-axis by a continuous change in alpha. Although the lines are drawn between two values (0, 1), the alpha does not seem to change evenly across the line (start as 0.01 alpha at value 0, and 0.9 alpha at value 1) . Instead, the lines almost immediately grows to the full 0.9 alpha - why is this and how do I fix it?

Icewaffle
  • 443
  • 2
  • 13
  • 1
    You could fix it by interpolating the points in between and assign values of `0.1`, `0.2`, etc. to them. Then, ggplot would be forced to make the change more continuous. At the moment, you only define the endpoints of the lines and their alphas. You pass no information on how the gradient should be visualized. – Martin Wettstein Jun 21 '21 at 13:01
  • This is a helpful partial answer that explains why ggplot doesn't visualize it as I expect it to - but how do I interpolate points between the assigned values? – Icewaffle Jun 21 '21 at 13:29
  • 1
    I don't have a fundamental fix, but it might be an aesthetic improvement here to use `lineend = "butt"`, thus avoiding the overlapping regions between segments. Especially when `n` is high, each segment with round or square lineends will be overlapped many times, resulting in a darker result than expected: https://stackoverflow.com/questions/57394946/control-alpha-blending-opacity-of-n-overlapping-areas – Jon Spring Jun 23 '21 at 23:25

3 Answers3

1

This effect occurs because the 500 points linked by geom_link2() add additional alpha to the plot.

To illustrate, we can repeat the plot with only 20 points which then become clearly visible:

ggplot(df, aes(x = Time, y = eyes)) +
  ggforce::geom_link2(aes(group = ID, color = cond, alpha = State),
                      size = 5, n = 20, lineend = "round") +
  scale_color_manual(values = c("#CC6666", "#9999CC")) +
  scale_alpha_continuous(range = c(0.01, 0.9)) +
  scale_shape_manual(values = c(13, 15))

Output:

gradient with fewer points

As far as I know, this is a limitation of the ggforce::geom_link() family of functions. You could, however, interpolate the points and try to calculate an adjusted alpha value (e.g. alpha ^ 2) that will look acceptable for your data.

ktiu
  • 2,606
  • 6
  • 20
1

Here's an approach adapted from the linked answer in @aosmith's comment to your related question: https://stackoverflow.com/a/17794763/2461552.

I divide the segments into 100 pieces and use formulas to define two fade-in gradients, each going to zero 25% of the way in from the State=0 end. (in the case of the first line, State 0 at both ends, I arbitrarily use a sine pattern.

seg = 100
df %>%
  group_by(ID) %>%
  summarize(minTime = first(Time), maxTime = last(Time),
            mineyes = first(eyes), maxeyes = last(eyes),
            firstFull = first(State), endFull = last(State)) %>%
  uncount(seg, .id = "frame") %>%
  mutate(frame = (frame - 1)/seg) %>%
  mutate(Time = maxTime * frame + minTime * (1-frame),
         eyes  = maxeyes * frame  + mineyes * (1-frame),
         alpha_OP = case_when(
           firstFull & !endFull ~ 1 - 1.33*frame,
           !firstFull & endFull ~ 1.33*frame - 0.33,
           TRUE ~ sin(frame*50)*0.5  # this is just for fun
         ) %>% pmax(0)) %>%
  
  ggplot(aes(Time, eyes, alpha = alpha_OP, group = ID)) +
  ggforce::geom_link2() +
  scale_alpha(range = c(0,1))
  

enter image description here

EDIT: Variation in fade that's a little smoother. Tweak the ^2 power to shift the midpoint of the alpha.

....
alpha_OP = case_when(
               firstFull & !endFull ~ 1 - frame^2
               !firstFull & endFull ~ frame^2,
               TRUE ~ sin(frame*50)*0.5  # this is just for fun
             ) %>% pmax(0)) %>%
...

enter image description here

Jon Spring
  • 55,165
  • 4
  • 35
  • 53
  • Perfect, this is the solution I was looking for but could not figure out. – Icewaffle Jun 25 '21 at 14:13
  • btw I just fixed a mistake in my "EDIT:" section where I had pasted the wrong code. Now it aligns with the pictured result. – Jon Spring Jun 25 '21 at 16:59
  • When I use the 'edit' fix, I no longer get the output as above - the 2nd line no longer 'fade out' at 1.75 but instead goes all the way to 2.00. The unedited version works well though, but I find that when I set line size to = 5, suddenly the lines have white vertical lines through them a few places - is there a way to avoid this? – Icewaffle Jun 25 '21 at 22:43
0

If you do not just define the end-points of the lines but the whole line using interpolation, you can set the color and alpha for segments of the line. In your example, you would just need to use sequences to get from the starting value to its end:

df <- data.frame(ID = c(rep(1,11),rep(2,11),rep(3,11)),
                 cond = c(rep(2,11),rep(2,11),rep(1,11)),
                 Time = c(seq(1,2,.1),seq(1,2,.1),seq(1,2,.1)),
                 State = c(rep(0,11),seq(0,1,.1),seq(1,0,-.1)),
                 eyes = c(seq(2,-3,-.5),seq(-2,1,.3),seq(0,-2,-.2)))
df$combination = as.character(df$ID)
df$cond = as.factor(df$cond)

However, the ggforce::geom_link2 you use to draw the lines does not seem to be able to make a nice gradient of the alphas in spite of the State variable taking values betweeen 0 and 1.

Martin Wettstein
  • 2,771
  • 2
  • 9
  • 15
  • As you yourself mention this does not solve the problem since it does not work with geom_link2 - do you have a suggestion for an alternative function to draw the lines other than geom_ink2 that is compatible with your suggestion? – Icewaffle Jun 22 '21 at 09:22
  • No, I have never done a line plot with varying alphas. Is there no other way to visualize all your data? – Martin Wettstein Jun 22 '21 at 10:38