I struggle to understand (and control) the blending of alphas - unfortunately, alpha values don't simply "add up" (0.5 + 0.5 is not 1). But how could I achieve that?
The aim is to define the (absolute) grey value of overlapping areas relative to the total number of observations. See example below.
I tried to set scale_alpha(range = c(0,1))
to no avail, maybe I did not use it correctly.
library(ggplot2)
library(ggforce)
grid_df = data.frame(x = c(1:2, 2.5), y = rep(1,3), r = 1)
ggplot()+
geom_circle(data = grid_df, mapping = aes(x0 = x, y0 = y, r = r), alpha = 0.33, fill = 'black') +
coord_fixed()
First off, +1 to @JonSpring—this is just an expansion of the idea at the end of their answer. If you make an sf
object, you can easily get the intersections of polygons. What you end up plotting isn't the circles themselves, but the polygons that come from splitting apart the intersecting pieces.
Starting from your grid, make a point for each row, convert that to a sf
data frame, then take the buffer of the points at the radius given in the column r
. This turns each point into a circle centered at the point's coordinates, and is flexible for different radii. Between the 3 circles are 6 intersecting polygons, as shown in the result.
library(dplyr)
library(sf)
library(ggplot2)
library(ggforce)
grid_df <- data.frame(x = c(1:2, 2.5), y = rep(1,3), r = 1)
grid_sf <- grid_df %>%
mutate(geometry = purrr::map2(x, y, ~st_point(c(.x, .y)))) %>%
st_as_sf() %>%
st_buffer(dist = .$r, nQuadSegs = 60) %>%
st_intersection()
grid_sf
#> Simple feature collection with 6 features and 5 fields
#> geometry type: GEOMETRY
#> dimension: XY
#> bbox: xmin: 0 ymin: 0 xmax: 3.5 ymax: 2
#> epsg (SRID): NA
#> proj4string: NA
#> x y r n.overlaps origins geometry
#> 1 1.0 1 1 1 1 POLYGON ((1.5 0.1339746, 1....
#> 1.1 1.0 1 1 2 1, 2 POLYGON ((1.75 0.3386862, 1...
#> 2 2.0 1 1 1 2 MULTIPOLYGON (((2.258819 0....
#> 1.2 1.0 1 1 3 1, 2, 3 POLYGON ((2 1, 1.999657 0.9...
#> 2.1 2.0 1 1 2 2, 3 POLYGON ((3 1, 2.999657 0.9...
#> 3 2.5 1 1 1 3 MULTIPOLYGON (((3.5 1, 3.49...
Use that n.overlaps
column that comes from st_intersection
to assign alpha. By default, alpha will scale from 0 to 1, but I figure you don't actually want a 0 alpha for the outer, non-overlapped parts of circles, so I scale it to get a minimum alpha.
alpha_range <- range(grid_sf$n.overlaps) / max(grid_sf$n.overlaps)
grid_sf %>%
ggplot() +
geom_sf(aes(alpha = n.overlaps), fill = "black") +
scale_alpha(range = alpha_range)
Just to expand a bit further and make the different polygons a bit more clear, take a look with a discrete fill scale instead of alpha:
grid_sf %>%
ggplot() +
geom_sf(aes(fill = as.factor(n.overlaps))) +
scale_fill_brewer(palette = "YlGnBu")
Adding to @MKBakker's answer, one could use a function to predict the resulting alpha from any number of layers and alpha values:
alpha_out <- function(alpha, num = 1) {
result = alpha
if(num == 1) return(result)
for(i in 2:num) { result = result + alpha * (1-result) }
return (result)
}
alpha_out(0.33, 1)
#[1] 0.33
alpha_out(0.33, 2)
#[1] 0.5511
alpha_out(0.33, 3)
#[1] 0.699237
This makes it easier to see that alpha asymptotically approaches 1 with more layers.
alpha_out(0.33, 40)
#[1] 0.9999999
If one presumes that 0.99 is "close enough," you need to use 0.8 to get there with three layers
alpha_out(0.8, 3)
#[1] 0.992
EDIT: Added chart of results
We can see what results we'd get from a range of alphas and layers:
library(tidyverse)
alpha_table <-
tibble(
alpha = rep(0.01*1:99, 10),
layers = rep(1:10, each = 99)
)
alpha_table <- alpha_table %>%
rowwise() %>%
mutate(result = alpha_out(alpha, layers))
ggplot(alpha_table, aes(alpha, result, color = as_factor(layers),
group = layers)) +
geom_line()
And we can also see how much alpha we need to pass a threshold of combined opacity, given each number of layers. For instance, here's how much alpha you need to reach 0.99 total opacity for a given number of layers. For 5 layers, you need alpha = 0.61
, for instance.
alpha_table %>%
group_by(layers) %>%
filter(result >= 0.99) %>%
slice(1)
## A tibble: 10 x 3
## Groups: layers [10]
# alpha layers result
# <dbl> <int> <dbl>
# 1 0.99 1 0.99
# 2 0.9 2 0.99
# 3 0.79 3 0.991
# 4 0.69 4 0.991
# 5 0.61 5 0.991
# 6 0.54 6 0.991
# 7 0.49 7 0.991
# 8 0.44 8 0.990
# 9 0.41 9 0.991
#10 0.37 10 0.990
All this to say that I don't think there is a simple implementation to get what you're looking for. If you want 100% dark in the overlapped area, you might try these approaches:
image manipulation after the fact (perhaps doable using imagemagick
) to apply a brightness curve to make the dark areas 100% black and make the others scale to the darkness levels you expect.
convert the graph to an sf
object and analyze the shapes to somehow count how many shapes are overlapping at any given point. You could then manually map those to the darkness levels you want.
Alpha can be added using the following approach (https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending)
The alpha of two merged shapes is calculated as follows:
A(out) = A(src) + A(dst) * 1-A(src)
Hence, for A(src) = A(dst) = 0.33, we get:
x = 0.33
y = x + x*(1-x)
y
[1] 0.5511
And if we have three shapes, with A = 0.33, we induce:
y = x + x*(1-x) + x*(1-(x + x*(1-x)))
y
[1] 0.699237
I could go on about which values will result in 1 when adding 2 or 3 shapes together, but the most useful comment is that alphas are not combined in an additive way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With