rggplot2alphaggforce

Same/Fix alpha even for overlapping areas in ggplot2


I would like to first draw a bunch of areas and then have the resulting overall area be displayed with the same single alpha value. So instead of this:

library(tidyverse)

dat <- tribble(
  ~xmin, ~xmax, ~ymin, ~ymax,
     10,    30,    10,    30,
     20,    40,    20,    40,
     15,    35,    15,    25,
     10,    15,    35,    40
)

ggplot() +
  geom_rect(data = dat,
            aes(
              xmin = xmin,
              xmax = xmax,
              ymin = ymin,
              ymax = ymax
            ),
            alpha = 0.5)

I would want to have this as my outcome:

Created on 2022-07-26 by the reprex package (v2.0.1)

I feel like the answer to my question may be similar to the one in this thread but I don't fully understand it and thus am not sure. Also note that I used geom_rect() for the reprex, but ultimately I want this to work for ggforce::geom_circle().

EDIT 1

Quinten's first answer, which points to scale_alpha(range = ..., limits = ...), is unfortunately not answering my question, since it can apparently only result in non-transparent areas.

EDIT 2

Quinten's updated answer is a workaround I could accept for the reprex above. However, as I've mentioned above, too, I want this to work for ggforce::geom_circle(). Unfortunately, I guess I have to be more specific now and create another reprex. (Sorry)

library(ggforce)
#> Lade nötiges Paket: ggplot2

dat <- data.frame(
  x = c(1, 1.3, 1.6),
  y = c(1, 1, 1),
  circle = c("yes", "yes", "no")
)

ggplot() +
  coord_equal() +
  theme_classic() +
  geom_circle(
    data = subset(dat, circle == "yes"),
    aes(x0 = x, y0 = y, r = 0.5, alpha = circle),
    fill = "grey",
    color = NA,
    show.legend = TRUE
  ) +
  geom_point(
    data = dat,
    aes(x, y, color = circle)
  ) +
  scale_color_manual(
    values = c("yes" = "blue", "no" = "red")
  ) +
  scale_alpha_manual(
    values = c("yes" = 0.25, "no" = 0)
  )

Created on 2022-08-17 by the reprex package (v2.0.1)


