Using `free()` with `plot_annotation()` raises an error (sometimes)
When running an older code of mine I stumbled over a possible bug introduced in version 1.3.0 when using free() and plot_annotation()
Without plot_annotation() the following reprex works fine:
library(ggplot2)
library(patchwork)
packageVersion("patchwork")
#> [1] '1.3.0'
list(free(ggplot()), ggplot()) |>
wrap_plots()

But adding plot_annotation() raises an error:
list(free(ggplot()), ggplot()) |>
wrap_plots() &
plot_annotation()
#> Error in split.default(names(settings), settings): first argument must be a vector
Moreover, the issue does not arise in general but depends on the position of the plot to which free() is applied, e.g. if I switch the plots which make up the patch we don't get an error.
list(ggplot(), free(ggplot())) |>
wrap_plots() &
plot_annotation()

And as a reference, the buggy code worked fine in version 1.2.0:
library(ggplot2)
library(patchwork)
packageVersion("patchwork")
#> [1] '1.2.0'
list(free(ggplot()), ggplot()) |>
wrap_plots() &
plot_annotation()

Were you able to solve this? I'm hitting the same error with the exact same setup.
@rdboyes Nop. For my use case it was sufficient to switch back to patchwork 1.2.0.
I'm seeing the exact same error on patchwork 1.3.1.
> sessionInfo()
R version 4.5.0 (2025-04-11)
Platform: aarch64-apple-darwin24.2.0
Running under: macOS Sequoia 15.5
Matrix products: default
BLAS: /opt/homebrew/Cellar/openblas/0.3.29/lib/libopenblasp-r0.3.29.dylib
LAPACK: /opt/homebrew/Cellar/r/4.5.0/lib/R/lib/libRlapack.dylib; LAPACK version 3.12.1
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/New_York
tzcode source: internal
attached base packages:
[1] grid graphics grDevices utils datasets stats methods
[8] base
other attached packages:
[1] MKmisc_1.9 latexpdf_0.1.8 ggplotify_0.1.2 treemap_2.4-4
[5] rrvgo_1.20.0 ggvenn_0.1.10 ggrepel_0.9.6 rlang_1.1.6
[9] ggrastr_1.0.2 parameters_0.27.0 assertthat_0.2.1 withr_3.0.2
[13] httr_1.4.7 qs2_0.1.5 shadowtext_0.1.5 ggstance_0.3.7
[17] ggbreak_0.1.5 gridExtra_2.3 gginnards_0.2.0-1 scales_1.4.0
[21] patchwork_1.3.1 broom_1.0.8 ggpmisc_0.6.2 ggpp_0.5.9
[25] fs_1.6.6 qs_0.27.3 lubridate_1.9.4 forcats_1.0.0
[29] purrr_1.1.0 readr_2.1.5 tibble_3.3.0 tidyverse_2.0.0
[33] rctutils_0.1.0 tidyr_1.3.1 future_1.58.0 devtools_2.4.5
[37] usethis_3.1.0 openxlsx_4.2.8 magrittr_2.0.3 dplyr_1.1.4
[41] rex_1.2.1 glue_1.8.0 stringr_1.5.1 ggplot2_3.5.2
[45] colorout_1.3-2
loaded via a namespace (and not attached):
[1] splines_4.5.0 later_1.4.2 R.oo_1.27.1
[4] datawizard_1.2.0 lifecycle_1.0.4 globals_0.18.0
[7] NLP_0.3-2 lattice_0.22-7 MASS_7.3-65
[10] insight_1.3.1 backports_1.5.0 limma_3.64.1
[13] remotes_2.5.0 httpuv_1.6.16 zip_2.3.3
[16] askpass_1.2.1 sessioninfo_1.2.3 pkgbuild_1.4.8
[19] reticulate_1.43.0 DBI_1.2.3 RColorBrewer_1.1-3
[22] pkgload_1.4.0 R.utils_2.13.0 BiocGenerics_0.54.0
[25] yulab.utils_0.2.0 GenomeInfoDbData_1.2.14 IRanges_2.42.0
[28] S4Vectors_0.46.0 tm_0.7-16 listenv_0.9.1
[31] pheatmap_1.0.13 umap_0.2.10.0 MatrixModels_0.5-4
[34] RSpectra_0.16-2 parallelly_1.45.0 codetools_0.2-20
[37] xml2_1.3.8 RApiSerialize_0.1.4 tidyselect_1.2.1
[40] aplot_0.2.8 UCSC.utils_1.4.0 farver_2.1.2
[43] stats4_4.5.0 jsonlite_2.0.0 ellipsis_0.3.2
[46] survival_3.8-3 tools_4.5.0 Rcpp_1.1.0
[49] mgcv_1.9-3 GenomeInfoDb_1.44.0 fastmap_1.2.0
[52] SparseM_1.84-2 openssl_2.3.3 digest_0.6.37
[55] timechange_0.3.0 R6_2.6.1 mime_0.13
[58] gridGraphics_0.5-1 colorspace_2.1-1 GO.db_3.21.0
[61] dichromat_2.0-0.1 RSQLite_2.4.2 R.methodsS3_1.8.2
[64] generics_0.1.4 data.table_1.17.8 robustbase_0.99-4-1
[67] htmlwidgets_1.6.4 pkgconfig_2.0.3 gtable_0.3.6
[70] blob_1.2.4 S7_0.2.0 XVector_0.48.0
[73] htmltools_0.5.8.1 profvis_0.4.0 Biobase_2.68.0
[76] png_0.1-8 wordcloud_2.6 ggfun_0.2.0
[79] tzdb_0.5.0 nlme_3.1-168 curl_6.4.0
[82] cachem_1.1.0 parallel_4.5.0 miniUI_0.1.2
[85] vipor_0.4.7 AnnotationDbi_1.70.0 pillar_1.11.0
[88] vctrs_0.6.5 slam_0.1-55 urlchecker_1.0.1
[91] promises_1.3.3 stringfish_0.17.0 xtable_1.8-4
[94] beeswarm_0.4.0 cli_3.6.5 compiler_4.5.0
[97] crayon_1.5.3 labeling_0.4.3 ggbeeswarm_0.7.2
[100] stringi_1.8.7 gridBase_0.4-7 Biostrings_2.76.0
[103] lazyeval_0.2.2 bayestestR_0.16.1 GOSemSim_2.34.0
[106] quantreg_6.1 Matrix_1.7-3 hms_1.1.3
[109] bit64_4.6.0-1 statmod_1.5.0 KEGGREST_1.48.0
[112] shiny_1.11.1 igraph_2.1.4 memoise_2.0.1
[115] RcppParallel_5.1.10 DEoptimR_1.1-3-1 bit_4.6.0
[118] polynom_1.4-1
I experimented a bit with some obvious-seeming solutions (modifying simplify_gt.free_table), and these seemed to fix things in testing, but not in my actual use case. I'll have to debug further when I have the time. Here's what I tried:
Test code:
# https://github.com/thomasp85/patchwork/issues/405
test_that("free() and wrap_plots() and plot_annotation() work together", {
expect_doppelganger('Combining free(), wrap_plots(), and plot_annotation()', {
list(free(ggplot()), ggplot()) |>
wrap_plots() &
plot_annotation()
})
})
Attempted fix:
simplify_gt.free_table <- function(gt) {
settings <- attr(gt, "free_settings")
if (is.null(settings)) {
settings <- c(t = "panel", r = "panel", b = "panel", l = "panel")
}
settings <- split(names(settings), settings)
gt_new <- NextMethod()
if (!is.null(settings$label)) {
gt_new <- free_label(gt_new, c("t", "r", "b", "l") %in% settings$label)
}
if (!is.null(settings$space)) {
gt_new <- free_space(gt_new, c("t", "r", "b", "l") %in% settings$space)
}
if (!is.null(settings$panel)) {
gt_new <- free_panel(gt_new, c("t", "r", "b", "l") %in% settings$panel)
}
gt_new
}
The above test fails on the current main branch and passes with the above modification to the package code, but my real use case still fails, so it's not a complete fix (and I'm not entirely sure it's correct in the cases where it does work anyway).