├── 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 |
--------------------------------------------------------------------------------