Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to blur part of a plot in ggplot?

Tags:

r

ggplot2

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.

like image 239
Emman Avatar asked Jul 22 '21 09:07

Emman


Video Answer


3 Answers

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)
like image 80
teunbrand Avatar answered Nov 03 '22 00:11

teunbrand


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

like image 35
zx8754 Avatar answered Nov 02 '22 22:11

zx8754


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?

like image 37
Emman Avatar answered Nov 02 '22 23:11

Emman