├── LICENSE ├── .Rbuildignore ├── man ├── example.pdf ├── example2.pdf ├── figures │ ├── README-unnamed-chunk-7-1.png │ └── README-unnamed-chunk-9-1.png ├── xp_get_legend.Rd ├── xp.Rd ├── xp_add_latex_package.Rd ├── theme_xp.Rd ├── xp_umap_axis.Rd ├── xp_compose_plots.Rd └── xp_init.Rd ├── .gitignore ├── NEWS.md ├── NAMESPACE ├── DESCRIPTION ├── LICENSE.md ├── README.Rmd ├── R ├── exactplot.R └── plot_composition.R └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2025 2 | COPYRIGHT HOLDER: Constantin Ahlmann-Eltze 3 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.Rmd$ 4 | ^LICENSE\.md$ 5 | -------------------------------------------------------------------------------- /man/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/const-ae/exactplot/HEAD/man/example.pdf -------------------------------------------------------------------------------- /man/example2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/const-ae/exactplot/HEAD/man/example2.pdf -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/const-ae/exactplot/HEAD/man/figures/README-unnamed-chunk-7-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-9-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/const-ae/exactplot/HEAD/man/figures/README-unnamed-chunk-9-1.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.Rproj 2 | .Rproj.user 3 | .Rhistory 4 | .RData 5 | .Ruserdata 6 | tmp_scripts 7 | inst/doc 8 | *.DS_Store 9 | 10 | *-tikzDictionary 11 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # exactplot 0.1.* 2 | 3 | * Fix bug that `xp_graphic` ignored surrounding `xp_origin` specification 4 | * Fix bug in `xp_origin`. 5 | * Fix bug in `xp_graphic`. 6 | * Fix bug in `xp_add_latex_package` 7 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(theme_xp) 4 | export(xp) 5 | export(xp_add_latex_package) 6 | export(xp_compose_plots) 7 | export(xp_get_legend) 8 | export(xp_graphic) 9 | export(xp_init) 10 | export(xp_origin) 11 | export(xp_plot) 12 | export(xp_text) 13 | export(xp_umap_axis) 14 | import(ggplot2) 15 | -------------------------------------------------------------------------------- /man/xp_get_legend.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exactplot.R 3 | \name{xp_get_legend} 4 | \alias{xp_get_legend} 5 | \title{Get legend} 6 | \usage{ 7 | xp_get_legend(plot) 8 | } 9 | \arguments{ 10 | \item{plot}{the plot from which the legend is extracted.} 11 | } 12 | \description{ 13 | Wraps \code{cowplot::get_plot_component} 14 | } 15 | -------------------------------------------------------------------------------- /man/xp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exactplot.R 3 | \docType{data} 4 | \name{xp} 5 | \alias{xp} 6 | \title{Object that holds exactplot defaults} 7 | \format{ 8 | An object of class \code{environment} of length 4. 9 | } 10 | \usage{ 11 | xp 12 | } 13 | \description{ 14 | See for example \code{xp$fontsize} 15 | } 16 | \keyword{datasets} 17 | -------------------------------------------------------------------------------- /man/xp_add_latex_package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exactplot.R 3 | \name{xp_add_latex_package} 4 | \alias{xp_add_latex_package} 5 | \title{Modify the tikzLualatexPackages option} 6 | \usage{ 7 | xp_add_latex_package(packages) 8 | } 9 | \arguments{ 10 | \item{packages}{the name of the packages. Can be the full import statement (\verb{\\usePackage\{abcd\}}) 11 | in which case it is not modified or just the package name in which case it is wrapped in 12 | \verb{\\usePackage\{...\}}.} 13 | } 14 | \description{ 15 | Modify the tikzLualatexPackages option 16 | } 17 | -------------------------------------------------------------------------------- /man/theme_xp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exactplot.R 3 | \name{theme_xp} 4 | \alias{theme_xp} 5 | \title{A simple theme for publications with few superfluous lines} 6 | \usage{ 7 | theme_xp( 8 | fontsize = xp$fontsize, 9 | fontsize_small = xp$fontsize_small, 10 | fontsize_tiny = xp$fontsize_tiny, 11 | fontsize_large = xp$fontsize_large, 12 | line_size = 0.3, 13 | panel.spacing = unit(2, "mm") 14 | ) 15 | } 16 | \arguments{ 17 | \item{fontsize, fontsize_small, fontsize_tiny, fontsize_large}{the font sizes} 18 | } 19 | \description{ 20 | This function extends \code{\link[cowplot:theme_cowplot]{cowplot::theme_cowplot}} 21 | } 22 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: exactplot 2 | Type: Package 3 | Title: Produce figures exactly. 4 | Version: 0.1.4 5 | Authors@R: person("Constantin", "Ahlmann-Eltze", email = "artjom31415@googlemail.com", 6 | role = c("aut", "cre"), comment = c(ORCID = "0000-0002-3762-068X")) 7 | Description: The goal of exactplo` is to produce millimeter-exact figure layouts and annotate your plots with the full power of Latex. 8 | exactplot wraps around tikzDevice and provides utility functions to compose grid plots (e.g., `ggplot2` output, but not base plots). 9 | URL: https://github.com/const-ae/exactplot 10 | BugReports: https://github.com/const-ae/exactplot/issues 11 | License: MIT + file LICENSE 12 | Encoding: UTF-8 13 | LazyData: true 14 | Imports: 15 | ggplot2, 16 | purrr, 17 | scales, 18 | stringr, 19 | tinytex, 20 | withr, 21 | cowplot, 22 | grid, 23 | plotgardener, 24 | tikzDevice 25 | RoxygenNote: 7.3.2 26 | Roxygen: list(markdown = TRUE) 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 Constantin Ahlmann-Eltze 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /man/xp_umap_axis.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exactplot.R 3 | \name{xp_umap_axis} 4 | \alias{xp_umap_axis} 5 | \title{Add a pretty UMAP (or tSNE) axis to a plot} 6 | \usage{ 7 | xp_umap_axis( 8 | label = "UMAP", 9 | fontsize = xp$fontsize_small, 10 | arrow_length = 10, 11 | label_offset = 1, 12 | fix_coord = TRUE, 13 | remove_axes = TRUE, 14 | arrow_spec = grid::arrow(ends = "both", type = "closed", angle = 20, length = 15 | unit(arrow_length/7, units)), 16 | units = "mm", 17 | ... 18 | ) 19 | } 20 | \arguments{ 21 | \item{label}{the axis label} 22 | 23 | \item{fontsize}{the font size of the axis label} 24 | 25 | \item{arrow_length}{length of the axis lines (in \code{units})} 26 | 27 | \item{label_offset}{how far away from the axis the text is positioned (in \code{units})} 28 | 29 | \item{fix_coord}{flag if a the aspect ratio is fixed to 1.} 30 | 31 | \item{remove_axes}{if the original axes are removed by modifying the theme.} 32 | 33 | \item{arrow_spec}{the definition of the \code{grid::arrow}} 34 | 35 | \item{units}{the unit used for the arguments.} 36 | 37 | \item{...}{additional arguments passed to \code{coord_fixed}.} 38 | } 39 | \description{ 40 | Add a pretty UMAP (or tSNE) axis to a plot 41 | } 42 | -------------------------------------------------------------------------------- /man/xp_compose_plots.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_composition.R 3 | \name{xp_compose_plots} 4 | \alias{xp_compose_plots} 5 | \alias{xp_plot} 6 | \alias{xp_text} 7 | \alias{xp_origin} 8 | \alias{xp_graphic} 9 | \title{Compose plots with millimeter precision} 10 | \usage{ 11 | xp_compose_plots( 12 | ..., 13 | .plot_objs = NULL, 14 | width = 180, 15 | height = 110, 16 | units = c("mm", "cm", "inches", "px"), 17 | show_grid_lines = FALSE, 18 | keep_tex_file = FALSE, 19 | latex_engine = "luatex", 20 | filename = NULL 21 | ) 22 | 23 | xp_plot(plot, x = 0, y = 0, width = NULL, height = NULL, units = NULL) 24 | 25 | xp_text(label, x = 0, y = 0, fontsize = xp$fontsize, hjust = 0, vjust = 1, ...) 26 | 27 | xp_origin(..., .plot_objs = NULL, x = 0, y = 0) 28 | 29 | xp_graphic( 30 | filename, 31 | x = 0, 32 | y = 0, 33 | width = NULL, 34 | height = NULL, 35 | units = c("mm", "cm", "inches", "px"), 36 | anchor = c("north west", "south west", "base") 37 | ) 38 | } 39 | \arguments{ 40 | \item{...}{the panels (have to be grid objects)} 41 | 42 | \item{.plot_objs}{alternative specification of the panels as a list} 43 | 44 | \item{width, height}{the size of the total plot area. Defaults to the width of a plot in 45 | Nature.} 46 | 47 | \item{units}{the unit of the plot dimensions.} 48 | 49 | \item{show_grid_lines}{boolean that indicates if helper lines are displayed in the background 50 | to help align panels.} 51 | 52 | \item{keep_tex_file}{boolean that indicates if the .tex file are retained after the latex 53 | compilation completes. Only applies if \code{filename} is not \code{NULL}.} 54 | 55 | \item{latex_engine}{the Latex engine used to compile the .tex document.} 56 | 57 | \item{filename}{optional filename where the plot is saved. If \code{NULL} it is plotted without rendering 58 | the latex.} 59 | 60 | \item{x, y}{the x and y position relative to the top left corner.} 61 | 62 | \item{fontsize}{the font size of the text.} 63 | } 64 | \description{ 65 | Compose plots with millimeter precision 66 | } 67 | \details{ 68 | Text that contains Latex code is only rendered when saving as a file. 69 | } 70 | -------------------------------------------------------------------------------- /man/xp_init.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exactplot.R 3 | \name{xp_init} 4 | \alias{xp_init} 5 | \title{Update the default ggplot theme, sets font defaults and extra latex packages} 6 | \usage{ 7 | xp_init( 8 | font_main = "IBM Plex Sans", 9 | font_math = "IBM Plex Math", 10 | font_mono = "IBM Plex Mono", 11 | fontsize = 8, 12 | fontsize_small = 6, 13 | fontsize_tiny = 5, 14 | fontsize_large = 10, 15 | additional_latex_packages = c("amsmath") 16 | ) 17 | } 18 | \arguments{ 19 | \item{font_main, font_math, font_mono}{the font choices for Latex. All IBM Plex fonts are 20 | freely available.} 21 | 22 | \item{fontsize, fontsize_small, fontsize_tiny, fontsize_large}{the font sizes} 23 | 24 | \item{additional_latex_packages}{latex packages that are added to \code{options("tikzLualatexPackages")}.} 25 | } 26 | \description{ 27 | To modify the Latex preamle defaults, take a look at \code{options("tikzDocumentDeclaration")} and 28 | \code{options("tikzLatexPackages")}. For all details, read the \code{vignette("tikzDevice")}. 29 | } 30 | \details{ 31 | I like the IBM Plex fonts for figures (and they are the recommended fonts by EMBL). You can install them on Mac 32 | using \verb{brew install --cask font-ibm-plex-sans font-ibm-plex-mono font-ibm-plex-math}. 33 | 34 | Here are some other font combinations that have been recommended: 35 | 36 | \if{html}{\out{
}}\preformatted{# Classical Latex look (serif font) 37 | \\setmainfont\{Latin Modern Roman\} 38 | \\setmathfont\{Latin Modern Math\} 39 | }\if{html}{\out{
}} 40 | 41 | \if{html}{\out{
}}\preformatted{# Times New Roman look (serif font) 42 | \\setmainfont\{TeX Gyre Termes\} 43 | \\setmathfont\{TeX Gyre Termes Math\} 44 | }\if{html}{\out{
}} 45 | 46 | \if{html}{\out{
}}\preformatted{# Libertinus (serif font) 47 | \\setmainfont\{Libertinus Serif\} 48 | \\setmathfont\{Libertinus Math\} 49 | }\if{html}{\out{
}} 50 | 51 | \if{html}{\out{
}}\preformatted{# Helvetica look (sans-serif font) 52 | \\setmainfont\{Helvetica\} 53 | \\setmathfont\{Fira Math\} 54 | }\if{html}{\out{
}} 55 | 56 | \if{html}{\out{
}}\preformatted{# Palatino (serif font) 57 | \\setmainfont\{Palatino\} 58 | \\setmathfont\{Asana Math\} 59 | }\if{html}{\out{
}} 60 | } 61 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # exactplot 17 | 18 | 19 | 20 | 21 | The goal of `exactplot` is to produce millimeter-exact figure layouts and annotate your plots with the full power of Latex. `exactplot` wraps around [`tikzDevice`](https://daqana.github.io/tikzDevice/) and provides utility functions to compose grid plots (e.g., `ggplot2` output, but not base plots). 22 | 23 | ## Disclaimer 24 | 25 | This is a *work-in-progress* package and mainly serves as a collection of scripts that I have used in my past papers to produce my scientific figures. I currently do not plan to release it on CRAN and I am only planning to add features if I need them for my work. If you would like to see some feature included, please open a pull request, or feel free to fork the package. 26 | 27 | ## Installation 28 | 29 | You can install the development version of `exactplot` from Github: 30 | 31 | ``` r 32 | devtools::install_github("const-ae/exactplot") 33 | ``` 34 | 35 | You might need to install additional fonts. By default, `exactplot` uses the [IBM Plex](https://github.com/IBM/plex) font family. On Mac, you can install them using brew. 36 | 37 | ``` shell 38 | brew install --cask font-ibm-plex-sans font-ibm-plex-mono font-ibm-plex-math 39 | ``` 40 | 41 | ## Example 42 | 43 | ```{r, eval=FALSE} 44 | library(exactplot) 45 | library(tidyverse) 46 | library(palmerpenguins) 47 | ``` 48 | ```{r, include=FALSE} 49 | devtools::load_all(".") 50 | library(tidyverse) 51 | library(palmerpenguins) 52 | ``` 53 | 54 | The `xp_init` function sets consistent fonts and font sizes, adds additional packages to the `options("tikzLatexPackages")`, and sets the default `ggplot2` theme. The default font sizes are available through the `xp` object. This is needed when you, want to use larger or smaller text in `geom_text()` (remember to change the `size.unit = "pt"`). 55 | 56 | ```{r} 57 | xp_init() 58 | xp$fontsize 59 | ``` 60 | 61 | 62 | Make some simple example plots. 63 | 64 | ```{r, paged.print=FALSE} 65 | scatter_plot <- penguins |> 66 | mutate(label = paste0(species, " (", island, ", $", year,"$)")) |> 67 | ggplot(aes(x = flipper_length_mm, y = bill_length_mm)) + 68 | geom_point(aes(color = species), show.legend = FALSE) + 69 | geom_label(data = \(x) slice_max(x, bill_length_mm, by = species), 70 | aes(label = label, hjust = ifelse(flipper_length_mm < 200, 0, 1)), vjust = -0.2) + 71 | labs(x = "Flipper length in $10^{-3}$ m", y = "Bill length in $10^{-3}$ m") + 72 | coord_cartesian(clip = "off") 73 | 74 | beak_lengths <- penguins |> 75 | ggplot(aes(x = bill_length_mm)) + 76 | geom_density(aes(fill = species), alpha = 0.7 ) + 77 | labs(x = "Bill length in $10^{-3}$ m") 78 | ``` 79 | 80 | 81 | Combine the two plots with some additional annotations. If you call this function without specifying the filename, you get a quick preview without the latex rendering. By default, `exactplot` uses LuaLatex for rendering, because of its superior font support. 82 | 83 | ```{r} 84 | xp_compose_plots( 85 | xp_text("Welcome to \\texttt{exactplot} with \\LaTeX{} support", x = 1, y = 2, 86 | fontsize = xp$fontsize_large, fontface = "bold"), 87 | xp_text("A) Flipper length vs.\\ beak size", x = 1, y = 8), 88 | xp_plot(scatter_plot, x = 0, y = 12, width = 80, height = 40), 89 | 90 | xp_text("B) Beak sizes", x = 84, y = 8), 91 | xp_plot(beak_lengths, x = 82, y = 12, width = 58, height = 40), 92 | xp_text("$\\textrm{density}(x) = \\frac{1}{n\\sigma}\\frac{1}{\\sqrt{2\\pi}}\\sum_{i=1}^N{\\exp\\left(\\frac{-(x-x_i)^2}{2\\sigma^2}\\right)}$", 93 | x = 99, y = 12, fontsize = xp$fontsize_small), 94 | 95 | width = 140, height = 52, 96 | keep_tex_file = FALSE, filename = "man/example.pdf" 97 | ) 98 | ``` 99 | 100 | 101 | 102 | Display the output: 103 | 104 | ```{r} 105 | print(magick::image_read_pdf("man/example.pdf", density = 600), info = FALSE) 106 | ``` 107 | 108 | # Load additional Latex packages 109 | 110 | The `\xleftrightarrow` is defined in the `mathtools` Latex package. You can load it by calling `xp_add_latex_package`. 111 | 112 | ```{r} 113 | xp_add_latex_package("mathtools") 114 | 115 | adv_label_pl <- scatter_plot + 116 | labs(x = "Smaller $\\xleftrightarrow[\\text{[cm]}]{\\text{\\quad Flipper length\\quad}}$ Larger") + 117 | theme(axis.title.x = element_text(margin = margin(4, 0, 0, 0, "pt"))) 118 | 119 | xp_compose_plots( 120 | xp_plot(adv_label_pl, x = 0, y = 12, width = 80, height = 40), 121 | 122 | width = 140, height = 52, filename = "man/example2.pdf" 123 | ) 124 | ``` 125 | 126 | 127 | Display the output: 128 | 129 | ```{r} 130 | print(magick::image_read_pdf("man/example2.pdf", density = 600), info = FALSE) 131 | ``` 132 | 133 | 134 | # Session Info 135 | 136 | ```{r} 137 | sessionInfo() 138 | ``` 139 | 140 | 141 | -------------------------------------------------------------------------------- /R/exactplot.R: -------------------------------------------------------------------------------- 1 | 2 | #' @import ggplot2 3 | NULL 4 | 5 | #' Object that holds exactplot defaults 6 | #' 7 | #' See for example `xp$fontsize` 8 | #' 9 | #' @export 10 | xp <- new.env(parent = emptyenv()) 11 | xp$fontsize <- 7 12 | xp$fontsize_small <- 6 13 | xp$fontsize_tiny <- 5 14 | xp$fontsize_large <- 10 15 | 16 | 17 | #' Update the default ggplot theme, sets font defaults and extra latex packages 18 | #' 19 | #' To modify the Latex preamle defaults, take a look at `options("tikzDocumentDeclaration")` and 20 | #' `options("tikzLatexPackages")`. For all details, read the `vignette("tikzDevice")`. 21 | #' 22 | #' @param font_main,font_math,font_mono the font choices for Latex. All IBM Plex fonts are 23 | #' freely available. 24 | #' @param fontsize,fontsize_small,fontsize_tiny,fontsize_large the font sizes 25 | #' @param additional_latex_packages latex packages that are added to `options("tikzLualatexPackages")`. 26 | #' 27 | #' @details 28 | #' I like the IBM Plex fonts for figures (and they are the recommended fonts by EMBL). You can install them on Mac 29 | #' using `brew install --cask font-ibm-plex-sans font-ibm-plex-mono font-ibm-plex-math`. 30 | #' 31 | #' Here are some other font combinations that have been recommended: 32 | #' 33 | #' ``` 34 | #' # Classical Latex look (serif font) 35 | #' \setmainfont{Latin Modern Roman} 36 | #' \setmathfont{Latin Modern Math} 37 | #' ``` 38 | #' 39 | #' ``` 40 | #' # Times New Roman look (serif font) 41 | #' \setmainfont{TeX Gyre Termes} 42 | #' \setmathfont{TeX Gyre Termes Math} 43 | #' ``` 44 | #' 45 | #' ``` 46 | #' # Libertinus (serif font) 47 | #' \setmainfont{Libertinus Serif} 48 | #' \setmathfont{Libertinus Math} 49 | #' ``` 50 | #' 51 | #' ``` 52 | #' # Helvetica look (sans-serif font) 53 | #' \setmainfont{Helvetica} 54 | #' \setmathfont{Fira Math} 55 | #' ``` 56 | #' 57 | #' ``` 58 | #' # Palatino (serif font) 59 | #' \setmainfont{Palatino} 60 | #' \setmathfont{Asana Math} 61 | #' ``` 62 | #' 63 | #' @export 64 | xp_init <- function(font_main = "IBM Plex Sans", font_math = "IBM Plex Math", font_mono = "IBM Plex Mono", 65 | fontsize = 8, fontsize_small = 6, fontsize_tiny = 5, fontsize_large = 10, 66 | additional_latex_packages = c("amsmath")){ 67 | # Update xp object 68 | xp$fontsize <- fontsize 69 | xp$fontsize_small <- fontsize_small 70 | xp$fontsize_tiny <- fontsize_tiny 71 | xp$fontsize_large <- fontsize_large 72 | 73 | # Prepare tikzDevice 74 | tikzDevice::setTikzDefaults() 75 | xp_add_latex_package(additional_latex_packages) 76 | options(tikzDocumentDeclaration = union(getOption("tikzDocumentDeclaration"), c( 77 | r"(\usepackage{fontspec})", 78 | r"(\usepackage{unicode-math})", 79 | paste0(r"(\setmainfont{)", font_main, "}"), 80 | paste0(r"(\setmathfont{)", font_math, "}"), 81 | paste0(r"(\setmonofont[Color={0019D4}]{)", font_mono, "}") 82 | ))) 83 | 84 | # Update ggplot defaults 85 | theme_set(theme_xp(fontsize, fontsize_small, fontsize_tiny, fontsize_large)) 86 | update_geom_defaults("text", list(size = fontsize_small / .pt)) 87 | update_geom_defaults("label", list(size = fontsize_small / .pt)) 88 | } 89 | 90 | #' Modify the tikzLualatexPackages option 91 | #' 92 | #' @param packages the name of the packages. Can be the full import statement (`\usePackage{abcd}`) 93 | #' in which case it is not modified or just the package name in which case it is wrapped in 94 | #' `\usePackage{...}`. 95 | #' 96 | #' @export 97 | xp_add_latex_package <- function(packages){ 98 | if(! is.null(packages)){ 99 | packages <- stringr::str_trim(packages) 100 | packages <- ifelse(stringr::str_starts(packages, "\\\\usepackage\\{"), packages, paste0("\\usepackage{", packages, "}")) 101 | options(tikzLualatexPackages = union(getOption("tikzLualatexPackages"), packages)) 102 | } 103 | } 104 | 105 | #' A simple theme for publications with few superfluous lines 106 | #' 107 | #' This function extends [`cowplot::theme_cowplot`] 108 | #' 109 | #' @param fontsize,fontsize_small,fontsize_tiny,fontsize_large the font sizes 110 | #' 111 | #' @export 112 | theme_xp <- function(fontsize = xp$fontsize, fontsize_small = xp$fontsize_small, fontsize_tiny = xp$fontsize_tiny, fontsize_large = xp$fontsize_large, 113 | line_size = 0.3, panel.spacing = unit(2, "mm")){ 114 | cowplot::theme_cowplot(font_size = fontsize, rel_small = fontsize_small / fontsize, 115 | rel_tiny = fontsize_tiny / fontsize, rel_large = fontsize_large / fontsize, 116 | line_size = line_size) + 117 | theme(plot.title = element_text(size = fontsize), 118 | axis.title = element_text(size = fontsize_small), 119 | legend.title = element_text(size = fontsize_small), 120 | strip.background = element_blank(), 121 | strip.text = element_text(size = fontsize_small), 122 | panel.spacing = panel.spacing) 123 | } 124 | 125 | #' Get legend 126 | #' 127 | #' Wraps `cowplot::get_plot_component` 128 | #' 129 | #' @param plot the plot from which the legend is extracted. 130 | #' 131 | #' @export 132 | xp_get_legend <- function(plot){ 133 | comps <- cowplot::get_plot_component(plot, "guide-box",return_all = TRUE) 134 | comps <- purrr::discard(comps, \(x) ggplot2:::is.zero(x)) 135 | comps[[1]] 136 | } 137 | 138 | 139 | #' Add a pretty UMAP (or tSNE) axis to a plot 140 | #' 141 | #' @param label the axis label 142 | #' @param fontsize the font size of the axis label 143 | #' @param arrow_length length of the axis lines (in `units`) 144 | #' @param label_offset how far away from the axis the text is positioned (in `units`) 145 | #' @param fix_coord flag if a the aspect ratio is fixed to 1. 146 | #' @param remove_axes if the original axes are removed by modifying the theme. 147 | #' @param arrow_spec the definition of the `grid::arrow` 148 | #' @param units the unit used for the arguments. 149 | #' @param ... additional arguments passed to `coord_fixed`. 150 | #' 151 | #' @export 152 | xp_umap_axis <- function(label = "UMAP", fontsize = xp$fontsize_small, arrow_length = 10, label_offset = 1, fix_coord = TRUE, remove_axes = TRUE, 153 | arrow_spec = grid::arrow(ends = "both", type = "closed", angle = 20, length = unit(arrow_length / 7, units)), 154 | units = "mm", ...){ 155 | coord <- if(fix_coord){ 156 | coord_fixed(clip = "off", ...) 157 | }else{ 158 | NULL 159 | } 160 | axis_theme <- if(remove_axes){ 161 | theme(axis.line = element_blank(), 162 | axis.ticks = element_blank(), 163 | axis.text = element_blank(), 164 | axis.title = element_blank()) 165 | }else{ 166 | NULL 167 | } 168 | lines <- annotation_custom(grid::polylineGrob(x = unit(c(0, 0, arrow_length), units), y = unit(c(arrow_length, 0, 0), units), 169 | gp = grid::gpar(fill = "black"), 170 | arrow = arrow_spec)) 171 | text <- if(! is.null(label)){ 172 | annotation_custom(grid::textGrob(label = label, gp = grid::gpar(fontsize = fontsize), 173 | x = unit(label_offset, units), y = unit(label_offset, units), hjust = 0, vjust = 0)) 174 | }else{ 175 | NULL 176 | } 177 | list(coord, axis_theme, lines, text) 178 | } 179 | 180 | xp_annotate_small_arrow <- function(position = c(0.8, 0.95), offset = 0.01, label = NULL, direction = c("x", "y"), 181 | fontsize = xp$fontsize_small, arrow_length = unit(10 / 7, "mm"), label_offset = 0, label_hjust = NULL, label_vjust = NULL, 182 | arrow_spec = grid::arrow(ends = "last", type = "closed", angle = 20, length = arrow_length), 183 | units = "npc"){ 184 | direction <- match.arg(direction) 185 | if(!grid::is.unit(position)){ 186 | position <- grid::unit(position, units = units) 187 | } 188 | if(!grid::is.unit(offset)){ 189 | offset <- grid::unit(offset, units = units) 190 | } 191 | if(!grid::is.unit(label_offset)){ 192 | label_offset <- grid::unit(label_offset, units = units) 193 | } 194 | if(direction == "x"){ 195 | arrow <- annotation_custom(grid::polylineGrob(x = position, y = c(offset, offset), 196 | gp = grid::gpar(fill = "black"), 197 | arrow = arrow_spec)) 198 | text <- if(! is.null(label)){ 199 | annotation_custom(grid::textGrob(label = label, gp = grid::gpar(fontsize = fontsize), 200 | x = (position[1] + position[2]) / 2, y = offset + label_offset, 201 | hjust = label_hjust, vjust = label_vjust)) 202 | } 203 | }else{ 204 | arrow <- annotation_custom(grid::polylineGrob(y = position, x = c(offset, offset), 205 | gp = grid::gpar(fill = "black"), 206 | arrow = arrow_spec)) 207 | text <- if(! is.null(label)){ 208 | annotation_custom(grid::textGrob(label = label, gp = grid::gpar(fontsize = fontsize), 209 | y = (position[1] + position[2]) / 2, x = offset + label_offset, 210 | hjust = label_hjust, vjust = label_vjust, rot = 90)) 211 | } 212 | } 213 | list(arrow, text) 214 | } 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /R/plot_composition.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | ######### Custom plotting functions ######### 4 | 5 | convert_dims <- function(width, height, units = c("inches", "in", "cm", "mm", "px"), dpi = 300, scale = 1){ 6 | units <- match.arg(units) 7 | if(units == "inches"){ 8 | units <- "in" 9 | } 10 | to_inches <- function(x) x/c(`in` = 1, cm = 2.54, mm = 2.54 * 11 | 10, px = dpi)[units] 12 | to_inches(c(width, height)) * scale 13 | } 14 | 15 | my_pdf <- function(filename, width, height, units = c("inches", "in", "cm", "mm", "px"), dpi = 300, scale = 1, ...){ 16 | dim <- convert_dims(width, height, units, dpi, scale) 17 | grDevices::pdf(filename, width = dim[1], height = dim[2], useDingbats = FALSE, ...) 18 | } 19 | 20 | 21 | my_tikz <- function(filename, width, height, units = c("inches", "in", "cm", "mm", "px"), dpi = 300, scale = 1, stand_alone = TRUE, ...){ 22 | dim <- convert_dims(width, height, units, dpi, scale) 23 | tikzDevice::tikz(filename, width = dim[1], height = dim[2], standAlone = stand_alone, 24 | engine = "luatex", 25 | documentDeclaration = getOption("tikzDocumentDeclaration"), ..., verbose = TRUE) 26 | } 27 | 28 | save_plot <- function(filename, plot = ggplot2::last_plot(), width = 6.2328, height = 3.71, units = c("inches", "cm", "mm", "px"), dpi = 300, scale = 1, latex_support = FALSE, ...){ 29 | 30 | old_dev <- grDevices::dev.cur() 31 | if(latex_support){ 32 | filename <- if(stringr::str_ends(filename, "\\.pdf")){ 33 | paste0(stringr::str_sub(filename, end = -5L), ".tex") 34 | } 35 | my_tikz(filename, width = width, height = height, units = units, dpi = dpi, scale = scale, stand_alone = TRUE) 36 | }else{ 37 | dim <- convert_dims(width, height, units, dpi, scale) 38 | dev <- ggplot2:::plot_dev(NULL, filename, dpi = dpi) 39 | dev(filename = filename, width = dim[1], height = dim[2], ...) 40 | on.exit(utils::capture.output({ 41 | grDevices::dev.off() 42 | if (old_dev > 1) grDevices::dev.set(old_dev) 43 | })) 44 | } 45 | 46 | grid::grid.draw(plot) 47 | 48 | if(latex_support){ 49 | grDevices::dev.off() 50 | if (old_dev > 1) grDevices::dev.set(old_dev) 51 | 52 | withr::with_dir(dirname(filename), { 53 | tools::texi2pdf(basename(filename), clean = TRUE) 54 | # Remove .tex file 55 | file.remove(basename(filename)) 56 | raw_file_name <- tools::file_path_sans_ext(basename(filename)) 57 | # rastered images 58 | ras_files <- list.files(pattern = paste0("^", raw_file_name, "_ras\\d*.png")) 59 | if(length(ras_files) > 0){ 60 | file.remove(ras_files) 61 | } 62 | }) 63 | } 64 | 65 | invisible(filename) 66 | } 67 | 68 | #' Compose plots with millimeter precision 69 | #' 70 | #' 71 | #' @param ... the panels (have to be grid objects) 72 | #' @param .plot_objs alternative specification of the panels as a list 73 | #' @param width,height the size of the total plot area. Defaults to the width of a plot in 74 | #' Nature. 75 | #' @param x,y the x and y position relative to the top left corner. 76 | #' @param fontsize the font size of the text. 77 | #' @param units the unit of the plot dimensions. 78 | #' @param show_grid_lines boolean that indicates if helper lines are displayed in the background 79 | #' to help align panels. 80 | #' @param keep_tex_file boolean that indicates if the .tex file are retained after the latex 81 | #' compilation completes. Only applies if `filename` is not `NULL`. 82 | #' @param latex_engine the Latex engine used to compile the .tex document. 83 | #' @param filename optional filename where the plot is saved. If `NULL` it is plotted without rendering 84 | #' the latex. 85 | #' 86 | #' @details 87 | #' Text that contains Latex code is only rendered when saving as a file. 88 | #' 89 | #' 90 | #' @export 91 | xp_compose_plots <- function(..., .plot_objs = NULL, width = 180, height = 110, units = c("mm", "cm", "inches", "px"), 92 | show_grid_lines = FALSE, keep_tex_file = FALSE, latex_engine = "luatex", filename = NULL){ 93 | units <- match.arg(units) 94 | old_latex_engine <- options(tikzDefaultEngine = latex_engine) 95 | on.exit({ 96 | options(tikzDefaultEngine = old_latex_engine) 97 | }) 98 | 99 | plots <- if(is.null(.plot_objs)){ 100 | rlang::dots_list(...) 101 | }else{ 102 | .plot_objs 103 | } 104 | 105 | if(show_grid_lines){ 106 | x_breaks <- scales::breaks_pretty(n = 10)(seq(0, width, length.out = 100)) 107 | y_breaks <- scales::breaks_pretty(n = 10)(seq(0, height, length.out = 100)) 108 | }else{ 109 | x_breaks <- c(0,Inf) 110 | y_breaks <- c(0,Inf) 111 | } 112 | 113 | if(! is.null(filename)){ 114 | old_dev <- grDevices::dev.cur() 115 | filename <- if(stringr::str_ends(filename, "\\.pdf")){ 116 | paste0(stringr::str_sub(filename, end = -5L), ".tex") 117 | } 118 | my_tikz(filename, width = width, height = height, units = units, stand_alone = TRUE) 119 | } 120 | 121 | 122 | plotgardener::pageCreate(width = width, height = height, default.units = units, xgrid = diff(x_breaks)[1], ygrid = diff(y_breaks)[1], showGuides = show_grid_lines) 123 | plot_elements <- function(plots, x0=0, y0=0){ 124 | for(obj in plots){ 125 | if(is.ggplot(obj)){ 126 | plotgardener::plotGG(obj, x = 0, y = 0, width = width, height = height, default.units = units) 127 | }else if(grid::is.grob(obj)){ 128 | grid::grid.draw(obj) 129 | }else if(inherits(obj, "exactplotter_function")){ 130 | grid::grid.draw(obj$FUN(width, height, units, x0 = x0, y0 = y0)) 131 | }else if(inherits(obj, "exactplot_origin")){ 132 | plot_elements(obj$plots, x0=x0+obj$x0, y0 = y0 + obj$y0) 133 | }else if(inherits(obj, "exactplot_panel")){ 134 | stopifnot(! is.null(names(obj))) 135 | stopifnot("plot" %in% names(obj)) 136 | .x <- obj$x %||% 0 137 | .y <- obj$y %||% 0 138 | .width <- obj$width %||% width 139 | .height <- obj$height %||% height 140 | .units <- obj$units %||% units 141 | plotgardener::plotGG(obj$plot, x = x0 + .x, y = y0 + .y, width = .width, height = .height, default.units = .units) 142 | }else{ 143 | warning("Cannot handle object of class: ", toString(class(obj))) 144 | } 145 | } 146 | } 147 | plot_elements(plots) 148 | 149 | if(! is.null(filename)){ 150 | grDevices::dev.off() 151 | if (old_dev > 1) grDevices::dev.set(old_dev) 152 | old <- setwd(dir = dirname(filename)) 153 | on.exit({ 154 | setwd(old) 155 | }) 156 | on.exit({ 157 | if(!keep_tex_file){ 158 | # Remove .tex file 159 | file.remove(basename(filename)) 160 | raw_file_name <- tools::file_path_sans_ext(basename(filename)) 161 | # rastered images 162 | ras_files <- list.files(pattern = paste0("^", raw_file_name, "_ras\\d*.png")) 163 | if(length(ras_files) > 0){ 164 | file.remove(ras_files) 165 | } 166 | } 167 | }, add=TRUE, after=FALSE) 168 | tinytex::latexmk(basename(filename), engine = latex_engine) 169 | } 170 | } 171 | 172 | panel <- function(plot, x = 0, y = 0, width = NULL, height = NULL, units = NULL){ 173 | res <- list(plot = plot, x = x, y = y, width = width, height = height, units = units) 174 | class(res) <- "exactplot_panel" 175 | res 176 | } 177 | 178 | #' @rdname xp_compose_plots 179 | #' @export 180 | xp_plot <- function(plot, x = 0, y = 0, width = NULL, height = NULL, units = NULL){ 181 | panel(plot = plot, x = x, y = y, width = width, height = height, units = units) 182 | } 183 | 184 | #' @rdname xp_compose_plots 185 | #' @export 186 | xp_text <- function(label, x = 0, y = 0, fontsize = xp$fontsize, hjust = 0, vjust = 1, ...){ 187 | panel(plot = cowplot::ggdraw() + cowplot::draw_label(label, size = fontsize, hjust = hjust, vjust = vjust, ...), x = x, y = y, width = 0, height = 0) 188 | } 189 | 190 | #' @rdname xp_compose_plots 191 | #' @export 192 | xp_origin <- function(..., .plot_objs = NULL, x = 0, y = 0){ 193 | plots <- if(is.null(.plot_objs)){ 194 | rlang::dots_list(...) 195 | }else{ 196 | .plot_objs 197 | } 198 | res <- list(plots = plots, x0 = x, y0 = y) 199 | class(res) <- "exactplot_origin" 200 | res 201 | } 202 | 203 | #' @rdname xp_compose_plots 204 | #' @export 205 | xp_graphic <- function(filename, x = 0, y = 0, width = NULL, height = NULL, 206 | units = c("mm", "cm", "inches", "px"), 207 | anchor = c("north west", "south west", "base")){ 208 | # Note that x and y are from the lower left corner, instead of upper left :/ 209 | stopifnot(file.exists(filename)) 210 | units <- match.arg(units) 211 | anchor <- anchor[1] 212 | abs_filepath <- tools::file_path_as_absolute(filename) 213 | size_spec <- if(!is.null(height) && !is.null(width)){ 214 | paste0("[width=", width, units, ", height=", height, units, "]") 215 | }else if(!is.null(height)){ 216 | paste0("[height=", height, units, "]") 217 | }else if(! is.null(width)){ 218 | paste0("[width=", width, units, "]") 219 | }else{ 220 | "" 221 | } 222 | content <- paste0(r"(\includegraphics)", size_spec, r"({")", abs_filepath, r"("})") 223 | res <- list(FUN = (\(figure_width, figure_height, fig_unit, x0 = 0, y0 = 0){ 224 | stopifnot(fig_unit == units) 225 | tikzDevice::grid.tikzNode(x = x + x0, y = figure_height - (y + y0), units = units, 226 | opts = paste0("draw=none,fill=none,anchor=", anchor), 227 | content = content, draw = FALSE) 228 | })) 229 | class(res) <- "exactplotter_function" 230 | res 231 | } 232 | 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # exactplot 5 | 6 | 7 | 8 | 9 | The goal of `exactplot` is to produce millimeter-exact figure layouts 10 | and annotate your plots with the full power of Latex. `exactplot` wraps 11 | around [`tikzDevice`](https://daqana.github.io/tikzDevice/) and provides 12 | utility functions to compose grid plots (e.g., `ggplot2` output, but not 13 | base plots). 14 | 15 | ## Disclaimer 16 | 17 | This is a *work-in-progress* package and mainly serves as a collection 18 | of scripts that I have used in my past papers to produce my scientific 19 | figures. I currently do not plan to release it on CRAN and I am only 20 | planning to add features if I need them for my work. If you would like 21 | to see some feature included, please open a pull request, or feel free 22 | to fork the package. 23 | 24 | ## Installation 25 | 26 | You can install the development version of `exactplot` from Github: 27 | 28 | ``` r 29 | devtools::install_github("const-ae/exactplot") 30 | ``` 31 | 32 | You might need to install additional fonts. By default, `exactplot` uses 33 | the [IBM Plex](https://github.com/IBM/plex) font family. On Mac, you can 34 | install them using brew. 35 | 36 | ``` shell 37 | brew install --cask font-ibm-plex-sans font-ibm-plex-mono font-ibm-plex-math 38 | ``` 39 | 40 | ## Example 41 | 42 | ``` r 43 | library(exactplot) 44 | library(tidyverse) 45 | library(palmerpenguins) 46 | ``` 47 | 48 | The `xp_init` function sets consistent fonts and font sizes, adds 49 | additional packages to the `options("tikzLatexPackages")`, and sets the 50 | default `ggplot2` theme. The default font sizes are available through 51 | the `xp` object. This is needed when you, want to use larger or smaller 52 | text in `geom_text()` (remember to change the `size.unit = "pt"`). 53 | 54 | ``` r 55 | xp_init() 56 | xp$fontsize 57 | #> [1] 8 58 | ``` 59 | 60 | Make some simple example plots. 61 | 62 | ``` r 63 | scatter_plot <- penguins |> 64 | mutate(label = paste0(species, " (", island, ", $", year,"$)")) |> 65 | ggplot(aes(x = flipper_length_mm, y = bill_length_mm)) + 66 | geom_point(aes(color = species), show.legend = FALSE) + 67 | geom_label(data = \(x) slice_max(x, bill_length_mm, by = species), 68 | aes(label = label, hjust = ifelse(flipper_length_mm < 200, 0, 1)), vjust = -0.2) + 69 | labs(x = "Flipper length in $10^{-3}$ m", y = "Bill length in $10^{-3}$ m") + 70 | coord_cartesian(clip = "off") 71 | 72 | beak_lengths <- penguins |> 73 | ggplot(aes(x = bill_length_mm)) + 74 | geom_density(aes(fill = species), alpha = 0.7 ) + 75 | labs(x = "Bill length in $10^{-3}$ m") 76 | ``` 77 | 78 | Combine the two plots with some additional annotations. If you call this 79 | function without specifying the filename, you get a quick preview 80 | without the latex rendering. By default, `exactplot` uses LuaLatex for 81 | rendering, because of its superior font support. 82 | 83 | ``` r 84 | xp_compose_plots( 85 | xp_text("Welcome to \\texttt{exactplot} with \\LaTeX{} support", x = 1, y = 2, 86 | fontsize = xp$fontsize_large, fontface = "bold"), 87 | xp_text("A) Flipper length vs.\\ beak size", x = 1, y = 8), 88 | xp_plot(scatter_plot, x = 0, y = 12, width = 80, height = 40), 89 | 90 | xp_text("B) Beak sizes", x = 84, y = 8), 91 | xp_plot(beak_lengths, x = 82, y = 12, width = 58, height = 40), 92 | xp_text("$\\textrm{density}(x) = \\frac{1}{n\\sigma}\\frac{1}{\\sqrt{2\\pi}}\\sum_{i=1}^N{\\exp\\left(\\frac{-(x-x_i)^2}{2\\sigma^2}\\right)}$", 93 | x = 99, y = 12, fontsize = xp$fontsize_small), 94 | 95 | width = 140, height = 52, 96 | keep_tex_file = FALSE, filename = "man/example.pdf" 97 | ) 98 | #> Using TikZ metrics dictionary at: 99 | #> README-tikzDictionary 100 | #> gg[gg1] 101 | #> gg[gg2] 102 | #> Warning: Removed 2 rows containing missing values or values outside the scale range 103 | #> (`geom_point()`). 104 | #> gg[gg3] 105 | #> gg[gg4] 106 | #> Warning: Removed 2 rows containing non-finite outside the scale range 107 | #> (`stat_density()`). 108 | #> gg[gg5] 109 | #> gg[gg6] 110 | #> [1] "example.pdf" 111 | ``` 112 | 113 | Display the output: 114 | 115 | ``` r 116 | print(magick::image_read_pdf("man/example.pdf", density = 600), info = FALSE) 117 | ``` 118 | 119 | 120 | 121 | # Load additional Latex packages 122 | 123 | The `\xleftrightarrow` is defined in the `mathtools` Latex package. You 124 | can load it by calling `xp_add_latex_package`. 125 | 126 | ``` r 127 | xp_add_latex_package("mathtools") 128 | 129 | adv_label_pl <- scatter_plot + 130 | labs(x = "Smaller $\\xleftrightarrow[\\text{[cm]}]{\\text{\\quad Flipper length\\quad}}$ Larger") + 131 | theme(axis.title.x = element_text(margin = margin(4, 0, 0, 0, "pt"))) 132 | 133 | xp_compose_plots( 134 | xp_plot(adv_label_pl, x = 0, y = 12, width = 80, height = 40), 135 | 136 | width = 140, height = 52, filename = "man/example2.pdf" 137 | ) 138 | #> Warning: Removed 2 rows containing missing values or values outside the scale range 139 | #> (`geom_point()`). 140 | #> gg[gg1] 141 | #> [1] "example2.pdf" 142 | ``` 143 | 144 | Display the output: 145 | 146 | ``` r 147 | print(magick::image_read_pdf("man/example2.pdf", density = 600), info = FALSE) 148 | ``` 149 | 150 | 151 | 152 | # Session Info 153 | 154 | ``` r 155 | sessionInfo() 156 | #> R version 4.4.1 (2024-06-14) 157 | #> Platform: aarch64-apple-darwin20 158 | #> Running under: macOS Sonoma 14.6 159 | #> 160 | #> Matrix products: default 161 | #> BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 162 | #> LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0 163 | #> 164 | #> locale: 165 | #> [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8 166 | #> 167 | #> time zone: Europe/London 168 | #> tzcode source: internal 169 | #> 170 | #> attached base packages: 171 | #> [1] stats graphics grDevices utils datasets methods base 172 | #> 173 | #> other attached packages: 174 | #> [1] palmerpenguins_0.1.1 lubridate_1.9.3 forcats_1.0.0 175 | #> [4] stringr_1.5.1 dplyr_1.1.4 purrr_1.0.2 176 | #> [7] readr_2.1.5 tidyr_1.3.1 tibble_3.2.1 177 | #> [10] ggplot2_3.5.1 tidyverse_2.0.0 exactplot_0.1.3 178 | #> 179 | #> loaded via a namespace (and not attached): 180 | #> [1] RColorBrewer_1.1-3 strawr_0.0.92 181 | #> [3] rstudioapi_0.17.1 jsonlite_1.8.9 182 | #> [5] magrittr_2.0.3 magick_2.8.5 183 | #> [7] farver_2.1.2 rmarkdown_2.29 184 | #> [9] fs_1.6.5 BiocIO_1.16.0 185 | #> [11] zlibbioc_1.52.0 vctrs_0.6.5 186 | #> [13] memoise_2.0.1 Rsamtools_2.22.0 187 | #> [15] RCurl_1.98-1.16 askpass_1.2.1 188 | #> [17] tinytex_0.54 htmltools_0.5.8.1 189 | #> [19] S4Arrays_1.6.0 usethis_3.1.0 190 | #> [21] curl_6.2.1 Rhdf5lib_1.28.0 191 | #> [23] SparseArray_1.6.0 rhdf5_2.50.0 192 | #> [25] gridGraphics_0.5-1 htmlwidgets_1.6.4 193 | #> [27] pdftools_3.4.1 desc_1.4.3 194 | #> [29] cachem_1.1.0 GenomicAlignments_1.42.0 195 | #> [31] mime_0.12 lifecycle_1.0.4 196 | #> [33] pkgconfig_2.0.3 Matrix_1.7-1 197 | #> [35] R6_2.5.1 fastmap_1.2.0 198 | #> [37] GenomeInfoDbData_1.2.13 tikzDevice_0.12.6 199 | #> [39] MatrixGenerics_1.18.0 shiny_1.10.0 200 | #> [41] digest_0.6.37 colorspace_2.1-1 201 | #> [43] S4Vectors_0.44.0 rprojroot_2.0.4 202 | #> [45] pkgload_1.4.0 GenomicRanges_1.58.0 203 | #> [47] labeling_0.4.3 fansi_1.0.6 204 | #> [49] timechange_0.3.0 httr_1.4.7 205 | #> [51] abind_1.4-8 compiler_4.4.1 206 | #> [53] remotes_2.5.0 withr_3.0.2 207 | #> [55] BiocParallel_1.40.0 pkgbuild_1.4.5 208 | #> [57] DelayedArray_0.32.0 sessioninfo_1.2.2 209 | #> [59] rjson_0.2.23 tools_4.4.1 210 | #> [61] filehash_2.4-6 httpuv_1.6.15 211 | #> [63] glue_1.8.0 restfulr_0.0.15 212 | #> [65] rhdf5filters_1.18.0 promises_1.3.2 213 | #> [67] grid_4.4.1 generics_0.1.3 214 | #> [69] gtable_0.3.6 tzdb_0.4.0 215 | #> [71] data.table_1.16.2 hms_1.1.3 216 | #> [73] utf8_1.2.4 XVector_0.46.0 217 | #> [75] BiocGenerics_0.52.0 pillar_1.9.0 218 | #> [77] yulab.utils_0.1.8 later_1.4.0 219 | #> [79] lattice_0.22-6 rtracklayer_1.66.0 220 | #> [81] tidyselect_1.2.1 Biostrings_2.74.0 221 | #> [83] miniUI_0.1.1.1 knitr_1.49 222 | #> [85] IRanges_2.40.0 SummarizedExperiment_1.36.0 223 | #> [87] stats4_4.4.1 xfun_0.50 224 | #> [89] Biobase_2.66.0 devtools_2.4.5 225 | #> [91] matrixStats_1.4.1 stringi_1.8.4 226 | #> [93] UCSC.utils_1.2.0 qpdf_1.3.4 227 | #> [95] yaml_2.3.10 evaluate_1.0.1 228 | #> [97] codetools_0.2-20 ggplotify_0.1.2 229 | #> [99] cli_3.6.3 xtable_1.8-4 230 | #> [101] munsell_0.5.1 Rcpp_1.0.13-1 231 | #> [103] GenomeInfoDb_1.42.0 XML_3.99-0.17 232 | #> [105] parallel_4.4.1 ellipsis_0.3.2 233 | #> [107] profvis_0.4.0 urlchecker_1.0.1 234 | #> [109] plyranges_1.26.0 bitops_1.0-9 235 | #> [111] scales_1.3.0 plotgardener_1.12.0 236 | #> [113] crayon_1.5.3 rlang_1.1.4 237 | #> [115] cowplot_1.1.3 238 | ``` 239 | --------------------------------------------------------------------------------