Skip to content

Instantly share code, notes, and snippets.

@z3tt
Last active January 23, 2025 12:46
Show Gist options
  • Save z3tt/8b2a06d05e8fae308abbf027ce357f01 to your computer and use it in GitHub Desktop.
Save z3tt/8b2a06d05e8fae308abbf027ce357f01 to your computer and use it in GitHub Desktop.
Polished raincloud plot using the Palmer penguins data
library(dplyr)
library(forcats)
library(ggplot2)
library(palmerpenguins)
library(ggtext)
library(colorspace)
library(ragg)
url <- "https://raw.githubusercontent.com/allisonhorst/palmerpenguins/master/man/figures/lter_penguins.png"
img <- magick::image_read((url))
pic <- grid::rasterGrob(img, interpolate = TRUE)
pal <- c("#FF8C00", "#A034F0", "#159090")
add_sample <- function(x) {
return(c(y = max(x) + .025,
label = length(x)))
}
penguins |>
group_by(species) |>
mutate(bill_ratio = bill_length_mm / bill_depth_mm) |>
filter(!is.na(bill_ratio)) |>
ggplot(aes(x = fct_rev(species), y = bill_ratio)) +
ggdist::stat_halfeye(
aes(color = species,
fill = after_scale(lighten(color, .5))),
adjust = .5,
width = .75,
.width = 0,
justification = -.4,
point_color = NA
) +
geom_boxplot(
aes(color = stage(species, after_scale = darken(color, .1, space = "HLS")),
fill = after_scale(desaturate(lighten(color, .8), .4))),
width = .42,
outlier.shape = NA
) +
geom_point(
aes(color = stage(species, after_scale = darken(color, .1, space = "HLS"))),
fill = "white",
shape = 21,
stroke = .4,
size = 2,
position = position_jitter(seed = 1, width = .12)
) +
geom_point(
aes(fill = species),
color = "transparent",
shape = 21,
stroke = .4,
size = 2,
alpha = .3,
position = position_jitter(seed = 1, width = .12)
) +
stat_summary(
geom = "text",
fun = "median",
aes(label = round(after_stat(y), 2),
color = stage(species, after_scale = darken(color, .1, space = "HLS"))),
family = "Roboto Mono",
fontface = "bold",
size = 4.5,
vjust = -3.5
) +
stat_summary(
geom = "text",
fun.data = add_sample,
aes(label = paste("n =", after_stat(label)),
color = stage(species, after_scale = darken(color, .1, space = "HLS"))),
family = "Roboto Condensed",
size = 4,
hjust = 0
) +
coord_flip(xlim = c(1.2, NA), clip = "off") +
annotation_custom(pic, ymin = 2.9, ymax = 3.85, xmin = 2.7, xmax = 4.7) +
scale_y_continuous(
limits = c(1.57, 3.8),
breaks = seq(1.6, 3.8, by = .2),
expand = c(.001, .001)
) +
scale_color_manual(values = pal, guide = "none") +
scale_fill_manual(values = pal, guide = "none") +
labs(
x = NULL,
y = "Bill ratio",
title = "Bill Ratios of Brush–Tailed Penguins (*Pygoscelis* spec.)",
subtitle = "Distribution of bill ratios, estimated as bill length divided by bill depth.",
caption = "Gorman, Williams & Fraser (2014) *PLoS ONE* DOI: 10.1371/journal.pone.0090081<br>Visualization: Cédric Scherer &bull; Illustration: Allison Horst"
) +
theme_minimal(base_family = "Zilla Slab", base_size = 15) +
theme(
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_text(family = "Roboto Mono"),
axis.text.y = element_text(
color = rev(darken(pal, .1, space = "HLS")),
size = 18
),
axis.title.x = element_text(margin = margin(t = 10),
size = 16),
plot.title = element_markdown(face = "bold", size = 21),
plot.subtitle = element_text(
color = "grey40", hjust = 0,
margin = margin(0, 0, 20, 0)
),
plot.title.position = "plot",
plot.caption = element_markdown(
color = "grey40", lineheight = 1.2,
margin = margin(20, 0, 0, 0)),
plot.margin = margin(15, 15, 10, 15)
)
@Creeki
Copy link

Creeki commented Oct 28, 2024

Superb Cédric!

@francisvolh
Copy link

francisvolh commented Oct 29, 2024

Hi Cédric, thanks for the awesome work! I have been able to use it for some plots I wanted to make. Thanks!!

I have a couple of quick questions for you though. First of all I get a few warnings (some are font related, which are not supported on my Windows database, so very unimportant) and one is

'1: Vectorized input to element_text() is not officially supported.'
And I think it may come from line 99 color = rev(darken(pal, .1, space = "HLS")), because if I comment out that axis.text.y = warning goes away.

Also, I am trying to avoid loading libraries in all my scripts, and just calling the function from the library like ggplot2::ggplot(). If I do this, the ggplot2::stage in the geom_boxplot (or any other stage) does not recognize the species as an object, on line 35 for example. If I simply define color = species (with no stage, it works.

I guess this is all unimportant! but I wondered if you hade any comments about this! thanks!

ps: I have a forked gist of what I have done if you have a minute to see it here

@z3tt
Copy link
Author

z3tt commented Nov 4, 2024

Thanks for your feedback @francisvolh 🙌

cf. warning fonts:
Exactly, the fonts need to be installed locally. All font files are available via GoogleFonts:
https://fonts.google.com/specimen/Zilla+Slab
https://fonts.google.com/specimen/Roboto+Condensed
https://fonts.google.com/specimen/Roboto+Mono

cf. warning element_text:
Yes, vectorized inputs are not supported for theme elements but work! The warning means the resulting behavior is not guaranteed and the trick might stop working at some point in future. I use it here to color the labels on the y-axis -- it doesn't matter if you use the darken function or not, the warning pops up because we feed a vector to the color argument. Just passing c("red", "green", "blue") would cause the same warning, while setting a single color e.g. "black" does not.

cf. namespace:
Hm, interesting. As I rarely use namespace convention when using ggplot2, I've never run into this issue. You mean when specifying ggplot2::aes(color = ggplot2::stage(species, after_scale = colorspace::darken(color, .1, space = "HLS")))), ggplot can't find the species column? There is a related issue on the ggplot2 repo, maybe this helps: tidyverse/ggplot2#6104.

@francisvolh
Copy link

Thanks for the comments @z3tt! And thanks a lot also for the input on my warnings/bullet points!

I checked the thread cf. namespace and it got a bit complicated for me to understand the idea of stage not being a proper function but I guess I can just avoid using "stage" and go on with my life for life! But you were on point, if no loading the library, and specifying ggplot2::stage and it seems expected from the issue you linked me to.... and I think they dont want or thinkg is something to be fixed (as stage seems not to be a proper function).

Cheers! and thanks again!

@z3tt
Copy link
Author

z3tt commented Nov 5, 2024

Tbh that discussion is also beyond me 🤓 Exactly, you can use the logic without stage() by repeating the aesthetic, e.g.

geom_point(
  aes(color = species, 
      color = after_scale(darken(color, .1, space = "HLS")))
)

It's not the official way to do it and you'll get a warning but it works perfectly fine and I often do it myself. Just trying to avoid spreading the unofficial approach in my workshops and tutorials.

PS: However, you might run into the same issue when using the after_scale() function without loading {ggplot2} because it's not considered a "true function" as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment