5

Is there a way to blur a certain part of a plot in ggplot?

For example, consider the following plot:

library(ggplot2)
library(magrittr)

p <- 
  mtcars %>%
  ggplot(aes(x = disp, y = mpg)) +
  geom_line() +
  theme_minimal() +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_blank(),
        panel.background = element_blank()) 

p +
  annotate("rect", xmin = 100, xmax = 200, ymin = 15, ymax = 30,
           alpha = .1,fill = "red") 

Created on 2021-07-22 by the reprex package (v2.0.0)


Desired Output

I want to apply a blur effect in p over the area where there's currently a red rectangle. A demonstration of what I'm looking for:

demo with blur

Is this even possible?


Reference: Similar question (and answer) in python.

Emman
  • 3,695
  • 2
  • 20
  • 44
  • 3
    You might want to look into the [ggfx](https://ggfx.data-imaginist.com/) package – teunbrand Jul 22 '21 at 10:00
  • I'd plot data that needs blurring as a separate layer, then apply blur to that layer – zx8754 Jul 22 '21 at 10:12
  • @zx8754, I'm not sure I know how to translate your suggestion to code. – Emman Jul 22 '21 at 10:24
  • 2
    Testing it out myself at the moment, but the idea is we have 2 datasets, one normal geom_line(d1), 2nd dataset is wrapped into with_blur(geom_line(d2)) – zx8754 Jul 22 '21 at 10:26
  • 1
    why don't you make your first attempt an answer for better visibility and also makes the question/answer thing a bit more clear. Also one can upvote this attempt as well. It deserves it. – tjebo Jul 22 '21 at 10:39
  • 1
    taking it from your example, I'd probably generate a theme_void plot with your blurred patch and superimpose it onto the first plot with either cowplot or patchwork. But I really haven't even started to look into that wicked looking ggfx package. – tjebo Jul 22 '21 at 10:45

3 Answers3

7

You can use ggfx' masks with a geom_rect() to determine a region of where to apply an effect. Example below:

library(ggplot2)
library(magrittr)
library(ggfx)

# Plot without line
p <- 
  mtcars %>%
  ggplot(aes(x = disp, y = mpg)) +
  theme_minimal() +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_blank(),
        panel.background = element_blank()) 

# Add mask as a reference
p + 
  as_reference(
    geom_rect(
      aes(xmin = 100, xmax = 200, ymin = 15, ymax = 30),
      inherit.aes = FALSE
    ),
    id = "draw_area" # <- set some name
  ) +
  # Plot blurry part with mask
  with_mask(
    with_blur(geom_line(), sigma = 5),
    mask = ch_alpha("draw_area")
  ) +
  # Plot crisp part with inverted mask
  with_mask(
    geom_line(),
    mask = ch_alpha("draw_area"),
    invert = TRUE
  )

Created on 2021-07-22 by the reprex package (v1.0.0)

If you plan on using this a lot, you can make a wrapping function like so:

# trbl: Top, Right, Bottom, Left
area_effect <- function(layer, effect = with_blur, ..., trbl) {
  id <- rlang::hash(trbl)
  ref <- as_reference(
    annotate(
      "rect", xmin = trbl[4], xmax = trbl[2], ymin = trbl[3], ymax = trbl[1]
    ),
    id = id
  )
  mask1 <- with_mask(
    effect(layer, ...),
    mask = ch_alpha(id)
  )
  mask2 <- with_mask(
    layer, mask = ch_alpha(id), invert = TRUE
  )
  list(
    ref, mask1, mask2
  )
}

p +
  area_effect(geom_line(), trbl = c(30, 200, 15, 100), sigma = 5)
teunbrand
  • 33,645
  • 4
  • 37
  • 63
  • Since I do plan to use this a lot, I'm very excited with your `area_effect()`. However, trying this on a different plot leads to undesired graphical distortion. `p_mpg <- mpg %>% ggplot(aes(x = manufacturer, y = cty)) + geom_line() ; p_mpg_with_blur <- p_mpg + area_effect(geom_line(), trbl = c(20, 6, 10, 0.5), sigma = 5)`. You can see [here](https://i.stack.imgur.com/uL1KL.png) that in `p_mpg_with_blur`, the lines outside the blurry area became strangely looking. – Emman Jul 22 '21 at 12:34
  • Actually, the same problem occurs also with your first method. So maybe it's an issue with `ggfx`? – Emman Jul 22 '21 at 12:37
  • Agreed, I think this might be an artefact of converting to rasters/pngs that happens under the hood. The lines are generally thicker. The lines on the right look like they might be weird due to overplotting and thereby stacking transparency values. – teunbrand Jul 22 '21 at 13:36
5

We can split the data into two: data for normal plotting, data for blurring:

library(ggplot2)
library(ggfx)

d1 <- mtcars
d1 <- d2 <- d1[ order(d1$disp), ]

ix1 <- range(which(d1$disp > 100 & d1$disp < 200)) + c(1, -1)
ix2 <- which(d1$disp < 100 | d1$disp > 200)

d1[ ix1[ 1 ]:ix1[ 2 ], "mpg" ] <- NA
d2[ ix2, "mpg" ] <- NA

ggplot() +
  geom_line(aes(x = disp, y = mpg), d1) +
  with_blur(
    geom_line(aes(x = disp, y = mpg), d2),
    sigma = unit(1, 'mm')
  )

enter image description here

zx8754
  • 52,746
  • 12
  • 114
  • 209
2

Here's my first attempt, using {ggfx} library, but still not quite the desired output.

library(ggplot2)
library(magrittr)
library(ggfx)

p_all_blurry <- 
  mtcars %>%
  ggplot(aes(x = disp, y = mpg)) +
  with_blur(
    geom_line(),
    sigma = unit(1, 'mm')
  )+
  theme_minimal() +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_blank(),
        panel.background = element_blank()) 

p_all_blurry

Created on 2021-07-22 by the reprex package (v2.0.0)

How could I limit the blurry area to where the red rectangle (in the question) is?

Emman
  • 3,695
  • 2
  • 20
  • 44