# We do want
ggplot() +
geom_point(aes(x = thing), size = 2)
# We don't want
g_title <- "I love geom_point"
# We don't want
geom_state_secret() <- function(){}We’re so excited to announce the launch of our new R package {ggplot2wrapped} which offers you a “Spotify Wrapped” like experience for your year in {ggplot2}! There’s a lot to say about the package but the easiest way to understand it is to checkout an example ggplot2wrapped report which is summarised in the GIF below.

The whole package works thanks to the {astgrep} package which allows us to build Abstract Syntax Trees (ASTs) from .R, .qmd and .Rmd files and then extract out geom_point() etc calls. You might mistakingly think you could just read a script file with readLines() and use regex.
Well, let’s make a mock script file that contains what we do want (a geom_point call) and other things we very much don’t want.
REGEX will not help you here. You want to build Abstract Syntax Trees (ASTs) which I achieved using the {astgrepr} package because it makes it very easy to extract arguments from the function call which we can then analyse.
code_file |>
tree_new() |>
tree_root() |>
node_find_all(ast_rule(
id = "geom_point",
pattern = "geom_point($$$A)")
) |>
node_text_all()
# $geom_point
# $geom_point$node_1
# [1] "geom_point(aes(x = thing), size = 2)"Making your own {ggplot2wrapped}
Great! So how would I use the actual package? Well, it’s easy. This will find file info for all the R code files in the filepaths you provide:
library("tidyverse")
library("ggplot2wrapped")
code_files <- get_code_file_info(c("~/Github/", "~/coding/r-projects-scrapbook/"), file_type = c(".qmd", ".R", ".Rmd"))Next the add_geom_usuage_to_files() function does the ASTs clever stuff and emits a note that the object contains potentially sensitive information because your actual function calls are included.
target_year <- 2024
code_files_from_target_year <- code_files |>
slice(1:5)
data_geoms_usage_target_year <- code_files_from_target_year |>
add_geom_usage_to_files()
data_geoms_usage_target_year
#> ℹ Remember this output object contains your actual geom calls therefore it
#> should be considered sensitive. The column with this sensitive information is
#> called `function_call`.
#> # A tibble: 15 × 12
#> file_path file_extension modified_time created_time
#> <chr> <chr> <dttm> <dttm>
#> 1 /Users/charliejhadley… R 2025-11-30 17:36:01 2025-11-30 17:36:01
#> 2 /Users/charliejhadley… R 2025-11-30 17:36:01 2025-11-30 17:36:01
#> 3 /Users/charliejhadley… R 2025-11-30 17:36:01 2025-11-30 17:36:01
#> # ℹ 8 more variables: access_time <dttm>, geom_name <chr>, has_aes <lgl>,
#> # function_call <list>, length_of_call <dbl>, n_args_in_call <dbl>,
#> # n_times_used <int>, package_name <chr>Oh, wait. Which geoms does this look for? Well, by default the data_geoms argument takes the ggplot2wrapped::data_geoms object which includes all of the geoms in {ggplot2}, {ggrepel} and {ggtext} as of 2025-12-01. You’re very welcome to add additional geoms to your analysis.
Next to generate your personalised scrollytelling report you just need to run this code:
data_geoms_usage_target_year %>%
ggplot_wrapped_2025(data_geoms, report_year = target_year)Visualisations included
I turned this package around within a week and an extra weekend. Out of the 10 visualisations I added into the scrollytelling report I managed to turn 6 of them into nice re-usable functions before my self-imposed deadline.

UpSet charts
I’m most proud of the UpSet charts that are produced with the amazing {upsetjs} package. You might not have heard of the UpSet chart before! That’s unsurprising as they’re quite rare and were only invented in 2012. Here’s an example:
data_example_geom_usage |>
make_geom_interactive_upset_chart(height = 600)
Now, quite rightly (I think) the authors of the visualisation ask for the original paper to be cited whenever they’re used.
Alexander Lex, Nils Gehlenborg, Hendrik Strobelt, Romain Vuillemot, Hanspeter Pfister. UpSet: Visualization of Intersecting Sets IEEE Transactions on Visualization and Computer Graphics (InfoVis), 20(12): 1983–1992, doi:10.1109/TVCG.2014.2346248, 2014.
Future plans
It was a real rush and lots of fun building this from a silly idea I had cycling home into a package that builds a programmatically generated scrollytelling report with {closeread} with a nice narrative. This is far from the end of {ggplot2wrapped}. I’ll be writing up a few blogposts from things learned along the way and in 2026 you can expect a much improved package with new things.
Oh! The wonderful folks who designed the {ggplot2} hex sticker made it CC0 licensed so I could add a bow to it for my package’s own hex sticker.