Solution

  • ggblend

    I also saw ggblend few moments ago, but wasn't sure it would fix your problem, luckily it does! You can do two things:

    1. You can change your Graphics in Rstudio to "Cairo" like this:

    enter image description here

    Code:

    #remotes::install_github("mjskay/ggblend")
    library(ggblend)
    library(ggforce)
    
    # reprex 1 ----------------------------------------------------------------
    library(tidyverse)
    
    dat <- tribble(
      ~xmin, ~xmax, ~ymin, ~ymax,
      10,    30,    10,    30,
      20,    40,    20,    40,
      15,    35,    15,    25,
      10,    15,    35,    40
    )
    
    p1 <- ggplot() +
      geom_rect(data = dat,
                aes(
                  xmin = xmin,
                  xmax = xmax,
                  ymin = ymin,
                  ymax = ymax
                ),
                alpha = 0.3) %>% blend("source")
    
    p1
    

    enter image description here

    # reprex 2 ----------------------------------------------------------------
    dat <- data.frame(
      x = c(1, 1.3, 1.6),
      y = c(1, 1, 1),
      circle = c("yes", "yes", "no")
    )
    
    p2 <- ggplot() +
      coord_equal() +
      theme_classic() +
      geom_circle(
        data = subset(dat, circle == "yes"),
        aes(x0 = x, y0 = y, r = 0.5, alpha = circle),
        fill = "grey",
        color = NA,
        show.legend = TRUE
      ) %>% blend("source") +
      geom_point(
        data = dat,
        aes(x, y, color = circle)
      ) +
      scale_color_manual(
        values = c("yes" = "blue", "no" = "red")
      ) +
      scale_alpha_manual(
        values = c("yes" = 0.25, "no" = 0)
      )
    
    p2
    #ggsave(plot = p2, "p2.pdf", device = cairo_pdf)
    

    enter image description here

    1. You can save the object as a png with type = "cairo" like this:
    library(ggblend)
    library(ggforce)
    
    # reprex 1 ----------------------------------------------------------------
    library(tidyverse)
    
    dat <- tribble(
      ~xmin, ~xmax, ~ymin, ~ymax,
      10,    30,    10,    30,
      20,    40,    20,    40,
      15,    35,    15,    25,
      10,    15,    35,    40
    )
    
    p1 <- ggplot() +
      geom_rect(data = dat,
                aes(
                  xmin = xmin,
                  xmax = xmax,
                  ymin = ymin,
                  ymax = ymax
                ),
                alpha = 0.3) %>% blend("source")
    #> Warning: Your graphics device, "quartz_off_screen", reports that blend = "source" is not supported.
    #>  - If the blending output IS NOT as expected (e.g. geoms are not being
    #>    drawn), then you must switch to a graphics device that supports
    #>    blending, like png(type = "cairo"), svg(), or cairo_pdf().
    #>  - If the blending output IS as expected despite this warning, this is
    #>    likely a bug *in the graphics device*. Unfortunately, several
    #>    graphics do not correctly report their capabilities. You may wish to
    #>    a report a bug to the authors of the graphics device. In the mean
    #>    time, you can disable this warning via options(ggblend.check_blend =
    #>    FALSE).
    #>  - For more information, see the Supported Devices section of
    #>    help('blend').
    
    png(filename = "plot1.png", type = "cairo")
    # Output in your own folder:
    
    p1
    dev.off()
    

    enter image description here 2

    #ggsave(plot = p1, "p1.pdf", device = cairo_pdf)
    
    # reprex 2 ----------------------------------------------------------------
    dat <- data.frame(
      x = c(1, 1.3, 1.6),
      y = c(1, 1, 1),
      circle = c("yes", "yes", "no")
    )
    
    p2 <- ggplot() +
      coord_equal() +
      theme_classic() +
      geom_circle(
        data = subset(dat, circle == "yes"),
        aes(x0 = x, y0 = y, r = 0.5, alpha = circle),
        fill = "grey",
        color = NA,
        show.legend = TRUE
      ) %>% blend("source") +
      geom_point(
        data = dat,
        aes(x, y, color = circle)
      ) +
      scale_color_manual(
        values = c("yes" = "blue", "no" = "red")
      ) +
      scale_alpha_manual(
        values = c("yes" = 0.25, "no" = 0)
      )
    #> Warning: Your graphics device, "quartz_off_screen", reports that blend = "source" is not supported.
    #>  - If the blending output IS NOT as expected (e.g. geoms are not being
    #>    drawn), then you must switch to a graphics device that supports
    #>    blending, like png(type = "cairo"), svg(), or cairo_pdf().
    #>  - If the blending output IS as expected despite this warning, this is
    #>    likely a bug *in the graphics device*. Unfortunately, several
    #>    graphics do not correctly report their capabilities. You may wish to
    #>    a report a bug to the authors of the graphics device. In the mean
    #>    time, you can disable this warning via options(ggblend.check_blend =
    #>    FALSE).
    #>  - For more information, see the Supported Devices section of
    #>    help('blend').
    
    png(filename = "plot2.png", type = "cairo")
    
    # Output in your folder
    p2
    

    enter image description here

    dev.off()
    #ggsave(plot = p2, "p2.pdf", device = cairo_pdf)
    

    Created on 2022-08-17 with reprex v2.0.2

    Update

    What you could do is take the UNION of the areas using st_union from the sf package so you get one area instead of overlapping areas like this:

    library(tidyverse)
    library(sf)
    
    dat <- tribble(
      ~xmin, ~xmax, ~ymin, ~ymax,
      10,    30,    10,    30,
      20,    40,    20,    40,
      15,    35,    15,    25,
      10,    15,    35,    40
    )
    
    area1 <- dat %>%
      slice(1) %>%
      as_vector() %>%
      st_bbox() %>%
      st_as_sfc()
    
    area2 <- dat %>%
      slice(2) %>%
      as_vector() %>%
      st_bbox() %>%
      st_as_sfc()
    
    area3 <- dat %>%
      slice(3) %>%
      as_vector() %>%
      st_bbox() %>%
      st_as_sfc()
    
    area4 <- dat %>%
      slice(4) %>%
      as_vector() %>%
      st_bbox() %>%
      st_as_sfc()
    
    all_areas <- st_union(area1, area2) %>%
      st_union(area3) %>%
      st_union(area4)
    
    ggplot(all_areas) +
      geom_sf(alpha = 0.5, fill = "grey", colour = "grey") +
      theme(legend.position = "none")
    

    ggplot(all_areas) +
      geom_sf(alpha = 0.8, fill = "grey", colour = "grey") +
      theme(legend.position = "none")
    

    Created on 2022-08-17 by the reprex package (v2.0.1)

    First answer

    Maybe you want this where you can use a scale_alpha with range and limits to keep the area in the same alpha like this:

    library(tidyverse)
    
    dat <- tribble(
      ~xmin, ~xmax, ~ymin, ~ymax,
      10,    30,    10,    30,
      20,    40,    20,    40,
      15,    35,    15,    25,
      10,    15,    35,    40
    )
    
    ggplot() +
      geom_rect(data = dat,
                aes(
                  xmin = xmin,
                  xmax = xmax,
                  ymin = ymin,
                  ymax = ymax,
                  alpha = 0.5
                )) +
      scale_alpha(range = c(0, 1), limits = c(0, 0.5)) +
      theme(legend.position = "none")
    

    Created on 2022-07-26 by the reprex package (v2.0.1)