diff --git a/.Rbuildignore b/.Rbuildignore index 444d2be..c28444d 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -23,6 +23,5 @@ ^codecov.yml$ ^doc$ ^Meta$ -^ggstackplot\.Rproj$ ^codecov\.yml$ ^\.covrignore$ diff --git a/DESCRIPTION b/DESCRIPTION index 45068fa..929b1bd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: ggstackplot Title: Create Overlapping Stacked Plots -Version: 0.3.0 +Version: 0.3.9999 Authors@R: c(person( given = "Sebastian", family = "Kopf", @@ -29,10 +29,14 @@ Description: License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.2 +Depends: + R (>= 4.1.0) Imports: + rlang, + cli, methods, - rlang (>= 0.4.11), + lifecycle, tidyselect, dplyr, tidyr, @@ -40,11 +44,11 @@ Imports: cowplot, RColorBrewer Suggests: - covr, knitr, rmarkdown, - roxygen2, - testthat, - lifecycle, - scales + testthat (>= 3.0.0), + vdiffr, + scales, + pangaear +Config/testthat/edition: 3 VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index 56632ec..c4b7f79 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,20 +1,10 @@ # Generated by roxygen2: do not edit by hand -export(":=") -export(.data) -export(as_label) -export(as_name) export(assemble_stackplot) -export(enquo) -export(enquos) export(ggstackplot) export(prepare_stackplot) export(theme_stackplot) +import(cli) import(ggplot2) import(rlang) -importFrom(rlang,":=") -importFrom(rlang,.data) -importFrom(rlang,as_label) -importFrom(rlang,as_name) -importFrom(rlang,enquo) -importFrom(rlang,enquos) +importFrom(lifecycle,deprecated) diff --git a/NEWS.md b/NEWS.md index 6bef173..4b0d24f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,26 +1,8 @@ -# isoorbi 1.1.0 +# ggstackplot 0.4.0 -This is a major release adding substantial new features and fixing a few bugs. +This is the first public release adding all core ggstackplot functionality. -## Breaking changes +## Features -There are no breaking changes in this release (all changes and new features are backwards compatible). - -## New features - -* implemented block annotation functionality (FIXME: explain more) -* implemented package settings (FIXME: explain more) - -## Enhancements - -* removed dependency on `stringr` -* implemented native pipe `|>` (R version requirement increased to 4.1.0) and removed dependency on `magrittr` -* added `.by` parameter for `orbi_summarize_results()` for option to manually adjust grouping - -## Bug fixes - -* `dplyr` changes to joins with explicit `multiple` argument are now implemented (#10) - -# isorbi 1.0.0 - -First public release. +* generate horizontally and vertically stacked plots +* modify color, plot templates, and individual plot elements diff --git a/R/ggstackplot-package.R b/R/ggstackplot-package.R new file mode 100644 index 0000000..ec1dec0 --- /dev/null +++ b/R/ggstackplot-package.R @@ -0,0 +1,15 @@ +## usethis namespace: start +#' @import rlang +#' @import cli +#' @import ggplot2 +#' @importFrom lifecycle deprecated +## usethis namespace: end +NULL + +#' @aliases NULL ggstackplot-package +#' @details +#' `r lifecycle::badge("stable")` +#' +#' Have you ever wanted to create (partly) overlapping line plots with matched color-coding of the data and axes? These kinds of plots are common in climatology and oceanography research but there is not an easy way to create them with ggplot facets. The ggstackplot package builds on [ggplot2](https://ggplot2.tidyverse.org/) to provide a straightforward approach to building these kinds of plots while retaining the powerful grammar of graphics approach of ggplots. Check out the functionality provided by ggstackplots at +#' +"_PACKAGE" diff --git a/R/ggstackplot.R b/R/ggstackplot.R index 6c32471..e43d929 100644 --- a/R/ggstackplot.R +++ b/R/ggstackplot.R @@ -21,7 +21,7 @@ #' @param shared_axis_size if simplify_shared_axes is true, this determines the size of the shared axis relative to the size of a single plot #' @param template a template plot (ggplot object) to use for the stacked plots #' @param add a list of ggplot component calls to add to specific panel plots, either by panel variable name (named list) or index (unnamed list) -#' @param debug debug flag to print the stackplot tibble and gtable intermediates +#' @param debug `r lifecycle::badge("experimental")` debug flag to print the stackplot tibble and gtable intermediates #' @examples #' #' # 1 step stackplot (most common use) @@ -122,48 +122,48 @@ prepare_stackplot <- function( # internal function to prepare the data for a ggstackplot create_stackplot_tibble <- function( - data, x, y, remove_na = TRUE, color = NA, palette = NA, both_axes = FALSE, alternate_axes = FALSE, switch_axes = FALSE) { + data, x, y, remove_na = TRUE, color = NA, palette = NA, both_axes = FALSE, alternate_axes = FALSE, switch_axes = FALSE, call = caller_env()) { # do we have a data frame? if (missing(data) || !is.data.frame(data)) { - abort("`data` must be a data frame or tibble.") + cli_abort("`data` must be a data frame or tibble.", call = call) } # do x and y evaluate correctly? x <- try_fetch( tidyselect::eval_select(rlang::enexpr(x), data), error = function(cnd) { - abort( + cli_abort( "`x` must be a valid tidyselect expression.", - parent = cnd + parent = cnd, call = call ) } ) y <- try_fetch( tidyselect::eval_select(rlang::enexpr(y), data), error = function(cnd) { - abort( + cli_abort( "`y` must be a valid tidyselect expression.", - parent = cnd + parent = cnd, call = call ) } ) # do we have at least 1 x and 1 y? if (length(x) < 1 || length(y) < 1) { - abort(c( + cli_abort(c( "insufficient number of columns", "x" = if (length(x) < 1) "no `x` column selected", "x" = if (length(y) < 1) "no `y` column selected" - )) + ), call = call) } # do we have both multiple x AND y? if (length(x) > 1 && length(y) > 1) { - abort(c( + cli_abort(c( "too many columns, only x OR y can select multiple columns", "x" = if (length(x) < 1) "no `x` column selected", "x" = if (length(y) < 1) "no `y` column selected" - )) + ), call = call) } # do we have valid remove_na, both_axes, alternate_axes, and switch_axes (the booleans) @@ -204,7 +204,7 @@ create_stackplot_tibble <- function( # do we have a valid length for color or palette? stopifnot("can only set either `color` or `palette`, not both" = is.na(color) | is.na(palette)) if (!(is.character(color) || all(is.na(color))) || !length(color) %in% c(1L, nrow(config))) { - abort(sprintf("`color` must be either a single color or one for each variable (%d)", nrow(config))) + cli_abort(sprintf("`color` must be either a single color or one for each variable (%d)", nrow(config)), call = call) } if (!all(is.na(palette))) { # palette argument provided @@ -212,7 +212,7 @@ create_stackplot_tibble <- function( color = RColorBrewer::brewer.pal(RColorBrewer::brewer.pal.info[palette, 1], palette)[1:nrow(config)] } else sprintf("`palette` must be a string identifying a valid RColorBrewer palette with at least %d colors. Use `RColorBrewer::display.brewer.all()` to see all available palettes.", nrow(config)) |> - abort() + cli_abort(call = call) } @@ -290,28 +290,33 @@ assemble_stackplot <- function(prepared_stackplot, overlap = 0, simplify_shared_ } # internal function to great a list of gtables for the combined plot -create_stackplot_gtables <- function(prepared_stackplot, overlap, simplify_shared_axis, shared_axis_size) { +create_stackplot_gtables <- function(prepared_stackplot, overlap, simplify_shared_axis, shared_axis_size, call = caller_env()) { # do we have a data frame? req_cols <- c(".var", "config", "data", "plot", "theme") if (missing(prepared_stackplot) || !is.data.frame(prepared_stackplot) || !all(req_cols %in% names(prepared_stackplot))) { - abort( - sprintf("`prepared_stackplot` must be a data frame or tibble with columns '%s'", paste(req_cols, collapse = "', '")) + cli_abort( + "{.var prepared_stackplot} must be a data frame or tibble with columns + {.emph {req_cols}}", call = call ) } # do we have a valid overlap value? if (missing(overlap) || !is.numeric(overlap) || !all(overlap >= 0) || !all(overlap <= 1) || !length(overlap) %in% c(1L, nrow(prepared_stackplot) - 1L)) { - abort(sprintf("`overlap` must be either a single numeric value (between 0 and 1) or one for each sequential plot overlap (%d)", - nrow(prepared_stackplot) - 1L)) + cli_abort( + c("{.var overlap} must be either a single numeric value (between 0 and 1) + or a vector with {nrow(prepared_stackplot) - 1L} numbers, one for the + overlap of each sequential plot", + "x" = "{.var overlap} is a {.obj_type_friendly {overlap}}"), + call = call) } # combine plots and themes and assembel the gtables gtables <- prepared_stackplot |> combine_plot_theme_add(simplify_shared_axis = simplify_shared_axis, include_adds = TRUE) |> - tidyr::unnest(.data$config) |> + tidyr::unnest("config") |> dplyr::select(".var", ".direction", "plot_w_theme") |> # could think about relative sizing here with size_adjust but that doesn't seem like a feature we need dplyr::mutate( diff --git a/R/helpers.R b/R/helpers.R index 0ebf562..99cb76d 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -2,9 +2,9 @@ #' Recommended base theme for stacked gg plots #' +#' Returns a basic ggplot2 theme that extends [ggplot2::theme_bw()] with a transparent plot background to make sure overlapping plots do not cover each other up. #' -#' -#' @return `ggplot2::theme()` object +#' @return [ggplot2::theme()] object #' @examples #' library(ggplot2) #' template <- ggplot() + geom_line() + theme_stackplot() @@ -208,21 +208,21 @@ process_add_ons <- function(prepared_stackplot, add) { sprintf("no match for `add` component(s) '%s' in variables ('%s')", paste(add_names[is.na(add_indices)], collapse = "', '"), paste(prepared_stackplot$.var, collapse = "', '")) |> - abort() + cli_abort() } # check for indices outside what's possible if (any(out_of_range <- !add_indices %in% seq_along(prepared_stackplot$.var))) { sprintf("`add` component(s) index out of range: %s", paste(add_indices[out_of_range], collapse = ", ")) |> - abort() + cli_abort() } # check for duplicates if (any(dups <- duplicated(add_indices))) { sprintf("multiple `add` component definitions for variable(s) '%s'", paste(prepared_stackplot$.var[unique(add_indices[dups])], collapse = "', '" )) |> - abort() + cli_abort() } # store adds @@ -295,7 +295,7 @@ combine_plot_theme_add <- function(prepared_stackplot, simplify_shared_axis, inc if(!is.null(simplify_axis)) { if (any(grepl(sprintf("scale_%s_", simplify_axis), as.character(add)))) { sprintf("invalid add-on for '%s' plot: `%s`. Modifications of the shared %s-axis are not allowed because it can lead to deceptive visualizations. You can modify the shared axis in the template or switch to `simplify_shared_axis = FALSE`.", var, as_label(add), simplify_axis) |> - abort() + cli_abort() } } diff --git a/R/import-standalone-purrr.R b/R/import-standalone-purrr.R index 4ec36e1..5a21be5 100644 --- a/R/import-standalone-purrr.R +++ b/R/import-standalone-purrr.R @@ -1,5 +1,6 @@ # Standalone file: do not edit by hand -# Source: +# Source: https://github.com/r-lib/rlang/blob/HEAD/R/standalone-purrr.R +# Generated by: usethis::use_standalone("r-lib/rlang", "purrr") # ---------------------------------------------------------------------- # # --- @@ -169,9 +170,7 @@ every <- function(.x, .p, ...) { .p <- as_function(.p, env = global_env()) for (i in seq_along(.x)) { - if (!rlang::is_true(.p(.x[[i]], ...))) { - return(FALSE) - } + if (!rlang::is_true(.p(.x[[i]], ...))) return(FALSE) } TRUE } @@ -179,9 +178,7 @@ some <- function(.x, .p, ...) { .p <- as_function(.p, env = global_env()) for (i in seq_along(.x)) { - if (rlang::is_true(.p(.x[[i]], ...))) { - return(TRUE) - } + if (rlang::is_true(.p(.x[[i]], ...))) return(TRUE) } FALSE } diff --git a/R/package.R b/R/package.R deleted file mode 100644 index eae3975..0000000 --- a/R/package.R +++ /dev/null @@ -1,5 +0,0 @@ -#' @keywords internal -#' @aliases ggstackplot-package -#' @import rlang -#' @import ggplot2 -"_PACKAGE" diff --git a/R/utils-tidy-eval.R b/R/utils-tidy-eval.R deleted file mode 100644 index 09c3698..0000000 --- a/R/utils-tidy-eval.R +++ /dev/null @@ -1,107 +0,0 @@ -#' Tidy eval helpers -#' -#' @description -#' This page lists the tidy eval tools reexported in this package from -#' rlang. To learn about using tidy eval in scripts and packages at a -#' high level, see the [dplyr programming -#' vignette](https://dplyr.tidyverse.org/articles/programming.html) -#' and the [ggplot2 in packages -#' vignette](https://ggplot2.tidyverse.org/articles/ggplot2-in-packages.html). -#' The [Metaprogramming -#' section](https://adv-r.hadley.nz/metaprogramming.html) of [Advanced -#' R](https://adv-r.hadley.nz) may also be useful for a deeper dive. -#' -#' * The tidy eval operators `{{`, `!!`, and `!!!` are syntactic -#' constructs which are specially interpreted by tidy eval functions. -#' You will mostly need `{{`, as `!!` and `!!!` are more advanced -#' operators which you should not have to use in simple cases. -#' -#' The curly-curly operator `{{` allows you to tunnel data-variables -#' passed from function arguments inside other tidy eval functions. -#' `{{` is designed for individual arguments. To pass multiple -#' arguments contained in dots, use `...` in the normal way. -#' -#' ``` -#' my_function <- function(data, var, ...) { -#' data %>% -#' group_by(...) %>% -#' summarise(mean = mean({{ var }})) -#' } -#' ``` -#' -#' * [enquo()] and [enquos()] delay the execution of one or several -#' function arguments. The former returns a single expression, the -#' latter returns a list of expressions. Once defused, expressions -#' will no longer evaluate on their own. They must be injected back -#' into an evaluation context with `!!` (for a single expression) and -#' `!!!` (for a list of expressions). -#' -#' ``` -#' my_function <- function(data, var, ...) { -#' # Defuse -#' var <- enquo(var) -#' dots <- enquos(...) -#' -#' # Inject -#' data %>% -#' group_by(!!!dots) %>% -#' summarise(mean = mean(!!var)) -#' } -#' ``` -#' -#' In this simple case, the code is equivalent to the usage of `{{` -#' and `...` above. Defusing with `enquo()` or `enquos()` is only -#' needed in more complex cases, for instance if you need to inspect -#' or modify the expressions in some way. -#' -#' * The `.data` pronoun is an object that represents the current -#' slice of data. If you have a variable name in a string, use the -#' `.data` pronoun to subset that variable with `[[`. -#' -#' ``` -#' my_var <- "disp" -#' mtcars %>% summarise(mean = mean(.data[[my_var]])) -#' ``` -#' -#' * Another tidy eval operator is `:=`. It makes it possible to use -#' glue and curly-curly syntax on the LHS of `=`. For technical -#' reasons, the R language doesn't support complex expressions on -#' the left of `=`, so we use `:=` as a workaround. -#' -#' ``` -#' my_function <- function(data, var, suffix = "foo") { -#' # Use `{{` to tunnel function arguments and the usual glue -#' # operator `{` to interpolate plain strings. -#' data %>% -#' summarise("{{ var }}_mean_{suffix}" := mean({{ var }})) -#' } -#' ``` -#' -#' * Many tidy eval functions like `dplyr::mutate()` or -#' `dplyr::summarise()` give an automatic name to unnamed inputs. If -#' you need to create the same sort of automatic names by yourself, -#' use `as_label()`. For instance, the glue-tunnelling syntax above -#' can be reproduced manually with: -#' -#' ``` -#' my_function <- function(data, var, suffix = "foo") { -#' var <- enquo(var) -#' prefix <- as_label(var) -#' data %>% -#' summarise("{prefix}_mean_{suffix}" := mean(!!var)) -#' } -#' ``` -#' -#' Expressions defused with `enquo()` (or tunnelled with `{{`) need -#' not be simple column names, they can be arbitrarily complex. -#' `as_label()` handles those cases gracefully. If your code assumes -#' a simple column name, use `as_name()` instead. This is safer -#' because it throws an error if the input is not a name as expected. -#' -#' @md -#' @name tidyeval -#' @keywords internal -#' @importFrom rlang enquo enquos .data := as_name as_label -#' @aliases enquo enquos .data := as_name as_label -#' @export enquo enquos .data := as_name as_label -NULL diff --git a/README.Rmd b/README.Rmd index 1a6470c..7cb8984 100644 --- a/README.Rmd +++ b/README.Rmd @@ -18,73 +18,154 @@ knitr::opts_chunk$set( [![R-CMD-check](https://github.com/KopfLab/ggstackplot/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/KopfLab/ggstackplot/actions/workflows/R-CMD-check.yaml) -[![Documentation](https://img.shields.io/badge/docs-online-green.svg)](https://ggstackplot.kopflab.org/) [![codecov](https://codecov.io/gh/kopflab/ggstackplot/branch/main/graph/badge.svg?token=SN0YDIJ6Y6)](https://app.codecov.io/gh/kopflab/ggstackplot) +[![Documentation](https://img.shields.io/badge/docs-online-green.svg)](https://ggstackplot.kopflab.org/) ## About -Have you ever wanted to compare multiple panels of line plots that do not share coordinate schemes? These kinds of plots are ubiquitous in the Earth sciences, but there is not an easy way to create them with ggplot facets. +Have you ever wanted to create (partly) overlapping line plots with matched color-coding of the data and axes? These kinds of plots are common, for example, in climatology and oceanography research but there is not an easy way to create them with ggplot facets. The ggstackplot package builds on [ggplot2](https://ggplot2.tidyverse.org/) to provide a straightforward approach to building these kinds of plots while retaining the powerful grammar of graphics functionality of ggplots. ## Installation -You can install ggstackplot from [GitHub](https://github.com/) with: +You can install the development version of ggstackplot from [GitHub](https://github.com/) with: -```{r, eval = FALSE} -if(!requireNamespace("devtools", quietly = TRUE)) install.packages("devtools") -devtools::install_github("KopfLab/ggstackplot") +``` +# install.packages("pak") +pak::pak("KopfLab/ggstackplot") ``` ## Show me some code -```{r example, fig.width=7, fig.height=7} +```{r mtcars} library(ggstackplot) -# using the built-in economics dataset in ggplot2 -ggplot2::economics |> +# using R's built-in mtcars dataset +mtcars |> ggstackplot( # define shared x axis - x = date, - # define stacked y axes - y = c(pce, pop, psavert, unemploy), - # add a color palette - palette = "Set1" + x = mpg, + # define multiple y axes + y = c("weight" = wt, "horsepower" = hp), + # set colors + color = c("#E41A1C", "#377EB8"), + # set to complete overlap + overlap = 1 + ) +``` + +## Show me some climate data + +```{r, message = FALSE} +# download a recent dataset from the public climate data repository PANGAEA +dataset <- pangaear::pg_data(doi = "10.1594/PANGAEA.967047")[[1]] + +# show what some of these data look like +dataset$data[ + c("Depth ice/snow [m] (Top Depth)", + "Age [ka BP]", + "[SO4]2- [ng/g] (Ion chromatography)")] |> + head() |> knitr::kable() +``` + +**These data were kindly made available on [PANGEA](`r dataset$url`) by Sigl et al. (2024).** + +Full citation: + +> `r dataset$citation` + +```{r geodata} +# visualize the data with ggstackplot +dataset$data |> + ggstackplot( + x = "Age [ka BP]", + y = c( + # vertical stack of the measurements through time + "sulfate [ng/g]" = "[SO4]2- [ng/g] (Ion chromatography)", + "δ34S [‰]" = "δ34S [SO4]2- [‰ CDT] (Multi-collector ICP-MS (MC-IC...)", + "Δ33S [‰]" = "Δ33S [SO4]2- [‰ CDT] (Multi-collector ICP-MS (MC-IC...)" + ), + # color palette + palette = "Dark2", + # partial overlap of the panels + overlap = 0.4 ) ``` ## Show me more -```{r example2, fig.width=7, fig.height=4} +```{r economics, fig.height=6, message = FALSE} library(ggplot2) -# creating a horizontal stack instead of vertical and using some of the many -# customization features available in ggstackplot +# using the built-in economics dataset in ggplot2 to create a horizontal stack +# instead of vertical and using many of the customization features available +# with ggstackplot and ggplot2 ggplot2::economics |> ggstackplot( - # define shared y axis - y = date, - # define the stacked x axes with custom axis labels - x = c( - "personal consumption expenditures" = pce, - "population" = pop, - "personal savings rate" = psavert, - "unemployed persons" = unemploy), - # add a different color palette + # define shared x axis + x = date, + # define the stacked y axes + y = c(pce, pop, psavert, unemploy), + # pick the RColorBrewer Dark2 palette (good color contrast) palette = "Dark2", - # overlay the pce & pop plots and psavert & unemploy plots + # overlay the pce & pop plots (1), then make a full break (0) to the once + # again overlaye psavert & unemploy plots (1) overlap = c(1, 0, 1), - # provide a custom plot template + # switch axes so unemploy and psavert are on the side where they are + # highest, respectively - not doing this here by changing the order of y + # because we want pop and unemploy on the same side + switch_axes = TRUE, + # make shared axis space a bit smaller + shared_axis_size = 0.15, + # provide a base plot with shared graphics eelements among all plots template = + # it's a ggplot ggplot() + - geom_path() + + # use a line plot for all + geom_line() + + # we want the default stackplot theme theme_stackplot() + - scale_y_date(), + # add custom theme modifications, such as text size + theme(text = element_text(size = 14)) + + # make the shared axis a date axis + scale_x_date("year") + + # include y=0 for all plots to contextualize data better + expand_limits(y = 0), # add plot specific elements add = list( - # add points just for 2 plots - `unemployed persons` = geom_point(), - `personal savings rate` = geom_point() + pce = + # show pce in trillions of dollars + scale_y_continuous( + "personal consumption expenditures", + # always keep the secondary axis duplicated so ggstackplot can + # manage axis placement for you + sec.axis = dup_axis(), + # labeling function for the dollar units + labels = function(x) sprintf("$%.1f T", x/1000), + ), + pop = + # show population in millions + scale_y_continuous( + "population", sec.axis = dup_axis(), + labels = function(x) sprintf("%.0f M", x/1000) + ), + psavert = + # savings is in % + scale_y_continuous( + "personal savings rate", sec.axis = dup_axis(), + labels = function(x) paste0(x, "%"), + ) + + # show data points in addition to line + geom_point(), + unemploy = + # unemploy in millions + scale_y_continuous( + "unemployed persons", sec.axis = dup_axis(), + labels = function(x) sprintf("%.0f M", x/1000) + ) + + # show data points in addition to line + geom_point() ) ) ``` @@ -92,4 +173,3 @@ ggplot2::economics |> ## What else can I do with ggstackplot? - check out the **[Features](https://ggstackplot.kopflab.org/articles/features.html)** vignette for full details on all available functionality -- check out the **[Examples](https://ggstackplot.kopflab.org/articles/examples.html)** vignette for scientific data examples diff --git a/README.md b/README.md index 921f043..bbb8630 100644 --- a/README.md +++ b/README.md @@ -6,89 +6,185 @@ [![R-CMD-check](https://github.com/KopfLab/ggstackplot/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/KopfLab/ggstackplot/actions/workflows/R-CMD-check.yaml) -[![Documentation](https://img.shields.io/badge/docs-online-green.svg)](https://ggstackplot.kopflab.org/) [![codecov](https://codecov.io/gh/kopflab/ggstackplot/branch/main/graph/badge.svg?token=SN0YDIJ6Y6)](https://app.codecov.io/gh/kopflab/ggstackplot) +[![Documentation](https://img.shields.io/badge/docs-online-green.svg)](https://ggstackplot.kopflab.org/) ## About -Have you ever wanted to compare multiple panels of line plots that do -not share coordinate schemes? These kinds of plots are ubiquitous in the -Earth sciences, but there is not an easy way to create them with ggplot -facets. +Have you ever wanted to create (partly) overlapping line plots with +matched color-coding of the data and axes? These kinds of plots are +common, for example, in climatology and oceanography research but there +is not an easy way to create them with ggplot facets. The ggstackplot +package builds on [ggplot2](https://ggplot2.tidyverse.org/) to provide a +straightforward approach to building these kinds of plots while +retaining the powerful grammar of graphics functionality of ggplots. ## Installation -You can install ggstackplot from [GitHub](https://github.com/) with: +You can install the development version of ggstackplot from +[GitHub](https://github.com/) with: -``` r -if(!requireNamespace("devtools", quietly = TRUE)) install.packages("devtools") -devtools::install_github("KopfLab/ggstackplot") -``` + # install.packages("pak") + pak::pak("KopfLab/ggstackplot") ## Show me some code ``` r library(ggstackplot) -# using the built-in economics dataset in ggplot2 -ggplot2::economics |> +# using R's built-in mtcars dataset +mtcars |> ggstackplot( # define shared x axis - x = date, - # define stacked y axes - y = c(pce, pop, psavert, unemploy), - # add a color palette - palette = "Set1" + x = mpg, + # define multiple y axes + y = c("weight" = wt, "horsepower" = hp), + # set colors + color = c("#E41A1C", "#377EB8"), + # set to complete overlap + overlap = 1 + ) +``` + + + +## Show me some climate data + +``` r +# download a recent dataset from the public climate data repository PANGAEA +dataset <- pangaear::pg_data(doi = "10.1594/PANGAEA.967047")[[1]] + +# show what some of these data look like +dataset$data[ + c("Depth ice/snow [m] (Top Depth)", + "Age [ka BP]", + "[SO4]2- [ng/g] (Ion chromatography)")] |> + head() |> knitr::kable() +``` + +| Depth ice/snow \[m\] (Top Depth) | Age \[ka BP\] | \[SO4\]2- \[ng/g\] (Ion chromatography) | +|---------------------------------:|--------------:|----------------------------------------:| +| 160.215 | 1.20662 | 52.00 | +| 160.183 | 1.20300 | 165.00 | +| 160.151 | 1.20276 | 93.50 | +| 160.022 | 1.20191 | 42.25 | +| 159.990 | 1.20155 | 74.50 | +| 159.958 | 1.20130 | 104.50 | + +**These data were kindly made available on +[PANGEA](https://doi.org/10.1594/PANGAEA.967047) by Sigl et +al. (2024).** + +Full citation: + +> Sigl, Michael; Gabriel, Imogen; Hutchison, William; Burke, Andrea +> (2024): Sulfate concentration and sulfur isotope data from Greenland +> TUNU2013 ice-core samples between 740-765 CE \[dataset\]. PANGAEA, +> + +``` r +# visualize the data with ggstackplot +dataset$data |> + ggstackplot( + x = "Age [ka BP]", + y = c( + # vertical stack of the measurements through time + "sulfate [ng/g]" = "[SO4]2- [ng/g] (Ion chromatography)", + "δ34S [‰]" = "δ34S [SO4]2- [‰ CDT] (Multi-collector ICP-MS (MC-IC...)", + "Δ33S [‰]" = "Δ33S [SO4]2- [‰ CDT] (Multi-collector ICP-MS (MC-IC...)" + ), + # color palette + palette = "Dark2", + # partial overlap of the panels + overlap = 0.4 ) ``` - + ## Show me more ``` r library(ggplot2) -# creating a horizontal stack instead of vertical and using some of the many -# customization features available in ggstackplot +# using the built-in economics dataset in ggplot2 to create a horizontal stack +# instead of vertical and using many of the customization features available +# with ggstackplot and ggplot2 ggplot2::economics |> ggstackplot( - # define shared y axis - y = date, - # define the stacked x axes with custom axis labels - x = c( - "personal consumption expenditures" = pce, - "population" = pop, - "personal savings rate" = psavert, - "unemployed persons" = unemploy), - # add a different color palette + # define shared x axis + x = date, + # define the stacked y axes + y = c(pce, pop, psavert, unemploy), + # pick the RColorBrewer Dark2 palette (good color contrast) palette = "Dark2", - # overlay the pce & pop plots and psavert & unemploy plots + # overlay the pce & pop plots (1), then make a full break (0) to the once + # again overlaye psavert & unemploy plots (1) overlap = c(1, 0, 1), - # provide a custom plot template + # switch axes so unemploy and psavert are on the side where they are + # highest, respectively - not doing this here by changing the order of y + # because we want pop and unemploy on the same side + switch_axes = TRUE, + # make shared axis space a bit smaller + shared_axis_size = 0.15, + # provide a base plot with shared graphics eelements among all plots template = + # it's a ggplot ggplot() + - geom_path() + + # use a line plot for all + geom_line() + + # we want the default stackplot theme theme_stackplot() + - scale_y_date(), + # add custom theme modifications, such as text size + theme(text = element_text(size = 14)) + + # make the shared axis a date axis + scale_x_date("year") + + # include y=0 for all plots to contextualize data better + expand_limits(y = 0), # add plot specific elements add = list( - # add points just for 2 plots - `unemployed persons` = geom_point(), - `personal savings rate` = geom_point() + pce = + # show pce in trillions of dollars + scale_y_continuous( + "personal consumption expenditures", + # always keep the secondary axis duplicated so ggstackplot can + # manage axis placement for you + sec.axis = dup_axis(), + # labeling function for the dollar units + labels = function(x) sprintf("$%.1f T", x/1000), + ), + pop = + # show population in millions + scale_y_continuous( + "population", sec.axis = dup_axis(), + labels = function(x) sprintf("%.0f M", x/1000) + ), + psavert = + # savings is in % + scale_y_continuous( + "personal savings rate", sec.axis = dup_axis(), + labels = function(x) paste0(x, "%"), + ) + + # show data points in addition to line + geom_point(), + unemploy = + # unemploy in millions + scale_y_continuous( + "unemployed persons", sec.axis = dup_axis(), + labels = function(x) sprintf("%.0f M", x/1000) + ) + + # show data points in addition to line + geom_point() ) ) ``` - + ## What else can I do with ggstackplot? - check out the **[Features](https://ggstackplot.kopflab.org/articles/features.html)** vignette for full details on all available functionality -- check out the - **[Examples](https://ggstackplot.kopflab.org/articles/examples.html)** - vignette for scientific data examples diff --git a/_pkgdown.yml b/_pkgdown.yml index 828a240..b389575 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -17,8 +17,6 @@ navbar: left: - text: Features href: articles/features.html - - text: Examples - href: articles/examples.html - text: Reference href: reference/index.html right: diff --git a/man/figures/README-economics-1.png b/man/figures/README-economics-1.png new file mode 100644 index 0000000..897d60d Binary files /dev/null and b/man/figures/README-economics-1.png differ diff --git a/man/figures/README-geodata-1.png b/man/figures/README-geodata-1.png new file mode 100644 index 0000000..9a71154 Binary files /dev/null and b/man/figures/README-geodata-1.png differ diff --git a/man/figures/README-mtcars-1.png b/man/figures/README-mtcars-1.png new file mode 100644 index 0000000..b4fdfe9 Binary files /dev/null and b/man/figures/README-mtcars-1.png differ diff --git a/man/ggstackplot-package.Rd b/man/ggstackplot-package.Rd index 413699c..710cc5e 100644 --- a/man/ggstackplot-package.Rd +++ b/man/ggstackplot-package.Rd @@ -1,13 +1,17 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/package.R +% Please edit documentation in R/ggstackplot-package.R \docType{package} \name{ggstackplot-package} \alias{ggstackplot-package} -\alias{_PACKAGE} \title{ggstackplot: Create Overlapping Stacked Plots} \description{ Easily create overlapping grammar of graphics plots for scientific data visualization. This style of plotting is particularly common in climatology and oceanography research communities. } +\details{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} + +Have you ever wanted to create (partly) overlapping line plots with matched color-coding of the data and axes? These kinds of plots are common in climatology and oceanography research but there is not an easy way to create them with ggplot facets. The ggstackplot package builds on \href{https://ggplot2.tidyverse.org/}{ggplot2} to provide a straightforward approach to building these kinds of plots while retaining the powerful grammar of graphics approach of ggplots. Check out the functionality provided by ggstackplots at \url{https://ggstackplot.kopflab.org} +} \author{ \strong{Maintainer}: Sebastian Kopf \email{sebastian.kopf@colorado.edu} (\href{https://orcid.org/0000-0002-2044-0201}{ORCID}) [copyright holder] @@ -19,4 +23,3 @@ Authors: } } -\keyword{internal} diff --git a/man/ggstackplot.Rd b/man/ggstackplot.Rd index 5996af4..4d2a056 100644 --- a/man/ggstackplot.Rd +++ b/man/ggstackplot.Rd @@ -76,7 +76,7 @@ assemble_stackplot( \item{add}{a list of ggplot component calls to add to specific panel plots, either by panel variable name (named list) or index (unnamed list)} -\item{debug}{debug flag to print the stackplot tibble and gtable intermediates} +\item{debug}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} debug flag to print the stackplot tibble and gtable intermediates} \item{prepared_stackplot}{a nested data frame, the output from \code{\link[=prepare_stackplot]{prepare_stackplot()}}} } diff --git a/man/theme_stackplot.Rd b/man/theme_stackplot.Rd index fadd785..50d4ae6 100644 --- a/man/theme_stackplot.Rd +++ b/man/theme_stackplot.Rd @@ -7,10 +7,10 @@ theme_stackplot() } \value{ -\code{ggplot2::theme()} object +\code{\link[ggplot2:theme]{ggplot2::theme()}} object } \description{ -Recommended base theme for stacked gg plots +Returns a basic ggplot2 theme that extends \code{\link[ggplot2:ggtheme]{ggplot2::theme_bw()}} with a transparent plot background to make sure overlapping plots do not cover each other up. } \examples{ library(ggplot2) diff --git a/man/tidyeval.Rd b/man/tidyeval.Rd deleted file mode 100644 index f773abf..0000000 --- a/man/tidyeval.Rd +++ /dev/null @@ -1,98 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-tidy-eval.R -\name{tidyeval} -\alias{tidyeval} -\alias{enquo} -\alias{enquos} -\alias{.data} -\alias{:=} -\alias{as_name} -\alias{as_label} -\title{Tidy eval helpers} -\description{ -This page lists the tidy eval tools reexported in this package from -rlang. To learn about using tidy eval in scripts and packages at a -high level, see the \href{https://dplyr.tidyverse.org/articles/programming.html}{dplyr programming vignette} -and the \href{https://ggplot2.tidyverse.org/articles/ggplot2-in-packages.html}{ggplot2 in packages vignette}. -The \href{https://adv-r.hadley.nz/metaprogramming.html}{Metaprogramming section} of \href{https://adv-r.hadley.nz}{Advanced R} may also be useful for a deeper dive. -\itemize{ -\item The tidy eval operators \verb{\{\{}, \verb{!!}, and \verb{!!!} are syntactic -constructs which are specially interpreted by tidy eval functions. -You will mostly need \verb{\{\{}, as \verb{!!} and \verb{!!!} are more advanced -operators which you should not have to use in simple cases. - -The curly-curly operator \verb{\{\{} allows you to tunnel data-variables -passed from function arguments inside other tidy eval functions. -\verb{\{\{} is designed for individual arguments. To pass multiple -arguments contained in dots, use \code{...} in the normal way. - -\if{html}{\out{
}}\preformatted{my_function <- function(data, var, ...) \{ - data \%>\% - group_by(...) \%>\% - summarise(mean = mean(\{\{ var \}\})) -\} -}\if{html}{\out{
}} -\item \code{\link[=enquo]{enquo()}} and \code{\link[=enquos]{enquos()}} delay the execution of one or several -function arguments. The former returns a single expression, the -latter returns a list of expressions. Once defused, expressions -will no longer evaluate on their own. They must be injected back -into an evaluation context with \verb{!!} (for a single expression) and -\verb{!!!} (for a list of expressions). - -\if{html}{\out{
}}\preformatted{my_function <- function(data, var, ...) \{ - # Defuse - var <- enquo(var) - dots <- enquos(...) - - # Inject - data \%>\% - group_by(!!!dots) \%>\% - summarise(mean = mean(!!var)) -\} -}\if{html}{\out{
}} - -In this simple case, the code is equivalent to the usage of \verb{\{\{} -and \code{...} above. Defusing with \code{enquo()} or \code{enquos()} is only -needed in more complex cases, for instance if you need to inspect -or modify the expressions in some way. -\item The \code{.data} pronoun is an object that represents the current -slice of data. If you have a variable name in a string, use the -\code{.data} pronoun to subset that variable with \code{[[}. - -\if{html}{\out{
}}\preformatted{my_var <- "disp" -mtcars \%>\% summarise(mean = mean(.data[[my_var]])) -}\if{html}{\out{
}} -\item Another tidy eval operator is \verb{:=}. It makes it possible to use -glue and curly-curly syntax on the LHS of \code{=}. For technical -reasons, the R language doesn't support complex expressions on -the left of \code{=}, so we use \verb{:=} as a workaround. - -\if{html}{\out{
}}\preformatted{my_function <- function(data, var, suffix = "foo") \{ - # Use `\{\{` to tunnel function arguments and the usual glue - # operator `\{` to interpolate plain strings. - data \%>\% - summarise("\{\{ var \}\}_mean_\{suffix\}" := mean(\{\{ var \}\})) -\} -}\if{html}{\out{
}} -\item Many tidy eval functions like \code{dplyr::mutate()} or -\code{dplyr::summarise()} give an automatic name to unnamed inputs. If -you need to create the same sort of automatic names by yourself, -use \code{as_label()}. For instance, the glue-tunnelling syntax above -can be reproduced manually with: - -\if{html}{\out{
}}\preformatted{my_function <- function(data, var, suffix = "foo") \{ - var <- enquo(var) - prefix <- as_label(var) - data \%>\% - summarise("\{prefix\}_mean_\{suffix\}" := mean(!!var)) -\} -}\if{html}{\out{
}} - -Expressions defused with \code{enquo()} (or tunnelled with \verb{\{\{}) need -not be simple column names, they can be arbitrarily complex. -\code{as_label()} handles those cases gracefully. If your code assumes -a simple column name, use \code{as_name()} instead. This is safer -because it throws an error if the input is not a name as expected. -} -} -\keyword{internal} diff --git a/project.Rproj b/package.Rproj similarity index 100% rename from project.Rproj rename to package.Rproj diff --git a/tests/testthat.R b/tests/testthat.R index e47101c..6e41963 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,2 +1,3 @@ library(testthat) +library(ggplot2) test_check("ggstackplot") diff --git a/tests/testthat/_snaps/ggstackplot.md b/tests/testthat/_snaps/ggstackplot.md new file mode 100644 index 0000000..028780a --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot.md @@ -0,0 +1,45 @@ +# test ggstackplot() functionality + + Code + plot_prep <- prepare_stackplot(mtcars, x = mpg, y = c(wt, qsec), palette = "Set1") + +--- + + Code + plot_prep$plot[[2]] <- ggplot(mtcars) + aes(mpg, drat) + geom_point() + plot_prep$theme[[2]] <- theme_bw() + plot_prep + Output + # A tibble: 2 x 6 + .var config data plot theme add + + 1 wt + 2 qsec + +--- + + Code + x <- ggstackplot(mtcars, x = mpg, y = c(qsec, drat), debug = TRUE) + Message + + [DEBUG] stackplot tibble + Output + # A tibble: 2 x 11 + .var .xvar .yvar .color .axis_switch .shared_axis_min .shared_axis_max .first + + 1 qsec mpg qsec NA TRUE 10.4 33.9 TRUE + 2 drat mpg drat NA FALSE 10.4 33.9 FALSE + # i 3 more variables: .last , .direction , data + Message + + [DEBUG] stackplot gtables + Output + # A tibble: 3 x 14 + .var .direction size size_adjust pos_adjust overlap pos total_size rel_pos + + 1 qsec vertical 1 0 0 0 1 2.2 0.545 + 2 drat vertical 1 0 0 0 2 2.2 0.0909 + 3 prim~ vertical 0.2 1 0 0 2.2 2.2 0 + # i 5 more variables: rel_size , x , y , width , + # height + diff --git a/tests/testthat/_snaps/ggstackplot/assembled-plot.svg b/tests/testthat/_snaps/ggstackplot/assembled-plot.svg new file mode 100644 index 0000000..a218667 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/assembled-plot.svg @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 +4 +5 +wt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + +drat + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/horizontally-stacked-plot.svg b/tests/testthat/_snaps/ggstackplot/horizontally-stacked-plot.svg new file mode 100644 index 0000000..583594c --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/horizontally-stacked-plot.svg @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 +drat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 +4 +5 +wt + +10 +15 +20 +25 +30 +35 + + + + + + +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/hstack-w-axis-defs-and-labs-in-template.svg b/tests/testthat/_snaps/ggstackplot/hstack-w-axis-defs-and-labs-in-template.svg new file mode 100644 index 0000000..30e6314 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/hstack-w-axis-defs-and-labs-in-template.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 +x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +x + +10 +15 +20 +25 +30 +35 + + + + + + +y + + diff --git a/tests/testthat/_snaps/ggstackplot/hstack-w-axis-defs-in-template.svg b/tests/testthat/_snaps/ggstackplot/hstack-w-axis-defs-in-template.svg new file mode 100644 index 0000000..f6e0da0 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/hstack-w-axis-defs-in-template.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 +drat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + +10 +15 +20 +25 +30 +35 + + + + + + +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-added-elements.svg b/tests/testthat/_snaps/ggstackplot/plot-with-added-elements.svg new file mode 100644 index 0000000..fec8471 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-added-elements.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + +drat + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-color-palette.svg b/tests/testthat/_snaps/ggstackplot/plot-with-color-palette.svg new file mode 100644 index 0000000..882da48 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-color-palette.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 +4 +5 +wt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 + + + + +qsec + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-custom-template.svg b/tests/testthat/_snaps/ggstackplot/plot-with-custom-template.svg new file mode 100644 index 0000000..24b4163 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-custom-template.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + +drat + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-duplicated-axes.svg b/tests/testthat/_snaps/ggstackplot/plot-with-duplicated-axes.svg new file mode 100644 index 0000000..ef22071 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-duplicated-axes.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 +4 +5 + + + + + + + + + +2 +3 +4 +5 +wt +wt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 + + + + + + + + + +16 +18 +20 +22 +qsec +qsec + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-large-shared-axis-space.svg b/tests/testthat/_snaps/ggstackplot/plot-with-large-shared-axis-space.svg new file mode 100644 index 0000000..337c9bc --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-large-shared-axis-space.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + +drat + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-manual-color-definition.svg b/tests/testthat/_snaps/ggstackplot/plot-with-manual-color-definition.svg new file mode 100644 index 0000000..882da48 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-manual-color-definition.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 +4 +5 +wt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 + + + + +qsec + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-overlap.svg b/tests/testthat/_snaps/ggstackplot/plot-with-overlap.svg new file mode 100644 index 0000000..f1f0273 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-overlap.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + +drat + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-switched-axes.svg b/tests/testthat/_snaps/ggstackplot/plot-with-switched-axes.svg new file mode 100644 index 0000000..6ba29fa --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-switched-axes.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 +4 +5 + + + + +wt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-with-template-sec-axis-labs.svg b/tests/testthat/_snaps/ggstackplot/plot-with-template-sec-axis-labs.svg new file mode 100644 index 0000000..130cd67 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-with-template-sec-axis-labs.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + +10 +15 +20 +25 +30 +35 +x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 + + + + + + + + + +16 +18 +20 +22 +y +y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 +y +y + + + + + + + +10 +15 +20 +25 +30 +35 +x + + diff --git a/tests/testthat/_snaps/ggstackplot/plot-without-alternating-axes.svg b/tests/testthat/_snaps/ggstackplot/plot-without-alternating-axes.svg new file mode 100644 index 0000000..208b7ae --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/plot-without-alternating-axes.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 +4 +5 + + + + +wt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 + + + + +qsec + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/vertically-stacked-plot.svg b/tests/testthat/_snaps/ggstackplot/vertically-stacked-plot.svg new file mode 100644 index 0000000..a34b240 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/vertically-stacked-plot.svg @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 +4 +5 + + + + +wt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + +drat + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/_snaps/ggstackplot/vstack-w-axis-defs-and-labs-in-template.svg b/tests/testthat/_snaps/ggstackplot/vstack-w-axis-defs-and-labs-in-template.svg new file mode 100644 index 0000000..7c83c38 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/vstack-w-axis-defs-and-labs-in-template.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + +y + + + + + + + +10 +15 +20 +25 +30 +35 +x + + diff --git a/tests/testthat/_snaps/ggstackplot/vstack-w-axis-defs-in-template.svg b/tests/testthat/_snaps/ggstackplot/vstack-w-axis-defs-in-template.svg new file mode 100644 index 0000000..24b4163 --- /dev/null +++ b/tests/testthat/_snaps/ggstackplot/vstack-w-axis-defs-in-template.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +16 +18 +20 +22 +qsec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3.0 +3.5 +4.0 +4.5 +5.0 + + + + + +drat + + + + + + + +10 +15 +20 +25 +30 +35 +mpg + + diff --git a/tests/testthat/test-ggstackplot.R b/tests/testthat/test-ggstackplot.R index bd86273..7eb7d83 100644 --- a/tests/testthat/test-ggstackplot.R +++ b/tests/testthat/test-ggstackplot.R @@ -1,7 +1,4 @@ -context("ggstackplot") - - -test_that("test create_stackplot_tibble() parameters", { +test_that("test create_stackplot_tibble() safety checks", { # data expect_error( create_stackplot_tibble(), @@ -81,11 +78,9 @@ test_that("test create_stackplot_tibble() parameters", { create_stackplot_tibble(mtcars, x = c(mpg, cyl, hp, drat, wt, qsec, vs, am, gear), y = disp, palette = "Accent"), "must be.*identifying a valid RColorBrewer palette" ) - }) - -test_that("test create_stackplot_gtables() parameters", { +test_that("test create_stackplot_gtables() safety checks", { # data expect_error( @@ -94,27 +89,146 @@ test_that("test create_stackplot_gtables() parameters", { ) expect_error( create_stackplot_gtables(data.frame(x = 5)), - "with columns '.var'" + "with columns.*.var" ) # prepared stackplot for testing - expect_is(prep_sp <- prepare_stackplot(mtcars, "mpg", "wt"), "data.frame") + expect_s3_class(prep_sp <- prepare_stackplot(mtcars, "mpg", "wt"), "data.frame") # overlap expect_error( create_stackplot_gtables(prep_sp, overlap = "42"), - "`overlap` must be either a single numeric value.*between 0 and 1.*or one for each" + "`overlap` must be either a single numeric value.*between 0 and 1.*one for.*each" ) expect_error( create_stackplot_gtables(prep_sp, overlap = -0.01), - "`overlap` must be either a single numeric value.*between 0 and 1.*or one for each" + "`overlap` must be either a single numeric value.*between 0 and 1.*one for.*each" ) expect_error( create_stackplot_gtables(prep_sp, overlap = 1.01), - "`overlap` must be either a single numeric value.*between 0 and 1.*or one for each" + "`overlap` must be either a single numeric value.*between 0 and 1.*one for.*each" ) expect_error( create_stackplot_gtables(prep_sp, overlap = c(0.5, 0.5)), - "`overlap` must be either a single numeric value.*between 0 and 1.*or one for each" + "`overlap` must be either a single numeric value.*between 0 and 1.*one for.*each" + ) + expect_warning( + ggstackplot(mtcars, x = mpg, y = c(qsec, drat), add = list(qsec = DNE)), + "failed to parse added code" ) }) + +test_that("test ggstackplot() functionality", { + # this indirectly tests all the other functionsˇ + + # `x` and `y` arguments + vdiffr::expect_doppelganger( + "vertically stacked plot", + ggstackplot(mtcars, x = mpg, y = c(wt, qsec, drat))) + vdiffr::expect_doppelganger( + "horizontally stacked plot", + ggstackplot(mtcars, y = mpg, x = c(wt, qsec, drat)) + ) + vdiffr::expect_doppelganger( + "plot with color palette", + ggstackplot(mtcars, x = mpg, y = c(wt, qsec), palette = "Set1") + ) + vdiffr::expect_doppelganger( + "plot with manual color definition", + ggstackplot(mtcars, x = mpg, y = c(wt, qsec), color = c("#E41A1C", "#377EB8")) + ) + vdiffr::expect_doppelganger( + "plot with duplicated axes", + ggstackplot(mtcars, x = mpg, y = c(wt, qsec), both_axes = TRUE) + ) + vdiffr::expect_doppelganger( + "plot without alternating axes", + ggstackplot(mtcars, x = mpg, y = c(wt, qsec), alternate_axes = FALSE) + ) + vdiffr::expect_doppelganger( + "plot with switched axes", + ggstackplot(mtcars, x = mpg, y = c(wt, qsec), switch_axes = TRUE) + ) + vdiffr::expect_doppelganger( + "plot with overlap", + ggstackplot(mtcars, x = mpg, y = c(qsec, drat), overlap = 1) + ) + vdiffr::expect_doppelganger( + "plot with large shared axis space", + ggstackplot(mtcars, x = mpg, y = c(qsec, drat), shared_axis_size = 0.5) + ) + vdiffr::expect_doppelganger( + "plot with custom template", + ggstackplot(mtcars, x = mpg, y = c(qsec, drat), + template = ggplot() + geom_line()) + ) + vdiffr::expect_doppelganger( + "plot with template sec axis labs", + ggstackplot(mtcars, x = mpg, y = c(qsec, drat), + alternate_axes = FALSE, both_axes = TRUE, + template = ggplot() + geom_line() + + scale_x_continuous(sec.axis = dup_axis())+ + labs(x = "x", y = "y")) + ) + vdiffr::expect_doppelganger( + "vstack w axis defs in template", + ggstackplot( + mtcars, x = mpg, y = c(qsec, drat), + template = + ggplot() + geom_line() + + scale_x_continuous() + + scale_y_continuous() + ) + ) + vdiffr::expect_doppelganger( + "vstack w axis defs and labs in template", + ggstackplot( + mtcars, x = mpg, y = c(qsec, drat), + template = + ggplot() + geom_line() + + scale_x_continuous() + + scale_y_continuous() + + labs(x = "x", y = "y") + ) + ) + vdiffr::expect_doppelganger( + "hstack w axis defs in template", + ggstackplot( + mtcars, y = mpg, x = c(qsec, drat), + template = + ggplot() + geom_line() + + scale_x_continuous() + + scale_y_continuous() + ) + ) + vdiffr::expect_doppelganger( + "hstack w axis defs and labs in template", + ggstackplot( + mtcars, y = mpg, x = c(qsec, drat), + template = + ggplot() + geom_line() + + scale_x_continuous() + + scale_y_continuous() + + labs(x = "x", y = "y") + ) + ) + vdiffr::expect_doppelganger( + "plot with added elements", + ggstackplot(mtcars, x = mpg, y = c(qsec, drat), add = list(qsec = geom_path())) + ) + # advanced + expect_snapshot({ + plot_prep <- mtcars |> prepare_stackplot(x = mpg, y = c(wt, qsec), palette = "Set1") + }) + expect_snapshot({ + plot_prep$plot[[2]] <- ggplot(mtcars) + aes(mpg, drat) + geom_point() + plot_prep$theme[[2]] <- theme_bw() + plot_prep + }) + vdiffr::expect_doppelganger("assembled plot", plot_prep |> assemble_stackplot()) + # debug output + expect_snapshot( + x <- ggstackplot(mtcars, x = mpg, y = c(qsec, drat), debug = TRUE) + ) +}) + diff --git a/tests/testthat/test-helpers.R b/tests/testthat/test-helpers.R index 5c48596..c93a022 100644 --- a/tests/testthat/test-helpers.R +++ b/tests/testthat/test-helpers.R @@ -1,5 +1,3 @@ -context("helpers") - test_that("test get_plot_component_grobs()", { # parameters expect_error(get_plot_component_grobs(), "`gg` must be a ggplot object") diff --git a/vignettes/.gitignore b/vignettes/.gitignore index 097b241..46c2dbc 100644 --- a/vignettes/.gitignore +++ b/vignettes/.gitignore @@ -1,2 +1,3 @@ *.html *.R +*unused diff --git a/vignettes/HAB_example_data.RDS b/vignettes/HAB_example_data.RDS deleted file mode 100644 index df73b4a..0000000 Binary files a/vignettes/HAB_example_data.RDS and /dev/null differ diff --git a/vignettes/examples.Rmd b/vignettes/examples.Rmd deleted file mode 100644 index a5a49e3..0000000 --- a/vignettes/examples.Rmd +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: "ggstackplot examples" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{ggstackplot examples} - %\VignetteEncoding{UTF-8} - %\VignetteEngine{knitr::rmarkdown} -editor_options: - chunk_output_type: console ---- - -```{r, include = FALSE} -knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>" -) -``` - -```{r setup, message=FALSE} -library(dplyr) -library(ggplot2) -library(ggstackplot) -``` - -# Example 1: high altitude balloon flight - -## Load example data - -The data in this example are from measurements acquired on a scientific high altitude balloon flight. Caro, T.A., Wendeln, M., Freeland, M. et al. Ultraviolet light measurements (280–400 nm) acquired from stratospheric balloon flight to assess influence on bioaerosols. Aerobiologia 35, 771–776 (2019). https://doi.org/10.1007/s10453-019-09597-9 - -```{r} -hab_data <- readRDS("HAB_example_data.RDS") |> - slice_head(n = 100) - -head(hab_data) -``` - -## Visualize example data - -```{r, fig.height=8, fig.width=7} -hab_data |> - ggstackplot( - x = `alt (m)`, - y = c(`Temperature (C)`, `Pressure (hPa)`, `Speed (m/s)` = `speed`, `Total UV Flux (mW/cm)`), - color = c("darkblue", "darkgreen", "black", "red"), - alternate_axes = TRUE, - switch_axes = TRUE, - shared_axis_size = 0.3, - overlap = 0.1, - template = - ggplot() + - scale_x_continuous( - name = "Altitude (m)", - n.breaks = 12, - sec.axis = sec_axis( - name = "Altitude (km)", - trans = ~ . / 1000 - ), - expand = c(0, 0) - ) + - expand_limits(x = 0) + - geom_rect(ymin = -Inf, ymax = Inf, - xmin = 0, xmax = 7500, - fill = "lightgray", color = NA, - alpha = 0.1) + - geom_rect(ymin = -Inf, ymax = Inf, - xmin = 7500, xmax = 35000, - fill = "lightblue1", color = NA, - alpha = 0.1) + - theme_stackplot(), - add = list( - `Temperature (C)` = geom_path(), - `Pressure (hPa)` = - geom_path() + - theme(axis.text.x = element_text(angle = 45, hjust = 0)), - `Speed (m/s)` = geom_path(), - `Total UV Flux (mW/cm)` = - scale_y_continuous(limits = c(0, 10), sec.axis = dup_axis()) + - geom_path() - ) - ) -``` - diff --git a/vignettes/features.Rmd b/vignettes/features.Rmd index dbfd7bf..f44d594 100644 --- a/vignettes/features.Rmd +++ b/vignettes/features.Rmd @@ -63,7 +63,7 @@ Select multiple x variables to stack: ```{r} # all examples shown in this document work the same way for a horizontal -# stack simplfy by switching out the x and y assignments +# stack, simply switch out the x and y assignments mtcars |> ggstackplot( y = mpg, x = c(wt, qsec, drat) @@ -294,7 +294,7 @@ mtcars |> ggstackplot( x = mpg, y = c(qsec, drat), color = c("#E41A1C", "#377EB8"), - #simplify_shared_axis = FALSE, + simplify_shared_axis = FALSE, alternate_axes = FALSE ) ``` @@ -421,7 +421,38 @@ mtcars |> geom_point(data = function(df) filter(df, .yvar == "drat")) + theme_stackplot() ) - +``` + +## Different plot elements + +One can also change the geoms in the default theme. Here we use `geom_path()` instead of `geom_line()` in a horizontal stack. This is a very common use case because `geom_line()` connects the data points by increasing x-axis which is not always what we want (for example in oceanographic depth plots where we want to connect the data points by increasing y-axis value). + +```{r} +# horizontal stack with default (geom_line()) +mtcars |> + ggstackplot( + y = mpg, x = c(qsec, drat), + color = c("#E41A1C", "#377EB8"), + template = + ggplot() + + geom_point() + + geom_line() + # default in template + theme_stackplot() + ) +# the following is the exact same data but using a +# horizontal stack with "depth-profile" like geom_path() +mtcars |> + # arrange data by the y-axis + arrange(mpg) |> + ggstackplot( + y = mpg, x = c(qsec, drat), + color = c("#E41A1C", "#377EB8"), + template = + ggplot() + + geom_point() + + geom_path() + # plots data in order + theme_stackplot() + ) ``` ## Additional plot elements @@ -442,8 +473,6 @@ mtcars |> ) ``` - - ## Axis modifications Sometimes secondary axes will still be desired, especially if that axis is a transformation of an existing one. For example, here, we create a square root mpg axis that is plotted against the mpg axis. Again, all of this is defined in the `template` argument by adding a `scale_x_continuous` argument, just as you would in a normal ggplot. @@ -464,7 +493,7 @@ mtcars |> name = "this is my mpg axis", # this can be the same with dup_axis() or as here have a transformed axis sec.axis = sec_axis( - trans = sqrt, + transform = sqrt, name = expression(sqrt(mpg)), breaks = scales::pretty_breaks(5) ) @@ -621,7 +650,7 @@ mtcars |> # Putting it all together ```{r, message = FALSE, fig.height=8} -# example with economics data bundled with ggplot2 +# example from the README with economics data bundled with ggplot2 ggplot2::economics |> ggstackplot( # define shared x axis @@ -630,7 +659,8 @@ ggplot2::economics |> y = c(pce, pop, psavert, unemploy), # pick the RColorBrewer Dark2 palette (good color contrast) palette = "Dark2", - # overlay the pce & pop plots (1), then make a full break (0) to the once again overlaye psavert & unemploy plots (1) + # overlay the pce & pop plots (1), then make a full break (0) to the once + # again overlaye psavert & unemploy plots (1) overlap = c(1, 0, 1), # switch axes so unemploy and psavert are on the side where they are # highest, respectively - not doing this here by changing the order of y @@ -649,7 +679,7 @@ ggplot2::economics |> # add custom theme modifications, such as text size theme(text = element_text(size = 14)) + # make the shared axis a date axis - scale_x_date() + + scale_x_date("year") + # include y=0 for all plots to contextualize data better expand_limits(y = 0), # add plot specific elements @@ -658,34 +688,32 @@ ggplot2::economics |> pce = # show pce in trillions of dollars scale_y_continuous( - "personal consumption expenditures", - labels = function(x) sprintf("$%.1f T", x/1000), + "personal consumption expenditures", # always keep the secondary axis duplicated so ggstackplot can # manage axis placement for you - sec.axis = dup_axis() + sec.axis = dup_axis(), + # labeling function for the dollar units + labels = function(x) sprintf("$%.1f T", x/1000), ), pop = # show population in millions scale_y_continuous( - "population", - labels = function(x) sprintf("%.0f M", x/1000), - sec.axis = dup_axis() + "population", sec.axis = dup_axis(), + labels = function(x) sprintf("%.0f M", x/1000) ), psavert = # savings is in % scale_y_continuous( - "personal savings rate", + "personal savings rate", sec.axis = dup_axis(), labels = function(x) paste0(x, "%"), - sec.axis = dup_axis() ) + # show data points in addition to line geom_point(), unemploy = # unemploy in millions scale_y_continuous( - "unemployed persons", - labels = function(x) sprintf("%.0f M", x/1000), - sec.axis = dup_axis() + "unemployed persons", sec.axis = dup_axis(), + labels = function(x) sprintf("%.0f M", x/1000) ) + # show data points in addition to line geom_point()