From a47be596b501ce3915a34f9d924d2beda231ac42 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:07:45 +0100 Subject: [PATCH 1/3] Upkeep 2024-10 (#6156) * record upkeep date * `usethis::use_mit_licence()` * fix broken urls * `usethis:::use_codecov_badge()` * `usethis::use_lifecycle()` * `usethis::use_tidy_logo()` & `pkgdown::build_favicons(overwrite = TRUE)` * Alt text for cheat sheet link * Use `knitr::convert_chunk_header(type = "yaml")` (#6149) * Use `expect_snapshot(error = TRUE)` instead of `expect_error()` (#6150) * replace `expect_error()` by `expect_snapshot(error = TRUE)` * censor machine dependent paths in snapshots * commit snapshots * skip gnarly snapshots * Bump R version * `usethis::use_tidy_description()` * distribute `test-guides.R` * convert chunk headers * regenerate snapshots after moving guides * adapt to gtable 0.3.6 * use 'core developer team' as copyright holder * replace `expect_warning()` * fix non portable file path check * Hmisc 5.2-0 requires R >= 4.2.0 --- .github/workflows/R-CMD-check.yaml | 2 +- DESCRIPTION | 5 +- LICENSE.md | 2 +- R/stat-ellipse.R | 2 +- README.Rmd | 15 +- README.md | 8 +- man/figures/lifecycle-deprecated.svg | 22 +- man/figures/lifecycle-experimental.svg | 22 +- man/figures/lifecycle-stable.svg | 30 +- man/figures/lifecycle-superseded.svg | 22 +- man/figures/logo.png | Bin 24183 -> 24069 bytes man/stat_ellipse.Rd | 2 +- pkgdown/favicon/apple-touch-icon.png | Bin 15859 -> 11877 bytes pkgdown/favicon/favicon-48x48.png | Bin 0 -> 2046 bytes pkgdown/favicon/favicon.ico | Bin 15086 -> 15086 bytes pkgdown/favicon/favicon.svg | 3 + pkgdown/favicon/site.webmanifest | 21 + pkgdown/favicon/web-app-manifest-192x192.png | Bin 0 -> 13222 bytes pkgdown/favicon/web-app-manifest-512x512.png | Bin 0 -> 72477 bytes tests/testthat/_snaps/aes-calculated.md | 8 + tests/testthat/_snaps/aes-setting.md | 36 + tests/testthat/_snaps/aes.md | 40 + tests/testthat/_snaps/coord-.md | 16 + tests/testthat/_snaps/coord-transform.md | 8 + tests/testthat/_snaps/empty-data.md | 27 + tests/testthat/_snaps/facet-.md | 33 + tests/testthat/_snaps/facet-labels.md | 21 + tests/testthat/_snaps/fortify.md | 150 ++++ tests/testthat/_snaps/geom-bar.md | 4 + tests/testthat/_snaps/geom-boxplot.md | 15 + tests/testthat/_snaps/geom-col.md | 8 + tests/testthat/_snaps/geom-dotplot.md | 8 + tests/testthat/_snaps/geom-path.md | 12 + tests/testthat/_snaps/geom-rect.md | 9 + tests/testthat/_snaps/geom-rug.md | 4 + tests/testthat/_snaps/geom-sf.md | 12 + tests/testthat/_snaps/geom-smooth.md | 4 + tests/testthat/_snaps/geom-text.md | 16 + tests/testthat/_snaps/ggsave.md | 69 ++ tests/testthat/_snaps/guide-.md | 11 + tests/testthat/_snaps/guide-axis.md | 19 + .../align-facet-labels-facets-horizontal.svg | 0 .../align-facet-labels-facets-vertical.svg | 0 .../axis-guides-basic.svg | 41 - .../axis-guides-check-overlap.svg | 41 - .../axis-guides-negative-rotation.svg | 41 - .../axis-guides-positive-rotation.svg | 41 - ...axis-guides-text-dodged-into-rows-cols.svg | 41 - ...axis-guides-vertical-negative-rotation.svg | 41 - .../axis-guides-vertical-rotation.svg | 41 - .../axis-guides-with-capped-ends.svg | 0 .../guide-axis/axis-guides-zero-breaks.svg | 26 + .../axis-guides-zero-rotation.svg | 41 - .../guide-axis-customization.svg | 0 ...de-axis-theta-in-cartesian-coordinates.svg | 0 ...xis-theta-with-angle-adapting-to-theta.svg | 0 .../guide-titles-with-coord-trans.svg | 0 .../guides-specified-in-guides.svg | 0 .../guides-with-minor-ticks.svg | 0 .../logtick-axes-with-customisation.svg | 0 .../position-guide-titles.svg | 0 .../{guides => guide-axis}/stacked-axes.svg | 0 .../stacked-radial-axes.svg | 0 .../thick-axis-lines.svg | 0 tests/testthat/_snaps/guide-colorbar.md | 14 + .../combined-colour-and-fill-aesthetics.svg} | 2 +- .../customized-colorbar.svg | 0 ...e-to-red-colorbar-white-ticks-no-frame.svg | 0 .../enlarged-guides.svg | 0 .../horizontal-legend-direction.svg | 0 .../left-aligned-legend-key.svg | 0 .../legend-byrow-true.svg | 0 .../legend-with-widely-spaced-keys.svg | 0 .../vertical-legend-direction.svg | 0 tests/testthat/_snaps/guides.md | 47 +- .../_snaps/guides/axis-guides-zero-breaks.svg | 67 -- tests/testthat/_snaps/labels.md | 16 + tests/testthat/_snaps/layer.md | 44 + tests/testthat/_snaps/performance.md | 9 + tests/testthat/_snaps/position_dodge.md | 11 + tests/testthat/_snaps/scale-date.md | 10 + tests/testthat/_snaps/scale-discrete.md | 60 ++ tests/testthat/_snaps/scale-gradient.md | 4 + tests/testthat/_snaps/scale-manual.md | 16 + tests/testthat/_snaps/scales-breaks-labels.md | 133 +++ tests/testthat/_snaps/scales.md | 28 + tests/testthat/_snaps/stat-bin.md | 20 + tests/testthat/_snaps/stat-contour.md | 14 + tests/testthat/_snaps/stat-density.md | 8 + tests/testthat/_snaps/stat-ecdf.md | 18 + tests/testthat/_snaps/stat-function.md | 5 + tests/testthat/_snaps/stat-ydensity.md | 9 + tests/testthat/_snaps/stats.md | 29 + tests/testthat/_snaps/theme.md | 24 + tests/testthat/_snaps/utilities-checks.md | 28 + tests/testthat/_snaps/utilities.md | 4 + tests/testthat/test-aes-calculated.R | 10 +- tests/testthat/test-aes-setting.R | 8 +- tests/testthat/test-aes.R | 27 +- tests/testthat/test-coord-.R | 4 +- tests/testthat/test-coord-transform.R | 4 +- tests/testthat/test-empty-data.R | 16 +- tests/testthat/test-facet-.R | 12 +- tests/testthat/test-facet-labels.R | 6 +- tests/testthat/test-fortify.R | 33 +- tests/testthat/test-geom-bar.R | 6 +- tests/testthat/test-geom-boxplot.R | 18 +- tests/testthat/test-geom-col.R | 11 +- tests/testthat/test-geom-dotplot.R | 6 +- tests/testthat/test-geom-path.R | 9 +- tests/testthat/test-geom-rect.R | 5 +- tests/testthat/test-geom-rug.R | 6 +- tests/testthat/test-geom-sf.R | 17 +- tests/testthat/test-geom-smooth.R | 2 +- tests/testthat/test-geom-text.R | 9 +- tests/testthat/test-geom-violin.R | 2 +- tests/testthat/test-ggsave.R | 30 +- tests/testthat/test-guide-.R | 49 ++ tests/testthat/test-guide-axis.R | 399 +++++++++ tests/testthat/test-guide-colorbar.R | 100 +++ tests/testthat/test-guide-legend.R | 213 +++++ tests/testthat/test-guides.R | 768 +----------------- tests/testthat/test-labels.R | 8 +- tests/testthat/test-layer.R | 24 +- tests/testthat/test-performance.R | 2 +- tests/testthat/test-position_dodge.R | 2 +- tests/testthat/test-qplot.R | 2 +- tests/testthat/test-scale-date.R | 12 +- tests/testthat/test-scale-discrete.R | 18 +- tests/testthat/test-scale-expansion.R | 2 +- tests/testthat/test-scale-gradient.R | 5 +- tests/testthat/test-scale-manual.R | 5 +- tests/testthat/test-scales-breaks-labels.R | 54 +- tests/testthat/test-scales.R | 38 +- tests/testthat/test-stat-bin.R | 4 +- tests/testthat/test-stat-contour.R | 9 +- tests/testthat/test-stat-density.R | 7 +- tests/testthat/test-stat-ecdf.R | 15 +- tests/testthat/test-stat-function.R | 2 +- tests/testthat/test-stat-ydensity.R | 10 +- tests/testthat/test-stats.R | 11 +- tests/testthat/test-theme.R | 9 +- tests/testthat/test-utilities-checks.R | 14 +- tests/testthat/test-utilities.R | 19 +- vignettes/articles/faq-annotation.Rmd | 3 +- vignettes/articles/faq-axes.Rmd | 21 +- vignettes/articles/faq-bars.Rmd | 3 +- vignettes/articles/faq-customising.Rmd | 6 +- vignettes/articles/faq-faceting.Rmd | 9 +- vignettes/articles/faq-reordering.Rmd | 3 +- vignettes/extending-ggplot2.Rmd | 24 +- vignettes/ggplot2-in-packages.Rmd | 18 +- vignettes/ggplot2-specs.Rmd | 16 +- vignettes/ggplot2.Rmd | 38 +- vignettes/profiling.Rmd | 8 +- 155 files changed, 2320 insertions(+), 1531 deletions(-) create mode 100644 pkgdown/favicon/favicon-48x48.png create mode 100644 pkgdown/favicon/favicon.svg create mode 100644 pkgdown/favicon/site.webmanifest create mode 100644 pkgdown/favicon/web-app-manifest-192x192.png create mode 100644 pkgdown/favicon/web-app-manifest-512x512.png create mode 100644 tests/testthat/_snaps/aes-setting.md create mode 100644 tests/testthat/_snaps/empty-data.md create mode 100644 tests/testthat/_snaps/facet-labels.md create mode 100644 tests/testthat/_snaps/geom-bar.md create mode 100644 tests/testthat/_snaps/geom-col.md create mode 100644 tests/testthat/_snaps/geom-rect.md create mode 100644 tests/testthat/_snaps/geom-smooth.md create mode 100644 tests/testthat/_snaps/guide-.md create mode 100644 tests/testthat/_snaps/guide-axis.md rename tests/testthat/_snaps/{guides => guide-axis}/align-facet-labels-facets-horizontal.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/align-facet-labels-facets-vertical.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-basic.svg (52%) rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-check-overlap.svg (83%) rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-negative-rotation.svg (76%) rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-positive-rotation.svg (76%) rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-text-dodged-into-rows-cols.svg (76%) rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-vertical-negative-rotation.svg (77%) rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-vertical-rotation.svg (77%) rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-with-capped-ends.svg (100%) create mode 100644 tests/testthat/_snaps/guide-axis/axis-guides-zero-breaks.svg rename tests/testthat/_snaps/{guides => guide-axis}/axis-guides-zero-rotation.svg (75%) rename tests/testthat/_snaps/{guides => guide-axis}/guide-axis-customization.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/guide-axis-theta-in-cartesian-coordinates.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/guide-axis-theta-with-angle-adapting-to-theta.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/guide-titles-with-coord-trans.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/guides-specified-in-guides.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/guides-with-minor-ticks.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/logtick-axes-with-customisation.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/position-guide-titles.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/stacked-axes.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/stacked-radial-axes.svg (100%) rename tests/testthat/_snaps/{guides => guide-axis}/thick-axis-lines.svg (100%) create mode 100644 tests/testthat/_snaps/guide-colorbar.md rename tests/testthat/_snaps/{guides/one-combined-colorbar-for-colour-and-fill-aesthetics.svg => guide-colorbar/combined-colour-and-fill-aesthetics.svg} (98%) rename tests/testthat/_snaps/{guides => guide-colorbar}/customized-colorbar.svg (100%) rename tests/testthat/_snaps/{guides => guide-colorbar}/white-to-red-colorbar-white-ticks-no-frame.svg (100%) rename tests/testthat/_snaps/{guides => guide-legend}/enlarged-guides.svg (100%) rename tests/testthat/_snaps/{guides => guide-legend}/horizontal-legend-direction.svg (100%) rename tests/testthat/_snaps/{guides => guide-legend}/left-aligned-legend-key.svg (100%) rename tests/testthat/_snaps/{guides => guide-legend}/legend-byrow-true.svg (100%) rename tests/testthat/_snaps/{guides => guide-legend}/legend-with-widely-spaced-keys.svg (100%) rename tests/testthat/_snaps/{guides => guide-legend}/vertical-legend-direction.svg (100%) delete mode 100644 tests/testthat/_snaps/guides/axis-guides-zero-breaks.svg create mode 100644 tests/testthat/_snaps/performance.md create mode 100644 tests/testthat/_snaps/position_dodge.md create mode 100644 tests/testthat/_snaps/scale-date.md create mode 100644 tests/testthat/_snaps/scale-gradient.md create mode 100644 tests/testthat/_snaps/scales-breaks-labels.md create mode 100644 tests/testthat/_snaps/stat-contour.md create mode 100644 tests/testthat/_snaps/stat-function.md create mode 100644 tests/testthat/_snaps/utilities-checks.md create mode 100644 tests/testthat/test-guide-.R create mode 100644 tests/testthat/test-guide-axis.R create mode 100644 tests/testthat/test-guide-colorbar.R create mode 100644 tests/testthat/test-guide-legend.R diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 2e6d40ab70..430ac87c5d 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -63,7 +63,7 @@ jobs: cache-version: 3 extra-packages: > any::rcmdcheck, - Hmisc=?ignore-before-r=4.1.0, + Hmisc=?ignore-before-r=4.2.0, quantreg=?ignore-before-r=4.3.0 needs: check diff --git a/DESCRIPTION b/DESCRIPTION index ff587e4b88..77755980b7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: ggplot2 -Version: 3.5.1.9000 Title: Create Elegant Data Visualisations Using the Grammar of Graphics +Version: 3.5.1.9000 Authors@R: c( person("Hadley", "Wickham", , "hadley@posit.co", role = "aut", comment = c(ORCID = "0000-0003-4757-117X")), @@ -30,7 +30,7 @@ License: MIT + file LICENSE URL: https://ggplot2.tidyverse.org, https://github.com/tidyverse/ggplot2 BugReports: https://github.com/tidyverse/ggplot2/issues Depends: - R (>= 3.5) + R (>= 4.0) Imports: cli, grDevices, @@ -75,6 +75,7 @@ VignetteBuilder: knitr Config/Needs/website: ggtext, tidyr, forcats, tidyverse/tidytemplate Config/testthat/edition: 3 +Config/usethis/last-upkeep: 2024-10-24 Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) diff --git a/LICENSE.md b/LICENSE.md index 0a8a19674c..ce73598634 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2020 ggplot2 authors +Copyright (c) 2024 ggplot2 core developer team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/R/stat-ellipse.R b/R/stat-ellipse.R index 152b27d280..7404e9eed6 100644 --- a/R/stat-ellipse.R +++ b/R/stat-ellipse.R @@ -5,7 +5,7 @@ #' #' @references John Fox and Sanford Weisberg (2011). An \R Companion to #' Applied Regression, Second Edition. Thousand Oaks CA: Sage. URL: -#' \url{https://socialsciences.mcmaster.ca/jfox/Books/Companion/} +#' \url{https://www.john-fox.ca/Companion/} #' @references Michael Friendly. Georges Monette. John Fox. "Elliptical Insights: Understanding Statistical Methods through Elliptical Geometry." #' Statist. Sci. 28 (1) 1 - 39, February 2013. URL: \url{https://projecteuclid.org/journals/statistical-science/volume-28/issue-1/Elliptical-Insights-Understanding-Statistical-Methods-through-Elliptical-Geometry/10.1214/12-STS402.full} #' diff --git a/README.Rmd b/README.Rmd index 337aa50b45..f546e5dc22 100644 --- a/README.Rmd +++ b/README.Rmd @@ -4,7 +4,8 @@ output: github_document -```{r, echo = FALSE} +```{r} +#| echo: false knitr::opts_chunk$set( collapse = TRUE, comment = "#>", @@ -12,12 +13,12 @@ knitr::opts_chunk$set( ) ``` -# ggplot2 +# ggplot2 ggplot2 website [![R-CMD-check](https://github.com/tidyverse/ggplot2/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tidyverse/ggplot2/actions/workflows/R-CMD-check.yaml) -[![Codecov test coverage](https://codecov.io/gh/tidyverse/ggplot2/branch/main/graph/badge.svg)](https://app.codecov.io/gh/tidyverse/ggplot2?branch=main) [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/ggplot2)](https://cran.r-project.org/package=ggplot2) +[![Codecov test coverage](https://codecov.io/gh/tidyverse/ggplot2/graph/badge.svg)](https://app.codecov.io/gh/tidyverse/ggplot2) ## Overview @@ -26,7 +27,8 @@ ggplot2 is a system for declaratively creating graphics, based on [The Grammar o ## Installation -```{r, eval = FALSE} +```{r} +#| eval: false # The easiest way to get ggplot2 is to install the whole tidyverse: install.packages("tidyverse") @@ -40,13 +42,14 @@ pak::pak("tidyverse/ggplot2") ## Cheatsheet - +ggplot2 cheatsheet ## Usage It's hard to succinctly describe how ggplot2 works because it embodies a deep philosophy of visualisation. However, in most cases you start with `ggplot()`, supply a dataset and aesthetic mapping (with `aes()`). You then add on layers (like `geom_point()` or `geom_histogram()`), scales (like `scale_colour_brewer()`), faceting specifications (like `facet_wrap()`) and coordinate systems (like `coord_flip()`). -```{r example} +```{r} +#| label: example #| fig.alt: "Scatterplot of engine displacement versus highway miles per #| gallon, for 234 cars coloured by 7 'types' of car. The displacement and miles #| per gallon are inversely correlated." diff --git a/README.md b/README.md index 71f0060465..7fa139829b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# ggplot2 +# ggplot2 ggplot2 website [![R-CMD-check](https://github.com/tidyverse/ggplot2/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tidyverse/ggplot2/actions/workflows/R-CMD-check.yaml) -[![Codecov test -coverage](https://codecov.io/gh/tidyverse/ggplot2/branch/main/graph/badge.svg)](https://app.codecov.io/gh/tidyverse/ggplot2?branch=main) [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/ggplot2)](https://cran.r-project.org/package=ggplot2) +[![Codecov test +coverage](https://codecov.io/gh/tidyverse/ggplot2/graph/badge.svg)](https://app.codecov.io/gh/tidyverse/ggplot2) ## Overview @@ -35,7 +35,7 @@ pak::pak("tidyverse/ggplot2") ## Cheatsheet - +ggplot2 cheatsheet ## Usage diff --git a/man/figures/lifecycle-deprecated.svg b/man/figures/lifecycle-deprecated.svg index 4baaee01cd..b61c57c3f9 100644 --- a/man/figures/lifecycle-deprecated.svg +++ b/man/figures/lifecycle-deprecated.svg @@ -1 +1,21 @@ -lifecyclelifecycledeprecateddeprecated \ No newline at end of file + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/man/figures/lifecycle-experimental.svg b/man/figures/lifecycle-experimental.svg index d1d060e923..5d88fc2c65 100644 --- a/man/figures/lifecycle-experimental.svg +++ b/man/figures/lifecycle-experimental.svg @@ -1 +1,21 @@ -lifecyclelifecycleexperimentalexperimental \ No newline at end of file + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/man/figures/lifecycle-stable.svg b/man/figures/lifecycle-stable.svg index e015dc8110..9bf21e76bc 100644 --- a/man/figures/lifecycle-stable.svg +++ b/man/figures/lifecycle-stable.svg @@ -1 +1,29 @@ -lifecyclelifecyclestablestable \ No newline at end of file + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/man/figures/lifecycle-superseded.svg b/man/figures/lifecycle-superseded.svg index 75f24f5534..db8d757f70 100644 --- a/man/figures/lifecycle-superseded.svg +++ b/man/figures/lifecycle-superseded.svg @@ -1 +1,21 @@ - lifecyclelifecyclesupersededsuperseded \ No newline at end of file + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + diff --git a/man/figures/logo.png b/man/figures/logo.png index 719583f61960cb16c0e19c5099583354af8eeba7..8007389c72657a700637d15b5d39e3ab808a8542 100644 GIT binary patch delta 30 mcmeyqhp}}J;{=t7CN5krxFk5#`PXh=b!lT$YV2gsI6VNsU<^Y5 delta 140 zcmZqO!}xs<;{+96mUKs7M+SzC{oH>NStlyGb3JEfXBW|6-nQ!I#-h~Nc-0cuh?11V zl2ohYqEsNoU}RuqtZM*7<{<{=Rz?O^#-`c^237_JlO8K=W?*1IXvob^$xN%nt>N*$ LS^Sf)#2NwsF>NaG diff --git a/man/stat_ellipse.Rd b/man/stat_ellipse.Rd index 138bda2e65..4bc30ef863 100644 --- a/man/stat_ellipse.Rd +++ b/man/stat_ellipse.Rd @@ -151,7 +151,7 @@ ggplot(faithful, aes(waiting, eruptions, fill = eruptions > 3)) + \references{ John Fox and Sanford Weisberg (2011). An \R Companion to Applied Regression, Second Edition. Thousand Oaks CA: Sage. URL: -\url{https://socialsciences.mcmaster.ca/jfox/Books/Companion/} +\url{https://www.john-fox.ca/Companion/} Michael Friendly. Georges Monette. John Fox. "Elliptical Insights: Understanding Statistical Methods through Elliptical Geometry." Statist. Sci. 28 (1) 1 - 39, February 2013. URL: \url{https://projecteuclid.org/journals/statistical-science/volume-28/issue-1/Elliptical-Insights-Understanding-Statistical-Methods-through-Elliptical-Geometry/10.1214/12-STS402.full} diff --git a/pkgdown/favicon/apple-touch-icon.png b/pkgdown/favicon/apple-touch-icon.png index c78bb690e52f2b5c588b19414cd1d3ccae2d31a7..74c6511d61f81ece0a87bde2f477734f6bc97d1b 100644 GIT binary patch literal 11877 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 literal 15859 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 T%)@u^3M3_YHM!3+rosOQxbuq< diff --git a/pkgdown/favicon/favicon-48x48.png b/pkgdown/favicon/favicon-48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..f554a3a108089367a89b09716a6ad20d7fa5abf2 GIT binary patch literal 2046 zcmVp+h6LfqEr zbOgdZ+R&$YRIg#fh7Fcd&Jsd2Nhv8TER1q;a+sz~n?`7}_l0Au=25zY5K;(XhLU5) zj-}+}WNK|~rH+mcCfp|_C2^k}h#AjlG@1bqTk)Y%$}}nEKO;trpt!g=s;{pnMNybk z0P27A#m2@G#^M<%Wg2+&yW)Dieuxlat(3A0GrDNeA}T5>awzHwfJ#@cT;XS=l%Q@Q z#5(ZwyW(1{c90O_Qz>N)gio0=g^WfcwYRt5r=&XoAUt#W^l9c{2U0tH`0%Na;TPqF z5FZF3ES-ZFl$27z%*>xZpDtd!=v25b z04R2X49K#Zl>!|M2E#KRD;^viJWxs*B!nmjlaP=Q%FN89wzjrjh5G`4q9`mYJw1ay z)beRXc3py@n_K1edi|SHN+XWAkt0XazJ2?sxw)Bz+)G_uUDVy(eP4T<(m_SlEo8MR zJO9=k@jVTqJ7AFe+-I+`jgE8WhVv)L=JZ=rX- z%AuW#<->v7nS1ZZ`Ay0|yS!%9ShW#*G^+r{sDiT`IoeGN$qwfX_2- z-vuD`a+h1>VgD&vCVFAa8hU=rdU`kPTl(V5D9Xsla46gP0DH|N02t)V!BkaM(So>C z3QGD<`S26Bsj=0$6YA;g#;y2)l~*Y`;TMk_S*MdRp`oDxr;(MeWYhex5rgy_V(0DS%Z8MjBV;_V6qO`Jq|HFv1OdW)`KzfQ}SFXxTe*H|dO zsFL3MY#S^8(c%QEZM66oz?e@$DXD}gxr8Y8yF@-MdSmPk0I0deMrGBF)Yj(ugi(AA z0Cc|kFOL}a2EZF0iZ=lMY`FFK0SKHBNVj#2wG}??k0KlYn@7~QhRKINE)YMeYePRzRUc8tAtXsE^XBKHiJO=;}lAD{$ z`(oCtSsX(_+5G%`HX+zKlE1rl?V@w%&e4`FTiCn+1g2O~Q9*Teb-o9HSp|rlJ9n~; zVgq8sV#6Igc#xMK;AYO8$;!mU#4r#w^9CMR0lfUE*$%7KN}D!q;>m#WIDtUe$UX;v zWruA-$%P9S@@WFX=DvLSGR>VkmqGS4p@-qf@{#)9wrv|9bv>EX*4FauV>4r8q78sx z0ytR^_@ON=EzJ?@`wD=?Vqu-^Y54+=5?kZPkEf$YkCM%1^U9HV1Hfjpv3Ugr1$ma( za+X?zNtjHgeg|MOnOOD%r50T!cabq(y?QmDD7~c??|mbC(9~j8RTUoqklCl?uDkR) z@})?hz$SgY0v3yfc|(Rz((CRD91|x_vNlb0gB+MGEx>!UjP6A07*qoM6N<$f){eqX#fBK literal 0 HcmV?d00001 diff --git a/pkgdown/favicon/favicon.ico b/pkgdown/favicon/favicon.ico index 325c248772a3440dea1c2fafed52e35a9872584e..1d64806627415546e3570926e11fa328cd6afb4a 100644 GIT binary patch literal 15086 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 literal 15086 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 diff --git a/pkgdown/favicon/favicon.svg b/pkgdown/favicon/favicon.svg new file mode 100644 index 0000000000..37c061c7fa --- /dev/null +++ b/pkgdown/favicon/favicon.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/pkgdown/favicon/site.webmanifest b/pkgdown/favicon/site.webmanifest new file mode 100644 index 0000000000..4ebda26b53 --- /dev/null +++ b/pkgdown/favicon/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/pkgdown/favicon/web-app-manifest-192x192.png b/pkgdown/favicon/web-app-manifest-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..dbfb8f092ccaa2f104b35f7dde4647c0ac28af7f GIT binary patch literal 13222 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%0DL>yKkawR$nNTYr{(giO<+ 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 literal 0 HcmV?d00001 diff --git a/tests/testthat/_snaps/aes-calculated.md b/tests/testthat/_snaps/aes-calculated.md index 629d2007d5..cd3424516b 100644 --- a/tests/testthat/_snaps/aes-calculated.md +++ b/tests/testthat/_snaps/aes-calculated.md @@ -6,6 +6,14 @@ Duplicated aesthetics after name standardisation: colour +# calculated aesthetics throw warnings when lengths mismatch + + Failed to apply `after_stat()` for the following aesthetic: colour. + +--- + + Failed to apply `after_scale()` for the following aesthetic: colour. + # A deprecated warning is issued when stat(var) or ..var.. is used `stat(foo)` was deprecated in ggplot2 3.4.0. diff --git a/tests/testthat/_snaps/aes-setting.md b/tests/testthat/_snaps/aes-setting.md new file mode 100644 index 0000000000..b0ba47a52a --- /dev/null +++ b/tests/testthat/_snaps/aes-setting.md @@ -0,0 +1,36 @@ +# aesthetic parameters match length of data + + Code + set_colours(rep("red", 2)) + Condition + Error in `geom_point()`: + ! Problem while setting up geom aesthetics. + i Error occurred in the 1st layer. + Caused by error in `check_aesthetics()`: + ! Aesthetics must be either length 1 or the same as the data (5). + x Fix the following mappings: `colour`. + +--- + + Code + set_colours(rep("red", 3)) + Condition + Error in `geom_point()`: + ! Problem while setting up geom aesthetics. + i Error occurred in the 1st layer. + Caused by error in `check_aesthetics()`: + ! Aesthetics must be either length 1 or the same as the data (5). + x Fix the following mappings: `colour`. + +--- + + Code + set_colours(rep("red", 4)) + Condition + Error in `geom_point()`: + ! Problem while setting up geom aesthetics. + i Error occurred in the 1st layer. + Caused by error in `check_aesthetics()`: + ! Aesthetics must be either length 1 or the same as the data (5). + x Fix the following mappings: `colour`. + diff --git a/tests/testthat/_snaps/aes.md b/tests/testthat/_snaps/aes.md index 7f7f3ddc89..4a891eacbe 100644 --- a/tests/testthat/_snaps/aes.md +++ b/tests/testthat/_snaps/aes.md @@ -1,3 +1,43 @@ +# accessing an undefined variable results in an error + + Code + get_layer_data(p) + Condition + Error in `geom_point()`: + ! Problem while computing aesthetics. + i Error occurred in the 1st layer. + Caused by error: + ! object 'foo' not found + +# aes standardises aesthetic names + + Duplicated aesthetics after name standardisation: colour + +# warn_for_aes_extract_usage() warns for discouraged uses of $ and [[ within aes() + + Use of `df$x` is discouraged. + i Use `x` instead. + +--- + + Use of `df[["x"]]` is discouraged. + i Use `.data[["x"]]` instead. + +--- + + Use of `df$x` is discouraged. + i Use `x` instead. + +# warn_for_aes_extract_usage() does not evaluate function calls + + Use of `df$x` is discouraged. + i Use `x` instead. + +# Warnings are issued when plots use discouraged extract usage within aes() + + Use of `df$x` is discouraged. + i Use `x` instead. + # aes evaluation fails with unknown input Unknown input: diff --git a/tests/testthat/_snaps/coord-.md b/tests/testthat/_snaps/coord-.md index c4f74d626c..acf9ad78c6 100644 --- a/tests/testthat/_snaps/coord-.md +++ b/tests/testthat/_snaps/coord-.md @@ -18,3 +18,19 @@ `coord()` has not implemented a `range()` method. +# check coord limits errors only on bad inputs + + Code + check_coord_limits(xlim(1, 2)) + Condition + Error: + ! `xlim(1, 2)` must be a vector of length 2, not a object. + +--- + + Code + check_coord_limits(1:3) + Condition + Error: + ! `1:3` must be a vector of length 2, not an integer vector of length 3. + diff --git a/tests/testthat/_snaps/coord-transform.md b/tests/testthat/_snaps/coord-transform.md index def35a0f27..2aaa3c156f 100644 --- a/tests/testthat/_snaps/coord-transform.md +++ b/tests/testthat/_snaps/coord-transform.md @@ -1,3 +1,11 @@ +# warnings are generated when coord_trans() results in new infinite values + + Transformation introduced infinite values in y-axis + +--- + + Transformation introduced infinite values in x-axis + # coord_trans() throws error when limits are badly specified `xlim` must be a vector of length 2, not a object. diff --git a/tests/testthat/_snaps/empty-data.md b/tests/testthat/_snaps/empty-data.md new file mode 100644 index 0000000000..9889c48a91 --- /dev/null +++ b/tests/testthat/_snaps/empty-data.md @@ -0,0 +1,27 @@ +# layers with empty data are silently omitted with facet_wrap + + Code + get_layer_data(d) + Condition + Error in `combine_vars()`: + ! Faceting variables must have at least one value. + +# layers with empty data are silently omitted with facet_grid + + Code + get_layer_data(d) + Condition + Error in `combine_vars()`: + ! Faceting variables must have at least one value. + +# Should error when totally empty data frame because there's no x and y + + Code + get_layer_data(d) + Condition + Error in `geom_point()`: + ! Problem while computing aesthetics. + i Error occurred in the 2nd layer. + Caused by error: + ! object 'wt' not found + diff --git a/tests/testthat/_snaps/facet-.md b/tests/testthat/_snaps/facet-.md index 2efa86bc64..17d76b1f86 100644 --- a/tests/testthat/_snaps/facet-.md +++ b/tests/testthat/_snaps/facet-.md @@ -1,3 +1,19 @@ +# facets reject aes() + + Code + facet_wrap(aes(foo)) + Condition + Error in `validate_facets()`: + ! Please use `vars()` to supply facet variables. + +--- + + Code + facet_grid(aes(foo)) + Condition + Error in `validate_facets()`: + ! Please use `vars()` to supply facet variables. + # facet_grid() fails if passed both a formula and a vars() `rows` must be `NULL` or a `vars()` list if `cols` is a `vars()` list. @@ -30,6 +46,14 @@ x Plot is missing `letter` Layer is missing `letter` +# at least one combination must exist in combine_vars() + + Code + combine_vars(list(df), vars = vars(letter = letter)) + Condition + Error in `combine_vars()`: + ! Faceting variables must have at least one value. + # combine_vars() generates the correct combinations At least one layer must contain all faceting variables: `b` and `c` @@ -40,6 +64,15 @@ Faceting variables must have at least one value. +# eval_facet() is tolerant for missing columns (#2963) + + Code + eval_facet(quo(no_such_variable * x), data_frame(foo = 1), possible_columns = c( + "x")) + Condition + Error: + ! object 'no_such_variable' not found + # validate_facets() provide meaningful errors Please use `vars()` to supply facet variables. diff --git a/tests/testthat/_snaps/facet-labels.md b/tests/testthat/_snaps/facet-labels.md new file mode 100644 index 0000000000..6cdd9c1ad0 --- /dev/null +++ b/tests/testthat/_snaps/facet-labels.md @@ -0,0 +1,21 @@ +# labeller() dispatches labellers + + Code + ggplotGrob(p3) + Condition + Error in `resolve_labeller()`: + ! Cannot supply both `rows` and `cols` to `facet_wrap()`. + +--- + + Code + ggplotGrob(p5) + Condition + Error in `labeller()`: + ! Conflict between `.cols` and `cyl`. + +# old school labellers still work + + The `labeller` API has been updated. Labellers taking `variable` and `value` arguments are now deprecated. + i See labellers documentation. + diff --git a/tests/testthat/_snaps/fortify.md b/tests/testthat/_snaps/fortify.md index 81c3decea5..2034f092fb 100644 --- a/tests/testthat/_snaps/fortify.md +++ b/tests/testthat/_snaps/fortify.md @@ -3,3 +3,153 @@ `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`, not a object. i Did you accidentally pass `aes()` to the `data` argument? +# fortify.default can handle healthy data-frame-like objects + + Code + fortify(X) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `dim(data)` must return an of length 2. + +--- + + Code + fortify(array(1:60, 5:3)) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `dim(data)` must return an of length 2. + +--- + + Code + fortify(cbind(X, Y, Z, deparse.level = 0)) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `colnames(data)` must return a of length `ncol(data)`. + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `dim.foo()`: + ! oops! + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `dim(data)` must return an of length 2. + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `dim(data)` must return an of length 2. + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `dim(data)` can't have `NA`s or negative values. + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `dim(data)` can't have `NA`s or negative values. + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `dimnames(x)[[2L]]`: + ! subscript out of bounds + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `colnames(data)` must return a of length `ncol(data)`. + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.prevalidate_data_frame_like_object()`: + ! `colnames(data)` must return a of length `ncol(data)`. + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `as.data.frame.foo()`: + ! oops! + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.postvalidate_data_frame_like_object()`: + ! `as.data.frame(data)` must return a . + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.postvalidate_data_frame_like_object()`: + ! `as.data.frame(data)` must preserve dimensions. + +--- + + Code + fortify(object) + Condition + Error in `fortify()`: + ! `data` must be a , or an object coercible by `fortify()`, or a valid -like object coercible by `as.data.frame()`. + Caused by error in `.postvalidate_data_frame_like_object()`: + ! `as.data.frame(data)` must preserve column names. + diff --git a/tests/testthat/_snaps/geom-bar.md b/tests/testthat/_snaps/geom-bar.md new file mode 100644 index 0000000000..0afff44c16 --- /dev/null +++ b/tests/testthat/_snaps/geom-bar.md @@ -0,0 +1,4 @@ +# geom_bar removes bars with parts outside the plot limits + + Removed 1 row containing missing values or values outside the scale range (`geom_bar()`). + diff --git a/tests/testthat/_snaps/geom-boxplot.md b/tests/testthat/_snaps/geom-boxplot.md index d50a9db5e9..10e75bb438 100644 --- a/tests/testthat/_snaps/geom-boxplot.md +++ b/tests/testthat/_snaps/geom-boxplot.md @@ -1,3 +1,18 @@ +# geom_boxplot for continuous x gives warning if more than one x (#992) + + Continuous x aesthetic + i did you forget `aes(group = ...)`? + +--- + + Continuous x aesthetic + i did you forget `aes(group = ...)`? + +--- + + Continuous x aesthetic + i did you forget `aes(group = ...)`? + # boxplots with a group size >1 error Can only draw one boxplot per group. diff --git a/tests/testthat/_snaps/geom-col.md b/tests/testthat/_snaps/geom-col.md new file mode 100644 index 0000000000..1dfce430b0 --- /dev/null +++ b/tests/testthat/_snaps/geom-col.md @@ -0,0 +1,8 @@ +# geom_col removes columns with parts outside the plot limits + + Removed 3 rows containing missing values or values outside the scale range (`geom_col()`). + +--- + + Removed 1 row containing missing values or values outside the scale range (`geom_col()`). + diff --git a/tests/testthat/_snaps/geom-dotplot.md b/tests/testthat/_snaps/geom-dotplot.md index ba2fa8558c..a559276853 100644 --- a/tests/testthat/_snaps/geom-dotplot.md +++ b/tests/testthat/_snaps/geom-dotplot.md @@ -14,3 +14,11 @@ Caused by error in `compute_group()`: ! `weight` must be nonnegative integers, not a double vector. +# geom_dotplot draws correctly + + Removed 2 rows containing missing values or values outside the scale range (`stat_bindot()`). + +--- + + Removed 2 rows containing missing values or values outside the scale range (`stat_bindot()`). + diff --git a/tests/testthat/_snaps/geom-path.md b/tests/testthat/_snaps/geom-path.md index 6516134f98..9396883a78 100644 --- a/tests/testthat/_snaps/geom-path.md +++ b/tests/testthat/_snaps/geom-path.md @@ -5,3 +5,15 @@ Caused by error in `draw_panel()`: ! `geom_path()` can't have varying colour, linewidth, and/or alpha along the line when linetype isn't solid. +# stairstep() exists with error when an invalid `direction` is given + + Code + stairstep(df, direction = "invalid") + Condition + Error in `stairstep()`: + ! `direction` must be one of "hv", "vh", or "mid", not "invalid". + +# NA linetype is dropped with warning + + Removed 2 rows containing missing values or values outside the scale range (`geom_path()`). + diff --git a/tests/testthat/_snaps/geom-rect.md b/tests/testthat/_snaps/geom-rect.md new file mode 100644 index 0000000000..8c04cbbb21 --- /dev/null +++ b/tests/testthat/_snaps/geom-rect.md @@ -0,0 +1,9 @@ +# geom_rect can derive corners + + Code + GeomRect$setup_data(test, NULL) + Condition + Error in `resolve_rect()`: + ! `geom_rect()` requires two of the following aesthetics: xmin, xmax, x, or width. + i Currently, x is present. + diff --git a/tests/testthat/_snaps/geom-rug.md b/tests/testthat/_snaps/geom-rug.md index 06e4ad195b..c9e2157860 100644 --- a/tests/testthat/_snaps/geom-rug.md +++ b/tests/testthat/_snaps/geom-rug.md @@ -5,3 +5,7 @@ Caused by error in `draw_panel()`: ! `length` must be a object, not the number 0.01. +# geom_rug() warns about missing values when na.rm = FALSE + + Removed 2 rows containing missing values or values outside the scale range (`geom_rug()`). + diff --git a/tests/testthat/_snaps/geom-sf.md b/tests/testthat/_snaps/geom-sf.md index 1cc4fbb7d1..2d5217dd4f 100644 --- a/tests/testthat/_snaps/geom-sf.md +++ b/tests/testthat/_snaps/geom-sf.md @@ -1,3 +1,15 @@ +# geom_sf() removes rows containing missing aes + + Removed 1 row containing missing values or values outside the scale range (`geom_sf()`). + +--- + + Removed 1 row containing missing values or values outside the scale range (`geom_sf()`). + +--- + + Removed 1 row containing missing values or values outside the scale range (`geom_sf()`). + # errors are correctly triggered Problem while converting geom to grob. diff --git a/tests/testthat/_snaps/geom-smooth.md b/tests/testthat/_snaps/geom-smooth.md new file mode 100644 index 0000000000..aaffd02403 --- /dev/null +++ b/tests/testthat/_snaps/geom-smooth.md @@ -0,0 +1,4 @@ +# geom_smooth() works when one group fails + + span too small. fewer data values than degrees of freedom. + diff --git a/tests/testthat/_snaps/geom-text.md b/tests/testthat/_snaps/geom-text.md index c9d11b2bc7..e86cc9c905 100644 --- a/tests/testthat/_snaps/geom-text.md +++ b/tests/testthat/_snaps/geom-text.md @@ -3,3 +3,19 @@ Both `position` and `nudge_x`/`nudge_y` are supplied. i Only use one approach to alter the position. +# geom_text() drops missing angles + + Removed 1 row containing missing values or values outside the scale range (`geom_text()`). + +# geom_text() rejects exotic units + + Code + ggplotGrob(p + geom_text(size = 10, size.unit = "npc")) + Condition + Error in `geom_text()`: + ! Problem while converting geom to grob. + i Error occurred in the 1st layer. + Caused by error in `resolve_text_unit()`: + ! `unit` must be one of "mm", "pt", "cm", "in", or "pc", not "npc". + i Did you mean "pc"? + diff --git a/tests/testthat/_snaps/ggsave.md b/tests/testthat/_snaps/ggsave.md index 8a16fc672b..372d324b95 100644 --- a/tests/testthat/_snaps/ggsave.md +++ b/tests/testthat/_snaps/ggsave.md @@ -1,7 +1,76 @@ +# ggsave can create directories + + Code + ggsave(path, p) + Condition + Error in `ggsave()`: + ! Cannot find directory 'PATH' + i Please supply an existing directory or use `create.dir = TRUE`. + +# ggsave warns about empty or multiple filenames + + Code + x <- suppressMessages(ggsave(c(file1, file2), plot)) + Condition + Warning in `ggsave()`: + `filename` must have length 1, not 2. + ! Only the first,'PATH', will be used. + +--- + + Code + ggsave(character(), plot) + Condition + Error in `ggsave()`: + ! `filename` must be a single string, not an empty character vector. + +# ggsave fails informatively for no-extension filenames + + Code + ggsave(tempfile(), plot) + Condition + Error in `ggsave()`: + ! Can't save to PATH + i Either supply `filename` with a file extension or supply `device`. + +# warned about large plot unless limitsize = FALSE + + Code + plot_dim(c(50, 50)) + Condition + Error: + ! Dimensions exceed 50 inches (`height` and `width` are specified in inches not pixels). + i If you're sure you want a plot that big, use `limitsize = FALSE`. + +--- + + Code + plot_dim(c(15000, 15000), units = "px") + Condition + Error: + ! Dimensions exceed 50 inches (`height` and `width` are specified in pixels). + i If you're sure you want a plot that big, use `limitsize = FALSE`. + # unknown device triggers error `device` must be a string, function or `NULL`, not the number 1. +--- + + Code + plot_dev("xyz") + Condition + Error: + ! Unknown graphics device "xyz" + +--- + + Code + plot_dev(NULL, "test.xyz") + Condition + Error: + ! Unknown graphics device "xyz" + # invalid single-string DPI values throw an error `dpi` must be one of "screen", "print", or "retina", not "abc". diff --git a/tests/testthat/_snaps/guide-.md b/tests/testthat/_snaps/guide-.md new file mode 100644 index 0000000000..600e56f797 --- /dev/null +++ b/tests/testthat/_snaps/guide-.md @@ -0,0 +1,11 @@ +# dots are checked when making guides + + Ignoring unknown argument to `guide_axis()`: `foo`. + +--- + + Arguments in `...` must be used. + x Problematic argument: + * foo = "bar" + i Did you misspell an argument name? + diff --git a/tests/testthat/_snaps/guide-axis.md b/tests/testthat/_snaps/guide-axis.md new file mode 100644 index 0000000000..97bb6b8fca --- /dev/null +++ b/tests/testthat/_snaps/guide-axis.md @@ -0,0 +1,19 @@ +# a warning is generated when guides are drawn at a location that doesn't make sense + + Position guide is perpendicular to the intended axis. + i Did you mean to specify a different guide `position`? + +# a warning is generated when more than one position guide is drawn at a location + + `guide_axis()`: Discarding guide on merge. + i Do you have more than one guide with the same `position`? + +# Using non-position guides for position scales results in an informative error + + `guide_legend()` cannot be used for x, xmin, xmax, or xend. + i Use any non position aesthetic instead. + +# guide_axis_logticks calculates appropriate ticks + + The `prescale.base` argument will override the scale's log-10 transformation in log-tick positioning. + diff --git a/tests/testthat/_snaps/guides/align-facet-labels-facets-horizontal.svg b/tests/testthat/_snaps/guide-axis/align-facet-labels-facets-horizontal.svg similarity index 100% rename from tests/testthat/_snaps/guides/align-facet-labels-facets-horizontal.svg rename to tests/testthat/_snaps/guide-axis/align-facet-labels-facets-horizontal.svg diff --git a/tests/testthat/_snaps/guides/align-facet-labels-facets-vertical.svg b/tests/testthat/_snaps/guide-axis/align-facet-labels-facets-vertical.svg similarity index 100% rename from tests/testthat/_snaps/guides/align-facet-labels-facets-vertical.svg rename to tests/testthat/_snaps/guide-axis/align-facet-labels-facets-vertical.svg diff --git a/tests/testthat/_snaps/guides/axis-guides-basic.svg b/tests/testthat/_snaps/guide-axis/axis-guides-basic.svg similarity index 52% rename from tests/testthat/_snaps/guides/axis-guides-basic.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-basic.svg index ae2a74c24d..32f3b568eb 100644 --- a/tests/testthat/_snaps/guides/axis-guides-basic.svg +++ b/tests/testthat/_snaps/guide-axis/axis-guides-basic.svg @@ -18,47 +18,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/guides/axis-guides-check-overlap.svg b/tests/testthat/_snaps/guide-axis/axis-guides-check-overlap.svg similarity index 83% rename from tests/testthat/_snaps/guides/axis-guides-check-overlap.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-check-overlap.svg index cf2312f98c..ad9d4f2f71 100644 --- a/tests/testthat/_snaps/guides/axis-guides-check-overlap.svg +++ b/tests/testthat/_snaps/guide-axis/axis-guides-check-overlap.svg @@ -18,47 +18,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/guides/axis-guides-negative-rotation.svg b/tests/testthat/_snaps/guide-axis/axis-guides-negative-rotation.svg similarity index 76% rename from tests/testthat/_snaps/guides/axis-guides-negative-rotation.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-negative-rotation.svg index 8902fa04cd..8bb60d982b 100644 --- a/tests/testthat/_snaps/guides/axis-guides-negative-rotation.svg +++ b/tests/testthat/_snaps/guide-axis/axis-guides-negative-rotation.svg @@ -18,47 +18,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/guides/axis-guides-positive-rotation.svg b/tests/testthat/_snaps/guide-axis/axis-guides-positive-rotation.svg similarity index 76% rename from tests/testthat/_snaps/guides/axis-guides-positive-rotation.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-positive-rotation.svg index e1cd91eb77..61084a3df1 100644 --- a/tests/testthat/_snaps/guides/axis-guides-positive-rotation.svg +++ b/tests/testthat/_snaps/guide-axis/axis-guides-positive-rotation.svg @@ -18,47 +18,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/guides/axis-guides-text-dodged-into-rows-cols.svg b/tests/testthat/_snaps/guide-axis/axis-guides-text-dodged-into-rows-cols.svg similarity index 76% rename from tests/testthat/_snaps/guides/axis-guides-text-dodged-into-rows-cols.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-text-dodged-into-rows-cols.svg index 79e94af549..0af3c6bcc1 100644 --- a/tests/testthat/_snaps/guides/axis-guides-text-dodged-into-rows-cols.svg +++ b/tests/testthat/_snaps/guide-axis/axis-guides-text-dodged-into-rows-cols.svg @@ -18,47 +18,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/guides/axis-guides-vertical-negative-rotation.svg b/tests/testthat/_snaps/guide-axis/axis-guides-vertical-negative-rotation.svg similarity index 77% rename from tests/testthat/_snaps/guides/axis-guides-vertical-negative-rotation.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-vertical-negative-rotation.svg index 1d83ebc1e2..06d782e9c8 100644 --- a/tests/testthat/_snaps/guides/axis-guides-vertical-negative-rotation.svg +++ b/tests/testthat/_snaps/guide-axis/axis-guides-vertical-negative-rotation.svg @@ -18,47 +18,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/guides/axis-guides-vertical-rotation.svg b/tests/testthat/_snaps/guide-axis/axis-guides-vertical-rotation.svg similarity index 77% rename from tests/testthat/_snaps/guides/axis-guides-vertical-rotation.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-vertical-rotation.svg index f379bb7797..76adc31334 100644 --- a/tests/testthat/_snaps/guides/axis-guides-vertical-rotation.svg +++ b/tests/testthat/_snaps/guide-axis/axis-guides-vertical-rotation.svg @@ -18,47 +18,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/guides/axis-guides-with-capped-ends.svg b/tests/testthat/_snaps/guide-axis/axis-guides-with-capped-ends.svg similarity index 100% rename from tests/testthat/_snaps/guides/axis-guides-with-capped-ends.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-with-capped-ends.svg diff --git a/tests/testthat/_snaps/guide-axis/axis-guides-zero-breaks.svg b/tests/testthat/_snaps/guide-axis/axis-guides-zero-breaks.svg new file mode 100644 index 0000000000..a89bf8e8f1 --- /dev/null +++ b/tests/testthat/_snaps/guide-axis/axis-guides-zero-breaks.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests/testthat/_snaps/guides/axis-guides-zero-rotation.svg b/tests/testthat/_snaps/guide-axis/axis-guides-zero-rotation.svg similarity index 75% rename from tests/testthat/_snaps/guides/axis-guides-zero-rotation.svg rename to tests/testthat/_snaps/guide-axis/axis-guides-zero-rotation.svg index bb81af4971..8d5dad8f65 100644 --- a/tests/testthat/_snaps/guides/axis-guides-zero-rotation.svg +++ b/tests/testthat/_snaps/guide-axis/axis-guides-zero-rotation.svg @@ -18,47 +18,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/guides/guide-axis-customization.svg b/tests/testthat/_snaps/guide-axis/guide-axis-customization.svg similarity index 100% rename from tests/testthat/_snaps/guides/guide-axis-customization.svg rename to tests/testthat/_snaps/guide-axis/guide-axis-customization.svg diff --git a/tests/testthat/_snaps/guides/guide-axis-theta-in-cartesian-coordinates.svg b/tests/testthat/_snaps/guide-axis/guide-axis-theta-in-cartesian-coordinates.svg similarity index 100% rename from tests/testthat/_snaps/guides/guide-axis-theta-in-cartesian-coordinates.svg rename to tests/testthat/_snaps/guide-axis/guide-axis-theta-in-cartesian-coordinates.svg diff --git a/tests/testthat/_snaps/guides/guide-axis-theta-with-angle-adapting-to-theta.svg b/tests/testthat/_snaps/guide-axis/guide-axis-theta-with-angle-adapting-to-theta.svg similarity index 100% rename from tests/testthat/_snaps/guides/guide-axis-theta-with-angle-adapting-to-theta.svg rename to tests/testthat/_snaps/guide-axis/guide-axis-theta-with-angle-adapting-to-theta.svg diff --git a/tests/testthat/_snaps/guides/guide-titles-with-coord-trans.svg b/tests/testthat/_snaps/guide-axis/guide-titles-with-coord-trans.svg similarity index 100% rename from tests/testthat/_snaps/guides/guide-titles-with-coord-trans.svg rename to tests/testthat/_snaps/guide-axis/guide-titles-with-coord-trans.svg diff --git a/tests/testthat/_snaps/guides/guides-specified-in-guides.svg b/tests/testthat/_snaps/guide-axis/guides-specified-in-guides.svg similarity index 100% rename from tests/testthat/_snaps/guides/guides-specified-in-guides.svg rename to tests/testthat/_snaps/guide-axis/guides-specified-in-guides.svg diff --git a/tests/testthat/_snaps/guides/guides-with-minor-ticks.svg b/tests/testthat/_snaps/guide-axis/guides-with-minor-ticks.svg similarity index 100% rename from tests/testthat/_snaps/guides/guides-with-minor-ticks.svg rename to tests/testthat/_snaps/guide-axis/guides-with-minor-ticks.svg diff --git a/tests/testthat/_snaps/guides/logtick-axes-with-customisation.svg b/tests/testthat/_snaps/guide-axis/logtick-axes-with-customisation.svg similarity index 100% rename from tests/testthat/_snaps/guides/logtick-axes-with-customisation.svg rename to tests/testthat/_snaps/guide-axis/logtick-axes-with-customisation.svg diff --git a/tests/testthat/_snaps/guides/position-guide-titles.svg b/tests/testthat/_snaps/guide-axis/position-guide-titles.svg similarity index 100% rename from tests/testthat/_snaps/guides/position-guide-titles.svg rename to tests/testthat/_snaps/guide-axis/position-guide-titles.svg diff --git a/tests/testthat/_snaps/guides/stacked-axes.svg b/tests/testthat/_snaps/guide-axis/stacked-axes.svg similarity index 100% rename from tests/testthat/_snaps/guides/stacked-axes.svg rename to tests/testthat/_snaps/guide-axis/stacked-axes.svg diff --git a/tests/testthat/_snaps/guides/stacked-radial-axes.svg b/tests/testthat/_snaps/guide-axis/stacked-radial-axes.svg similarity index 100% rename from tests/testthat/_snaps/guides/stacked-radial-axes.svg rename to tests/testthat/_snaps/guide-axis/stacked-radial-axes.svg diff --git a/tests/testthat/_snaps/guides/thick-axis-lines.svg b/tests/testthat/_snaps/guide-axis/thick-axis-lines.svg similarity index 100% rename from tests/testthat/_snaps/guides/thick-axis-lines.svg rename to tests/testthat/_snaps/guide-axis/thick-axis-lines.svg diff --git a/tests/testthat/_snaps/guide-colorbar.md b/tests/testthat/_snaps/guide-colorbar.md new file mode 100644 index 0000000000..95818b07a1 --- /dev/null +++ b/tests/testthat/_snaps/guide-colorbar.md @@ -0,0 +1,14 @@ +# colorsteps and bins checks the breaks format + + Breaks are not formatted correctly for a bin legend. + i Use `(, ]` format to indicate bins. + +--- + + Breaks are not formatted correctly for a bin legend. + i Use `(, ]` format to indicate bins. + +# guide_colourbar warns about discrete scales + + `guide_colourbar()` needs continuous scales. + diff --git a/tests/testthat/_snaps/guides/one-combined-colorbar-for-colour-and-fill-aesthetics.svg b/tests/testthat/_snaps/guide-colorbar/combined-colour-and-fill-aesthetics.svg similarity index 98% rename from tests/testthat/_snaps/guides/one-combined-colorbar-for-colour-and-fill-aesthetics.svg rename to tests/testthat/_snaps/guide-colorbar/combined-colour-and-fill-aesthetics.svg index 9d656ece9f..75f9b641d3 100644 --- a/tests/testthat/_snaps/guides/one-combined-colorbar-for-colour-and-fill-aesthetics.svg +++ b/tests/testthat/_snaps/guide-colorbar/combined-colour-and-fill-aesthetics.svg @@ -79,6 +79,6 @@ 5 6 7 -one combined colorbar for colour and fill aesthetics +combined colour and fill aesthetics diff --git a/tests/testthat/_snaps/guides/customized-colorbar.svg b/tests/testthat/_snaps/guide-colorbar/customized-colorbar.svg similarity index 100% rename from tests/testthat/_snaps/guides/customized-colorbar.svg rename to tests/testthat/_snaps/guide-colorbar/customized-colorbar.svg diff --git a/tests/testthat/_snaps/guides/white-to-red-colorbar-white-ticks-no-frame.svg b/tests/testthat/_snaps/guide-colorbar/white-to-red-colorbar-white-ticks-no-frame.svg similarity index 100% rename from tests/testthat/_snaps/guides/white-to-red-colorbar-white-ticks-no-frame.svg rename to tests/testthat/_snaps/guide-colorbar/white-to-red-colorbar-white-ticks-no-frame.svg diff --git a/tests/testthat/_snaps/guides/enlarged-guides.svg b/tests/testthat/_snaps/guide-legend/enlarged-guides.svg similarity index 100% rename from tests/testthat/_snaps/guides/enlarged-guides.svg rename to tests/testthat/_snaps/guide-legend/enlarged-guides.svg diff --git a/tests/testthat/_snaps/guides/horizontal-legend-direction.svg b/tests/testthat/_snaps/guide-legend/horizontal-legend-direction.svg similarity index 100% rename from tests/testthat/_snaps/guides/horizontal-legend-direction.svg rename to tests/testthat/_snaps/guide-legend/horizontal-legend-direction.svg diff --git a/tests/testthat/_snaps/guides/left-aligned-legend-key.svg b/tests/testthat/_snaps/guide-legend/left-aligned-legend-key.svg similarity index 100% rename from tests/testthat/_snaps/guides/left-aligned-legend-key.svg rename to tests/testthat/_snaps/guide-legend/left-aligned-legend-key.svg diff --git a/tests/testthat/_snaps/guides/legend-byrow-true.svg b/tests/testthat/_snaps/guide-legend/legend-byrow-true.svg similarity index 100% rename from tests/testthat/_snaps/guides/legend-byrow-true.svg rename to tests/testthat/_snaps/guide-legend/legend-byrow-true.svg diff --git a/tests/testthat/_snaps/guides/legend-with-widely-spaced-keys.svg b/tests/testthat/_snaps/guide-legend/legend-with-widely-spaced-keys.svg similarity index 100% rename from tests/testthat/_snaps/guides/legend-with-widely-spaced-keys.svg rename to tests/testthat/_snaps/guide-legend/legend-with-widely-spaced-keys.svg diff --git a/tests/testthat/_snaps/guides/vertical-legend-direction.svg b/tests/testthat/_snaps/guide-legend/vertical-legend-direction.svg similarity index 100% rename from tests/testthat/_snaps/guides/vertical-legend-direction.svg rename to tests/testthat/_snaps/guide-legend/vertical-legend-direction.svg diff --git a/tests/testthat/_snaps/guides.md b/tests/testthat/_snaps/guides.md index be7c84d7f3..a47fba746b 100644 --- a/tests/testthat/_snaps/guides.md +++ b/tests/testthat/_snaps/guides.md @@ -1,19 +1,3 @@ -# dots are checked when making guides - - Ignoring unknown argument to `guide_axis()`: `foo`. - ---- - - Arguments in `...` must be used. - x Problematic argument: - * foo = "bar" - i Did you misspell an argument name? - -# Using non-position guides for position scales results in an informative error - - `guide_legend()` cannot be used for x, xmin, xmax, or xend. - i Use any non position aesthetic instead. - # guide specifications are properly checked Unknown guide: test @@ -53,19 +37,21 @@ `nrow` * `ncol` needs to be larger than the number of breaks (5). -# colorsteps and bins checks the breaks format +# get_guide_data retrieves keys appropriately - Breaks are not formatted correctly for a bin legend. - i Use `(, ]` format to indicate bins. + Code + get_guide_data(b, 1) + Condition + Error in `get_guide_data()`: + ! `aesthetic` must be a single string, not the number 1. --- - Breaks are not formatted correctly for a bin legend. - i Use `(, ]` format to indicate bins. - -# guide_axis_logticks calculates appropriate ticks - - The `prescale.base` argument will override the scale's log-10 transformation in log-tick positioning. + Code + get_guide_data(b, "x", panel = "a") + Condition + Error in `get_guide_data()`: + ! `panel` must be a whole number, not the string "a". # binning scales understand the different combinations of limits, breaks, labels, and show.limits @@ -77,10 +63,15 @@ `show.limits` is ignored when `labels` are given as a character vector. i Either add the limits to `breaks` or provide a function for `labels`. -# a warning is generated when guides( = FALSE) is specified +# guides() warns if unnamed guides are provided + + Guides provided to `guides()` must be named. + i All guides are unnamed. + +--- - The `guide` argument in `scale_*()` cannot be `FALSE`. This was deprecated in ggplot2 3.3.4. - i Please use "none" instead. + Guides provided to `guides()` must be named. + i The 2nd guide is unnamed. # old S3 guides can be implemented diff --git a/tests/testthat/_snaps/guides/axis-guides-zero-breaks.svg b/tests/testthat/_snaps/guides/axis-guides-zero-breaks.svg deleted file mode 100644 index 2ef933e31b..0000000000 --- a/tests/testthat/_snaps/guides/axis-guides-zero-breaks.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/testthat/_snaps/labels.md b/tests/testthat/_snaps/labels.md index 49efe59a72..80b541e2e4 100644 --- a/tests/testthat/_snaps/labels.md +++ b/tests/testthat/_snaps/labels.md @@ -25,3 +25,19 @@ `plot.tag.position` must be one of "topleft", "top", "topright", "left", "right", "bottomleft", "bottom", or "bottomright", not "foobar". +--- + + Code + ggplotGrob(p + theme(plot.tag.position = c(0, 0.5, 1))) + Condition + Error in `theme()`: + ! A `plot.tag.position` theme setting must have length 2. + +--- + + Code + ggplotGrob(p + theme(plot.tag.position = c(0, 0), plot.tag.location = "margin")) + Condition + Error in `theme()`: + ! A `plot.tag.position` cannot be used with `"margin"` as `plot.tag.location`. + diff --git a/tests/testthat/_snaps/layer.md b/tests/testthat/_snaps/layer.md index d95e11bed6..70573a3d7c 100644 --- a/tests/testthat/_snaps/layer.md +++ b/tests/testthat/_snaps/layer.md @@ -27,6 +27,18 @@ `x` must be either a string or a object, not an environment. +# unknown params create warning + + Ignoring unknown parameters: `blah` + +# unknown aesthetics create warning + + Ignoring unknown aesthetics: blah + +# empty aesthetics create warning + + Ignoring empty aesthetics: `fill` and `shape`. + # invalid aesthetics throws errors Problem while computing aesthetics. @@ -47,6 +59,28 @@ x `fill = after_stat(data)` i Did you map your stat in the wrong layer? +# missing aesthetics trigger informative error + + Code + ggplot_build(ggplot(df) + geom_line()) + Condition + Error in `geom_line()`: + ! Problem while setting up geom. + i Error occurred in the 1st layer. + Caused by error in `compute_geom_1()`: + ! `geom_line()` requires the following missing aesthetics: x and y. + +--- + + Code + ggplot_build(ggplot(df) + geom_col()) + Condition + Error in `geom_col()`: + ! Problem while setting up geom. + i Error occurred in the 1st layer. + Caused by error in `compute_geom_1()`: + ! `geom_col()` requires the following missing aesthetics: x and y. + # function aesthetics are wrapped with after_stat() Problem while computing aesthetics. @@ -89,6 +123,16 @@ All aesthetics have length 1, but the data has 32 rows. i Please consider using `annotate()` or provide this layer with data containing a single row. +# layer names can be resolved + + Code + p + l + l + Condition + Error in `new_layer_names()`: + ! Names must be unique. + x These names are duplicated: + * "foobar" at locations 3 and 4. + # layer_data returns a data.frame `layer_data()` must return a . diff --git a/tests/testthat/_snaps/performance.md b/tests/testthat/_snaps/performance.md new file mode 100644 index 0000000000..153fde6c57 --- /dev/null +++ b/tests/testthat/_snaps/performance.md @@ -0,0 +1,9 @@ +# modifyList is masked + + Code + modifyList(testlist, testappend) + Condition + Error in `modifyList()`: + ! Please use `modify_list()` instead of `modifyList()` for better performance. + i See the vignette ggplot2 internal programming guidelines for details. + diff --git a/tests/testthat/_snaps/position_dodge.md b/tests/testthat/_snaps/position_dodge.md new file mode 100644 index 0000000000..044f2b4392 --- /dev/null +++ b/tests/testthat/_snaps/position_dodge.md @@ -0,0 +1,11 @@ +# position_dodge warns about missing required aesthetics + + Code + ggplot_build(p) + Condition + Error: + ! Problem while computing position. + i Error occurred in the 1st layer. + Caused by error in `setup_params()`: + ! `position_dodge()` requires the following missing aesthetics: x or xmin. + diff --git a/tests/testthat/_snaps/scale-date.md b/tests/testthat/_snaps/scale-date.md new file mode 100644 index 0000000000..9717f0f785 --- /dev/null +++ b/tests/testthat/_snaps/scale-date.md @@ -0,0 +1,10 @@ +# date(time) scales throw warnings when input is numeric + + A value was passed to a Date scale. + i The value was converted to a object. + +--- + + A value was passed to a Datetime scale. + i The value was converted to a object. + diff --git a/tests/testthat/_snaps/scale-discrete.md b/tests/testthat/_snaps/scale-discrete.md index c668bceba9..a3251c4c4e 100644 --- a/tests/testthat/_snaps/scale-discrete.md +++ b/tests/testthat/_snaps/scale-discrete.md @@ -1,3 +1,39 @@ +# Scale is checked in default colour scale + + Code + scale_colour_discrete(type = scale_colour_gradient) + Condition + Error in `scale_colour_discrete()`: + ! The `type` argument must return a discrete scale for the colour aesthetic. + x The provided scale is continuous. + +--- + + Code + scale_fill_discrete(type = scale_fill_gradient) + Condition + Error in `scale_fill_discrete()`: + ! The `type` argument must return a discrete scale for the fill aesthetic. + x The provided scale is continuous. + +--- + + Code + scale_colour_discrete(type = scale_fill_hue) + Condition + Error in `scale_colour_discrete()`: + ! The `type` argument must return a continuous scale for the colour aesthetic. + x The provided scale works with the following aesthetics: fill. + +--- + + Code + scale_fill_discrete(type = scale_colour_hue) + Condition + Error in `scale_fill_discrete()`: + ! The `type` argument must return a continuous scale for the fill aesthetic. + x The provided scale works with the following aesthetics: colour. + # Aesthetics with no continuous interpretation fails when called A continuous variable cannot be mapped to the linetype aesthetic. @@ -8,3 +44,27 @@ A continuous variable cannot be mapped to the shape aesthetic. i Choose a different aesthetic or use `scale_shape_binned()`. +# mapped_discrete vectors behaves as predicted + + Code + mapped_discrete(letters) + Condition + Error in `mapped_discrete()`: + ! Can't convert `x` to . + +# invalid palettes trigger errors + + Code + ggplot_build(p + scale_x_discrete(palette = function(x) LETTERS[1:3])) + Condition + Error in `scale_x_discrete()`: + ! The `palette` function must return a vector. + +--- + + Code + ggplot_build(p + scale_x_discrete(palette = function(x) 1:2)) + Condition + Error in `scale_x_discrete()`: + ! The `palette` function must return at least 3 values. + diff --git a/tests/testthat/_snaps/scale-gradient.md b/tests/testthat/_snaps/scale-gradient.md new file mode 100644 index 0000000000..ebb18a84e7 --- /dev/null +++ b/tests/testthat/_snaps/scale-gradient.md @@ -0,0 +1,4 @@ +# midpoints are transformed + + log-10 transformation introduced infinite values in `midpoint`. + diff --git a/tests/testthat/_snaps/scale-manual.md b/tests/testthat/_snaps/scale-manual.md index faf69a7899..70c2fa4dfd 100644 --- a/tests/testthat/_snaps/scale-manual.md +++ b/tests/testthat/_snaps/scale-manual.md @@ -2,3 +2,19 @@ No shared levels found between `names(values)` of the manual scale and the data's colour values. +# insufficient values raise an error + + Code + ggplot_build(p + scale_colour_manual(values = "black")) + Condition + Error in `palette()`: + ! Insufficient values in manual scale. 2 needed but only 1 provided. + +# fewer values (#3451) + + Code + s2$map(c("4", "6", "8")) + Condition + Error in `palette()`: + ! Insufficient values in manual scale. 3 needed but only 2 provided. + diff --git a/tests/testthat/_snaps/scales-breaks-labels.md b/tests/testthat/_snaps/scales-breaks-labels.md new file mode 100644 index 0000000000..e3b5f28532 --- /dev/null +++ b/tests/testthat/_snaps/scales-breaks-labels.md @@ -0,0 +1,133 @@ +# labels match breaks + + Code + scale_x_discrete(breaks = 1:3, labels = 1:2) + Condition + Error in `scale_x_discrete()`: + ! `breaks` and `labels` must have the same length. + +--- + + Code + scale_x_continuous(breaks = 1:3, labels = 1:2) + Condition + Error in `scale_x_continuous()`: + ! `breaks` and `labels` must have the same length. + +# passing continuous limits to a discrete scale generates a warning + + Continuous limits supplied to discrete scale. + i Did you mean `limits = factor(...)` or `scale_*_continuous()`? + +# suppressing breaks, minor_breask, and labels works + + Code + scale_x_date(breaks = NA, limits = lims)$get_breaks() + Condition + Error in `scale_x_date()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + scale_x_date(labels = NA, limits = lims)$get_labels() + Condition + Error in `scale_x_date()`: + ! Invalid `labels` specification. Use `NULL`, not `NA`. + +--- + + Code + scale_x_date(minor_breaks = NA, limits = lims)$get_breaks_minor() + Condition + Error in `scale_x_date()`: + ! Invalid `minor_breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + scale_x_datetime(breaks = NA, limits = lims)$get_breaks() + Condition + Error in `scale_x_datetime()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + scale_x_datetime(labels = NA, limits = lims)$get_labels() + Condition + Error in `scale_x_datetime()`: + ! Invalid `labels` specification. Use `NULL`, not `NA`. + +--- + + Code + scale_x_datetime(minor_breaks = NA, limits = lims)$get_breaks_minor() + Condition + Error in `scale_x_datetime()`: + ! Invalid `minor_breaks` specification. Use `NULL`, not `NA`. + +# scale_breaks with explicit NA options (deprecated) + + Code + sxc$get_breaks() + Condition + Error in `scale_x_continuous()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + sxc$get_breaks_minor() + Condition + Error in `scale_x_continuous()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + syc$get_breaks() + Condition + Error in `scale_y_continuous()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + syc$get_breaks_minor() + Condition + Error in `scale_y_continuous()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + sac$get_breaks() + Condition + Error in `scale_alpha_continuous()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + ssc$get_breaks() + Condition + Error in `scale_size_continuous()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + sfc$get_breaks() + Condition + Error in `scale_fill_continuous()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + +--- + + Code + scc$get_breaks() + Condition + Error in `scale_colour_continuous()`: + ! Invalid `breaks` specification. Use `NULL`, not `NA`. + diff --git a/tests/testthat/_snaps/scales.md b/tests/testthat/_snaps/scales.md index 61754a645a..40298a1836 100644 --- a/tests/testthat/_snaps/scales.md +++ b/tests/testthat/_snaps/scales.md @@ -1,3 +1,31 @@ +# oob affects position values + + Removed 1 row containing missing values or values outside the scale range (`geom_bar()`). + +--- + + Removed 3 rows containing missing values or values outside the scale range (`geom_bar()`). + +# scales warn when transforms introduces non-finite values + + log-10 transformation introduced infinite values. + +# size and alpha scales throw appropriate warnings for factors + + Using size for a discrete variable is not advised. + +--- + + Using alpha for a discrete variable is not advised. + +--- + + Using linewidth for a discrete variable is not advised. + +# shape scale throws appropriate warnings for factors + + Using shapes for an ordinal variable is not advised + # scale_apply preserves class and attributes `scale_id` must not contain any "NA". diff --git a/tests/testthat/_snaps/stat-bin.md b/tests/testthat/_snaps/stat-bin.md index dd7a8127bf..f92e737d94 100644 --- a/tests/testthat/_snaps/stat-bin.md +++ b/tests/testthat/_snaps/stat-bin.md @@ -53,6 +53,26 @@ Caused by error in `bin_breaks_bins()`: ! `bins` must be a whole number larger than or equal to 1, not the number -4. +# setting boundary and center + + Code + comp_bin(df, boundary = 5, center = 0) + Condition + Error in `stat_bin()`: + ! Problem while computing stat. + i Error occurred in the 1st layer. + Caused by error in `setup_params()`: + ! Only one of `boundary` and `center` may be specified in `stat_bin()`. + +# bin errors at high bin counts + + Code + bin_breaks_width(c(1, 2e+06), 1) + Condition + Error in `bin_breaks_width()`: + ! The number of histogram bins must be less than 1,000,000. + i Did you make `binwidth` too small? + # stat_count throws error when both x and y aesthetic present Problem while computing stat. diff --git a/tests/testthat/_snaps/stat-contour.md b/tests/testthat/_snaps/stat-contour.md new file mode 100644 index 0000000000..d20d5aee72 --- /dev/null +++ b/tests/testthat/_snaps/stat-contour.md @@ -0,0 +1,14 @@ +# a warning is issued when there is more than one z per x+y + + Contour data has duplicated x, y coordinates. + i 1 duplicated row have been dropped. + +# contouring sparse data results in a warning + + `stat_contour()`: Zero contours were generated + +# stat_contour() removes duplicated coordinates + + Contour data has duplicated x, y coordinates. + i 4 duplicated rows have been dropped. + diff --git a/tests/testthat/_snaps/stat-density.md b/tests/testthat/_snaps/stat-density.md index 17cbca4b7d..94902bca55 100644 --- a/tests/testthat/_snaps/stat-density.md +++ b/tests/testthat/_snaps/stat-density.md @@ -1,3 +1,7 @@ +# stat_density handles data outside of `bounds` + + Some data points are outside of `bounds`. Removing them. + # stat_density works in both directions Problem while computing stat. @@ -5,6 +9,10 @@ Caused by error in `setup_params()`: ! `stat_density()` requires an x or y aesthetic. +# compute_density returns useful df and throws warning when <2 values + + Groups with fewer than two data points have been dropped. + # precompute_bandwidth() errors appropriately `bw` must be one of "nrd0", "nrd", "ucv", "bcv", "sj", "sj-ste", or "sj-dpi", not "foobar". diff --git a/tests/testthat/_snaps/stat-ecdf.md b/tests/testthat/_snaps/stat-ecdf.md index e4da4c47f9..92edd3193f 100644 --- a/tests/testthat/_snaps/stat-ecdf.md +++ b/tests/testthat/_snaps/stat-ecdf.md @@ -5,3 +5,21 @@ Caused by error in `setup_params()`: ! `stat_ecdf()` requires an x or y aesthetic. +# weighted ecdf warns about weird weights + + The weight aesthetic does not support non-finite or `NA` values. + i These weights were replaced by "0". + +--- + + The sum of the weight aesthetic is close to "0". + i Computed eCDF might be unstable. + +--- + + Code + wecdf(1:10, rep(c(-1, 1), 5)) + Condition + Error in `wecdf()`: + ! Cannot compute eCDF when the weight aesthetic sums up to "0". + diff --git a/tests/testthat/_snaps/stat-function.md b/tests/testthat/_snaps/stat-function.md new file mode 100644 index 0000000000..329b6cc8de --- /dev/null +++ b/tests/testthat/_snaps/stat-function.md @@ -0,0 +1,5 @@ +# Warn when drawing multiple copies of the same function + + Multiple drawing groups in `geom_function()` + i Did you use the correct group, colour, or fill aesthetics? + diff --git a/tests/testthat/_snaps/stat-ydensity.md b/tests/testthat/_snaps/stat-ydensity.md index 1511b0b462..f72f4ebd57 100644 --- a/tests/testthat/_snaps/stat-ydensity.md +++ b/tests/testthat/_snaps/stat-ydensity.md @@ -6,3 +6,12 @@ `test` is not a valid bandwidth rule. +# `drop = FALSE` preserves groups with 1 observations + + Groups with fewer than two datapoints have been dropped. + i Set `drop = FALSE` to consider such groups for position adjustment purposes. + +--- + + Cannot compute density for groups with fewer than two datapoints. + diff --git a/tests/testthat/_snaps/stats.md b/tests/testthat/_snaps/stats.md index 92a4296185..f5b46f9669 100644 --- a/tests/testthat/_snaps/stats.md +++ b/tests/testthat/_snaps/stats.md @@ -1,6 +1,35 @@ +# plot succeeds even if some computation fails + + Computation failed in `stat_summary()`. + Caused by error in `fun()`: + ! Failed computation + +# error message is thrown when aesthetics are missing + + Code + ggplot_build(p) + Condition + Error in `stat_sum()`: + ! Problem while computing stat. + i Error occurred in the 1st layer. + Caused by error in `compute_layer()`: + ! `stat_sum()` requires the following missing aesthetics: x and y. + # erroneously dropped aesthetics are found and issue a warning + The following aesthetics were dropped during statistical transformation: fill. + i This can happen when ggplot fails to infer the correct grouping structure in the data. + i Did you forget to specify a `group` aesthetic or to convert a numerical variable into a factor? + +--- + The following aesthetics were dropped during statistical transformation: colour and fill. i This can happen when ggplot fails to infer the correct grouping structure in the data. i Did you forget to specify a `group` aesthetic or to convert a numerical variable into a factor? +--- + + The following aesthetics were dropped during statistical transformation: colour. + i This can happen when ggplot fails to infer the correct grouping structure in the data. + i Did you forget to specify a `group` aesthetic or to convert a numerical variable into a factor? + diff --git a/tests/testthat/_snaps/theme.md b/tests/testthat/_snaps/theme.md index 259a887c1a..fa7237d37d 100644 --- a/tests/testthat/_snaps/theme.md +++ b/tests/testthat/_snaps/theme.md @@ -1,3 +1,19 @@ +# modifying theme element properties with + operator works + + Code + theme_grey() + "asdf" + Condition + Error: + ! Can't add `"asdf"` to a theme object. + +# replacing theme elements with %+replace% operator works + + Code + theme_grey() + "asdf" + Condition + Error: + ! Can't add `"asdf"` to a theme object. + # theme validation happens at build stage The `text` theme element must be a object. @@ -40,6 +56,14 @@ The `blablabla` theme element must be a object. +# elements can be merged + + Code + merge_element(text_base, rect_base) + Condition + Error in `merge_element()`: + ! Only elements of the same class can be merged. + # Theme elements are checked during build `plot.title.position` must be one of "panel" or "plot", not "test". diff --git a/tests/testthat/_snaps/utilities-checks.md b/tests/testthat/_snaps/utilities-checks.md new file mode 100644 index 0000000000..e356c2f701 --- /dev/null +++ b/tests/testthat/_snaps/utilities-checks.md @@ -0,0 +1,28 @@ +# check_device checks R versions correctly + + R 4.0.0 does not support gradients. + +--- + + R 4.1.0 does not support gradients. + +--- + + R 4.2.0 does not support glyphs. + +# check_device finds device capabilities + + The pdf device does not support clipping paths. + +--- + + Unable to check the capabilities of the foobar device. + +# check_device finds ragg capabilities + + The agg_tiff device does not support compositing. + +# check_device finds svglite capabilities + + The devSVG device does not support compositing. + diff --git a/tests/testthat/_snaps/utilities.md b/tests/testthat/_snaps/utilities.md index 37138df4e9..4ccf6d67d1 100644 --- a/tests/testthat/_snaps/utilities.md +++ b/tests/testthat/_snaps/utilities.md @@ -18,6 +18,10 @@ `na.rm` must be `TRUE` or `FALSE`, not an integer vector. +# characters survive remove_missing + + Removed 1 row containing non-finite outside the scale range. + # tolower() and toupper() has been masked Please use `to_lower_ascii()`, which works fine in all locales. diff --git a/tests/testthat/test-aes-calculated.R b/tests/testthat/test-aes-calculated.R index 9d5c49c68e..ac77df4c48 100644 --- a/tests/testthat/test-aes-calculated.R +++ b/tests/testthat/test-aes-calculated.R @@ -76,18 +76,16 @@ test_that("calculated aesthetics throw warnings when lengths mismatch", { p <- ggplot(df, aes(x, x)) - expect_warning( + expect_snapshot_warning( ggplot_build( p + geom_point(aes(colour = after_stat(c("A", "B", "C")))) - ), - "Failed to apply" + ) ) - expect_warning( + expect_snapshot_warning( ggplot_build( p + geom_point(aes(colour = after_scale(c("red", "green", "blue")))) - ), - "Failed to apply" + ) ) }) diff --git a/tests/testthat/test-aes-setting.R b/tests/testthat/test-aes-setting.R index 422032017f..2071921c03 100644 --- a/tests/testthat/test-aes-setting.R +++ b/tests/testthat/test-aes-setting.R @@ -7,9 +7,9 @@ test_that("aesthetic parameters match length of data", { } set_colours("red") - expect_error(set_colours(rep("red", 2)), "must be either length 1") - expect_error(set_colours(rep("red", 3)), "must be either length 1") - expect_error(set_colours(rep("red", 4)), "must be either length 1") + expect_snapshot(set_colours(rep("red", 2)), error = TRUE) + expect_snapshot(set_colours(rep("red", 3)), error = TRUE) + expect_snapshot(set_colours(rep("red", 4)), error = TRUE) set_colours(rep("red", 5)) }) @@ -31,7 +31,7 @@ test_that("legend filters out aesthetics not of length 1", { # Ideally would test something in the legend data structure, but # that's not easily accessible currently. - expect_error(ggplot_gtable(ggplot_build(p)), NA) + expect_no_error(ggplot_gtable(ggplot_build(p))) }) test_that("alpha affects only fill colour of solid geoms", { diff --git a/tests/testthat/test-aes.R b/tests/testthat/test-aes.R index 1cb333fcac..a42b4a3ae1 100644 --- a/tests/testthat/test-aes.R +++ b/tests/testthat/test-aes.R @@ -48,8 +48,10 @@ test_that("aes evaluated in environment where plot created", { df <- data_frame(x = 1, y = 1) p <- ggplot(df, aes(foo, y)) + geom_point() - # Accessing an undefined variable should result in error - expect_error(get_layer_data(p), "'foo' not found") + test_that("accessing an undefined variable results in an error", { + skip_if(getRversion() <= "4.4.0") + expect_snapshot(get_layer_data(p), error = TRUE) + }) # Once it's defined we should get it back foo <- 0 @@ -114,29 +116,26 @@ test_that("aes standardises aesthetic names", { expect_identical(aes(color_point = x), aes(colour_point = x)) # warning when standardisation creates duplicates - expect_warning(aes(color = x, colour = y), "Duplicated aesthetics") + expect_snapshot_warning(aes(color = x, colour = y)) }) test_that("warn_for_aes_extract_usage() warns for discouraged uses of $ and [[ within aes()", { df <- data_frame(x = 1:5, nested_df = data_frame(x = 6:10)) - expect_warning( - warn_for_aes_extract_usage(aes(df$x), df), - "Use of `df\\$x` is discouraged" + expect_snapshot_warning( + warn_for_aes_extract_usage(aes(df$x), df) ) - expect_warning( - warn_for_aes_extract_usage(aes(df[["x"]]), df), - 'Use of `df\\[\\["x"\\]\\]` is discouraged' + expect_snapshot_warning( + warn_for_aes_extract_usage(aes(df[["x"]]), df) ) # Check that rownames are ignored (#5392) df2 <- df rownames(df2) <- LETTERS[seq_len(nrow(df))] - expect_warning( - warn_for_aes_extract_usage(aes(df$x), df2), - "Use of `df\\$x` is discouraged" + expect_snapshot_warning( + warn_for_aes_extract_usage(aes(df$x), df2) ) }) @@ -144,7 +143,7 @@ test_that("warn_for_aes_extract_usage() does not evaluate function calls", { df <- data_frame(x = 1:5, nested_df = data_frame(x = 6:10)) returns_df <- function() df - expect_warning(warn_for_aes_extract_usage(aes(df$x), df)) + expect_snapshot_warning(warn_for_aes_extract_usage(aes(df$x), df)) expect_silent(warn_for_aes_extract_usage(aes(returns_df()$x), df)) }) @@ -163,7 +162,7 @@ test_that("warn_for_aes_extract_usage() does not warn for valid uses of $ and [[ test_that("Warnings are issued when plots use discouraged extract usage within aes()", { df <- data_frame(x = 1:3, y = 1:3) p <- ggplot(df, aes(df$x, y)) + geom_point() - expect_warning(ggplot_build(p), "Use of `df\\$x` is discouraged") + expect_snapshot_warning(ggplot_build(p)) }) test_that("aes evaluation fails with unknown input", { diff --git a/tests/testthat/test-coord-.R b/tests/testthat/test-coord-.R index f1e910c807..b0cef2de26 100644 --- a/tests/testthat/test-coord-.R +++ b/tests/testthat/test-coord-.R @@ -47,10 +47,10 @@ test_that("check coord limits errors only on bad inputs", { expect_null(check_coord_limits(c(1,2))) # Should raise error if Scale object is passed - expect_error(check_coord_limits(xlim(1,2))) + expect_snapshot(check_coord_limits(xlim(1,2)), error = TRUE) # Should raise error if vector of wrong length is passed - expect_error(check_coord_limits(1:3)) + expect_snapshot(check_coord_limits(1:3), error = TRUE) }) test_that("coords append a column to the layout correctly", { diff --git a/tests/testthat/test-coord-transform.R b/tests/testthat/test-coord-transform.R index abb05a3cae..f3dd4a6b00 100644 --- a/tests/testthat/test-coord-transform.R +++ b/tests/testthat/test-coord-transform.R @@ -10,8 +10,8 @@ test_that("warnings are generated when coord_trans() results in new infinite val # TODO: These multiple warnings should be summarized nicely. Until this gets # fixed, this test ignores all the following errors than the first one. suppressWarnings({ - expect_warning(ggplot_gtable(ggplot_build(p)), "Transformation introduced infinite values in y-axis") - expect_warning(ggplot_gtable(ggplot_build(p2)), "Transformation introduced infinite values in x-axis") + expect_snapshot_warning(ggplot_gtable(ggplot_build(p))) + expect_snapshot_warning(ggplot_gtable(ggplot_build(p2))) }) }) diff --git a/tests/testthat/test-empty-data.R b/tests/testthat/test-empty-data.R index bdcc02003c..e6fe24ce38 100644 --- a/tests/testthat/test-empty-data.R +++ b/tests/testthat/test-empty-data.R @@ -34,7 +34,7 @@ test_that("layers with empty data are silently omitted with facet_wrap", { d <- ggplot(df0, aes(mpg, wt)) + geom_point() + facet_wrap(~cyl) - expect_error(get_layer_data(d), "must have at least one value") + expect_snapshot(get_layer_data(d), error = TRUE) d <- d + geom_point(data = mtcars) expect_equal(nrow(get_layer_data(d, 1)), 0) @@ -45,7 +45,7 @@ test_that("layers with empty data are silently omitted with facet_grid", { d <- ggplot(df0, aes(mpg, wt)) + geom_point() + facet_grid(am ~ cyl) - expect_error(get_layer_data(d), "must have at least one value") + expect_snapshot(get_layer_data(d), error = TRUE) d <- d + geom_point(data = mtcars) expect_equal(nrow(get_layer_data(d, 1)), 0) @@ -53,11 +53,13 @@ test_that("layers with empty data are silently omitted with facet_grid", { }) test_that("empty data overrides plot defaults", { - # Should error when totally empty data frame because there's no x and y - d <- ggplot(mtcars, aes(mpg, wt)) + - geom_point() + - geom_point(data = data_frame()) - expect_error(get_layer_data(d), "not found") + test_that("Should error when totally empty data frame because there's no x and y", { + skip_if(getRversion() <= "4.4.0") + d <- ggplot(mtcars, aes(mpg, wt)) + + geom_point() + + geom_point(data = data_frame()) + expect_snapshot(get_layer_data(d), error = TRUE) + }) # No extra points when x and y vars don't exist but are set d <- ggplot(mtcars, aes(mpg, wt)) + diff --git a/tests/testthat/test-facet-.R b/tests/testthat/test-facet-.R index 5084737622..11e86247ca 100644 --- a/tests/testthat/test-facet-.R +++ b/tests/testthat/test-facet-.R @@ -43,8 +43,8 @@ test_that("as_facets_list() coerces quosures objectss", { }) test_that("facets reject aes()", { - expect_error(facet_wrap(aes(foo)), "Please use `vars()` to supply facet variables", fixed = TRUE) - expect_error(facet_grid(aes(foo)), "Please use `vars()` to supply facet variables", fixed = TRUE) + expect_snapshot(facet_wrap(aes(foo)), error = TRUE) + expect_snapshot(facet_grid(aes(foo)), error = TRUE) }) test_that("compact_facets() returns a quosures object with compacted", { @@ -353,9 +353,9 @@ test_that("at least one layer must contain all facet variables in combine_vars() test_that("at least one combination must exist in combine_vars()", { df <- data_frame(letter = character(0)) - expect_error( + expect_snapshot( combine_vars(list(df), vars = vars(letter = letter)), - "Faceting variables must have at least one value" + error = TRUE ) }) @@ -463,9 +463,9 @@ test_that("eval_facet() is tolerant for missing columns (#2963)", { ) # If the expression contains any non-existent variable, it fails - expect_error( + expect_snapshot( eval_facet(quo(no_such_variable * x), data_frame(foo = 1), possible_columns = c("x")), - "object 'no_such_variable' not found" + error = TRUE ) }) diff --git a/tests/testthat/test-facet-labels.R b/tests/testthat/test-facet-labels.R index c8613bc978..b0b014cd2e 100644 --- a/tests/testthat/test-facet-labels.R +++ b/tests/testthat/test-facet-labels.R @@ -86,7 +86,7 @@ test_that("labeller() dispatches labellers", { # facet_wrap() shouldn't get both rows and cols p3 <- p + facet_wrap(~cyl, labeller = labeller( .cols = label_both, .rows = label_both)) - expect_error(ggplotGrob(p3)) + expect_snapshot(ggplotGrob(p3), error = TRUE) # facet_grid() can get both rows and cols p4 <- p + facet_grid(am ~ cyl, labeller = labeller( @@ -98,7 +98,7 @@ test_that("labeller() dispatches labellers", { # margin-wide labeller p5 <- p + facet_wrap(~cyl, labeller = labeller( .rows = label_both, cyl = label_value)) - expect_error(ggplotGrob(p5)) + expect_snapshot(ggplotGrob(p5), error = TRUE) # Variables can be attributed labellers p6 <- p + facet_grid(am + cyl ~ ., labeller = labeller( @@ -135,7 +135,7 @@ test_that("old school labellers still work", { paste0("var = ", as.character(value)) } - expect_warning(p <- + expect_snapshot_warning(p <- ggplot(mtcars, aes(disp, drat)) + geom_point() + facet_grid(~cyl, labeller = my_labeller)) diff --git a/tests/testthat/test-fortify.R b/tests/testthat/test-fortify.R index 3a48c76ba0..e98edad549 100644 --- a/tests/testthat/test-fortify.R +++ b/tests/testthat/test-fortify.R @@ -67,12 +67,12 @@ test_that("fortify.default can handle healthy data-frame-like objects", { # Not even data-frame-like - expect_error(fortify(X)) - expect_error(fortify(array(1:60, 5:3))) + expect_snapshot(fortify(X), error = TRUE) + expect_snapshot(fortify(array(1:60, 5:3)), error = TRUE) # Unhealthy data-frame-like (matrix with no colnames) - expect_error(fortify(cbind(X, Y, Z, deparse.level=0))) + expect_snapshot(fortify(cbind(X, Y, Z, deparse.level=0)), error = TRUE) # Healthy data-frame-like (matrix with colnames) @@ -100,25 +100,27 @@ test_that("fortify.default can handle healthy data-frame-like objects", { # Rejected by fortify.default() because of unhealthy dim() behavior + skip_if(getRversion() <= "4.4.0") + dim.foo <- function(x) stop("oops!") registerS3method("dim", "foo", dim.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) dim.foo <- function(x) c(length(x), 2) registerS3method("dim", "foo", dim.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) dim.foo <- function(x) 5:2 registerS3method("dim", "foo", dim.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) dim.foo <- function(x) c(length(x), NA_integer_) registerS3method("dim", "foo", dim.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) dim.foo <- function(x) c(length(x), -5L) registerS3method("dim", "foo", dim.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) # Repair dim() @@ -129,18 +131,18 @@ test_that("fortify.default can handle healthy data-frame-like objects", { dimnames.foo <- function(x) list() # this breaks colnames() registerS3method("dimnames", "foo", dimnames.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) dimnames.foo <- function(x) list(format(seq_along(x)), toupper) registerS3method("dimnames", "foo", dimnames.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) # Rejected by fortify.default() because behaviors of dim() and colnames() # don't align dimnames.foo <- function(x) list(NULL, c("X1", "X2", "X3")) registerS3method("dimnames", "foo", dimnames.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) # Repair colnames() @@ -151,20 +153,21 @@ test_that("fortify.default can handle healthy data-frame-like objects", { as.data.frame.foo <- function(x, row.names = NULL, ...) stop("oops!") registerS3method("as.data.frame", "foo", as.data.frame.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) as.data.frame.foo <- function(x, row.names = NULL, ...) "whatever" registerS3method("as.data.frame", "foo", as.data.frame.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) as.data.frame.foo <- function(x, row.names = NULL, ...) data.frame() registerS3method("as.data.frame", "foo", as.data.frame.foo) - expect_error(fortify(object)) + + expect_snapshot(fortify(object), error = TRUE) as.data.frame.foo <- function(x, row.names = NULL, ...) { key <- if (is.null(names(x))) rownames(x) else names(x) data.frame(oops=key, value=unname(unclass(x))) } registerS3method("as.data.frame", "foo", as.data.frame.foo) - expect_error(fortify(object)) + expect_snapshot(fortify(object), error = TRUE) }) diff --git a/tests/testthat/test-geom-bar.R b/tests/testthat/test-geom-bar.R index 28d2430c09..4fb34ef4e6 100644 --- a/tests/testthat/test-geom-bar.R +++ b/tests/testthat/test-geom-bar.R @@ -3,10 +3,8 @@ test_that("geom_bar removes bars with parts outside the plot limits", { p <- ggplot(dat, aes(x)) + geom_bar() - expect_warning( # warning created at render stage - ggplotGrob(p + ylim(0, 2.5)), - "Removed 1 row containing missing values or values outside the scale range" - ) + # warning created at render stage + expect_snapshot_warning(ggplotGrob(p + ylim(0, 2.5))) }) test_that("geom_bar works in both directions", { diff --git a/tests/testthat/test-geom-boxplot.R b/tests/testthat/test-geom-boxplot.R index 195a3d9ade..dabcb6ddeb 100644 --- a/tests/testthat/test-geom-boxplot.R +++ b/tests/testthat/test-geom-boxplot.R @@ -42,15 +42,15 @@ test_that("geom_boxplot for continuous x gives warning if more than one x (#992) ggplot_build(ggplot(dat, aes) + geom_boxplot(aes) + extra) } - expect_warning(bplot(aes(x, y)), "Continuous x aesthetic") - expect_warning(bplot(aes(x, y), facet_wrap(~x)), "Continuous x aesthetic") - expect_warning(bplot(aes(Sys.Date() + x, y)), "Continuous x aesthetic") - - expect_warning(bplot(aes(x, group = x, y)), NA) - expect_warning(bplot(aes(1, y)), NA) - expect_warning(bplot(aes(factor(x), y)), NA) - expect_warning(bplot(aes(x == 1, y)), NA) - expect_warning(bplot(aes(as.character(x), y)), NA) + expect_snapshot_warning(bplot(aes(x, y))) + expect_snapshot_warning(bplot(aes(x, y), facet_wrap(~x))) + expect_snapshot_warning(bplot(aes(Sys.Date() + x, y))) + + expect_silent(bplot(aes(x, group = x, y))) + expect_silent(bplot(aes(1, y))) + expect_silent(bplot(aes(factor(x), y))) + expect_silent(bplot(aes(x == 1, y))) + expect_silent(bplot(aes(as.character(x), y))) }) test_that("can use US spelling of colour", { diff --git a/tests/testthat/test-geom-col.R b/tests/testthat/test-geom-col.R index 5849ea0bcc..32840fbd9e 100644 --- a/tests/testthat/test-geom-col.R +++ b/tests/testthat/test-geom-col.R @@ -3,14 +3,9 @@ test_that("geom_col removes columns with parts outside the plot limits", { p <- ggplot(dat, aes(x, x)) + geom_col() - expect_warning( # warning created at render stage - ggplotGrob(p + ylim(0.5, 4)), - "Removed 3 rows containing missing values or values outside the scale range" - ) - expect_warning( # warning created at render stage - ggplotGrob(p + ylim(0, 2.5)), - "Removed 1 row containing missing values or values outside the scale range" - ) + # warnings created at render stage + expect_snapshot_warning(ggplotGrob(p + ylim(0.5, 4))) + expect_snapshot_warning(ggplotGrob(p + ylim(0, 2.5))) }) test_that("geom_col works in both directions", { diff --git a/tests/testthat/test-geom-dotplot.R b/tests/testthat/test-geom-dotplot.R index 648c52f926..f7159bdd80 100644 --- a/tests/testthat/test-geom-dotplot.R +++ b/tests/testthat/test-geom-dotplot.R @@ -230,10 +230,12 @@ test_that("geom_dotplot draws correctly", { dat2 <- dat dat2$x[c(1, 10)] <- NA - expect_warning(expect_doppelganger("2 NA values, dot-density binning, binwidth = .4", + expect_snapshot_warning(expect_doppelganger( + "2 NA values, dot-density binning, binwidth = .4", ggplot(dat2, aes(x)) + geom_dotplot(binwidth = 0.4) )) - expect_warning(expect_doppelganger("2 NA values, bin along y, stack center", + expect_snapshot_warning(expect_doppelganger( + "2 NA values, bin along y, stack center", ggplot(dat2, aes(0, x)) + geom_dotplot(binwidth = 0.4, binaxis = "y", stackdir = "center") )) }) diff --git a/tests/testthat/test-geom-path.R b/tests/testthat/test-geom-path.R index 3255a2f4cb..161508459a 100644 --- a/tests/testthat/test-geom-path.R +++ b/tests/testthat/test-geom-path.R @@ -35,7 +35,7 @@ test_that("stairstep() does not error with too few observations", { test_that("stairstep() exists with error when an invalid `direction` is given", { df <- data_frame(x = 1:3, y = 1:3) - expect_error(stairstep(df, direction="invalid")) + expect_snapshot(stairstep(df, direction = "invalid"), error = TRUE) }) test_that("stairstep() output is correct for direction = 'vh'", { @@ -90,11 +90,8 @@ test_that("geom_path draws correctly", { test_that("NA linetype is dropped with warning", { df <- data_frame(x = 1:2, y = 1:2, z = "a") - expect_warning( - expect_doppelganger( + expect_snapshot_warning(expect_doppelganger( "NA linetype", ggplot(df, aes(x, y)) + geom_path(linetype = NA) - ), - "containing missing values or values outside the scale range" - ) + )) }) diff --git a/tests/testthat/test-geom-rect.R b/tests/testthat/test-geom-rect.R index a0d90899f8..204df65ef2 100644 --- a/tests/testthat/test-geom-rect.R +++ b/tests/testthat/test-geom-rect.R @@ -29,8 +29,5 @@ test_that("geom_rect can derive corners", { expect_equal(full[, corners], test[, corners]) test <- full[, c("x", "y")] - expect_error( - GeomRect$setup_data(test, NULL), - "requires two of the following aesthetics" - ) + expect_snapshot(GeomRect$setup_data(test, NULL), error = TRUE) }) diff --git a/tests/testthat/test-geom-rug.R b/tests/testthat/test-geom-rug.R index 7539a494a6..108d030ca0 100644 --- a/tests/testthat/test-geom-rug.R +++ b/tests/testthat/test-geom-rug.R @@ -50,11 +50,7 @@ test_that( p1 <- ggplot(df2, aes(x = x)) + geom_rug() p2 <- ggplot(df2, aes(x = x)) + geom_rug(na.rm = TRUE) - expect_warning( - ggplotGrob(p1), - paste0("Removed ", n_missing, " rows containing missing values or values outside the scale range") - ) - + expect_snapshot_warning(ggplotGrob(p1)) expect_no_warning(ggplotGrob(p2)) } ) diff --git a/tests/testthat/test-geom-sf.R b/tests/testthat/test-geom-sf.R index e52a13e917..e0db498a2b 100644 --- a/tests/testthat/test-geom-sf.R +++ b/tests/testthat/test-geom-sf.R @@ -93,19 +93,16 @@ test_that("geom_sf() removes rows containing missing aes", { ) p <- ggplot(pts) - expect_warning( - expect_identical(grob_xy_length(p + geom_sf(aes(size = size))), c(1L, 1L)), - "Removed 1 row containing missing values or values outside the scale range" + expect_snapshot_warning( + expect_identical(grob_xy_length(p + geom_sf(aes(size = size))), c(1L, 1L)) ) - expect_warning( - expect_identical(grob_xy_length(p + geom_sf(aes(shape = shape))), c(1L, 1L)), - "Removed 1 row containing missing values or values outside the scale range" + expect_snapshot_warning( + expect_identical(grob_xy_length(p + geom_sf(aes(shape = shape))), c(1L, 1L)) ) # default colour scale maps a colour even to a NA, so identity scale is needed to see if NA is removed - expect_warning( + expect_snapshot_warning( expect_identical(grob_xy_length(p + geom_sf(aes(colour = colour)) + scale_colour_identity()), - c(1L, 1L)), - "Removed 1 row containing missing values or values outside the scale range" + c(1L, 1L)) ) }) @@ -177,7 +174,7 @@ test_that("geom_sf draws correctly", { # Perform minimal tests pts <- sf::st_sf(a = 1:2, geometry = sf::st_sfc(sf::st_point(0:1), sf::st_point(1:2))) plot <- ggplot() + geom_sf(data = pts) - expect_error(regexp = NA, ggplot_build(plot)) + expect_no_error(ggplot_build(plot)) expect_doppelganger("North Carolina county boundaries", ggplot() + geom_sf(data = nc) + coord_sf(datum = 4326) diff --git a/tests/testthat/test-geom-smooth.R b/tests/testthat/test-geom-smooth.R index 42c82108c7..270bf3760f 100644 --- a/tests/testthat/test-geom-smooth.R +++ b/tests/testthat/test-geom-smooth.R @@ -92,7 +92,7 @@ test_that("geom_smooth() works when one group fails", { geom_smooth(method = "loess", formula = y ~ x) suppressWarnings( - expect_warning(ld <- get_layer_data(p), "Failed to fit group 1") + expect_snapshot_warning(ld <- get_layer_data(p)) ) expect_equal(unique(ld$group), 2) expect_gte(nrow(ld), 2) diff --git a/tests/testthat/test-geom-text.R b/tests/testthat/test-geom-text.R index ca5b495968..a6fe3359d9 100644 --- a/tests/testthat/test-geom-text.R +++ b/tests/testthat/test-geom-text.R @@ -12,10 +12,7 @@ test_that("geom_text() drops missing angles", { ) df$angle <- NA - expect_warning( - geom$geom$handle_na(df, geom$geom_params), - "Removed 1 row" - ) + expect_snapshot_warning(geom$geom$handle_na(df, geom$geom_params)) }) test_that("geom_text() accepts mm and pt size units", { @@ -30,9 +27,9 @@ test_that("geom_text() accepts mm and pt size units", { test_that("geom_text() rejects exotic units", { p <- ggplot(data_frame0(x = 1, y = 1, label = "A"), aes(x, y, label = label)) - expect_error( + expect_snapshot( ggplotGrob(p + geom_text(size = 10, size.unit = "npc")), - "must be one of" + error = TRUE ) }) diff --git a/tests/testthat/test-geom-violin.R b/tests/testthat/test-geom-violin.R index 7d73c04e94..a93a534b40 100644 --- a/tests/testthat/test-geom-violin.R +++ b/tests/testthat/test-geom-violin.R @@ -72,7 +72,7 @@ test_that("quantiles do not issue warning", { p <- ggplot(data, aes(x = x, y = y)) + geom_violin(draw_quantiles = 0.5) - expect_warning(plot(p), regexp = NA) + expect_silent(plot(p)) }) diff --git a/tests/testthat/test-ggsave.R b/tests/testthat/test-ggsave.R index a5d7a5283c..4180eadc98 100644 --- a/tests/testthat/test-ggsave.R +++ b/tests/testthat/test-ggsave.R @@ -16,7 +16,10 @@ test_that("ggsave can create directories", { p <- ggplot(mpg, aes(displ, hwy)) + geom_point() - expect_error(ggsave(path, p)) + expect_snapshot( + ggsave(path, p), error = TRUE, + transform = function(x) gsub("directory '.*'\\.$", "directory 'PATH'", x) + ) expect_false(dir.exists(dirname(path))) # 2 messages: 1 for saving and 1 informing about directory creation @@ -77,23 +80,20 @@ test_that("ggsave warns about empty or multiple filenames", { plot <- ggplot(mtcars, aes(disp, mpg)) + geom_point() withr::with_tempfile(c("file1", "file2"), fileext = ".png", { - expect_warning( - suppressMessages(ggsave(c(file1, file2), plot)), - "`filename` must have length 1" + expect_snapshot( + x <- suppressMessages(ggsave(c(file1, file2), plot)), + transform = function(x) gsub(" \\'.*\\.png\\'", "'PATH'", x) ) }) - expect_error( - ggsave(character(), plot), - "`filename` must be a single string" - ) + expect_snapshot(ggsave(character(), plot), error = TRUE) }) test_that("ggsave fails informatively for no-extension filenames", { plot <- ggplot(mtcars, aes(disp, mpg)) + geom_point() - expect_error( - ggsave(tempfile(), plot), - "Can't save to" + expect_snapshot( + ggsave(tempfile(), plot), error = TRUE, + transform = function(x) gsub("to .*\\.$", "to PATH", x) ) }) @@ -112,9 +112,9 @@ test_that("uses 7x7 if no graphics device open", { }) test_that("warned about large plot unless limitsize = FALSE", { - expect_error(plot_dim(c(50, 50)), "exceed 50 inches") + expect_snapshot(plot_dim(c(50, 50)), error = TRUE) expect_equal(plot_dim(c(50, 50), limitsize = FALSE), c(50, 50)) - expect_error(plot_dim(c(15000, 15000), units = "px"), "in pixels).") + expect_snapshot(plot_dim(c(15000, 15000), units = "px"), error = TRUE) }) test_that("scale multiplies height & width", { @@ -126,8 +126,8 @@ test_that("scale multiplies height & width", { test_that("unknown device triggers error", { expect_snapshot_error(plot_dev(1)) - expect_error(plot_dev("xyz"), "Unknown graphics device") - expect_error(plot_dev(NULL, "test.xyz"), "Unknown graphics device") + expect_snapshot(plot_dev("xyz"), error = TRUE) + expect_snapshot(plot_dev(NULL, "test.xyz"), error = TRUE) }) diff --git a/tests/testthat/test-guide-.R b/tests/testthat/test-guide-.R new file mode 100644 index 0000000000..4f66920c3e --- /dev/null +++ b/tests/testthat/test-guide-.R @@ -0,0 +1,49 @@ +skip_on_cran() + +test_that("plotting does not induce state changes in guides", { + + guides <- guides( + x = guide_axis(title = "X-axis"), + colour = guide_colourbar(title = "Colourbar"), + shape = guide_legend(title = "Legend"), + size = guide_bins(title = "Bins") + ) + + p <- ggplot(mpg, aes(displ, hwy, colour = cty, shape = factor(cyl), + size = cyl)) + + geom_point() + + guides + + snapshot <- serialize(as.list(p$guides), NULL) + + grob <- ggplotGrob(p) + + expect_identical(as.list(p$guides), unserialize(snapshot)) +}) + +test_that("adding guides doesn't change plot state", { + + p1 <- ggplot(mtcars, aes(disp, mpg)) + + expect_length(p1$guides$guides, 0) + + p2 <- p1 + guides(y = guide_axis(angle = 45)) + + expect_length(p1$guides$guides, 0) + expect_length(p2$guides$guides, 1) + + p3 <- p2 + guides(y = guide_axis(angle = 90)) + + expect_length(p3$guides$guides, 1) + expect_equal(p3$guides$guides[[1]]$params$angle, 90) + expect_equal(p2$guides$guides[[1]]$params$angle, 45) +}) + +test_that("dots are checked when making guides", { + expect_snapshot_warning( + new_guide(foo = "bar", super = GuideAxis) + ) + expect_snapshot_warning( + guide_legend(foo = "bar") + ) +}) diff --git a/tests/testthat/test-guide-axis.R b/tests/testthat/test-guide-axis.R new file mode 100644 index 0000000000..5e2010d1c5 --- /dev/null +++ b/tests/testthat/test-guide-axis.R @@ -0,0 +1,399 @@ +skip_on_cran() # This test suite is long-running (on cran) and is skipped + +test_that("axis_label_overlap_priority always returns the correct number of elements", { + expect_identical(axis_label_priority(0), numeric(0)) + expect_setequal(axis_label_priority(1), seq_len(1)) + expect_setequal(axis_label_priority(5), seq_len(5)) + expect_setequal(axis_label_priority(10), seq_len(10)) + expect_setequal(axis_label_priority(100), seq_len(100)) +}) + +test_that("a warning is generated when guides are drawn at a location that doesn't make sense", { + plot <- ggplot(mpg, aes(class, hwy)) + + geom_point() + + scale_y_continuous(guide = guide_axis(position = "top")) + expect_snapshot_warning(ggplot_build(plot)) +}) + +test_that("a warning is not generated when a guide is specified with duplicate breaks", { + plot <- ggplot(mpg, aes(class, hwy)) + + geom_point() + + scale_y_continuous(breaks = c(20, 20)) + built <- expect_silent(ggplot_build(plot)) + expect_silent(ggplot_gtable(built)) +}) + +test_that("a warning is generated when more than one position guide is drawn at a location", { + plot <- ggplot(mpg, aes(class, hwy)) + + geom_point() + + guides( + y = guide_axis(position = "left"), + y.sec = guide_axis(position = "left") + ) + built <- expect_silent(ggplot_build(plot)) + + expect_snapshot_warning(ggplot_gtable(built)) +}) + +test_that("a warning is not generated when properly changing the position of a guide_axis()", { + plot <- ggplot(mpg, aes(class, hwy)) + + geom_point() + + guides( + y = guide_axis(position = "right") + ) + built <- expect_silent(ggplot_build(plot)) + expect_silent(ggplot_gtable(built)) +}) + +test_that("Using non-position guides for position scales results in an informative error", { + p <- ggplot(mpg, aes(cty, hwy)) + + geom_point() + + scale_x_continuous(guide = guide_legend()) + expect_snapshot_warning(ggplot_build(p)) +}) + +test_that("guide_axis_logticks calculates appropriate ticks", { + + test_scale <- function(transform = transform_identity(), limits = c(NA, NA)) { + scale <- scale_x_continuous(transform = transform) + scale$train(scale$transform(limits)) + view_scale_primary(scale) + } + + train_guide <- function(guide, scale) { + params <- guide$params + params$position <- "bottom" + guide$train(params, scale, "x") + } + + guide <- guide_axis_logticks(negative.small = 10) + outcome <- c((1:10)*10, (2:10)*100) + + # Test the classic log10 transformation + scale <- test_scale(transform_log10(), c(10, 1000)) + key <- train_guide(guide, scale)$logkey + + expect_equal(sort(key$x), log10(outcome)) + expect_equal(key$.type, rep(c(1,2,3), c(3, 2, 14))) + + # Test compound transformation + scale <- test_scale(transform_compose(transform_log10(), transform_reverse()), c(10, 1000)) + key <- train_guide(guide, scale)$logkey + + expect_equal(sort(key$x), -log10(rev(outcome))) + + # Test transformation with negatives + scale <- test_scale(transform_pseudo_log(), c(-1000, 1000)) + key <- train_guide(guide, scale)$logkey + + unlog <- sort(transform_pseudo_log()$inverse(key$x)) + expect_equal(unlog, c(-rev(outcome), 0, outcome)) + expect_equal(key$.type, rep(c(1,2,3), c(7, 4, 28))) + + # Test expanded argument + scale <- test_scale(transform_log10(), c(20, 900)) + scale$continuous_range <- c(1, 3) + + guide <- guide_axis_logticks(expanded = TRUE) + key <- train_guide(guide, scale)$logkey + + expect_equal(sort(key$x), log10(outcome)) + + guide <- guide_axis_logticks(expanded = FALSE) + key <- train_guide(guide, scale)$logkey + + expect_equal(sort(key$x), log10(outcome[-c(1, length(outcome))])) + + # Test with prescaled input + guide <- guide_axis_logticks(prescale.base = 2) + scale <- test_scale(limits = log2(c(10, 1000))) + + key <- train_guide(guide, scale)$logkey + expect_equal(sort(key$x), log2(outcome)) + + # Should warn when scale also has transformation + scale <- test_scale(transform_log10(), limits = c(10, 1000)) + expect_snapshot_warning(train_guide(guide, scale)$logkey) +}) + +# Visual tests ------------------------------------------------------------ + +test_that("axis guides are drawn correctly", { + theme_test_axis <- theme_test() + theme(axis.line = element_line(linewidth = 0.5)) + test_draw_axis <- function(n_breaks = 3, + break_positions = seq_len(n_breaks) / (n_breaks + 1), + labels = as.character, + positions = c("top", "right", "bottom", "left"), + theme = theme_test_axis, + ...) { + + break_labels <- labels(seq_along(break_positions)) + + # create the axes + axes <- lapply(positions, function(position) { + draw_axis(break_positions, break_labels, axis_position = position, theme = theme, ...) + }) + axes_grob <- gTree(children = do.call(gList, axes)) + + # arrange them so there's some padding on each side + gt <- gtable( + widths = unit(c(0.05, 0.9, 0.05), "npc"), + heights = unit(c(0.05, 0.9, 0.05), "npc") + ) + gt <- gtable_add_grob(gt, list(axes_grob), 2, 2, clip = "off") + plot(gt) + } + + # basic + expect_doppelganger("axis guides basic", function() test_draw_axis()) + expect_doppelganger("axis guides, zero breaks", function() test_draw_axis(n_breaks = 0)) + + # overlapping text + expect_doppelganger( + "axis guides, check overlap", + function() test_draw_axis(20, labels = function(b) comma(b * 1e9), check.overlap = TRUE) + ) + + # rotated text + expect_doppelganger( + "axis guides, zero rotation", + function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = 0) + ) + + expect_doppelganger( + "axis guides, positive rotation", + function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = 45) + ) + + expect_doppelganger( + "axis guides, negative rotation", + function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = -45) + ) + + expect_doppelganger( + "axis guides, vertical rotation", + function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = 90) + ) + + expect_doppelganger( + "axis guides, vertical negative rotation", + function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = -90) + ) + + # dodged text + expect_doppelganger( + "axis guides, text dodged into rows/cols", + function() test_draw_axis(10, labels = function(b) comma(b * 1e9), n.dodge = 2) + ) +}) + +test_that("axis guides are drawn correctly in plots", { + expect_doppelganger("align facet labels, facets horizontal", + ggplot(mpg, aes(hwy, reorder(model, hwy))) + + geom_point() + + facet_grid(manufacturer ~ ., scales = "free", space = "free") + + theme_test() + + theme(strip.text.y = element_text(angle = 0)) + ) + expect_doppelganger("align facet labels, facets vertical", + ggplot(mpg, aes(reorder(model, hwy), hwy)) + + geom_point() + + facet_grid(. ~ manufacturer, scales = "free", space = "free") + + theme_test() + + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) + ) + expect_doppelganger("thick axis lines", + ggplot(mtcars, aes(wt, mpg)) + + geom_point() + + theme_test() + + theme(axis.line = element_line(linewidth = 5, lineend = "square")) + ) +}) + +test_that("axis guides can be customized", { + plot <- ggplot(mpg, aes(class, hwy)) + + geom_point() + + scale_y_continuous( + sec.axis = dup_axis(guide = guide_axis(n.dodge = 2)), + guide = guide_axis(n.dodge = 2) + ) + + scale_x_discrete(guide = guide_axis(n.dodge = 2)) + + expect_doppelganger("guide_axis() customization", plot) +}) + +test_that("guides can be specified in guides()", { + plot <- ggplot(mpg, aes(class, hwy)) + + geom_point() + + guides( + x = guide_axis(n.dodge = 2), + y = guide_axis(n.dodge = 2), + x.sec = guide_axis(n.dodge = 2), + y.sec = guide_axis(n.dodge = 2) + ) + + expect_doppelganger("guides specified in guides()", plot) +}) + +test_that("guides have the final say in x and y", { + df <- data_frame(x = 1, y = 1) + plot <- ggplot(df, aes(x, y)) + + geom_point() + + guides( + x = guide_none(title = "x (primary)"), + y = guide_none(title = "y (primary)"), + x.sec = guide_none(title = "x (secondary)"), + y.sec = guide_none(title = "y (secondary)") + ) + + expect_doppelganger("position guide titles", plot) +}) + +test_that("Axis titles won't be blown away by coord_*()", { + df <- data_frame(x = 1, y = 1) + plot <- ggplot(df, aes(x, y)) + + geom_point() + + guides( + x = guide_axis(title = "x (primary)"), + y = guide_axis(title = "y (primary)"), + x.sec = guide_axis(title = "x (secondary)"), + y.sec = guide_axis(title = "y (secondary)") + ) + + expect_doppelganger("guide titles with coord_trans()", plot + coord_trans()) + # TODO + # expect_doppelganger("guide titles with coord_polar()", plot + coord_polar()) + # TODO + # expect_doppelganger("guide titles with coord_sf()", plot + coord_sf()) +}) + +test_that("guide_axis() draws minor ticks correctly", { + p <- ggplot(mtcars, aes(wt, disp)) + + geom_point() + + theme(axis.ticks.length = unit(1, "cm"), + axis.ticks.x.bottom = element_line(linetype = 2), + axis.ticks.length.x.top = unit(-0.5, "cm"), + axis.minor.ticks.x.bottom = element_line(colour = "red"), + axis.minor.ticks.length.y.left = unit(-0.5, "cm"), + axis.minor.ticks.length.x.top = unit(-0.5, "cm"), + axis.minor.ticks.length.x.bottom = unit(0.75, "cm"), + axis.minor.ticks.length.y.right = unit(5, "cm")) + + scale_x_continuous(labels = label_math()) + + guides( + # Test for styling and style inheritance + x = guide_axis(minor.ticks = TRUE), + # # Test for opposed lengths + y = guide_axis(minor.ticks = TRUE), + # # Test for flipped lenghts + x.sec = guide_axis(minor.ticks = TRUE), + # # Test that minor.length doesn't influence spacing when no minor ticks are drawn + y.sec = guide_axis(minor.ticks = FALSE) + ) + expect_doppelganger("guides with minor ticks", p) +}) + +test_that("axis guides can be capped", { + p <- ggplot(mtcars, aes(hp, disp)) + + geom_point() + + theme(axis.line = element_line()) + + guides( + x = guide_axis(cap = "both"), + y = guide_axis(cap = "upper"), + y.sec = guide_axis(cap = "lower"), + x.sec = guide_axis(cap = "none") + ) + expect_doppelganger("axis guides with capped ends", p) +}) + +test_that("guide_axis_stack stacks axes", { + + left <- guide_axis_stack("axis", guide_axis(cap = "both"), title = "left") + right <- guide_axis_stack("axis", guide_axis(cap = "both"), title = "right") + bottom <- guide_axis_stack("axis", guide_axis(cap = "both"), title = "bottom") + top <- guide_axis_stack("axis", guide_axis(cap = "both"), title = "top") + + p <- ggplot(mtcars, aes(hp, disp)) + + geom_point() + + theme(axis.line = element_line()) + + guides(x = bottom, x.sec = top, y = left, y.sec = right) + expect_doppelganger("stacked axes", p) + + bottom <- guide_axis_stack("axis_theta", guide_axis_theta(cap = "both")) + top <- guide_axis_stack("axis_theta", guide_axis_theta(cap = "both")) + + p <- ggplot(mtcars, aes(hp, disp)) + + geom_point() + + theme(axis.line = element_line()) + + coord_radial(start = 0.25 * pi, end = 1.75 * pi, inner.radius = 0.5) + + guides(theta = top, theta.sec = bottom, r = left, r.sec = right) + expect_doppelganger("stacked radial axes", p) + +}) + +test_that("logticks look as they should", { + + p <- ggplot(data.frame(x = c(-100, 100), y = c(10, 1000)), aes(x, y)) + + geom_point() + + scale_y_continuous( + transform = transform_compose(transform_log10(), transform_reverse()), + expand = expansion(add = 0.5) + ) + + scale_x_continuous( + breaks = c(-100, -10, -1, 0, 1, 10, 100) + ) + + coord_trans(x = transform_pseudo_log()) + + theme_test() + + theme(axis.line = element_line(colour = "black"), + panel.border = element_blank(), + axis.ticks.length.x.top = unit(-2.75, "pt")) + + guides( + x = guide_axis_logticks( + title = "Pseudo-logticks with 1 as smallest tick", + negative.small = 1 + ), + y = guide_axis_logticks( + title = "Inverted logticks with swapped tick lengths", + long = 0.75, short = 2.25 + ), + x.sec = guide_axis_logticks( + negative.small = 0.1, + title = "Negative length pseudo-logticks with 0.1 as smallest tick" + ), + y.sec = guide_axis_logticks( + expanded = FALSE, cap = "both", + title = "Capped and not-expanded inverted logticks" + ) + ) + expect_doppelganger("logtick axes with customisation", p) +}) + +test_that("guide_axis_theta sets relative angle", { + + p <- ggplot(mtcars, aes(disp, mpg)) + + geom_point() + + scale_x_continuous(breaks = breaks_width(25)) + + coord_radial(inner.radius = 0.5) + + guides( + theta = guide_axis_theta(angle = 0, cap = "none"), + theta.sec = guide_axis_theta(angle = 90, cap = "both") + ) + + theme(axis.line = element_line(colour = "black")) + + expect_doppelganger("guide_axis_theta with angle adapting to theta", p) +}) + +test_that("guide_axis_theta can be used in cartesian coordinates", { + + p <- ggplot(mtcars, aes(disp, mpg)) + + geom_point() + + guides(x = "axis_theta", y = "axis_theta", + x.sec = "axis_theta", y.sec = "axis_theta") + + theme( + axis.line.x.bottom = element_line(colour = "tomato"), + axis.line.x.top = element_line(colour = "limegreen"), + axis.line.y.left = element_line(colour = "dodgerblue"), + axis.line.y.right = element_line(colour = "orchid") + ) + + expect_doppelganger("guide_axis_theta in cartesian coordinates", p) +}) diff --git a/tests/testthat/test-guide-colorbar.R b/tests/testthat/test-guide-colorbar.R new file mode 100644 index 0000000000..9f44adb371 --- /dev/null +++ b/tests/testthat/test-guide-colorbar.R @@ -0,0 +1,100 @@ +skip_on_cran() # This test suite is long-running (on cran) and is skipped + +test_that("colourbar trains without labels", { + g <- guide_colorbar() + sc <- scale_colour_continuous(limits = c(0, 4), labels = NULL) + + out <- g$train(scale = sc) + expect_named(out$key, c("colour", ".value")) +}) + +test_that("Colorbar respects show.legend in layer", { + df <- data_frame(x = 1:3, y = 1) + p <- ggplot(df, aes(x = x, y = y, color = x)) + + geom_point(size = 20, shape = 21, show.legend = FALSE) + expect_length(ggplot_build(p)$plot$guides$guides, 0L) + p <- ggplot(df, aes(x = x, y = y, color = x)) + + geom_point(size = 20, shape = 21, show.legend = TRUE) + expect_length(ggplot_build(p)$plot$guides$guides, 1L) +}) + +test_that("colorsteps and bins checks the breaks format", { + p <- ggplot(mtcars) + + geom_point(aes(mpg, disp, colour = paste("A", gear))) + + guides(colour = "colorsteps") + expect_snapshot_error(suppressWarnings(ggplotGrob(p))) + p <- ggplot(mtcars) + + geom_point(aes(mpg, disp, colour = paste("A", gear))) + + guides(colour = "bins") + expect_snapshot_error(suppressWarnings(ggplotGrob(p))) +}) + +test_that("guide_colourbar merging preserves both aesthetics", { + # See issue 5324 + + scale1 <- scale_colour_viridis_c() + scale1$train(c(0, 2)) + + scale2 <- scale_fill_viridis_c() + scale2$train(c(0, 2)) + + g <- guide_colourbar() + p <- g$params + + p1 <- g$train(p, scale1, "colour") + p2 <- g$train(p, scale2, "fill") + + merged <- g$merge(p1, g, p2) + + expect_true(all(c("colour", "fill") %in% names(merged$params$key))) +}) + + +test_that("guide_colourbar warns about discrete scales", { + + g <- guide_colourbar() + s <- scale_colour_discrete() + s$train(LETTERS[1:3]) + + expect_snapshot_warning(g <- g$train(g$params, s, "colour")) + expect_null(g) + +}) + +test_that("colorbar can be styled", { + df <- data_frame(x = c(0, 1, 2)) + p <- ggplot(df, aes(x, x, color = x)) + geom_point() + + expect_doppelganger( + "white-to-red colorbar, white ticks, no frame", + p + scale_color_gradient(low = 'white', high = 'red') + ) + + expect_doppelganger( + "customized colorbar", + p + scale_color_gradient( + low = 'white', high = 'red', + guide = guide_colorbar( + theme = theme( + legend.frame = element_rect(colour = "green", linewidth = 1.5 / .pt), + legend.ticks = element_line("black", linewidth = 2.5 / .pt), + legend.ticks.length = unit(0.4, "npc") + ), alpha = 0.75 + ) + ) + labs(subtitle = "white-to-red semitransparent colorbar, long thick black ticks, green frame") + ) +}) + +test_that("guides can handle multiple aesthetics for one scale", { + df <- data_frame(x = c(1, 2, 3), + y = c(6, 5, 7)) + + p <- ggplot(df, aes(x, y, color = x, fill = y)) + + geom_point(shape = 21, size = 3, stroke = 2) + + scale_colour_viridis_c( + name = "value", + option = "B", aesthetics = c("colour", "fill") + ) + + expect_doppelganger("combined colour and fill aesthetics", p) +}) diff --git a/tests/testthat/test-guide-legend.R b/tests/testthat/test-guide-legend.R new file mode 100644 index 0000000000..ff79bd7aa7 --- /dev/null +++ b/tests/testthat/test-guide-legend.R @@ -0,0 +1,213 @@ +skip_on_cran() + +test_that("show.legend handles named vectors", { + n_legends <- function(p) { + g <- ggplotGrob(p) + gb <- grep("guide-box", g$layout$name) + n <- vapply(g$grobs[gb], function(x) { + if (is.zero(x)) return(0) + length(x$grobs) - 1 + }, numeric(1)) + sum(n) + } + + df <- data_frame(x = 1:3, y = 20:22) + p <- ggplot(df, aes(x = x, y = y, color = x, shape = factor(y))) + + geom_point(size = 20) + expect_equal(n_legends(p), 2) + + p <- ggplot(df, aes(x = x, y = y, color = x, shape = factor(y))) + + geom_point(size = 20, show.legend = c(color = FALSE)) + expect_equal(n_legends(p), 1) + + p <- ggplot(df, aes(x = x, y = y, color = x, shape = factor(y))) + + geom_point(size = 20, show.legend = c(color = FALSE, shape = FALSE)) + expect_equal(n_legends(p), 0) + + # c.f.https://github.com/tidyverse/ggplot2/issues/3461 + p <- ggplot(df, aes(x = x, y = y, color = x, shape = factor(y))) + + geom_point(size = 20, show.legend = c(shape = FALSE, color = TRUE)) + expect_equal(n_legends(p), 1) +}) + +test_that("guide merging for guide_legend() works as expected", { + + merge_test_guides <- function(scale1, scale2) { + scale1$guide <- guide_legend(direction = "vertical") + scale2$guide <- guide_legend(direction = "vertical") + scales <- scales_list() + scales$add(scale1) + scales$add(scale2) + scales <- scales$scales + + aesthetics <- lapply(scales, `[[`, "aesthetics") + scales <- rep.int(scales, lengths(aesthetics)) + aesthetics <- unlist(aesthetics, FALSE, FALSE) + + guides <- guides_list(NULL) + guides <- guides$setup(scales, aesthetics) + guides$train(scales, labs()) + guides$merge() + guides$params + } + + different_limits <- merge_test_guides( + scale_colour_discrete(limits = c("a", "b", "c", "d")), + scale_linetype_discrete(limits = c("a", "b", "c")) + ) + expect_length(different_limits, 2) + + same_limits <- merge_test_guides( + scale_colour_discrete(limits = c("a", "b", "c")), + scale_linetype_discrete(limits = c("a", "b", "c")) + ) + expect_length(same_limits, 1) + expect_equal(same_limits[[1]]$key$.label, c("a", "b", "c")) + + same_labels_different_limits <- merge_test_guides( + scale_colour_discrete(limits = c("a", "b", "c")), + scale_linetype_discrete(limits = c("one", "two", "three"), labels = c("a", "b", "c")) + ) + expect_length(same_labels_different_limits, 1) + expect_equal(same_labels_different_limits[[1]]$key$.label, c("a", "b", "c")) + + same_labels_different_scale <- merge_test_guides( + scale_colour_continuous(limits = c(0, 4), breaks = 1:3, labels = c("a", "b", "c")), + scale_linetype_discrete(limits = c("a", "b", "c")) + ) + expect_length(same_labels_different_scale, 1) + expect_equal(same_labels_different_scale[[1]]$key$.label, c("a", "b", "c")) + + repeated_identical_labels <- merge_test_guides( + scale_colour_discrete(limits = c("one", "two", "three"), labels = c("label1", "label1", "label2")), + scale_linetype_discrete(limits = c("1", "2", "3"), labels = c("label1", "label1", "label2")) + ) + expect_length(repeated_identical_labels, 1) + expect_equal(repeated_identical_labels[[1]]$key$.label, c("label1", "label1", "label2")) +}) + +test_that("size = NA doesn't throw rendering errors", { + df <- data.frame( + x = c(1, 2), + group = c("a","b") + ) + p <- ggplot(df, aes(x = x, y = 0, colour = group)) + + geom_point(size = NA, na.rm = TRUE) + + expect_silent(plot(p)) +}) + +test_that("legend reverse argument reverses the key", { + + scale <- scale_colour_discrete() + scale$train(LETTERS[1:4]) + + guides <- guides_list(NULL) + guides <- guides$setup(list(scale), "colour") + + guides$params[[1]]$reverse <- FALSE + guides$train(list(scale), labels = labs()) + fwd <- guides$get_params(1)$key + + guides$params[[1]]$reverse <- TRUE + guides$train(list(scale), labels = labs()) + rev <- guides$get_params(1)$key + + expect_equal(fwd$colour, rev(rev$colour)) +}) + +test_that("legends can be forced to display unrelated geoms", { + + df <- data.frame(x = 1:2) + + p <- ggplot(df, aes(x, x)) + + geom_tile(fill = "red", show.legend = TRUE) + + scale_colour_discrete( + limits = c("A", "B") + ) + + b <- ggplot_build(p) + legend <- b$plot$guides$params[[1]] + + expect_equal( + legend$decor[[1]]$data$fill, + c("red", "red") + ) +}) + + +# Visual tests ------------------------------------------------------------ + +test_that("legend directions are set correctly", { + + p <- ggplot(mtcars, aes(disp, mpg, shape = factor(cyl), colour = drat)) + + geom_point() + + theme_test() + + expect_doppelganger( + "vertical legend direction", + p + theme(legend.direction = "vertical") + ) + + expect_doppelganger( + "horizontal legend direction", + p + theme(legend.direction = "horizontal") + ) +}) + +test_that("guide_legend uses key.spacing correctly", { + p <- ggplot(mtcars, aes(disp, mpg, colour = factor(carb))) + + geom_point() + + guides(colour = guide_legend(ncol = 2)) + + theme_test() + + theme( + legend.key.spacing.x = unit(2, "lines"), + legend.key.spacing.y = unit(1, "lines") + ) + + expect_doppelganger("legend with widely spaced keys", p) +}) + +test_that("absent titles don't take up space", { + + p <- ggplot(mtcars, aes(disp, mpg, colour = factor(cyl))) + + geom_point() + + theme( + legend.title = element_blank(), + legend.margin = margin(), + legend.position = "top", + legend.justification = "left", + legend.key = element_rect(colour = "black"), + axis.line = element_line(colour = "black") + ) + + expect_doppelganger("left aligned legend key", p) +}) + +test_that("size and linewidth affect key size", { + df <- data_frame(x = c(0, 1, 2)) + p <- ggplot(df, aes(x, x)) + + geom_point(aes(size = x)) + + geom_line(aes(linewidth = 2 - x)) + + scale_size_continuous(range = c(1, 12)) + + scale_linewidth_continuous(range = c(1, 20)) + + expect_doppelganger("enlarged guides", p) +}) + +test_that("legend.byrow works in `guide_legend()`", { + + df <- data.frame(x = 1:6, f = LETTERS[1:6]) + + p <- ggplot(df, aes(x, x, colour = f)) + + geom_point() + + scale_colour_discrete( + guide = guide_legend( + ncol = 3, + theme = theme(legend.byrow = TRUE) + ) + ) + + expect_doppelganger("legend.byrow = TRUE", p) +}) + diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index 23df1f75e2..5904676541 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -1,146 +1,5 @@ skip_on_cran() # This test suite is long-running (on cran) and is skipped -test_that("plotting does not induce state changes in guides", { - - guides <- guides( - x = guide_axis(title = "X-axis"), - colour = guide_colourbar(title = "Colourbar"), - shape = guide_legend(title = "Legend"), - size = guide_bins(title = "Bins") - ) - - p <- ggplot(mpg, aes(displ, hwy, colour = cty, shape = factor(cyl), - size = cyl)) + - geom_point() + - guides - - snapshot <- serialize(as.list(p$guides), NULL) - - grob <- ggplotGrob(p) - - expect_identical(as.list(p$guides), unserialize(snapshot)) -}) - -test_that("adding guides doesn't change plot state", { - - p1 <- ggplot(mtcars, aes(disp, mpg)) - - expect_length(p1$guides$guides, 0) - - p2 <- p1 + guides(y = guide_axis(angle = 45)) - - expect_length(p1$guides$guides, 0) - expect_length(p2$guides$guides, 1) - - p3 <- p2 + guides(y = guide_axis(angle = 90)) - - expect_length(p3$guides$guides, 1) - expect_equal(p3$guides$guides[[1]]$params$angle, 90) - expect_equal(p2$guides$guides[[1]]$params$angle, 45) -}) - -test_that("colourbar trains without labels", { - g <- guide_colorbar() - sc <- scale_colour_continuous(limits = c(0, 4), labels = NULL) - - out <- g$train(scale = sc) - expect_named(out$key, c("colour", ".value")) -}) - -test_that("Colorbar respects show.legend in layer", { - df <- data_frame(x = 1:3, y = 1) - p <- ggplot(df, aes(x = x, y = y, color = x)) + - geom_point(size = 20, shape = 21, show.legend = FALSE) - expect_length(ggplot_build(p)$plot$guides$guides, 0L) - p <- ggplot(df, aes(x = x, y = y, color = x)) + - geom_point(size = 20, shape = 21, show.legend = TRUE) - expect_length(ggplot_build(p)$plot$guides$guides, 1L) -}) - -test_that("show.legend handles named vectors", { - n_legends <- function(p) { - g <- ggplotGrob(p) - gb <- grep("guide-box", g$layout$name) - n <- vapply(g$grobs[gb], function(x) { - if (is.zero(x)) return(0) - length(x$grobs) - 1 - }, numeric(1)) - sum(n) - } - - df <- data_frame(x = 1:3, y = 20:22) - p <- ggplot(df, aes(x = x, y = y, color = x, shape = factor(y))) + - geom_point(size = 20) - expect_equal(n_legends(p), 2) - - p <- ggplot(df, aes(x = x, y = y, color = x, shape = factor(y))) + - geom_point(size = 20, show.legend = c(color = FALSE)) - expect_equal(n_legends(p), 1) - - p <- ggplot(df, aes(x = x, y = y, color = x, shape = factor(y))) + - geom_point(size = 20, show.legend = c(color = FALSE, shape = FALSE)) - expect_equal(n_legends(p), 0) - - # c.f.https://github.com/tidyverse/ggplot2/issues/3461 - p <- ggplot(df, aes(x = x, y = y, color = x, shape = factor(y))) + - geom_point(size = 20, show.legend = c(shape = FALSE, color = TRUE)) - expect_equal(n_legends(p), 1) -}) - -test_that("dots are checked when making guides", { - expect_snapshot_warning( - new_guide(foo = "bar", super = GuideAxis) - ) - expect_snapshot_warning( - guide_legend(foo = "bar") - ) -}) - -test_that("axis_label_overlap_priority always returns the correct number of elements", { - expect_identical(axis_label_priority(0), numeric(0)) - expect_setequal(axis_label_priority(1), seq_len(1)) - expect_setequal(axis_label_priority(5), seq_len(5)) - expect_setequal(axis_label_priority(10), seq_len(10)) - expect_setequal(axis_label_priority(100), seq_len(100)) -}) - -test_that("a warning is generated when guides are drawn at a location that doesn't make sense", { - plot <- ggplot(mpg, aes(class, hwy)) + - geom_point() + - scale_y_continuous(guide = guide_axis(position = "top")) - expect_warning(ggplot_build(plot), "Position guide is perpendicular") -}) - -test_that("a warning is not generated when a guide is specified with duplicate breaks", { - plot <- ggplot(mpg, aes(class, hwy)) + - geom_point() + - scale_y_continuous(breaks = c(20, 20)) - built <- expect_silent(ggplot_build(plot)) - expect_silent(ggplot_gtable(built)) -}) - -test_that("a warning is generated when more than one position guide is drawn at a location", { - plot <- ggplot(mpg, aes(class, hwy)) + - geom_point() + - guides( - y = guide_axis(position = "left"), - y.sec = guide_axis(position = "left") - ) - built <- expect_silent(ggplot_build(plot)) - - expect_warning(ggplot_gtable(built), "Discarding guide") -}) - -test_that("a warning is not generated when properly changing the position of a guide_axis()", { - plot <- ggplot(mpg, aes(class, hwy)) + - geom_point() + - guides( - y = guide_axis(position = "right") - ) - built <- expect_silent(ggplot_build(plot)) - expect_silent(ggplot_gtable(built)) -}) - test_that("guide_none() can be used in non-position scales", { p <- ggplot(mpg, aes(cty, hwy, colour = class)) + geom_point() + @@ -158,80 +17,6 @@ test_that("guide_none() can be used in non-position scales", { expect_length(guides$guides, 0) }) -test_that("Using non-position guides for position scales results in an informative error", { - p <- ggplot(mpg, aes(cty, hwy)) + - geom_point() + - scale_x_continuous(guide = guide_legend()) - expect_snapshot_warning(ggplot_build(p)) -}) - -test_that("guide merging for guide_legend() works as expected", { - - merge_test_guides <- function(scale1, scale2) { - scale1$guide <- guide_legend(direction = "vertical") - scale2$guide <- guide_legend(direction = "vertical") - scales <- scales_list() - scales$add(scale1) - scales$add(scale2) - scales <- scales$scales - - aesthetics <- lapply(scales, `[[`, "aesthetics") - scales <- rep.int(scales, lengths(aesthetics)) - aesthetics <- unlist(aesthetics, FALSE, FALSE) - - guides <- guides_list(NULL) - guides <- guides$setup(scales, aesthetics) - guides$train(scales, labs()) - guides$merge() - guides$params - } - - different_limits <- merge_test_guides( - scale_colour_discrete(limits = c("a", "b", "c", "d")), - scale_linetype_discrete(limits = c("a", "b", "c")) - ) - expect_length(different_limits, 2) - - same_limits <- merge_test_guides( - scale_colour_discrete(limits = c("a", "b", "c")), - scale_linetype_discrete(limits = c("a", "b", "c")) - ) - expect_length(same_limits, 1) - expect_equal(same_limits[[1]]$key$.label, c("a", "b", "c")) - - same_labels_different_limits <- merge_test_guides( - scale_colour_discrete(limits = c("a", "b", "c")), - scale_linetype_discrete(limits = c("one", "two", "three"), labels = c("a", "b", "c")) - ) - expect_length(same_labels_different_limits, 1) - expect_equal(same_labels_different_limits[[1]]$key$.label, c("a", "b", "c")) - - same_labels_different_scale <- merge_test_guides( - scale_colour_continuous(limits = c(0, 4), breaks = 1:3, labels = c("a", "b", "c")), - scale_linetype_discrete(limits = c("a", "b", "c")) - ) - expect_length(same_labels_different_scale, 1) - expect_equal(same_labels_different_scale[[1]]$key$.label, c("a", "b", "c")) - - repeated_identical_labels <- merge_test_guides( - scale_colour_discrete(limits = c("one", "two", "three"), labels = c("label1", "label1", "label2")), - scale_linetype_discrete(limits = c("1", "2", "3"), labels = c("label1", "label1", "label2")) - ) - expect_length(repeated_identical_labels, 1) - expect_equal(repeated_identical_labels[[1]]$key$.label, c("label1", "label1", "label2")) -}) - -test_that("size = NA doesn't throw rendering errors", { - df <- data.frame( - x = c(1, 2), - group = c("a","b") - ) - p <- ggplot(df, aes(x = x, y = 0, colour = group)) + - geom_point(size = NA, na.rm = TRUE) - - expect_silent(plot(p)) -}) - test_that("guide specifications are properly checked", { expect_snapshot_error(validate_guide("test")) expect_snapshot_error(validate_guide(1)) @@ -266,36 +51,6 @@ test_that("guide specifications are properly checked", { expect_snapshot_error(ggplotGrob(p)) }) -test_that("colorsteps and bins checks the breaks format", { - p <- ggplot(mtcars) + - geom_point(aes(mpg, disp, colour = paste("A", gear))) + - guides(colour = "colorsteps") - expect_snapshot_error(suppressWarnings(ggplotGrob(p))) - p <- ggplot(mtcars) + - geom_point(aes(mpg, disp, colour = paste("A", gear))) + - guides(colour = "bins") - expect_snapshot_error(suppressWarnings(ggplotGrob(p))) -}) - -test_that("legend reverse argument reverses the key", { - - scale <- scale_colour_discrete() - scale$train(LETTERS[1:4]) - - guides <- guides_list(NULL) - guides <- guides$setup(list(scale), "colour") - - guides$params[[1]]$reverse <- FALSE - guides$train(list(scale), labels = labs()) - fwd <- guides$get_params(1)$key - - guides$params[[1]]$reverse <- TRUE - guides$train(list(scale), labels = labs()) - rev <- guides$get_params(1)$key - - expect_equal(fwd$colour, rev(rev$colour)) -}) - test_that("guide_coloursteps and guide_bins return ordered breaks", { scale <- scale_colour_viridis_c(breaks = c(2, 3, 1)) scale$train(c(0, 4)) @@ -334,27 +89,6 @@ test_that("guide_coloursteps can parse (un)even steps from discrete scales", { expect_equal(decor$max - decor$min, c(0.3, 0.2, 0.5)) }) - -test_that("guide_colourbar merging preserves both aesthetics", { - # See issue 5324 - - scale1 <- scale_colour_viridis_c() - scale1$train(c(0, 2)) - - scale2 <- scale_fill_viridis_c() - scale2$train(c(0, 2)) - - g <- guide_colourbar() - p <- g$params - - p1 <- g$train(p, scale1, "colour") - p2 <- g$train(p, scale2, "fill") - - merged <- g$merge(p1, g, p2) - - expect_true(all(c("colour", "fill") %in% names(merged$params$key))) -}) - test_that("get_guide_data retrieves keys appropriately", { p <- ggplot(mtcars, aes(mpg, disp, colour = drat, size = drat, fill = wt)) + @@ -381,8 +115,8 @@ test_that("get_guide_data retrieves keys appropriately", { # Non-existent panels expect_null(get_guide_data(b, "x", panel = 4)) - expect_error(get_guide_data(b, 1), "must be a single string") - expect_error(get_guide_data(b, "x", panel = "a"), "must be a whole number") + expect_snapshot(get_guide_data(b, 1), error = TRUE) + expect_snapshot(get_guide_data(b, "x", panel = "a"), error = TRUE) }) test_that("get_guide_data retrieves keys from exotic coords", { @@ -404,111 +138,6 @@ test_that("get_guide_data retrieves keys from exotic coords", { expect_equal(test$theta.labels, c("15", "20", "25", "30")) }) -test_that("guide_colourbar warns about discrete scales", { - - g <- guide_colourbar() - s <- scale_colour_discrete() - s$train(LETTERS[1:3]) - - expect_warning(g <- g$train(g$params, s, "colour"), "needs continuous scales") - expect_null(g) - -}) - -test_that("legend directions are set correctly", { - - p <- ggplot(mtcars, aes(disp, mpg, shape = factor(cyl), colour = drat)) + - geom_point() + - theme_test() - - expect_doppelganger( - "vertical legend direction", - p + theme(legend.direction = "vertical") - ) - - expect_doppelganger( - "horizontal legend direction", - p + theme(legend.direction = "horizontal") - ) -}) - -test_that("guide_axis_logticks calculates appropriate ticks", { - - test_scale <- function(transform = transform_identity(), limits = c(NA, NA)) { - scale <- scale_x_continuous(transform = transform) - scale$train(scale$transform(limits)) - view_scale_primary(scale) - } - - train_guide <- function(guide, scale) { - params <- guide$params - params$position <- "bottom" - guide$train(params, scale, "x") - } - - guide <- guide_axis_logticks(negative.small = 10) - outcome <- c((1:10)*10, (2:10)*100) - - # Test the classic log10 transformation - scale <- test_scale(transform_log10(), c(10, 1000)) - key <- train_guide(guide, scale)$logkey - - expect_equal(sort(key$x), log10(outcome)) - expect_equal(key$.type, rep(c(1,2,3), c(3, 2, 14))) - - # Test compound transformation - scale <- test_scale(transform_compose(transform_log10(), transform_reverse()), c(10, 1000)) - key <- train_guide(guide, scale)$logkey - - expect_equal(sort(key$x), -log10(rev(outcome))) - - # Test transformation with negatives - scale <- test_scale(transform_pseudo_log(), c(-1000, 1000)) - key <- train_guide(guide, scale)$logkey - - unlog <- sort(transform_pseudo_log()$inverse(key$x)) - expect_equal(unlog, c(-rev(outcome), 0, outcome)) - expect_equal(key$.type, rep(c(1,2,3), c(7, 4, 28))) - - # Test expanded argument - scale <- test_scale(transform_log10(), c(20, 900)) - scale$continuous_range <- c(1, 3) - - guide <- guide_axis_logticks(expanded = TRUE) - key <- train_guide(guide, scale)$logkey - - expect_equal(sort(key$x), log10(outcome)) - - guide <- guide_axis_logticks(expanded = FALSE) - key <- train_guide(guide, scale)$logkey - - expect_equal(sort(key$x), log10(outcome[-c(1, length(outcome))])) - - # Test with prescaled input - guide <- guide_axis_logticks(prescale.base = 2) - scale <- test_scale(limits = log2(c(10, 1000))) - - key <- train_guide(guide, scale)$logkey - expect_equal(sort(key$x), log2(outcome)) - - # Should warn when scale also has transformation - scale <- test_scale(transform_log10(), limits = c(10, 1000)) - expect_snapshot_warning(train_guide(guide, scale)$logkey) -}) - -test_that("guide_legend uses key.spacing correctly", { - p <- ggplot(mtcars, aes(disp, mpg, colour = factor(carb))) + - geom_point() + - guides(colour = guide_legend(ncol = 2)) + - theme_test() + - theme( - legend.key.spacing.x = unit(2, "lines"), - legend.key.spacing.y = unit(1, "lines") - ) - - expect_doppelganger("legend with widely spaced keys", p) -}) - test_that("empty guides are dropped", { df <- data.frame(x = 1:2) @@ -561,292 +190,8 @@ test_that("bins can be parsed by guides for all scale types", { ) }) -test_that("legends can be forced to display unrelated geoms", { - - df <- data.frame(x = 1:2) - - p <- ggplot(df, aes(x, x)) + - geom_tile(fill = "red", show.legend = TRUE) + - scale_colour_discrete( - limits = c("A", "B") - ) - - b <- ggplot_build(p) - legend <- b$plot$guides$params[[1]] - - expect_equal( - legend$decor[[1]]$data$fill, - c("red", "red") - ) -}) - # Visual tests ------------------------------------------------------------ -test_that("axis guides are drawn correctly", { - theme_test_axis <- theme_test() + theme(axis.line = element_line(linewidth = 0.5)) - test_draw_axis <- function(n_breaks = 3, - break_positions = seq_len(n_breaks) / (n_breaks + 1), - labels = as.character, - positions = c("top", "right", "bottom", "left"), - theme = theme_test_axis, - ...) { - - break_labels <- labels(seq_along(break_positions)) - - # create the axes - axes <- lapply(positions, function(position) { - draw_axis(break_positions, break_labels, axis_position = position, theme = theme, ...) - }) - axes_grob <- gTree(children = do.call(gList, axes)) - - # arrange them so there's some padding on each side - gt <- gtable( - widths = unit(c(0.05, 0.9, 0.05), "npc"), - heights = unit(c(0.05, 0.9, 0.05), "npc") - ) - gt <- gtable_add_grob(gt, list(axes_grob), 2, 2, clip = "off") - plot(gt) - } - - # basic - expect_doppelganger("axis guides basic", function() test_draw_axis()) - expect_doppelganger("axis guides, zero breaks", function() test_draw_axis(n_breaks = 0)) - - # overlapping text - expect_doppelganger( - "axis guides, check overlap", - function() test_draw_axis(20, labels = function(b) comma(b * 1e9), check.overlap = TRUE) - ) - - # rotated text - expect_doppelganger( - "axis guides, zero rotation", - function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = 0) - ) - - expect_doppelganger( - "axis guides, positive rotation", - function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = 45) - ) - - expect_doppelganger( - "axis guides, negative rotation", - function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = -45) - ) - - expect_doppelganger( - "axis guides, vertical rotation", - function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = 90) - ) - - expect_doppelganger( - "axis guides, vertical negative rotation", - function() test_draw_axis(10, labels = function(b) comma(b * 1e3), angle = -90) - ) - - # dodged text - expect_doppelganger( - "axis guides, text dodged into rows/cols", - function() test_draw_axis(10, labels = function(b) comma(b * 1e9), n.dodge = 2) - ) -}) - -test_that("axis guides are drawn correctly in plots", { - expect_doppelganger("align facet labels, facets horizontal", - ggplot(mpg, aes(hwy, reorder(model, hwy))) + - geom_point() + - facet_grid(manufacturer ~ ., scales = "free", space = "free") + - theme_test() + - theme(strip.text.y = element_text(angle = 0)) - ) - expect_doppelganger("align facet labels, facets vertical", - ggplot(mpg, aes(reorder(model, hwy), hwy)) + - geom_point() + - facet_grid(. ~ manufacturer, scales = "free", space = "free") + - theme_test() + - theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) - ) - expect_doppelganger("thick axis lines", - ggplot(mtcars, aes(wt, mpg)) + - geom_point() + - theme_test() + - theme(axis.line = element_line(linewidth = 5, lineend = "square")) - ) -}) - -test_that("axis guides can be customized", { - plot <- ggplot(mpg, aes(class, hwy)) + - geom_point() + - scale_y_continuous( - sec.axis = dup_axis(guide = guide_axis(n.dodge = 2)), - guide = guide_axis(n.dodge = 2) - ) + - scale_x_discrete(guide = guide_axis(n.dodge = 2)) - - expect_doppelganger("guide_axis() customization", plot) -}) - -test_that("guides can be specified in guides()", { - plot <- ggplot(mpg, aes(class, hwy)) + - geom_point() + - guides( - x = guide_axis(n.dodge = 2), - y = guide_axis(n.dodge = 2), - x.sec = guide_axis(n.dodge = 2), - y.sec = guide_axis(n.dodge = 2) - ) - - expect_doppelganger("guides specified in guides()", plot) -}) - -test_that("guides have the final say in x and y", { - df <- data_frame(x = 1, y = 1) - plot <- ggplot(df, aes(x, y)) + - geom_point() + - guides( - x = guide_none(title = "x (primary)"), - y = guide_none(title = "y (primary)"), - x.sec = guide_none(title = "x (secondary)"), - y.sec = guide_none(title = "y (secondary)") - ) - - expect_doppelganger("position guide titles", plot) -}) - -test_that("Axis titles won't be blown away by coord_*()", { - df <- data_frame(x = 1, y = 1) - plot <- ggplot(df, aes(x, y)) + - geom_point() + - guides( - x = guide_axis(title = "x (primary)"), - y = guide_axis(title = "y (primary)"), - x.sec = guide_axis(title = "x (secondary)"), - y.sec = guide_axis(title = "y (secondary)") - ) - - expect_doppelganger("guide titles with coord_trans()", plot + coord_trans()) - # TODO - # expect_doppelganger("guide titles with coord_polar()", plot + coord_polar()) - # TODO - # expect_doppelganger("guide titles with coord_sf()", plot + coord_sf()) -}) - -test_that("guide_axis() draws minor ticks correctly", { - p <- ggplot(mtcars, aes(wt, disp)) + - geom_point() + - theme(axis.ticks.length = unit(1, "cm"), - axis.ticks.x.bottom = element_line(linetype = 2), - axis.ticks.length.x.top = unit(-0.5, "cm"), - axis.minor.ticks.x.bottom = element_line(colour = "red"), - axis.minor.ticks.length.y.left = unit(-0.5, "cm"), - axis.minor.ticks.length.x.top = unit(-0.5, "cm"), - axis.minor.ticks.length.x.bottom = unit(0.75, "cm"), - axis.minor.ticks.length.y.right = unit(5, "cm")) + - scale_x_continuous(labels = label_math()) + - guides( - # Test for styling and style inheritance - x = guide_axis(minor.ticks = TRUE), - # # Test for opposed lengths - y = guide_axis(minor.ticks = TRUE), - # # Test for flipped lenghts - x.sec = guide_axis(minor.ticks = TRUE), - # # Test that minor.length doesn't influence spacing when no minor ticks are drawn - y.sec = guide_axis(minor.ticks = FALSE) - ) - expect_doppelganger("guides with minor ticks", p) -}) - -test_that("absent titles don't take up space", { - - p <- ggplot(mtcars, aes(disp, mpg, colour = factor(cyl))) + - geom_point() + - theme( - legend.title = element_blank(), - legend.margin = margin(), - legend.position = "top", - legend.justification = "left", - legend.key = element_rect(colour = "black"), - axis.line = element_line(colour = "black") - ) - - expect_doppelganger("left aligned legend key", p) -}) - -test_that("axis guides can be capped", { - p <- ggplot(mtcars, aes(hp, disp)) + - geom_point() + - theme(axis.line = element_line()) + - guides( - x = guide_axis(cap = "both"), - y = guide_axis(cap = "upper"), - y.sec = guide_axis(cap = "lower"), - x.sec = guide_axis(cap = "none") - ) - expect_doppelganger("axis guides with capped ends", p) -}) - -test_that("guide_axis_stack stacks axes", { - - left <- guide_axis_stack("axis", guide_axis(cap = "both"), title = "left") - right <- guide_axis_stack("axis", guide_axis(cap = "both"), title = "right") - bottom <- guide_axis_stack("axis", guide_axis(cap = "both"), title = "bottom") - top <- guide_axis_stack("axis", guide_axis(cap = "both"), title = "top") - - p <- ggplot(mtcars, aes(hp, disp)) + - geom_point() + - theme(axis.line = element_line()) + - guides(x = bottom, x.sec = top, y = left, y.sec = right) - expect_doppelganger("stacked axes", p) - - bottom <- guide_axis_stack("axis_theta", guide_axis_theta(cap = "both")) - top <- guide_axis_stack("axis_theta", guide_axis_theta(cap = "both")) - - p <- ggplot(mtcars, aes(hp, disp)) + - geom_point() + - theme(axis.line = element_line()) + - coord_radial(start = 0.25 * pi, end = 1.75 * pi, inner.radius = 0.5) + - guides(theta = top, theta.sec = bottom, r = left, r.sec = right) - expect_doppelganger("stacked radial axes", p) - -}) - -test_that("logticks look as they should", { - - p <- ggplot(data.frame(x = c(-100, 100), y = c(10, 1000)), aes(x, y)) + - geom_point() + - scale_y_continuous( - transform = transform_compose(transform_log10(), transform_reverse()), - expand = expansion(add = 0.5) - ) + - scale_x_continuous( - breaks = c(-100, -10, -1, 0, 1, 10, 100) - ) + - coord_trans(x = transform_pseudo_log()) + - theme_test() + - theme(axis.line = element_line(colour = "black"), - panel.border = element_blank(), - axis.ticks.length.x.top = unit(-2.75, "pt")) + - guides( - x = guide_axis_logticks( - title = "Pseudo-logticks with 1 as smallest tick", - negative.small = 1 - ), - y = guide_axis_logticks( - title = "Inverted logticks with swapped tick lengths", - long = 0.75, short = 2.25 - ), - x.sec = guide_axis_logticks( - negative.small = 0.1, - title = "Negative length pseudo-logticks with 0.1 as smallest tick" - ), - y.sec = guide_axis_logticks( - expanded = FALSE, cap = "both", - title = "Capped and not-expanded inverted logticks" - ) - ) - expect_doppelganger("logtick axes with customisation", p) -}) - test_that("guides are positioned correctly", { df <- data_frame(x = 1, y = 1, z = factor("a")) @@ -1033,53 +378,6 @@ test_that("guides title and text are positioned correctly", { expect_doppelganger("legends with all title justifications", p) }) -test_that("size and linewidth affect key size", { - df <- data_frame(x = c(0, 1, 2)) - p <- ggplot(df, aes(x, x)) + - geom_point(aes(size = x)) + - geom_line(aes(linewidth = 2 - x)) + - scale_size_continuous(range = c(1, 12)) + - scale_linewidth_continuous(range = c(1, 20)) - - expect_doppelganger("enlarged guides", p) -}) - -test_that("colorbar can be styled", { - df <- data_frame(x = c(0, 1, 2)) - p <- ggplot(df, aes(x, x, color = x)) + geom_point() - - expect_doppelganger("white-to-red colorbar, white ticks, no frame", - p + scale_color_gradient(low = 'white', high = 'red') - ) - - expect_doppelganger("customized colorbar", - p + scale_color_gradient( - low = 'white', high = 'red', - guide = guide_colorbar( - theme = theme( - legend.frame = element_rect(colour = "green", linewidth = 1.5 / .pt), - legend.ticks = element_line("black", linewidth = 2.5 / .pt), - legend.ticks.length = unit(0.4, "npc") - ), alpha = 0.75 - ) - ) + labs(subtitle = "white-to-red semitransparent colorbar, long thick black ticks, green frame") - ) -}) - -test_that("guides can handle multiple aesthetics for one scale", { - df <- data_frame(x = c(1, 2, 3), - y = c(6, 5, 7)) - - p <- ggplot(df, aes(x, y, color = x, fill = y)) + - geom_point(shape = 21, size = 3, stroke = 2) + - scale_colour_viridis_c( - name = "value", - option = "B", aesthetics = c("colour", "fill") - ) - - expect_doppelganger("one combined colorbar for colour and fill aesthetics", p) -}) - test_that("bin guide can be styled correctly", { df <- data_frame(x = c(1, 2, 3), y = c(6, 5, 7)) @@ -1184,79 +482,25 @@ test_that("binning scales understand the different combinations of limits, break expect_snapshot_warning(ggplotGrob(p + scale_color_binned(labels = 1:4, show.limits = TRUE))) }) -test_that("guide_axis_theta sets relative angle", { - - p <- ggplot(mtcars, aes(disp, mpg)) + - geom_point() + - scale_x_continuous(breaks = breaks_width(25)) + - coord_radial(inner.radius = 0.5) + - guides( - theta = guide_axis_theta(angle = 0, cap = "none"), - theta.sec = guide_axis_theta(angle = 90, cap = "both") - ) + - theme(axis.line = element_line(colour = "black")) - - expect_doppelganger("guide_axis_theta with angle adapting to theta", p) -}) - -test_that("guide_axis_theta can be used in cartesian coordinates", { - - p <- ggplot(mtcars, aes(disp, mpg)) + - geom_point() + - guides(x = "axis_theta", y = "axis_theta", - x.sec = "axis_theta", y.sec = "axis_theta") + - theme( - axis.line.x.bottom = element_line(colour = "tomato"), - axis.line.x.top = element_line(colour = "limegreen"), - axis.line.y.left = element_line(colour = "dodgerblue"), - axis.line.y.right = element_line(colour = "orchid") - ) - - expect_doppelganger("guide_axis_theta in cartesian coordinates", p) -}) - test_that("a warning is generated when guides( = FALSE) is specified", { df <- data_frame(x = c(1, 2, 4), y = c(6, 5, 7)) # warn on guide( = FALSE) - expect_warning(g <- guides(colour = FALSE), "The `` argument of `guides()` cannot be `FALSE`. Use \"none\" instead as of ggplot2 3.3.4.", fixed = TRUE) + lifecycle::expect_deprecated(g <- guides(colour = FALSE)) expect_equal(g$guides[["colour"]], "none") # warn on scale_*(guide = FALSE) p <- ggplot(df, aes(x, y, colour = x)) + scale_colour_continuous(guide = FALSE) - expect_snapshot_warning(ggplot_build(p)) + lifecycle::expect_deprecated(ggplot_build(p)) }) test_that("guides() warns if unnamed guides are provided", { - expect_warning( - guides("axis"), - "All guides are unnamed." - ) - expect_warning( - guides(x = "axis", "axis"), - "The 2nd guide is unnamed" - ) + expect_snapshot_warning(guides("axis")) + expect_snapshot_warning(guides(x = "axis", "axis")) expect_null(guides()) }) -test_that("legend.byrow works in `guide_legend()`", { - - df <- data.frame(x = 1:6, f = LETTERS[1:6]) - - p <- ggplot(df, aes(x, x, colour = f)) + - geom_point() + - scale_colour_discrete( - guide = guide_legend( - ncol = 3, - theme = theme(legend.byrow = TRUE) - ) - ) - - expect_doppelganger("legend.byrow = TRUE", p) - -}) - test_that("old S3 guides can be implemented", { my_env <- env() diff --git a/tests/testthat/test-labels.R b/tests/testthat/test-labels.R index 6a26578c0b..b8b002a3db 100644 --- a/tests/testthat/test-labels.R +++ b/tests/testthat/test-labels.R @@ -116,13 +116,13 @@ test_that("plot.tag.position rejects invalid input", { expect_snapshot_error( ggplotGrob(p + theme(plot.tag.position = "foobar")) ) - expect_error( + expect_snapshot( ggplotGrob(p + theme(plot.tag.position = c(0, 0.5, 1))), - "must have length 2" + error = TRUE ) - expect_error( + expect_snapshot( ggplotGrob(p + theme(plot.tag.position = c(0, 0), plot.tag.location = "margin")), - "cannot be used with `\"margin\"" + error = TRUE ) }) diff --git a/tests/testthat/test-layer.R b/tests/testthat/test-layer.R index 51f0cd9eee..964211a4ee 100644 --- a/tests/testthat/test-layer.R +++ b/tests/testthat/test-layer.R @@ -18,18 +18,15 @@ test_that("aesthetics go in aes_params", { }) test_that("unknown params create warning", { - expect_warning(geom_point(blah = "red"), "unknown parameters") + expect_snapshot_warning(geom_point(blah = "red")) }) test_that("unknown aesthetics create warning", { - expect_warning(geom_point(aes(blah = "red")), "unknown aesthetics") + expect_snapshot_warning(geom_point(aes(blah = "red"))) }) test_that("empty aesthetics create warning", { - expect_warning( - geom_point(fill = NULL, shape = character()), - "Ignoring empty aesthetics" - ) + expect_snapshot_warning(geom_point(fill = NULL, shape = character())) }) test_that("invalid aesthetics throws errors", { @@ -43,7 +40,7 @@ test_that("invalid aesthetics throws errors", { }) test_that("unknown NULL aesthetic doesn't create warning (#1909)", { - expect_warning(geom_point(aes(blah = NULL)), NA) + expect_silent(geom_point(aes(blah = NULL))) }) test_that("column vectors are allowed (#2609)", { @@ -55,13 +52,13 @@ test_that("column vectors are allowed (#2609)", { test_that("missing aesthetics trigger informative error", { df <- data_frame(x = 1:10) - expect_error( + expect_snapshot( ggplot_build(ggplot(df) + geom_line()), - "requires the following missing aesthetics:" + error = TRUE ) - expect_error( + expect_snapshot( ggplot_build(ggplot(df) + geom_col()), - "requires the following missing aesthetics:" + error = TRUE ) }) @@ -154,10 +151,7 @@ test_that("layer names can be resolved", { expect_equal(names(p$layers), c("foo", "bar")) l <- geom_point(name = "foobar") - expect_error( - p + l + l, - "names are duplicated" - ) + expect_snapshot(p + l + l, error = TRUE) }) diff --git a/tests/testthat/test-performance.R b/tests/testthat/test-performance.R index 1c65622b4a..bd737f7c26 100644 --- a/tests/testthat/test-performance.R +++ b/tests/testthat/test-performance.R @@ -12,7 +12,7 @@ testappend <- list( ) test_that("modifyList is masked", { - expect_error(modifyList(testlist, testappend)) + expect_snapshot(modifyList(testlist, testappend), error = TRUE) }) test_that("modify_list retains unreferenced elements", { diff --git a/tests/testthat/test-position_dodge.R b/tests/testthat/test-position_dodge.R index 9107de1d92..14b79d3cad 100644 --- a/tests/testthat/test-position_dodge.R +++ b/tests/testthat/test-position_dodge.R @@ -51,5 +51,5 @@ test_that("position_dodge warns about missing required aesthetics", { mapping = aes(x = NULL) ) - expect_error(ggplot_build(p), "requires the following missing aesthetics") + expect_snapshot(ggplot_build(p), error = TRUE) }) diff --git a/tests/testthat/test-qplot.R b/tests/testthat/test-qplot.R index 58e8fa1e14..74ab153c39 100644 --- a/tests/testthat/test-qplot.R +++ b/tests/testthat/test-qplot.R @@ -46,7 +46,7 @@ test_that("qplot() evaluates layers in package environment", { } lifecycle::expect_deprecated( - expect_error(p <- qplot(1, 1, geom = "line"), NA) + expect_no_error(p <- qplot(1, 1, geom = "line")) ) }) diff --git a/tests/testthat/test-scale-date.R b/tests/testthat/test-scale-date.R index 1183fcd756..33356545af 100644 --- a/tests/testthat/test-scale-date.R +++ b/tests/testthat/test-scale-date.R @@ -72,14 +72,6 @@ test_that("datetime colour scales work", { test_that("date(time) scales throw warnings when input is numeric", { p <- ggplot(data.frame(x = 1, y = 1), aes(x, y)) + geom_point() - expect_warning( - ggplot_build(p + scale_x_date()), - "The value was converted to a object." - ) - - expect_warning( - ggplot_build(p + scale_x_datetime()), - "The value was converted to a object." - ) - + expect_snapshot_warning(ggplot_build(p + scale_x_date())) + expect_snapshot_warning(ggplot_build(p + scale_x_datetime())) }) diff --git a/tests/testthat/test-scale-discrete.R b/tests/testthat/test-scale-discrete.R index 9e8eeaf717..50f7b585fe 100644 --- a/tests/testthat/test-scale-discrete.R +++ b/tests/testthat/test-scale-discrete.R @@ -134,12 +134,12 @@ test_that("discrete scale defaults can be set globally", { test_that("Scale is checked in default colour scale", { # Check scale type - expect_error(scale_colour_discrete(type = scale_colour_gradient)) - expect_error(scale_fill_discrete(type = scale_fill_gradient)) + expect_snapshot(scale_colour_discrete(type = scale_colour_gradient), error = TRUE) + expect_snapshot(scale_fill_discrete(type = scale_fill_gradient), error = TRUE) # Check aesthetic - expect_error(scale_colour_discrete(type = scale_fill_hue)) - expect_error(scale_fill_discrete(type = scale_colour_hue)) + expect_snapshot(scale_colour_discrete(type = scale_fill_hue), error = TRUE) + expect_snapshot(scale_fill_discrete(type = scale_colour_hue), error = TRUE) }) test_that("Aesthetics with no continuous interpretation fails when called", { @@ -153,7 +153,7 @@ test_that("mapped_discrete vectors behaves as predicted", { expect_null(mapped_discrete(NULL)) expect_s3_class(mapped_discrete(c(0, 3.5)), "mapped_discrete") expect_s3_class(mapped_discrete(seq_len(4)), "mapped_discrete") - expect_error(mapped_discrete(letters)) + expect_snapshot(mapped_discrete(letters), error = TRUE) x <- mapped_discrete(1:10) expect_s3_class(x[2:4], "mapped_discrete") @@ -192,14 +192,14 @@ test_that("invalid palettes trigger errors", { p <- ggplot(df, aes(x, y)) + geom_point() - expect_error( + expect_snapshot( ggplot_build(p + scale_x_discrete(palette = function(x) LETTERS[1:3])), - "must return a .+ vector\\." + error = TRUE ) - expect_error( + expect_snapshot( ggplot_build(p + scale_x_discrete(palette = function(x) 1:2)), - "must return at least 3 values" + error = TRUE ) }) diff --git a/tests/testthat/test-scale-expansion.R b/tests/testthat/test-scale-expansion.R index 26d25abfdc..378742de8c 100644 --- a/tests/testthat/test-scale-expansion.R +++ b/tests/testthat/test-scale-expansion.R @@ -1,6 +1,6 @@ test_that("expand_scale() produces a deprecation warning", { - expect_warning(expand_scale(), "deprecated") + lifecycle::expect_deprecated(expand_scale()) }) test_that("expansion() checks input", { diff --git a/tests/testthat/test-scale-gradient.R b/tests/testthat/test-scale-gradient.R index 3b3a1944dd..771490f945 100644 --- a/tests/testthat/test-scale-gradient.R +++ b/tests/testthat/test-scale-gradient.R @@ -20,8 +20,7 @@ test_that("midpoints are transformed", { scale$train(scale$transform(c(1, 1000))) ans <- scale$rescale(c(0, 3), c(0.25, 1)) - expect_warning( - scale_colour_gradient2(midpoint = 0, transform = "log10"), - "introduced infinite values" + expect_snapshot_warning( + scale_colour_gradient2(midpoint = 0, transform = "log10") ) }) diff --git a/tests/testthat/test-scale-manual.R b/tests/testthat/test-scale-manual.R index 2ad47425f7..324485952b 100644 --- a/tests/testthat/test-scale-manual.R +++ b/tests/testthat/test-scale-manual.R @@ -54,8 +54,7 @@ test_that("insufficient values raise an error", { df <- data_frame(x = 1, y = 1:3, z = factor(c(1:2, NA), exclude = NULL)) p <- ggplot(df, aes(x, y, colour = z)) + geom_point() - expect_error(ggplot_build(p + scale_colour_manual(values = "black")), - "Insufficient values") + expect_snapshot(ggplot_build(p + scale_colour_manual(values = "black")), error = TRUE) # Should be sufficient ggplot_build(p + scale_colour_manual(values = c("black", "black"))) @@ -122,7 +121,7 @@ test_that("fewer values (#3451)", { # unnamed character vector s2 <- scale_colour_manual(values = c("4", "8"), na.value = NA) s2$train(c("4", "6", "8")) - expect_error(s2$map(c("4", "6", "8")), "Insufficient values") + expect_snapshot(s2$map(c("4", "6", "8")), error = TRUE) }) test_that("limits and breaks (#4619)", { diff --git a/tests/testthat/test-scales-breaks-labels.R b/tests/testthat/test-scales-breaks-labels.R index c3a314cacc..4599b0bc03 100644 --- a/tests/testthat/test-scales-breaks-labels.R +++ b/tests/testthat/test-scales-breaks-labels.R @@ -7,10 +7,8 @@ test_that("labels match breaks, even when outside limits", { }) test_that("labels match breaks", { - expect_error(scale_x_discrete(breaks = 1:3, labels = 1:2), - "must have the same length") - expect_error(scale_x_continuous(breaks = 1:3, labels = 1:2), - "must have the same length") + expect_snapshot(scale_x_discrete(breaks = 1:3, labels = 1:2), error = TRUE) + expect_snapshot(scale_x_continuous(breaks = 1:3, labels = 1:2), error = TRUE) }) test_that("labels don't have to match null breaks", { @@ -137,7 +135,7 @@ test_that("discrete scales with no data have no breaks or labels", { }) test_that("passing continuous limits to a discrete scale generates a warning", { - expect_warning(scale_x_discrete(limits = 1:3), "Continuous limits supplied to discrete scale") + expect_snapshot_warning(scale_x_discrete(limits = 1:3)) }) test_that("suppressing breaks, minor_breask, and labels works", { @@ -152,20 +150,38 @@ test_that("suppressing breaks, minor_breask, and labels works", { lims <- as.Date(c("2000/1/1", "2000/2/1")) expect_null(scale_x_date(breaks = NULL, limits = lims)$get_breaks()) # NA is defunct, should throw error - expect_error(scale_x_date(breaks = NA, limits = lims)$get_breaks()) + expect_snapshot( + scale_x_date(breaks = NA, limits = lims)$get_breaks(), + error = TRUE + ) expect_null(scale_x_date(labels = NULL, limits = lims)$get_labels()) - expect_error(scale_x_date(labels = NA, limits = lims)$get_labels()) + expect_snapshot( + scale_x_date(labels = NA, limits = lims)$get_labels(), + error = TRUE + ) expect_null(scale_x_date(minor_breaks = NULL, limits = lims)$get_breaks_minor()) - expect_error(scale_x_date(minor_breaks = NA, limits = lims)$get_breaks_minor()) + expect_snapshot( + scale_x_date(minor_breaks = NA, limits = lims)$get_breaks_minor(), + error = TRUE + ) # date, datetime lims <- as.POSIXct(c("2000/1/1 0:0:0", "2010/1/1 0:0:0")) expect_null(scale_x_datetime(breaks = NULL, limits = lims)$get_breaks()) - expect_error(scale_x_datetime(breaks = NA, limits = lims)$get_breaks()) + expect_snapshot( + scale_x_datetime(breaks = NA, limits = lims)$get_breaks(), + error = TRUE + ) expect_null(scale_x_datetime(labels = NULL, limits = lims)$get_labels()) - expect_error(scale_x_datetime(labels = NA, limits = lims)$get_labels()) + expect_snapshot( + scale_x_datetime(labels = NA, limits = lims)$get_labels(), + error = TRUE + ) expect_null(scale_x_datetime(minor_breaks = NULL, limits = lims)$get_breaks_minor()) - expect_error(scale_x_datetime(minor_breaks = NA, limits = lims)$get_breaks_minor()) + expect_snapshot( + scale_x_datetime(minor_breaks = NA, limits = lims)$get_breaks_minor(), + error = TRUE + ) }) test_that("scale_breaks with explicit NA options (deprecated)", { @@ -174,34 +190,34 @@ test_that("scale_breaks with explicit NA options (deprecated)", { # X sxc <- scale_x_continuous(breaks = NA) sxc$train(1:3) - expect_error(sxc$get_breaks()) - expect_error(sxc$get_breaks_minor()) + expect_snapshot(sxc$get_breaks(), error = TRUE) + expect_snapshot(sxc$get_breaks_minor(), error = TRUE) # Y syc <- scale_y_continuous(breaks = NA) syc$train(1:3) - expect_error(syc$get_breaks()) - expect_error(syc$get_breaks_minor()) + expect_snapshot(syc$get_breaks(), error = TRUE) + expect_snapshot(syc$get_breaks_minor(), error = TRUE) # Alpha sac <- scale_alpha_continuous(breaks = NA) sac$train(1:3) - expect_error(sac$get_breaks()) + expect_snapshot(sac$get_breaks(), error = TRUE) # Size ssc <- scale_size_continuous(breaks = NA) ssc$train(1:3) - expect_error(ssc$get_breaks()) + expect_snapshot(ssc$get_breaks(), error = TRUE) # Fill sfc <- scale_fill_continuous(breaks = NA) sfc$train(1:3) - expect_error(sfc$get_breaks()) + expect_snapshot(sfc$get_breaks(), error = TRUE) # Colour scc <- scale_colour_continuous(breaks = NA) scc$train(1:3) - expect_error(scc$get_breaks()) + expect_snapshot(scc$get_breaks(), error = TRUE) }) test_that("breaks can be specified by names of labels", { diff --git a/tests/testthat/test-scales.R b/tests/testthat/test-scales.R index 0ba2989e39..c44910011e 100644 --- a/tests/testthat/test-scales.R +++ b/tests/testthat/test-scales.R @@ -115,10 +115,12 @@ test_that("oob affects position values", { mid_censor <- cdata(base + y_scale(c(3, 7), censor)) handle <- GeomBar$handle_na - expect_warning(low_censor[[1]] <- handle(low_censor[[1]], list(na.rm = FALSE)), - "Removed 1 row containing missing values or values outside the scale range") - expect_warning(mid_censor[[1]] <- handle(mid_censor[[1]], list(na.rm = FALSE)), - "Removed 3 rows containing missing values or values outside the scale range") + expect_snapshot_warning( + low_censor[[1]] <- handle(low_censor[[1]], list(na.rm = FALSE)), + ) + expect_snapshot_warning( + mid_censor[[1]] <- handle(mid_censor[[1]], list(na.rm = FALSE)), + ) low_squish <- cdata(base + y_scale(c(0, 5), squish)) mid_squish <- cdata(base + y_scale(c(3, 7), squish)) @@ -201,7 +203,7 @@ test_that("scales warn when transforms introduces non-finite values", { geom_point(size = 5) + scale_y_log10() - expect_warning(ggplot_build(p), "log-10 transformation introduced infinite values.") + expect_snapshot_warning(ggplot_build(p)) }) test_that("size and alpha scales throw appropriate warnings for factors", { @@ -214,21 +216,18 @@ test_that("size and alpha scales throw appropriate warnings for factors", { p <- ggplot(df, aes(x, y)) # There should be warnings when unordered factors are mapped to size/alpha - expect_warning( - ggplot_build(p + geom_point(aes(size = d))), - "Using size for a discrete variable is not advised." + expect_snapshot_warning( + ggplot_build(p + geom_point(aes(size = d))) ) - expect_warning( - ggplot_build(p + geom_point(aes(alpha = d))), - "Using alpha for a discrete variable is not advised." + expect_snapshot_warning( + ggplot_build(p + geom_point(aes(alpha = d))) ) - expect_warning( - ggplot_build(p + geom_line(aes(linewidth = d, group = 1))), - "Using linewidth for a discrete variable is not advised." + expect_snapshot_warning( + ggplot_build(p + geom_line(aes(linewidth = d, group = 1))) ) # There should be no warnings for ordered factors - expect_warning(ggplot_build(p + geom_point(aes(size = o))), NA) - expect_warning(ggplot_build(p + geom_point(aes(alpha = o))), NA) + expect_no_warning(ggplot_build(p + geom_point(aes(size = o)))) + expect_no_warning(ggplot_build(p + geom_point(aes(alpha = o)))) }) test_that("shape scale throws appropriate warnings for factors", { @@ -241,12 +240,11 @@ test_that("shape scale throws appropriate warnings for factors", { p <- ggplot(df, aes(x, y)) # There should be no warnings when unordered factors are mapped to shape - expect_warning(ggplot_build(p + geom_point(aes(shape = d))), NA) + expect_no_warning(ggplot_build(p + geom_point(aes(shape = d)))) # There should be warnings for ordered factors - expect_warning( - ggplot_build(p + geom_point(aes(shape = o))), - "Using shapes for an ordinal variable is not advised" + expect_snapshot_warning( + ggplot_build(p + geom_point(aes(shape = o))) ) }) diff --git a/tests/testthat/test-stat-bin.R b/tests/testthat/test-stat-bin.R index 9b55054604..5baedf9223 100644 --- a/tests/testthat/test-stat-bin.R +++ b/tests/testthat/test-stat-bin.R @@ -194,7 +194,7 @@ test_that("setting boundary and center", { df <- data_frame(x = c(0, 30)) # Error if both boundary and center are specified - expect_error(comp_bin(df, boundary = 5, center = 0), "one of `boundary` and `center`") + expect_snapshot(comp_bin(df, boundary = 5, center = 0), error = TRUE) res <- comp_bin(df, binwidth = 10, boundary = 0, pad = FALSE) expect_identical(res$count, c(1, 0, 1)) @@ -216,7 +216,7 @@ test_that("weights are added", { }) test_that("bin errors at high bin counts", { - expect_error(bin_breaks_width(c(1, 2e6), 1), "The number of histogram bins") + expect_snapshot(bin_breaks_width(c(1, 2e6), 1), error = TRUE) }) # stat_count -------------------------------------------------------------- diff --git a/tests/testthat/test-stat-contour.R b/tests/testthat/test-stat-contour.R index bab39b7b6d..b603d5072f 100644 --- a/tests/testthat/test-stat-contour.R +++ b/tests/testthat/test-stat-contour.R @@ -3,7 +3,7 @@ test_that("a warning is issued when there is more than one z per x+y", { p <- ggplot(tbl, aes(x, y, z = z)) + geom_contour() # Ignore other warnings than the one stat_contour() issued suppressWarnings( - expect_warning(ggplot_build(p), "Zero contours were generated") + expect_snapshot_warning(ggplot_build(p)) ) }) @@ -14,7 +14,7 @@ test_that("contouring sparse data results in a warning", { # TODO: These multiple warnings should be summarized nicely. Until this gets # fixed, this test ignores all the following errors than the first one. suppressWarnings( - expect_warning(ggplot_build(p), "Zero contours were generated") + expect_snapshot_warning(ggplot_build(p)) ) }) @@ -93,9 +93,8 @@ test_that("stat_contour() removes duplicated coordinates", { layer <- stat_contour() expect_silent(layer$stat$setup_data(df)) - expect_warning( - new <- layer$stat$setup_data(transform(df, group = 1)), - "has duplicated" + expect_snapshot_warning( + new <- layer$stat$setup_data(transform(df, group = 1)) ) expect_equal(new, df[1:4,], ignore_attr = TRUE) }) diff --git a/tests/testthat/test-stat-density.R b/tests/testthat/test-stat-density.R index 0894fc2944..62104feb7a 100644 --- a/tests/testthat/test-stat-density.R +++ b/tests/testthat/test-stat-density.R @@ -80,12 +80,11 @@ test_that("stat_density handles data outside of `bounds`", { cutoff <- mtcars$mpg[1] # Both `x` and `weight` should be filtered out for out of `bounds` points - expect_warning( + expect_snapshot_warning( data_actual <- get_layer_data( ggplot(mtcars, aes(mpg, weight = cyl)) + stat_density(bounds = c(cutoff, Inf)) - ), - "outside of `bounds`" + ) ) mtcars_filtered <- mtcars[mtcars$mpg >= cutoff, ] @@ -120,7 +119,7 @@ test_that("stat_density works in both directions", { }) test_that("compute_density returns useful df and throws warning when <2 values", { - expect_warning(dens <- compute_density(1, NULL, from = 0, to = 0)) + expect_snapshot_warning(dens <- compute_density(1, NULL, from = 0, to = 0)) expect_equal(nrow(dens), 1) expect_named(dens, c("x", "density", "scaled", "ndensity", "count", "wdensity", "n")) diff --git a/tests/testthat/test-stat-ecdf.R b/tests/testthat/test-stat-ecdf.R index ce839bb3c4..1ea0a69a56 100644 --- a/tests/testthat/test-stat-ecdf.R +++ b/tests/testthat/test-stat-ecdf.R @@ -45,22 +45,13 @@ test_that("weighted ecdf computes sensible results", { test_that("weighted ecdf warns about weird weights", { # Should warn when provided with illegal weights - expect_warning( - wecdf(1:10, c(NA, rep(1, 9))), - "does not support non-finite" - ) + expect_snapshot_warning(wecdf(1:10, c(NA, rep(1, 9)))) # Should warn when provided with near-0 weights - expect_warning( - wecdf(1:10, .Machine$double.eps), - "might be unstable" - ) + expect_snapshot_warning(wecdf(1:10, .Machine$double.eps)) # Should error when weights sum to 0 - expect_error( - wecdf(1:10, rep(c(-1, 1), 5)), - "Cannot compute eCDF" - ) + expect_snapshot(wecdf(1:10, rep(c(-1, 1), 5)), error = TRUE) }) # See #5113 and #5112 diff --git a/tests/testthat/test-stat-function.R b/tests/testthat/test-stat-function.R index 483578d97f..f9073086df 100644 --- a/tests/testthat/test-stat-function.R +++ b/tests/testthat/test-stat-function.R @@ -134,7 +134,7 @@ test_that("Warn when drawing multiple copies of the same function", { df <- data_frame(x = 1:3, y = letters[1:3]) p <- ggplot(df, aes(x, color = y)) + stat_function(fun = identity) f <- function() {pdf(NULL); print(p); dev.off()} - expect_warning(f(), "Multiple drawing groups") + expect_snapshot_warning(f()) }) test_that("Line style can be changed via provided data", { diff --git a/tests/testthat/test-stat-ydensity.R b/tests/testthat/test-stat-ydensity.R index 3b70fc7673..d43fbcc0e3 100644 --- a/tests/testthat/test-stat-ydensity.R +++ b/tests/testthat/test-stat-ydensity.R @@ -13,15 +13,13 @@ test_that("`drop = FALSE` preserves groups with 1 observations", { p <- ggplot(df, mapping = aes(x, y, fill = g)) - expect_warning( - ld <- get_layer_data(p + geom_violin(drop = TRUE)), - "Groups with fewer than two datapoints have been dropped" + expect_snapshot_warning( + ld <- get_layer_data(p + geom_violin(drop = TRUE)) ) expect_length(unique(ld$x), 3) - expect_warning( - ld <- get_layer_data(p + geom_violin(drop = FALSE)), - "Cannot compute density for groups with fewer than two datapoints" + expect_snapshot_warning( + ld <- get_layer_data(p + geom_violin(drop = FALSE)) ) expect_length(unique(ld$x), 4) }) diff --git a/tests/testthat/test-stats.R b/tests/testthat/test-stats.R index 25c30e2fb1..8545b485fd 100644 --- a/tests/testthat/test-stats.R +++ b/tests/testthat/test-stats.R @@ -7,13 +7,13 @@ test_that("plot succeeds even if some computation fails", { p2 <- p1 + stat_summary(fun = function(x) stop("Failed computation")) - expect_warning(b2 <- ggplot_build(p2), "Computation failed") + expect_snapshot_warning(b2 <- ggplot_build(p2)) expect_length(b2$data, 2) }) test_that("error message is thrown when aesthetics are missing", { p <- ggplot(mtcars) + stat_sum() - expect_error(ggplot_build(p), "x and y\\.$") + expect_snapshot(ggplot_build(p), error = TRUE) }) test_that("erroneously dropped aesthetics are found and issue a warning", { @@ -29,7 +29,7 @@ test_that("erroneously dropped aesthetics are found and issue a warning", { g = rep(1:2, each = 5) ) p1 <- ggplot(df1, aes(x, fill = g)) + geom_density() - expect_warning(ggplot_build(p1), "aesthetics were dropped") + expect_snapshot_warning(ggplot_build(p1)) # case 2-1) dropped partially @@ -59,10 +59,7 @@ test_that("erroneously dropped aesthetics are found and issue a warning", { p3 <- ggplot(df3, aes(id, colour = colour, fill = fill)) + geom_bar() + scale_fill_continuous(na.value = "#123") - expect_warning( - b3 <- ggplot_build(p3), - "The following aesthetics were dropped during statistical transformation: .*colour.*" - ) + expect_snapshot_warning(b3 <- ggplot_build(p3)) # colour is dropped because group a's colour is not constant (GeomBar$default_aes$colour is NA) expect_true(all(is.na(b3$data[[1]]$colour))) diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R index 36ad577c65..cf98a1bb3f 100644 --- a/tests/testthat/test-theme.R +++ b/tests/testthat/test-theme.R @@ -49,7 +49,7 @@ test_that("modifying theme element properties with + operator works", { t <- theme_grey() + theme() expect_identical(t, theme_grey()) - expect_error(theme_grey() + "asdf") + expect_snapshot(theme_grey() + "asdf", error = TRUE) }) test_that("adding theme object to ggplot object with + operator works", { @@ -115,7 +115,7 @@ test_that("replacing theme elements with %+replace% operator works", { t <- theme_grey() %+replace% theme() expect_identical(t, theme_grey()) - expect_error(theme_grey() + "asdf") + expect_snapshot(theme_grey() + "asdf", error = TRUE) }) test_that("calculating theme element inheritance works", { @@ -368,10 +368,7 @@ test_that("elements can be merged", { merge_element(element_line(colour = "blue"), line_base), element_line(colour = "blue", linewidth = 10) ) - expect_error( - merge_element(text_base, rect_base), - "Only elements of the same class can be merged" - ) + expect_snapshot(merge_element(text_base, rect_base), error = TRUE) }) test_that("theme elements that don't inherit from element can be combined", { diff --git a/tests/testthat/test-utilities-checks.R b/tests/testthat/test-utilities-checks.R index 0619ccc707..6d27c4e85b 100644 --- a/tests/testthat/test-utilities-checks.R +++ b/tests/testthat/test-utilities-checks.R @@ -8,14 +8,14 @@ test_that("check_device checks R versions correctly", { # R 4.0.0 doesn't support any new features with_mocked_bindings( getRversion = function() package_version("4.0.0"), - expect_warning(check_device("gradients"), "R 4.0.0 does not support"), + expect_snapshot_warning(check_device("gradients")), .package = "base" ) # R 4.1.0 doesn't support vectorised patterns with_mocked_bindings( getRversion = function() package_version("4.1.0"), - expect_warning(check_device("gradients"), "R 4.1.0 does not support"), + expect_snapshot_warning(check_device("gradients")), .package = "base" ) @@ -29,7 +29,7 @@ test_that("check_device checks R versions correctly", { # Glyphs are only supported in R 4.3.0 onwards with_mocked_bindings( getRversion = function() package_version("4.2.0"), - expect_warning(check_device("glyphs"), "R 4.2.0 does not support"), + expect_snapshot_warning(check_device("glyphs")), .package = "base" ) @@ -56,13 +56,13 @@ test_that("check_device finds device capabilities", { with_mocked_bindings( dev.capabilities = function() list(clippingPaths = FALSE), - expect_warning(check_device("clippingPaths"), "does not support"), + expect_snapshot_warning(check_device("clippingPaths")), .package = "grDevices" ) with_mocked_bindings( dev.cur = function() c(foobar = 1), - expect_warning(check_device(".test_feature"), "Unable to check"), + expect_snapshot_warning(check_device(".test_feature")), .package = "grDevices" ) @@ -77,7 +77,7 @@ test_that("check_device finds ragg capabilities", { ragg::agg_tiff(tmp) expect_true(check_device("gradients")) - expect_warning(check_device("compositing"), "does not support") + expect_snapshot_warning(check_device("compositing")) dev.off() }) @@ -92,7 +92,7 @@ test_that("check_device finds svglite capabilities", { svglite::svglite(tmp) expect_true(check_device("gradients")) - expect_warning(check_device("compositing"), "does not support") + expect_snapshot_warning(check_device("compositing")) dev.off() }) diff --git a/tests/testthat/test-utilities.R b/tests/testthat/test-utilities.R index a602eb22c7..107e22e063 100644 --- a/tests/testthat/test-utilities.R +++ b/tests/testthat/test-utilities.R @@ -106,9 +106,7 @@ test_that("remove_missing checks input", { test_that("characters survive remove_missing", { data <- data_frame0(x = c("A", NA)) - expect_warning( - new <- remove_missing(data, finite = TRUE) - ) + expect_snapshot_warning(new <- remove_missing(data, finite = TRUE)) expect_equal(new, data_frame0(x = "A")) }) @@ -140,20 +138,17 @@ test_that("vec_rbind0 can combined ordered factors", { # However, it was technically challenging to reduce the numbers of warnings # See #5139 for more details - expect_warning( - expect_warning( - expect_warning( + lifecycle::expect_deprecated( + lifecycle::expect_deprecated( + lifecycle::expect_deprecated( { test <- vec_rbind0( data_frame0(a = factor(c("A", "B"), ordered = TRUE)), data_frame0(a = factor(c("B", "C"), ordered = TRUE)) ) - }, - " and ", class = "lifecycle_warning_deprecated" - ), - " and ", class = "lifecycle_warning_deprecated" - ), - " and ", class = "lifecycle_warning_deprecated" + } + ) + ) ) # Should be not , hence the 'exact' diff --git a/vignettes/articles/faq-annotation.Rmd b/vignettes/articles/faq-annotation.Rmd index b92e93e9e1..a5a5c61f3c 100644 --- a/vignettes/articles/faq-annotation.Rmd +++ b/vignettes/articles/faq-annotation.Rmd @@ -13,7 +13,8 @@ title: "FAQ: Annotation" } ``` -```{r, include = FALSE} +```{r} +#| include: false library(ggplot2) library(dplyr) knitr::opts_chunk$set( diff --git a/vignettes/articles/faq-axes.Rmd b/vignettes/articles/faq-axes.Rmd index a6996dbe36..f37195a84f 100644 --- a/vignettes/articles/faq-axes.Rmd +++ b/vignettes/articles/faq-axes.Rmd @@ -13,7 +13,8 @@ title: "FAQ: Axes" } ``` -```{r, include = FALSE} +```{r} +#| include: false library(ggplot2) knitr::opts_chunk$set( fig.dpi = 300, @@ -36,7 +37,8 @@ Set the angle of the text in the `axis.text.x` or `axis.text.y` components of th In the following plot the labels on the x-axis are overlapping. -```{r msleep-order-sleep-total} +```{r} +#| label: msleep-order-sleep-total #| fig.alt: "A boxplot showing the total amount of sleep on the y-axis for 19 #| taxonomical orders of mammals on the x-axis. The horizontal labels on the #| x-axis for the orders overlap and are unreadable." @@ -46,7 +48,8 @@ ggplot(msleep, aes(x = order, y = sleep_total)) + - Rotate axis labels: We can do this by components of the `theme()`, specifically the `axis.text.x` component. Applying some vertical and horizontal justification to the labels centers them at the axis ticks. The `angle` can be set as desired within the 0 to 360 degree range, here we set it to 90 degrees. -```{r msleep-order-sleep-total-rotate} +```{r} +#| label: msleep-order-sleep-total-rotate #| fig.alt: "A boxplot showing the total amount of sleep on the y-axis for 19 #| taxonomical orders of mammals on the x-axis. The x-axis labels are oriented #| vertically and are readable." @@ -57,7 +60,8 @@ ggplot(msleep, aes(x = order, y = sleep_total)) + - Flip the axes: Use the y-axis for long labels. -```{r msleep-order-sleep-total-flip} +```{r} +#| label: msleep-order-sleep-total-flip #| fig.alt: "A boxplot showing the total amount of sleep on the x-axis for 19 #| taxonomical orders of mammals on the y-axis. The y-axis labels are oriented #| horizontally and are readable." @@ -67,7 +71,8 @@ ggplot(msleep, aes(y = order, x = sleep_total)) + - Dodge axis labels: Add a `scale_*()` layer, e.g. `scale_x_continuous()`, `scale_y_discrete()`, etc., and customise the `guide` argument with the `guide_axis()` function. In this case we want to customise the x-axis, and the variable on the x-axis is discrete, so we'll use `scale_x_continuous()`. In the `guide` argument we use the `guide_axis()` and specify how many rows to dodge the labels into with `n.dodge`. This is likely a trial-and-error exercise, depending on the lengths of your labels and the width of your plot. In this case we've settled on 3 rows to render the labels. -```{r msleep-order-sleep-total-dodge} +```{r} +#| label: msleep-order-sleep-total-dodge #| fig.alt: "A boxplot showing the total amount of sleep on the y-axis for 19 #| taxonomical orders of mammals on the x-axis. The horizontal labels on the #| x-axis are dodged to three levels so that they remain readable." @@ -78,7 +83,8 @@ ggplot(msleep, aes(x = order, y = sleep_total)) + - Omit overlapping labels: Alternatively, you can set `guide_axis(check.overlap = TRUE)` to omit axis labels that overlap. ggplot2 will prioritize the first, last, and middle labels. Note that this option might be more preferable for axes representing variables that have an inherent ordering that is obvious to the audience of the plot, so that it's trivial to guess what the missing labels are. (This is not the case for the following plot.) -```{r msleep-order-sleep-total-check-overlap} +```{r} +#| label: msleep-order-sleep-total-check-overlap #| fig.alt: "A boxplot showing the total amount of sleep on the y-axis for 19 #| taxonomical orders of mammals on the x-axis. Several of the x-axis labels #| have been omitted, but the one that remain are readable and don't overlap." @@ -99,7 +105,8 @@ Add a `theme()` layer and set relevant arguments, e.g. `axis.title.x`, `axis.tex Suppose we want to remove the axis labels entirely. -```{r ref.label="msleep-order-sleep-total"} +```{r} +#| ref-label: msleep-order-sleep-total #| fig.alt: "A boxplot showing the total amount of sleep on the y-axis for 19 #| taxonomical orders of mammals on the x-axis. The horizontal labels on the #| x-axis for the orders overlap and are unreadable." diff --git a/vignettes/articles/faq-bars.Rmd b/vignettes/articles/faq-bars.Rmd index 3cbacf4d79..daae53ef58 100644 --- a/vignettes/articles/faq-bars.Rmd +++ b/vignettes/articles/faq-bars.Rmd @@ -13,7 +13,8 @@ title: "FAQ: Barplots" } ``` -```{r, include = FALSE} +```{r} +#| include: false library(ggplot2) library(dplyr) library(tidyr) diff --git a/vignettes/articles/faq-customising.Rmd b/vignettes/articles/faq-customising.Rmd index 0112d64627..9b008af042 100644 --- a/vignettes/articles/faq-customising.Rmd +++ b/vignettes/articles/faq-customising.Rmd @@ -13,7 +13,8 @@ title: "FAQ: Customising" } ``` -```{r, include = FALSE} +```{r} +#| include: false library(ggplot2) library(tibble) knitr::opts_chunk$set( @@ -370,7 +371,8 @@ ggplot(mpg, aes(x = hwy, y = cty, color = class)) + If you would like all plots within a session/document to use a particular base size, you can set it with `set_theme()`. Run the following at the beginning of your session or include on top of your R Markdown document. -```{r eval = FALSE} +```{r} +#| eval: false set_theme(theme_gray(base_size = 18)) ``` diff --git a/vignettes/articles/faq-faceting.Rmd b/vignettes/articles/faq-faceting.Rmd index bb7112edbb..d5a6926d83 100644 --- a/vignettes/articles/faq-faceting.Rmd +++ b/vignettes/articles/faq-faceting.Rmd @@ -13,7 +13,8 @@ title: "FAQ: Faceting" } ``` -```{r, include = FALSE} +```{r} +#| include: false library(ggplot2) knitr::opts_chunk$set( fig.dpi = 300, @@ -77,7 +78,8 @@ In `facet_grid()` these values are determined by the number of levels of the var Similarly, you can also use `facet_grid()` to facet by a single categorical variable as well. In the formula notation, you use a `.` to indicate that no faceting should be done along that axis, i.e. `cyl ~ .` facets across the y-axis (within a column) while `. ~ cyl` facets across the x-axis (within a row). -```{r out.width = "50%"} +```{r} +#| out-width: 50% #| fig.alt: #| - "A histogram showing the city miles per gallon distribution. The plot has #| four panels in a 4-row, 1-column layout, showing four numbers of cylinders." @@ -303,7 +305,8 @@ df You can plot `price` versus `time` and facet by `country`, but the resulting plot can be a bit difficult to read due to the shared y-axis label. -```{r warning = FALSE} +```{r} +#| warning: false #| fig.alt: "A timeseries plot showing price over time for two countries, Japan #| and the US, in two panels in a 2-row, 1-column layout. The countries are #| indicated at the top of each panel. The two y-axes have different ranges." diff --git a/vignettes/articles/faq-reordering.Rmd b/vignettes/articles/faq-reordering.Rmd index d820c7a50e..3bbc180d6f 100644 --- a/vignettes/articles/faq-reordering.Rmd +++ b/vignettes/articles/faq-reordering.Rmd @@ -13,7 +13,8 @@ title: "FAQ: Reordering" } ``` -```{r, include = FALSE} +```{r} +#| include: false library(ggplot2) library(dplyr) library(tibble) diff --git a/vignettes/extending-ggplot2.Rmd b/vignettes/extending-ggplot2.Rmd index adac6896ea..c10fbcf4c2 100644 --- a/vignettes/extending-ggplot2.Rmd +++ b/vignettes/extending-ggplot2.Rmd @@ -9,7 +9,8 @@ vignette: > %\VignetteEncoding{UTF-8} --- -```{r, include = FALSE} +```{r} +#| include: false knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 7, fig.align = "center") library(ggplot2) ``` @@ -28,7 +29,8 @@ It's strange to say, but this is a case where inventing a new OO system was actu Here's a quick demo of ggproto in action: -```{r ggproto-intro} +```{r} +#| label: ggproto-intro A <- ggproto("A", NULL, x = 1, inc = function(self) { @@ -53,7 +55,8 @@ To create a new geom or stat, you will just create a new ggproto that inherits f We'll start by creating a very simple stat: one that gives the convex hull (the _c_ hull) of a set of points. First we create a new ggproto object that inherits from `Stat`: -```{r chull} +```{r} +#| label: chull StatChull <- ggproto("StatChull", Stat, compute_group = function(data, scales) { data[chull(data$x, data$y), , drop = FALSE] @@ -374,7 +377,8 @@ It's harder to create a new geom than a new stat because you also need to know s It's easiest to start with a simple example. The code below is a simplified version of `geom_point()`: -```{r GeomSimplePoint} +```{r} +#| label: GeomSimplePoint #| fig.alt: "Scatterplot of engine displacement versus highway miles per #| gallon, for 234 cars. The points are larger than the default." GeomSimplePoint <- ggproto("GeomSimplePoint", Geom, @@ -625,7 +629,8 @@ title | `element_text()` | all text in title elements (plot, axes & lege These set default properties that are inherited by more specific settings. These are most useful for setting an overall "background" colour and overall font settings (e.g. family and size). -```{r axis-line-ex} +```{r} +#| label: axis-line-ex #| fig.alt: #| - "Scatterplot of three observations arranged diagonally. The axis titles 'x' #| and 'y' are coloured in black" @@ -1150,7 +1155,8 @@ guide_key <- function( Our new guide can now be used inside the `guides()` function or as the `guide` argument in a position scale. -```{r key_example} +```{r} +#| label: key_example #| fig.alt: > #| Scatterplot of engine displacement versus highway miles per #| gallon. The x-axis axis ticks are at 2.5, 3.5, 4.5, 5.5 and 6.5. @@ -1174,7 +1180,8 @@ We'll edit the method so that the labels are drawn with a `colour` set in the ke In addition to the `key` and `params` variable we've seen before, we now also have an `elements` variable, which is a list of precomputed theme elements. We can use the `elements$text` element to draw a graphical object (grob) in the style of axis text. Perhaps the most finicky thing about drawing guides is that a lot of settings depend on the guide's `position` parameter. -```{r key_ggproto_edit} +```{r} +#| label: key_ggproto_edit # Same as before GuideKey <- ggproto( "Guide", GuideAxis, @@ -1205,7 +1212,8 @@ GuideKey <- ggproto( Because we are incorporating the `...` argument to `guide_key()` in the key, adding a `colour` column to the key is straightforward. We can check that are guide looks correct in the different positions around the panel. -```{r key_example_2} +```{r} +#| label: key_example_2 #| fig.alt: > #| Scatterplot of engine displacement versus highway miles per #| gallon. There are two x-axes at the bottom and top of the plot. The bottom diff --git a/vignettes/ggplot2-in-packages.Rmd b/vignettes/ggplot2-in-packages.Rmd index 691dac0298..06c6a30eec 100644 --- a/vignettes/ggplot2-in-packages.Rmd +++ b/vignettes/ggplot2-in-packages.Rmd @@ -9,7 +9,8 @@ vignette: > %\VignetteEncoding{UTF-8} --- -```{r, include = FALSE} +```{r} +#| include: false knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.show = "hide") library(ggplot2) ``` @@ -28,7 +29,8 @@ mpg_drv_summary <- function() { } ``` -```{r, include=FALSE} +```{r} +#| include: false # make sure this function runs! mpg_drv_summary() ``` @@ -44,7 +46,8 @@ mpg_drv_summary <- function() { } ``` -```{r, include=FALSE} +```{r} +#| include: false # make sure this function runs! mpg_drv_summary() ``` @@ -100,7 +103,8 @@ col_summary(mpg, "drv", "year") If the column name or expression is supplied by the user, you can also pass it to `aes()` or `vars()` using `{{ col }}`. This tidy eval operator captures the expression supplied by the user and forwards it to another tidy eval-enabled function such as `aes()` or `vars()`. -```{r, eval = (packageVersion("rlang") >= "0.3.4.9003")} +```{r} +#| eval: !expr (packageVersion("rlang") >= "0.3.4.9003") col_summary <- function(df, col, by) { ggplot(df) + geom_bar(aes(y = {{ col }})) + @@ -225,14 +229,16 @@ theme_custom <- function(...) { } ``` -```{r, include=FALSE} +```{r} +#| include: false # make sure this function runs! mpg_drv_summary() + theme_custom() ``` Generally, if you add a method for a ggplot2 generic like `autoplot()`, ggplot2 should be in `Imports`. If for some reason you would like to keep ggplot2 in `Suggests`, it is possible to register your generics only if ggplot2 is installed using `vctrs::s3_register()`. If you do this, you should copy and paste the source of `vctrs::s3_register()` into your own package to avoid adding a [vctrs](https://vctrs.r-lib.org/) dependency. -```{r, eval=FALSE} +```{r} +#| eval: false .onLoad <- function(...) { if (requireNamespace("ggplot2", quietly = TRUE)) { vctrs::s3_register("ggplot2::autoplot", "discrete_distr") diff --git a/vignettes/ggplot2-specs.Rmd b/vignettes/ggplot2-specs.Rmd index 7829f05f6a..79c6f29d01 100644 --- a/vignettes/ggplot2-specs.Rmd +++ b/vignettes/ggplot2-specs.Rmd @@ -9,7 +9,8 @@ vignette: > %\VignetteEncoding{UTF-8} --- -```{r, include = FALSE} +```{r} +#| include: false library(ggplot2) knitr::opts_chunk$set(fig.dpi = 96, collapse = TRUE, comment = "#>") ``` @@ -106,7 +107,9 @@ with this mistake. * The appearance of the line end is controlled by the `lineend` paramter, and can be one of "round", "butt" (the default), or "square". - ```{r, out.width = "30%", fig.show = "hold"} + ```{r} + #| out-width: 30% + #| fig-show: hold #| fig.alt: #| - "A plot showing a line with an angle. A thinner red line is placed over #| a thicker black line. The black line ends where the red line ends." @@ -134,7 +137,9 @@ with this mistake. * The appearance of line joins is controlled by `linejoin` and can be one of "round" (the default), "mitre", or "bevel". - ```{r, out.width = "30%", fig.show = "hold"} + ```{r} + #| out-width: 30% + #| fig-show: hold #| fig.alt: #| - "A plot showing a thin red line on top of a thick black line shaped like #| the letter 'V'. The corner in the black V-shape is rounded." @@ -192,7 +197,10 @@ Shapes take five types of values: * The __name__ of the shape: - ```{r out.width = "90%", fig.asp = 0.4, fig.width = 8} + ```{r} + #| out-width: 90% + #| fig-asp: 0.4 + #| fig-width: 8 #| fig.alt: "An irregular 6-by-7 grid of point symbols annotated by the #| names that can be used to represent the symbols. Broadly, from top to #| bottom, the symbols are circles, squares, diamonds, triangles and diff --git a/vignettes/ggplot2.Rmd b/vignettes/ggplot2.Rmd index 36193b65a4..988bbd8310 100644 --- a/vignettes/ggplot2.Rmd +++ b/vignettes/ggplot2.Rmd @@ -9,7 +9,8 @@ vignette: > %\VignetteEncoding{UTF-8} --- -```{r, include = FALSE} +```{r} +#| include: false knitr::opts_chunk$set( collapse = TRUE, comment = "#>" @@ -22,7 +23,9 @@ This allows you to 'speak' a graph from composable elements, instead of being li More complete information about how to use ggplot2 can be found in the [book](https://ggplot2-book.org/), but here you'll find a brief overview of the plot components and some terse examples to build a plot like this: -```{r cake, echo = FALSE} +```{r} +#| label: cake +#| echo: false #| fig.alt: "Scatterplot of city versus highway miles per gallon, for many cars #| coloured by engine displacement. The plot has six panels in a 2-row, #| 3-column layout, showing the combinations of three types of drive train and @@ -40,7 +43,9 @@ ggplot(mpg, aes(cty, hwy)) + For structure, we go over the 7 composable parts that come together as a set of instructions on how to draw a chart. -```{r overview_graphic, echo=FALSE} +```{r} +#| label: overview_graphic +#| echo: false #| fig.alt: "A schematic displaying seven overlaying rhombuses indicating the #| different composable parts. From bottom to top, the labels read 'Data', #| 'Mapping', 'Layers', 'Scales', 'Facets', 'Coordinates' and 'Theme'." @@ -81,7 +86,9 @@ The system works best if the data is provided in a [tidy](https://tidyr.tidyvers As the first step in many plots, you would pass the data to the `ggplot()` function, which stores the data to be used later by other parts of the plotting system. For example, if we intend to make a graphic about the `mpg` dataset, we would start as follows: -```{r example_data, fig.show='hide'} +```{r} +#| label: example_data +#| fig-show: hide ggplot(data = mpg) ``` @@ -92,7 +99,9 @@ The [mapping](https://ggplot2-book.org/getting-started.html#aesthetics) of a plo A mapping can be made by using the `aes()` function to make pairs of graphical attributes and parts of the data. If we want the `cty` and `hwy` columns to map to the x- and y-coordinates in the plot, we can do that as follows: -```{r example_mapping, fig.show='hide'} +```{r} +#| label: example_mapping +#| fig-show: hide ggplot(mpg, mapping = aes(x = cty, y = hwy)) ``` @@ -107,7 +116,9 @@ Every layer consists of three important parts: A layer can be constructed using the `geom_*()` and `stat_*()` functions. These functions often determine one of the three parts of a layer, while the other two can still be specified. Here is how we can use two layers to display the `cty` and `hwy` columns of the `mpg` dataset as points and stack a trend line on top. -```{r example_layer, fig.show='hold'} +```{r} +#| label: example_layer +#| fig-show: hold #| fig.alt: "A scatterplot showing city versus highway miles per gallon for #| many cars. The plot has a blue trendline with a positive slope." ggplot(mpg, aes(cty, hwy)) + @@ -124,7 +135,8 @@ Scales are responsible for updating the limits of a plot, setting the breaks, fo To use scales, one can use one of the scale functions that are patterned as `scale_{aesthetic}_{type}()` functions, where `{aesthetic}` is one of the pairings made in the mapping part of a plot. To map the `class` column in the `mpg` dataset to the viridis colour palette, we can write the following: -```{r example_scales} +```{r} +#| label: example_scales #| fig.alt: "A scatterplot showing city versus highway miles per gallon for #| many cars. The points are coloured according to seven classes of cars." ggplot(mpg, aes(cty, hwy, colour = class)) + @@ -140,7 +152,8 @@ It is a powerful tool to quickly split up the data into smaller panels, based on The facets have their own mapping that can be given as a formula. To plot subsets of the `mpg` dataset based on levels of the `drv` and `year` variables, we can use `facet_grid()` as follows: -```{r example_facets} +```{r} +#| label: example_facets #| fig.alt: "Scatterplot of city versus highway miles per gallon, for many cars. #| The plot has six panels in a 2-row, 3-column layout, showing the #| combinations of three types of drive train and year of manifacture." @@ -156,7 +169,8 @@ While typically Cartesian coordinates are used, the coordinate system powers the We can also use coordinates to display a plot with a fixed aspect ratio so that one unit has the same length in both the x and y directions. The `coord_fixed()` function sets this ratio automatically. -```{r example_coords} +```{r} +#| label: example_coords #| fig.alt: "A scatterplot showing city versus highway miles per gallon for #| many cars. The aspect ratio of the plot is such that units on the x-axis #| have the same length as units on the y-axis." @@ -171,7 +185,8 @@ The [theme](https://ggplot2-book.org/themes) system controls almost any visuals To tweak the look of the plot, one can use many of the built-in `theme_*()` functions and/or detail specific aspects with the `theme()` function. The `element_*()` functions control the graphical attributes of theme components. -```{r example_theme} +```{r} +#| label: example_theme #| fig.alt: "A scatterplot showing city versus highway miles per gallon for #| many cars. The points are coloured according to seven classes of cars. The #| legend of the colour is displayed on top of the plot. The plot has thick @@ -190,7 +205,8 @@ ggplot(mpg, aes(cty, hwy, colour = class)) + As mentioned at the start, you can layer all of the pieces to build a customized plot of your data, like the one shown at the beginning of this vignette: -```{r outro} +```{r} +#| label: outro #| fig.alt: "Scatterplot of city versus highway miles per gallon, for many cars #| coloured by engine displacement. The plot has six panels in a 2-row, #| 3-column layout, showing the combinations of three types of drive train and diff --git a/vignettes/profiling.Rmd b/vignettes/profiling.Rmd index a0a77340df..d08f8e5a26 100644 --- a/vignettes/profiling.Rmd +++ b/vignettes/profiling.Rmd @@ -10,7 +10,9 @@ vignette: > %\VignetteEncoding{UTF-8} --- -```{r setup, include = FALSE} +```{r} +#| label: setup +#| include: false knitr::opts_chunk$set( collapse = TRUE, comment = "#>" @@ -33,7 +35,9 @@ profile <- profvis(for (i in seq_len(100)) ggplotGrob(p)) profile ``` -```{r, eval=FALSE, include=FALSE} +```{r} +#| eval: false +#| include: false saveRDS(profile, file.path('profilings', paste0(packageVersion('ggplot2'), '.rds'))) ``` From 2959336f7eb8298f9c696975115a54aec50838a9 Mon Sep 17 00:00:00 2001 From: Jonathan Carroll Date: Wed, 30 Oct 2024 18:39:25 +1030 Subject: [PATCH 2/3] Typo in geom-map.R (#6163) * Typo in geom-map.R * document --- R/geom-map.R | 2 +- man/geom_map.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/geom-map.R b/R/geom-map.R index 7f4b860378..0632ba36ee 100644 --- a/R/geom-map.R +++ b/R/geom-map.R @@ -22,7 +22,7 @@ NULL #' # how `geom_map()` works. It requires two data frames: #' # One contains the coordinates of each polygon (`positions`), and is #' # provided via the `map` argument. The other contains the -#' # other the values associated with each polygon (`values`). An id +#' # values associated with each polygon (`values`). An id #' # variable links the two together. #' #' ids <- factor(c("1.1", "2.1", "1.2", "2.2", "1.3", "2.3")) diff --git a/man/geom_map.Rd b/man/geom_map.Rd index 58e83adad4..55c06b5b26 100644 --- a/man/geom_map.Rd +++ b/man/geom_map.Rd @@ -126,7 +126,7 @@ Learn more about setting these aesthetics in \code{vignette("ggplot2-specs")}. # how `geom_map()` works. It requires two data frames: # One contains the coordinates of each polygon (`positions`), and is # provided via the `map` argument. The other contains the -# other the values associated with each polygon (`values`). An id +# values associated with each polygon (`values`). An id # variable links the two together. ids <- factor(c("1.1", "2.1", "1.2", "2.2", "1.3", "2.3")) From 3faa53a27465c8770954bf9191f4edc20de9ea96 Mon Sep 17 00:00:00 2001 From: Elio Campitelli Date: Wed, 30 Oct 2024 19:11:06 +1100 Subject: [PATCH 3/3] Checks if svglite is installed when saving to svg. (#6168) Closes #6166 --- NEWS.md | 1 + R/save.R | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 540e662f0c..34750ee04c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -232,6 +232,7 @@ documentation updates. * `annotate()` now warns about `stat` or `position` arguments (@teunbrand, #5151) * `guide_coloursteps(even.steps = FALSE)` now works with discrete data that has been formatted by `cut()` (@teunbrand, #3877). +* `ggsave()` now offers to install svglite if needed (@eliocamp, #6166). # ggplot2 3.5.0 diff --git a/R/save.R b/R/save.R index ffe6945410..b06c567b2e 100644 --- a/R/save.R +++ b/R/save.R @@ -277,7 +277,10 @@ plot_dev <- function(device, filename = NULL, dpi = 300, call = caller_env()) { ps = eps, tex = function(filename, ...) grDevices::pictex(file = filename, ...), pdf = function(filename, ..., version = "1.4") grDevices::pdf(file = filename, ..., version = version), - svg = function(filename, ...) svglite::svglite(file = filename, ...), + svg = function(filename, ...) { + check_installed("svglite", reason = "to save as SVG.") + svglite::svglite(file = filename, ...) + }, # win.metafile() doesn't have `bg` arg so we need to absorb it before passing `...` emf = function(..., bg = NULL) grDevices::win.metafile(...), wmf = function(..., bg = NULL) grDevices::win.metafile(...),