library(ggplot2)
self_document(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram") +
theme(axis.title = element_text(debug = TRUE))
)
TJ Mahr
March 10, 2022
July 28, 2023
When I am showing off a plotting technique in ggplot2, I sometimes like to include the R code that produced the plot as part of the plot. Here is an example I made to demonstrate the debug parameter in element_text():
library(ggplot2)
self_document(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram") +
theme(axis.title = element_text(debug = TRUE))
)
Let’s call these “self-documenting plots”. If we’re feeling nerdy, we might also call them “qquines”, although they are not true quines.
In this post, we will build up a self_document() function from scratch. Here are the problems we need to sort out:
As a first step, let’s just treat our plotting code as a string that is ready to use for annotation.
p_text <- 'ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram")'
p_plot <- ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram")In order to have a titled plot along with this annotation, we need some way to combine these two graphical objects together (the code and the plot produced by ggplot2). I like the patchwork package for this job. Here we use wrap_elements() to capture the plot into a “patch” that patchwork can annotate.
library(patchwork)
wrap_elements(p_plot) +
plot_annotation(title = p_text)
Let’s style this title to use a monospaced font. I use Windows and like Consolas, so I will use that font.
# Use default mono font if "Consolas" is not available
extrafont::loadfonts(device = "win", quiet = TRUE)
monofont <- ifelse(
extrafont::choose_font("Consolas") == "",
"mono",
"Consolas"
)
title_theme <- theme(
plot.title = element_text(
family = monofont, hjust = 0, size = rel(.9),
margin = margin(0, 0, 5.5, 0, unit = "pt")
)
)
wrap_elements(p_plot) +
plot_annotation(title = p_text, theme = title_theme) 
One problem with this setup is that the plotting code has to be edited in two places: the plot p_plot and the title p_text. As a result, it’s easy for these two pieces of code to fall out of sync with each other, turning our self-documenting plot into a lying liar plot.
The solution is pretty easy: Tell R that p_text is code with parse() and evaluate the code with eval():
wrap_elements(eval(parse(text = p_text))) +
plot_annotation(title = p_text, theme = title_theme) 
This works. It gets the job done. But we find ourselves in a clumsy workflow, either having to edit R code inside of quotes or editing the plot interactively and then having to wrap it in quotes. Let’s do better.
Time for some nonstandard evaluation. I will use the rlang package, although in principle we could use functions in base R to accomplish these goals.
First, we are going to use rlang::expr() to capture/quote/defuse the R code as an expression. We can print the code as code, print it as text, and use eval() to show the plot.
p_code <- rlang::expr(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram")
)
# print the expressions
p_code
#> ggplot(mtcars, aes(x = mpg)) + geom_histogram(bins = 20, color = "white") +
#> labs(title = "A basic histogram")
# expression => text
rlang::expr_text(p_code)
#> [1] "ggplot(mtcars, aes(x = mpg)) + geom_histogram(bins = 20, color = \"white\") + \n labs(title = \"A basic histogram\")"
eval(p_code)
Then, it should be straightforward to make the self-documenting plot, right?
p_code <- rlang::expr(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram")
)
wrap_elements(eval(p_code)) +
plot_annotation(title = rlang::expr_text(p_code), theme = title_theme) 
Hey, it reformatted the title! Indeed, in the process of capturing the code, the code formatting was lost. To get something closer to the source code we provided, we have to reformat the captured code before we print it.
The styler package provides a suite of functions for reformatting code. We can define our own coding styles/formatting rules to customize how styler works. I like the styler rules used by Garrick Aden-Buie in his grkstyle package, so I will use grkstyle::grk_style_text() to reformat the code.
p_code <- rlang::expr(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram")
)
wrap_elements(eval(p_code)) +
plot_annotation(
title = rlang::expr_text(p_code) |>
grkstyle::grk_style_text() |>
# reformatting returns a vector of lines,
# so we have to combine them
paste0(collapse = "\n"),
theme = title_theme
) 
When we write our self_document() function, the only change we have to make is using rlang::enexpr() instead rlang::expr(). The en-variant is used when we want to en-quote exactly what the user provided. Aside from that change, our self_document() function just bundles together all of the code we developed above:
self_document <- function(expr) {
monofont <- ifelse(
extrafont::choose_font("Consolas") == "",
"mono",
"Consolas"
)
p <- rlang::enexpr(expr)
title <- rlang::expr_text(p) |>
grkstyle::grk_style_text() |>
paste0(collapse = "\n")
patchwork::wrap_elements(eval(p)) +
patchwork::plot_annotation(
title = title,
theme = theme(
plot.title = element_text(
family = monofont, hjust = 0, size = rel(.9),
margin = margin(0, 0, 5.5, 0, unit = "pt")
)
)
)
}And let’s confirm that it works.
library(ggplot2)
self_document(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram")
)
Because we developed this function on top of rlang, we can do some tricks like injecting a variable’s value when capturing the code. For example, here I use !! color to replace the color variable with the actual value.
color <- "white"
self_document(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = !! color) +
labs(title = "A basic histogram")
)
And if you are wondering, yes, we can self_document() a self_document() plot.
self_document(
self_document(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = "white") +
labs(title = "A basic histogram")
)
)
One downside of this approach is that helpful comments are lost.
self_document(
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = !! color) +
# get rid of that grey
theme_minimal() +
labs(title = "A basic histogram")
)
I am not sure how to include comments. One place where comments are stored and printed is in function bodies:
f <- function() {
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(bins = 20, color = !! color) +
# get rid of that grey
theme_minimal() +
labs(title = "A basic histogram")
}
print(f, useSource = TRUE)
#> function() {
#> ggplot(mtcars, aes(x = mpg)) +
#> geom_histogram(bins = 20, color = !! color) +
#> # get rid of that grey
#> theme_minimal() +
#> labs(title = "A basic histogram")
#> }I have no idea how to go about exploiting this feature for self-documenting plots, however.
─ Session info ───────────────────────────────────────────────────────────────
setting value
version R version 4.3.0 (2023-04-21 ucrt)
os Windows 11 x64 (build 22621)
system x86_64, mingw32
ui RTerm
language (EN)
collate English_United States.utf8
ctype English_United States.utf8
tz America/Chicago
date 2023-07-28
pandoc 3.1.1 @ C:/Program Files/RStudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)
quarto 1.3.353
─ Packages ───────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
cli 3.6.1 2023-03-23 [1] CRAN (R 4.3.0)
colorspace 2.1-0 2023-01-23 [1] CRAN (R 4.3.0)
digest 0.6.33 2023-07-07 [1] CRAN (R 4.3.1)
dplyr 1.1.2 2023-04-20 [1] CRAN (R 4.3.0)
evaluate 0.21 2023-05-05 [1] CRAN (R 4.3.0)
extrafont 0.19 2023-01-18 [1] CRAN (R 4.3.0)
extrafontdb 1.0 2012-06-11 [1] CRAN (R 4.3.0)
fansi 1.0.4 2023-01-22 [1] CRAN (R 4.3.0)
farver 2.1.1 2022-07-06 [1] CRAN (R 4.3.0)
fastmap 1.1.1 2023-02-24 [1] CRAN (R 4.3.0)
generics 0.1.3 2022-07-05 [1] CRAN (R 4.3.0)
ggplot2 * 3.4.2 2023-04-03 [1] CRAN (R 4.3.0)
glue 1.6.2 2022-02-24 [1] CRAN (R 4.3.0)
grkstyle 0.2.1 2023-04-24 [1] Github (gadenbuie/grkstyle@2dac4d7)
gtable 0.3.3 2023-03-21 [1] CRAN (R 4.3.0)
htmltools 0.5.5 2023-03-23 [1] CRAN (R 4.3.0)
htmlwidgets 1.6.2 2023-03-17 [1] CRAN (R 4.3.0)
jsonlite 1.8.7 2023-06-29 [1] CRAN (R 4.3.1)
knitr 1.43 2023-05-25 [1] CRAN (R 4.3.0)
labeling 0.4.2 2020-10-20 [1] CRAN (R 4.3.0)
lifecycle 1.0.3 2022-10-07 [1] CRAN (R 4.3.0)
magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.3.0)
munsell 0.5.0 2018-06-12 [1] CRAN (R 4.3.0)
patchwork * 1.1.2 2022-08-19 [1] CRAN (R 4.3.0)
pillar 1.9.0 2023-03-22 [1] CRAN (R 4.3.0)
pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.3.0)
purrr 1.0.1 2023-01-10 [1] CRAN (R 4.3.0)
R.cache 0.16.0 2022-07-21 [1] CRAN (R 4.3.0)
R.methodsS3 1.8.2 2022-06-13 [1] CRAN (R 4.3.0)
R.oo 1.25.0 2022-06-12 [1] CRAN (R 4.3.0)
R.utils 2.12.2 2022-11-11 [1] CRAN (R 4.3.0)
R6 2.5.1 2021-08-19 [1] CRAN (R 4.3.0)
ragg 1.2.5 2023-01-12 [1] CRAN (R 4.3.0)
rlang 1.1.1 2023-04-28 [1] CRAN (R 4.3.0)
rmarkdown 2.23 2023-07-01 [1] CRAN (R 4.3.0)
rprojroot 2.0.3 2022-04-02 [1] CRAN (R 4.3.0)
rstudioapi 0.15.0 2023-07-07 [1] CRAN (R 4.3.1)
Rttf2pt1 1.3.12 2023-01-22 [1] CRAN (R 4.3.0)
scales 1.2.1 2022-08-20 [1] CRAN (R 4.3.0)
sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.3.0)
styler 1.10.1 2023-06-05 [1] CRAN (R 4.3.1)
systemfonts 1.0.4 2022-02-11 [1] CRAN (R 4.3.0)
textshaping 0.3.6 2021-10-13 [1] CRAN (R 4.3.0)
tibble 3.2.1 2023-03-20 [1] CRAN (R 4.3.0)
tidyselect 1.2.0 2022-10-10 [1] CRAN (R 4.3.0)
utf8 1.2.3 2023-01-31 [1] CRAN (R 4.3.0)
vctrs 0.6.3 2023-06-14 [1] CRAN (R 4.3.1)
withr 2.5.0 2022-03-03 [1] CRAN (R 4.3.0)
xfun 0.39 2023-04-20 [1] CRAN (R 4.3.0)
yaml 2.3.7 2023-01-23 [1] CRAN (R 4.3.0)
[1] C:/Users/Tristan/AppData/Local/R/win-library/4.3
[2] C:/Program Files/R/R-4.3.0/library
──────────────────────────────────────────────────────────────────────────────