diff --git a/.Rbuildignore b/.Rbuildignore
index 16ac7717f1..dbde8f70f3 100644
--- a/.Rbuildignore
+++ b/.Rbuildignore
@@ -26,3 +26,4 @@ visual_test
^vignettes/profiling.Rmd$
^cran-comments\.md$
^LICENSE\.md$
+^vignettes/articles$
diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml
index 9fd3500247..1f23e4080c 100644
--- a/.github/workflows/R-CMD-check.yaml
+++ b/.github/workflows/R-CMD-check.yaml
@@ -10,7 +10,7 @@ name: R-CMD-check
# Increment this version when we want to clear cache
env:
- cache-version: v4
+ cache-version: v6
jobs:
R-CMD-check:
@@ -24,12 +24,12 @@ jobs:
config:
- {os: windows-latest, r: '4.0', vdiffr: true, xref: true}
- {os: macOS-latest, r: '4.0', vdiffr: true, xref: true}
- - {os: ubuntu-16.04, r: 'devel', vdiffr: false, xref: true}
- - {os: ubuntu-16.04, r: '4.0', vdiffr: true, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
- - {os: ubuntu-16.04, r: '3.6', vdiffr: false, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
- - {os: ubuntu-16.04, r: '3.5', vdiffr: false, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
- - {os: ubuntu-16.04, r: '3.4', vdiffr: false, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
- - {os: ubuntu-16.04, r: '3.3', vdiffr: false, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
+ - {os: ubuntu-18.04, r: 'devel', vdiffr: false, xref: true}
+ - {os: ubuntu-18.04, r: '4.0', vdiffr: true, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"}
+ - {os: ubuntu-18.04, r: '3.6', vdiffr: false, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"}
+ - {os: ubuntu-18.04, r: '3.5', vdiffr: false, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"}
+ - {os: ubuntu-18.04, r: '3.4', vdiffr: false, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"}
+ - {os: ubuntu-18.04, r: '3.3', vdiffr: false, xref: true, rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"}
env:
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
@@ -72,13 +72,13 @@ jobs:
while read -r cmd
do
eval sudo $cmd
- done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "16.04"))')
+ done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "18.04"))')
- name: Install system dependencies on macOS
if: runner.os == 'macOS'
run: |
# XQuartz is needed by vdiffr
- brew cask install xquartz
+ brew install xquartz
# Use only binary packages
echo 'options(pkgType = "binary")' >> ~/.Rprofile
diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml
index 01d9b32a91..793f5ff67d 100644
--- a/.github/workflows/pkgdown.yaml
+++ b/.github/workflows/pkgdown.yaml
@@ -36,8 +36,8 @@ jobs:
- name: Install dependencies
run: |
remotes::install_deps(dependencies = TRUE, type = "binary")
- remotes::install_github("tidyverse/tidytemplate")
- remotes::install_dev("pkgdown")
+ remotes::install_github("tidyverse/tidytemplate", type = "binary")
+ remotes::install_dev("pkgdown", type = "binary")
shell: Rscript {0}
- name: Install package
diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml
index c1a0c04cb5..3ba34d271c 100644
--- a/.github/workflows/test-coverage.yaml
+++ b/.github/workflows/test-coverage.yaml
@@ -41,7 +41,7 @@ jobs:
- name: Install system dependencies on macOS
run: |
# XQuartz is needed by vdiffr
- brew cask install xquartz
+ brew install xquartz
- name: Install dependencies
run: |
diff --git a/DESCRIPTION b/DESCRIPTION
index f8eb4760de..20dc66d1c4 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -35,17 +35,19 @@ Imports:
isoband,
MASS,
mgcv,
- rlang (>= 0.3.0),
+ rlang (>= 0.4.10),
scales (>= 0.5.0),
stats,
tibble,
withr (>= 2.0.0)
Suggests:
covr,
+ ragg,
dplyr,
ggplot2movies,
hexbin,
Hmisc,
+ interp,
knitr,
lattice,
mapproj,
@@ -267,3 +269,7 @@ VignetteBuilder: knitr
RoxygenNote: 7.1.1
Roxygen: list(markdown = TRUE)
Encoding: UTF-8
+Config/Needs/website:
+ ggtext,
+ tidyr,
+ forcats
diff --git a/NEWS.md b/NEWS.md
index 95fdb0541d..f96283e156 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,43 @@
# ggplot2 (development version)
+
+* Make sure that default labels from default mappings doesn't overwrite default
+ labels from explicit mappings (@thomasp85, #2406)
+
+* `stat_count()` now computes width based on the full dataset instead of per
+ group (@thomasp85, #2047)
+
+* Fix bug in `labeller()` where parsing was turned off if `.multiline = FALSE`
+ (@thomasp85, #4084)
+
+* Fix a bug in `qplot()` when supplying `c(NA, NA)` as axis limits
+ (@thomasp85, #4027)
+
+* Fix bug in `geom_dotplot()` where dots would be positioned wrong with
+ `stackgroups = TRUE` (@thomasp85, #1745)
+
+* Make sure position_jitter creates the same jittering independent of whether it
+ is called by name or with constructor (@thomasp85, #2507)
+
+* Fix a bug in `position_dodge2()` where `NA` values in thee data would cause an
+ error (@thomasp85, #2905)
+
+* Fix a bug in `position_jitter()` where different jitters would be applied to
+ different position aesthetics of the same axis (@thomasp85, #2941)
+
+* `ggsave()` now uses ragg to render raster output if ragg is available
+ (@thomasp85, #4388)
+
+* `coord_sf()` now has an argument `default_crs` that specifies the coordinate
+ reference system (CRS) for non-sf layers and scale/coord limits. This argument
+ defaults to the World Geodetic System 1984 (WGS84), which means x and y positions
+ are interpreted as longitude and latitude. This is a potentially breaking change
+ for users who use projected coordinates in non-sf layers or in limits. Setting
+ `default_crs = NULL` recovers the old behavior. Further, authors of extension
+ packages implementing `stat_sf()`-like functionality are encouraged to look at the
+ source code of `stat_sf()`'s `compute_group()` function to see how to provide
+ scale-limit hints to `coord_sf()` (@clauswilke, #3659).
+
* `ggsave()` now sets the default background to match the fill value of the
`plot.background` theme element (@karawoo, #4057)
@@ -22,8 +60,23 @@
* `stat_bin()`'s computed variable `width` is now documented (#3522).
+* Fixed a bug in strip assembly when theme has `strip.text = element_blank()`
+ and plots are faceted with multi-layered strips (@teunbrand, #4384).
+
* ggplot2 now requires R >= 3.3 (#4247).
+* ggplot2 now uses `rlang::check_installed()` to check if a suggested package is
+ installed, which will offer to install the package before continuing (#4375,
+ @malcolmbarrett)
+
+* Improved error with hint when piping a `ggplot` object into a facet function
+ (#4379, @mitchelloharawild).
+
+* Fix a bug that `after_stat()` and `after_scale()` cannot refer to aesthetics
+ if it's specified in the plot-global mapping (@yutannihilation, #4260).
+
+* `ggsave()` now returns the saved file location invisibly (#3379, @eliocamp).
+
# ggplot2 3.3.3
This is a small patch release mainly intended to address changes in R and CRAN.
It further changes the licensing model of ggplot2 to an MIT license.
@@ -34,6 +87,9 @@ It further changes the licensing model of ggplot2 to an MIT license.
* Update tests to work with the new `all.equal()` defaults in R >4.0.3
+* Fixed a bug that `guide_bins()` mistakenly ignore `override.aes` argument
+ (@yutannihilation, #4085).
+
# ggplot2 3.3.2
This is a small release focusing on fixing regressions introduced in 3.3.1.
@@ -42,16 +98,6 @@ This is a small release focusing on fixing regressions introduced in 3.3.1.
* `annotation_raster()` adds support for native rasters. For large rasters,
native rasters render significantly faster than arrays (@kent37, #3388)
-
-* `coord_sf()` now has an argument `default_crs` that specifies the coordinate
- reference system (CRS) for non-sf layers and scale/coord limits. This argument
- defaults to the World Geodetic System 1984 (WGS84), which means x and y positions
- are interpreted as longitude and latitude. This is a potentially breaking change
- for users who use projected coordinates in non-sf layers or in limits. Setting
- `default_crs = NULL` recovers the old behavior. Further, authors of extension
- packages implementing `stat_sf()`-like functionality are encouraged to look at the
- source code of `stat_sf()`'s `compute_group()` function to see how to provide
- scale-limit hints to `coord_sf()` (@clauswilke, #3659).
* Facet strips now have dedicated position-dependent theme elements
(`strip.text.x.top`, `strip.text.x.bottom`, `strip.text.y.left`,
diff --git a/R/coord-map.r b/R/coord-map.r
index 7c3ae2e706..4f55880f63 100644
--- a/R/coord-map.r
+++ b/R/coord-map.r
@@ -318,6 +318,7 @@ CoordMap <- ggproto("CoordMap", Coord,
mproject <- function(coord, x, y, orientation) {
+ check_installed("mapproj", reason = "for `coord_map()`")
suppressWarnings(mapproj::mapproject(x, y,
projection = coord$projection,
parameters = coord$params,
diff --git a/R/coord-sf.R b/R/coord-sf.R
index 3ab03d9769..0739977116 100644
--- a/R/coord-sf.R
+++ b/R/coord-sf.R
@@ -62,7 +62,9 @@ CoordSf <- ggproto("CoordSf", CoordCartesian,
return(layer_data)
}
- sf::st_transform(layer_data, params$crs)
+ idx <- vapply(layer_data, inherits, what = "sfc", FUN.VALUE = logical(1L))
+ layer_data[idx] <- lapply(layer_data[idx], sf::st_transform, crs = params$crs)
+ layer_data
})
},
diff --git a/R/facet-.r b/R/facet-.r
index a7b852ae11..75474b4d1e 100644
--- a/R/facet-.r
+++ b/R/facet-.r
@@ -276,9 +276,7 @@ df.grid <- function(a, b) {
# facetting variables.
as_facets_list <- function(x) {
- if (inherits(x, "uneval")) {
- abort("Please use `vars()` to supply facet variables")
- }
+ x <- validate_facets(x)
if (is_quosures(x)) {
x <- quos_auto_name(x)
return(list(x))
@@ -315,11 +313,24 @@ as_facets_list <- function(x) {
x
}
+validate_facets <- function(x) {
+ if (inherits(x, "uneval")) {
+ abort("Please use `vars()` to supply facet variables")
+ }
+ if (inherits(x, "ggplot")) {
+ abort(
+ "Please use `vars()` to supply facet variables\nDid you use %>% instead of +?"
+ )
+ }
+ x
+}
+
+
# Flatten a list of quosures objects to a quosures object, and compact it
compact_facets <- function(x) {
x <- flatten_if(x, is_list)
- null <- vapply(x, quo_is_null, logical(1))
- new_quosures(x[!null])
+ null_or_missing <- vapply(x, function(x) quo_is_null(x) || quo_is_missing(x), logical(1))
+ new_quosures(x[!null_or_missing])
}
# Compatibility with plyr::as.quoted()
diff --git a/R/facet-grid-.r b/R/facet-grid-.r
index 4bdb4e137a..0c976e3d35 100644
--- a/R/facet-grid-.r
+++ b/R/facet-grid-.r
@@ -157,7 +157,14 @@ grid_as_facets_list <- function(rows, cols) {
is_rows_vars <- is.null(rows) || is_quosures(rows)
if (!is_rows_vars) {
if (!is.null(cols)) {
- abort("`rows` must be `NULL` or a `vars()` list if `cols` is a `vars()` list")
+ msg <- "`rows` must be `NULL` or a `vars()` list if `cols` is a `vars()` list"
+ if(inherits(rows, "ggplot")) {
+ msg <- paste0(
+ msg, "\n",
+ "Did you use %>% instead of +?"
+ )
+ }
+ abort(msg)
}
# For backward-compatibility
facets_list <- as_facets_list(rows)
diff --git a/R/facet-wrap.r b/R/facet-wrap.r
index 05fbdd29a0..47bf1f471f 100644
--- a/R/facet-wrap.r
+++ b/R/facet-wrap.r
@@ -86,6 +86,13 @@ facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed",
x = any(scales %in% c("free_x", "free")),
y = any(scales %in% c("free_y", "free"))
)
+
+ # Check for deprecated labellers
+ labeller <- check_labeller(labeller)
+
+ # Flatten all facets dimensions into a single one
+ facets <- wrap_as_facets_list(facets)
+
if (!is.null(switch)) {
.Deprecated("strip.position", old = "switch")
strip.position <- if (switch == "x") "bottom" else "left"
@@ -102,12 +109,6 @@ facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed",
ncol <- sanitise_dim(ncol)
}
- # Check for deprecated labellers
- labeller <- check_labeller(labeller)
-
- # Flatten all facets dimensions into a single one
- facets <- wrap_as_facets_list(facets)
-
ggproto(NULL, FacetWrap,
shrink = shrink,
params = list(
diff --git a/R/fortify-map.r b/R/fortify-map.r
index acd5904fe8..81463f79e6 100644
--- a/R/fortify-map.r
+++ b/R/fortify-map.r
@@ -75,7 +75,7 @@ fortify.map <- function(model, data, ...) {
#' coord_map("albers", lat0 = 45.5, lat1 = 29.5)
#' }
map_data <- function(map, region = ".", exact = FALSE, ...) {
- try_require("maps", "map_data")
+ check_installed("maps", reason = "for `map_data()`")
map_obj <- maps::map(map, region, exact = exact, plot = FALSE, fill = TRUE, ...)
fortify(map_obj)
}
diff --git a/R/fortify.r b/R/fortify.r
index f74901eb1f..2b2b8267ac 100644
--- a/R/fortify.r
+++ b/R/fortify.r
@@ -17,9 +17,7 @@ fortify.data.frame <- function(model, data, ...) model
fortify.tbl_df <- function(model, data, ...) model
#' @export
fortify.tbl <- function(model, data, ...) {
- if (!requireNamespace("dplyr", quietly = TRUE)) {
- abort("dplyr must be installed to work with tbl objects")
- }
+ check_installed("dplyr", reason = "to work with `tbl` objects")
dplyr::collect(model)
}
#' @export
@@ -31,9 +29,7 @@ fortify.function <- function(model, data, ...) model
fortify.formula <- function(model, data, ...) as_function(model)
#' @export
fortify.grouped_df <- function(model, data, ...) {
- if (!requireNamespace("dplyr", quietly = TRUE)) {
- abort("dplyr must be installed to work with grouped_df objects")
- }
+ check_installed("dplyr", reason = "to work with `grouped_df` objects")
model$.group <- dplyr::group_indices(model)
model
}
diff --git a/R/geom-contour.r b/R/geom-contour.r
index 17ac1c1733..cc25a70da4 100644
--- a/R/geom-contour.r
+++ b/R/geom-contour.r
@@ -1,12 +1,16 @@
#' 2D contours of a 3D surface
#'
#' ggplot2 can not draw true 3D surfaces, but you can use `geom_contour()`,
-#' `geom_contour_filled()`, and [geom_tile()] to visualise 3D surfaces in 2D.
-#' To specify a valid surface, the data must contain `x`, `y`, and `z` coordinates,
-#' and each unique combination of `x` and `y` can appear exactly once. Contouring
-#' tends to work best when `x` and `y` form a (roughly) evenly
-#' spaced grid. If your data is not evenly spaced, you may want to interpolate
-#' to a grid before visualising, see [geom_density_2d()].
+#' `geom_contour_filled()`, and [geom_tile()] to visualise 3D surfaces in 2D. To
+#' specify a valid surface, the data must contain `x`, `y`, and `z` coordinates,
+#' and each unique combination of `x` and `y` can appear at most once.
+#' Contouring requires that the points can be rearranged so that the `z` values
+#' form a matrix, with rows corresponding to unique `x` values, and columns
+#' corresponding to unique `y` values. Missing entries are allowed, but contouring
+#' will only be done on cells of the grid with all four `z` values present. If
+#' your data is irregular, you can interpolate to a grid before visualising
+#' using the [interp::interp()] function from the `interp` package
+#' (or one of the interpolating functions from the `akima` package.)
#'
#' @eval rd_aesthetics("geom", "contour")
#' @eval rd_aesthetics("geom", "contour_filled")
@@ -15,9 +19,9 @@
#' @inheritParams geom_path
#' @param bins Number of contour bins. Overridden by `binwidth`.
#' @param binwidth The width of the contour bins. Overridden by `breaks`.
-#' @param breaks Numeric vector to set the contour breaks.
-#' Overrides `binwidth` and `bins`. By default, this is a vector of
-#' length ten with [pretty()] breaks.
+#' @param breaks Numeric vector to set the contour breaks. Overrides `binwidth`
+#' and `bins`. By default, this is a vector of length ten with [pretty()]
+#' breaks.
#' @seealso [geom_density_2d()]: 2d density contours
#' @export
#' @examples
@@ -47,6 +51,22 @@
#' v + geom_contour(colour = "red")
#' v + geom_raster(aes(fill = density)) +
#' geom_contour(colour = "white")
+#'
+#' # Irregular data
+#' if (requireNamespace("interp")) {
+#' # Use a dataset from the interp package
+#' data(franke, package = "interp")
+#' origdata <- as.data.frame(interp::franke.data(1, 1, franke))
+#' grid <- with(origdata, interp::interp(x, y, z))
+#' griddf <- subset(data.frame(x = rep(grid$x, nrow(grid$z)),
+#' y = rep(grid$y, each = ncol(grid$z)),
+#' z = as.numeric(grid$z)),
+#' !is.na(z))
+#' ggplot(griddf, aes(x, y, z = z)) +
+#' geom_contour_filled() +
+#' geom_point(data = origdata)
+#' } else
+#' message("Irregular data requires the 'interp' package")
#' }
geom_contour <- function(mapping = NULL, data = NULL,
stat = "contour", position = "identity",
diff --git a/R/geom-dotplot.r b/R/geom-dotplot.r
index 1c82f374c9..9b34e517f1 100644
--- a/R/geom-dotplot.r
+++ b/R/geom-dotplot.r
@@ -149,6 +149,7 @@ geom_dotplot <- function(mapping = NULL, data = NULL,
if (stackgroups && method == "dotdensity" && binpositions == "bygroup")
message('geom_dotplot called with stackgroups=TRUE and method="dotdensity". You probably want to set binpositions="all"')
+ stackdir <- arg_match0(stackdir, c("up", "down", "center", "centerwhole"), "stackdir")
layer(
data = data,
mapping = mapping,
@@ -210,23 +211,27 @@ GeomDotplot <- ggproto("GeomDotplot", Geom,
stackaxismax <- .5
}
-
# Fill the bins: at a given x (or y), if count=3, make 3 entries at that x
data <- data[rep(1:nrow(data), data$count), ]
# Next part will set the position of each dot within each stack
# If stackgroups=TRUE, split only on x (or y) and panel; if not stacking, also split by group
plyvars <- params$binaxis %||% "x"
+ stackaxis <- setdiff(c("x", "y"), plyvars)
plyvars <- c(plyvars, "PANEL")
if (is.null(params$stackgroups) || !params$stackgroups)
plyvars <- c(plyvars, "group")
+ if (stackaxis == "x") {
+ plyvars <- c(plyvars, "x")
+ }
+
# Within each x, or x+group, set countidx=1,2,3, and set stackpos according to stack function
data <- dapply(data, plyvars, function(xx) {
- xx$countidx <- 1:nrow(xx)
- xx$stackpos <- stackdots(xx$countidx)
- xx
- })
+ xx$countidx <- 1:nrow(xx)
+ xx$stackpos <- stackdots(xx$countidx)
+ xx
+ })
# Set the bounding boxes for the dots
diff --git a/R/geom-text.r b/R/geom-text.r
index ae49597a6a..8d6dcbf3eb 100644
--- a/R/geom-text.r
+++ b/R/geom-text.r
@@ -25,9 +25,9 @@
#'
#' @eval rd_aesthetics("geom", "text")
#' @section `geom_label()`:
-#' Currently `geom_label()` does not support the `angle` aesthetic and
-#' is considerably slower than `geom_text()`. The `fill` aesthetic
-#' controls the background colour of the label.
+#' Currently `geom_label()` does not support the `check_overlap` argument
+#' or the `angle` aesthetic. Also, it is considerably slower than `geom_text()`.
+#' The `fill` aesthetic controls the background colour of the label.
#'
#' @section Alignment:
#' You can modify text alignment with the `vjust` and `hjust`
@@ -50,7 +50,8 @@
#' @param check_overlap If `TRUE`, text that overlaps previous text in the
#' same layer will not be plotted. `check_overlap` happens at draw time and in
#' the order of the data. Therefore data should be arranged by the label
-#' column before calling `geom_label()` or `geom_text()`.
+#' column before calling `geom_text()`. Note that this argument is not
+#' supported by `geom_label()`.
#' @export
#' @examples
#' p <- ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars)))
diff --git a/R/guide-bins.R b/R/guide-bins.R
index 1ad9b6fab8..41f3b3b3fe 100644
--- a/R/guide-bins.R
+++ b/R/guide-bins.R
@@ -116,6 +116,7 @@ guide_bins <- function(
# general
direction = direction,
+ override.aes = rename_aes(override.aes),
default.unit = default.unit,
reverse = reverse,
order = order,
diff --git a/R/guides-axis.r b/R/guides-axis.r
index 9f6a5c94db..5f853e3282 100644
--- a/R/guides-axis.r
+++ b/R/guides-axis.r
@@ -280,7 +280,7 @@ draw_axis <- function(break_positions, break_labels, axis_position, theme,
non_position_sizes <- paste0(non_position_size, "s")
label_dims <- do.call(unit.c, lapply(label_grobs, measure_labels_non_pos))
grobs <- c(list(ticks_grob), label_grobs)
- grob_dims <- unit.c(tick_length, label_dims)
+ grob_dims <- unit.c(max(tick_length, unit(0, "pt")), label_dims)
if (labels_first_gtable) {
grobs <- rev(grobs)
diff --git a/R/labeller.r b/R/labeller.r
index 26d2263557..099a2d961d 100644
--- a/R/labeller.r
+++ b/R/labeller.r
@@ -88,8 +88,13 @@
NULL
collapse_labels_lines <- function(labels) {
+ is_exp <- vapply(labels, function(l) length(l) > 0 && is.expression(l[[1]]), logical(1))
out <- do.call("Map", c(list(paste, sep = ", "), labels))
- list(unname(unlist(out)))
+ label <- list(unname(unlist(out)))
+ if (all(is_exp)) {
+ label <- lapply(label, function(l) list(parse(text = paste0("list(", l, ")"))))
+ }
+ label
}
#' @rdname labellers
@@ -553,7 +558,11 @@ build_strip <- function(label_df, labeller, theme, horizontal) {
#'
#' @noRd
assemble_strips <- function(grobs, theme, horizontal = TRUE, clip) {
- if (length(grobs) == 0 || is.zero(grobs[[1]])) return(grobs)
+ if (length(grobs) == 0 || is.zero(grobs[[1]])) {
+ # Subsets matrix of zeroGrobs to correct length (#4050)
+ grobs <- grobs[seq_len(NROW(grobs))]
+ return(grobs)
+ }
# Add margins to non-titleGrobs so they behave eqivalently
grobs[] <- lapply(grobs, function(g) {
diff --git a/R/layer.r b/R/layer.r
index 0db859978a..44c75fc0df 100644
--- a/R/layer.r
+++ b/R/layer.r
@@ -203,16 +203,18 @@ Layer <- ggproto("Layer", NULL,
# hook to allow a layer access to the final layer data
# in input form and to global plot info
setup_layer = function(self, data, plot) {
+ # For annotation geoms, it is useful to be able to ignore the default aes
+ if (isTRUE(self$inherit.aes)) {
+ self$mapping <- defaults(self$mapping, plot$mapping)
+ # defaults() strips class, but it needs to be preserved for now
+ class(self$mapping) <- "uneval"
+ }
+
data
},
compute_aesthetics = function(self, data, plot) {
- # For annotation geoms, it is useful to be able to ignore the default aes
- if (self$inherit.aes) {
- aesthetics <- defaults(self$mapping, plot$mapping)
- } else {
- aesthetics <- self$mapping
- }
+ aesthetics <- self$mapping
# Drop aesthetics that are set or calculated
set <- names(aesthetics) %in% names(self$aes_params)
@@ -289,9 +291,6 @@ Layer <- ggproto("Layer", NULL,
# Assemble aesthetics from layer, plot and stat mappings
aesthetics <- self$mapping
- if (self$inherit.aes) {
- aesthetics <- defaults(aesthetics, plot$mapping)
- }
aesthetics <- defaults(aesthetics, self$stat$default_aes)
aesthetics <- compact(aesthetics)
diff --git a/R/performance.R b/R/performance.R
index 8ed0e53da3..24a20e0ae5 100644
--- a/R/performance.R
+++ b/R/performance.R
@@ -26,13 +26,6 @@ data_frame <- function(...) {
new_data_frame(list(...))
}
-data.frame <- function(...) {
- abort(glue("
- Please use `data_frame()` or `new_data_frame()` instead of `data.frame()` for better performance.
- See the vignette 'ggplot2 internal programming guidelines' for details.
- "))
-}
-
split_matrix <- function(x, col_names = colnames(x)) {
force(col_names)
x <- lapply(seq_len(ncol(x)), function(i) x[, i])
diff --git a/R/plot-construction.r b/R/plot-construction.r
index 6248c4914e..2c8c7c2e28 100644
--- a/R/plot-construction.r
+++ b/R/plot-construction.r
@@ -167,8 +167,16 @@ ggplot_add.Layer <- function(object, plot, object_name) {
# Add any new labels
mapping <- make_labels(object$mapping)
- default <- make_labels(object$stat$default_aes)
+ default <- lapply(make_labels(object$stat$default_aes), function(l) {
+ attr(l, "fallback") <- TRUE
+ l
+ })
new_labels <- defaults(mapping, default)
- plot$labels <- defaults(plot$labels, new_labels)
+ current_labels <- plot$labels
+ current_fallbacks <- vapply(current_labels, function(l) isTRUE(attr(l, "fallback")), logical(1))
+ plot$labels <- defaults(current_labels[!current_fallbacks], new_labels)
+ if (any(current_fallbacks)) {
+ plot$labels <- defaults(plot$labels, current_labels)
+ }
plot
}
diff --git a/R/position-dodge2.r b/R/position-dodge2.r
index 2423bb396b..4f27cb7676 100644
--- a/R/position-dodge2.r
+++ b/R/position-dodge2.r
@@ -138,7 +138,7 @@ find_x_overlaps <- function(df) {
overlaps[1] <- counter <- 1
for (i in seq_asc(2, nrow(df))) {
- if (df$xmin[i] >= df$xmax[i - 1]) {
+ if (is.na(df$xmin[i]) || is.na(df$xmax[i - 1]) || df$xmin[i] >= df$xmax[i - 1]) {
counter <- counter + 1
}
overlaps[i] <- counter
diff --git a/R/position-jitter.r b/R/position-jitter.r
index 1e314aa2c1..2c6d78323a 100644
--- a/R/position-jitter.r
+++ b/R/position-jitter.r
@@ -46,10 +46,6 @@
#' geom_point(position = jitter) +
#' geom_point(position = jitter, color = "red", aes(am + 0.2, vs + 0.2))
position_jitter <- function(width = NULL, height = NULL, seed = NA) {
- if (!is.null(seed) && is.na(seed)) {
- seed <- sample.int(.Machine$integer.max, 1L)
- }
-
ggproto(NULL, PositionJitter,
width = width,
height = height,
@@ -62,13 +58,19 @@ position_jitter <- function(width = NULL, height = NULL, seed = NA) {
#' @usage NULL
#' @export
PositionJitter <- ggproto("PositionJitter", Position,
+ seed = NA,
required_aes = c("x", "y"),
setup_params = function(self, data) {
+ if (!is.null(self$seed) && is.na(self$seed)) {
+ seed <- sample.int(.Machine$integer.max, 1L)
+ } else {
+ seed <- self$seed
+ }
list(
width = self$width %||% (resolution(data$x, zero = FALSE) * 0.4),
height = self$height %||% (resolution(data$y, zero = FALSE) * 0.4),
- seed = self$seed
+ seed = seed
)
},
@@ -76,6 +78,17 @@ PositionJitter <- ggproto("PositionJitter", Position,
trans_x <- if (params$width > 0) function(x) jitter(x, amount = params$width)
trans_y <- if (params$height > 0) function(x) jitter(x, amount = params$height)
- with_seed_null(params$seed, transform_position(data, trans_x, trans_y))
+ # Make sure x and y jitter is only calculated once for all position aesthetics
+ x_aes <- intersect(ggplot_global$x_aes, names(data))
+ x <- if (length(x_aes) == 0) 0 else data[[x_aes[1]]]
+ y_aes <- intersect(ggplot_global$y_aes, names(data))
+ y <- if (length(y_aes) == 0) 0 else data[[y_aes[1]]]
+ dummy_data <- new_data_frame(list(x = x, y = y), nrow(data))
+ fixed_jitter <- with_seed_null(params$seed, transform_position(dummy_data, trans_x, trans_y))
+ x_jit <- fixed_jitter$x - x
+ y_jit <- fixed_jitter$y - y
+
+ # Apply jitter
+ transform_position(data, function(x) x + x_jit, function(x) x + y_jit)
}
)
diff --git a/R/position-jitterdodge.R b/R/position-jitterdodge.R
index 0e078e6cf9..abb5dd8ace 100644
--- a/R/position-jitterdodge.R
+++ b/R/position-jitterdodge.R
@@ -13,7 +13,7 @@
#' @inheritParams position_jitter
#' @export
#' @examples
-#' dsub <- diamonds[ sample(nrow(diamonds), 1000), ]
+#' dsub <- diamonds[sample(nrow(diamonds), 1000), ]
#' ggplot(dsub, aes(x = cut, y = carat, fill = clarity)) +
#' geom_boxplot(outlier.size = 0) +
#' geom_point(pch = 21, position = position_jitterdodge())
diff --git a/R/quick-plot.r b/R/quick-plot.r
index 6870e69387..9e320f6746 100644
--- a/R/quick-plot.r
+++ b/R/quick-plot.r
@@ -166,8 +166,8 @@ qplot <- function(x, y, ..., data, facets = NULL, margins = FALSE,
if (!missing(xlab)) p <- p + xlab(xlab)
if (!missing(ylab)) p <- p + ylab(ylab)
- if (!missing(xlim)) p <- p + xlim(xlim)
- if (!missing(ylim)) p <- p + ylim(ylim)
+ if (!missing(xlim) && !all(is.na(xlim))) p <- p + xlim(xlim)
+ if (!missing(ylim) && !all(is.na(ylim))) p <- p + ylim(ylim)
p
}
diff --git a/R/save.r b/R/save.r
index 98bd10e99a..b71579ece9 100644
--- a/R/save.r
+++ b/R/save.r
@@ -25,13 +25,13 @@
#' @param filename File name to create on disk.
#' @param plot Plot to save, defaults to last plot displayed.
#' @param device Device to use. Can either be a device function
-#' (e.g. [png()]), or one of "eps", "ps", "tex" (pictex),
+#' (e.g. [png]), or one of "eps", "ps", "tex" (pictex),
#' "pdf", "jpeg", "tiff", "png", "bmp", "svg" or "wmf" (windows only).
#' @param path Path of the directory to save plot to: `path` and `filename`
#' are combined to create the fully qualified file name. Defaults to the
#' working directory.
#' @param scale Multiplicative scaling factor.
-#' @param width,height,units Plot size in `units` ("in", "cm", or "mm").
+#' @param width,height,units Plot size in `units` ("in", "cm", "mm", or "px").
#' If not supplied, uses the size of current graphics device.
#' @param dpi Plot resolution. Also accepts a string input: "retina" (320),
#' "print" (300), or "screen" (72). Applies only to raster output types.
@@ -75,13 +75,13 @@
#' }
ggsave <- function(filename, plot = last_plot(),
device = NULL, path = NULL, scale = 1,
- width = NA, height = NA, units = c("in", "cm", "mm"),
+ width = NA, height = NA, units = c("in", "cm", "mm", "px"),
dpi = 300, limitsize = TRUE, bg = NULL, ...) {
dpi <- parse_dpi(dpi)
dev <- plot_dev(device, filename, dpi = dpi)
dim <- plot_dim(c(width, height), scale = scale, units = units,
- limitsize = limitsize)
+ limitsize = limitsize, dpi = dpi)
if (!is.null(path)) {
filename <- file.path(path, filename)
@@ -97,7 +97,7 @@ ggsave <- function(filename, plot = last_plot(),
}))
grid.draw(plot)
- invisible()
+ invisible(filename)
}
#' Parse a DPI input from the user
@@ -122,12 +122,12 @@ parse_dpi <- function(dpi) {
}
}
-plot_dim <- function(dim = c(NA, NA), scale = 1, units = c("in", "cm", "mm"),
- limitsize = TRUE) {
+plot_dim <- function(dim = c(NA, NA), scale = 1, units = c("in", "cm", "mm", "px"),
+ limitsize = TRUE, dpi = 300) {
units <- match.arg(units)
- to_inches <- function(x) x / c(`in` = 1, cm = 2.54, mm = 2.54 * 10)[units]
- from_inches <- function(x) x * c(`in` = 1, cm = 2.54, mm = 2.54 * 10)[units]
+ to_inches <- function(x) x / c(`in` = 1, cm = 2.54, mm = 2.54 * 10, px = dpi)[units]
+ from_inches <- function(x) x * c(`in` = 1, cm = 2.54, mm = 2.54 * 10, px = dpi)[units]
dim <- to_inches(dim) * scale
@@ -158,18 +158,34 @@ plot_dev <- function(device, filename = NULL, dpi = 300) {
force(dpi)
if (is.function(device)) {
- if ("file" %in% names(formals(device))) {
- dev <- function(filename, ...) device(file = filename, ...)
- return(dev)
- } else {
- return(device)
+ args <- formals(device)
+ call_args <- list()
+ if ("file" %in% names(args)) {
+ call_args$file <- filename
+ }
+ if ("res" %in% names(args)) {
+ call_args$res <- dpi
+ }
+ if ("units" %in% names(args)) {
+ call_args$units <- 'in'
}
+ dev <- function(...) do.call(device, modify_list(list(...), call_args))
+ return(dev)
}
eps <- function(filename, ...) {
grDevices::postscript(file = filename, ..., onefile = FALSE, horizontal = FALSE,
paper = "special")
}
+ if (requireNamespace('ragg', quietly = TRUE)) {
+ png_dev <- ragg::agg_png
+ jpeg_dev <- ragg::agg_jpeg
+ tiff_dev <- ragg::agg_tiff
+ } else {
+ png_dev <- grDevices::png
+ jpeg_dev <- grDevices::jpeg
+ tiff_dev <- grDevices::tiff
+ }
devices <- list(
eps = eps,
ps = eps,
@@ -178,11 +194,11 @@ plot_dev <- function(device, filename = NULL, dpi = 300) {
svg = function(filename, ...) svglite::svglite(file = filename, ...),
emf = function(...) grDevices::win.metafile(...),
wmf = function(...) grDevices::win.metafile(...),
- png = function(...) grDevices::png(..., res = dpi, units = "in"),
- jpg = function(...) grDevices::jpeg(..., res = dpi, units = "in"),
- jpeg = function(...) grDevices::jpeg(..., res = dpi, units = "in"),
+ png = function(...) png_dev(..., res = dpi, units = "in"),
+ jpg = function(...) jpeg_dev(..., res = dpi, units = "in"),
+ jpeg = function(...) jpeg_dev(..., res = dpi, units = "in"),
bmp = function(...) grDevices::bmp(..., res = dpi, units = "in"),
- tiff = function(...) grDevices::tiff(..., res = dpi, units = "in")
+ tiff = function(...) tiff_dev(..., res = dpi, units = "in")
)
if (is.null(device)) {
diff --git a/R/stat-bindot.r b/R/stat-bindot.r
index 4dbb738895..93f731bf62 100644
--- a/R/stat-bindot.r
+++ b/R/stat-bindot.r
@@ -9,7 +9,7 @@ StatBindot <- ggproto("StatBindot", Stat,
setup_params = function(data, params) {
if (is.null(params$binwidth)) {
- message("`stat_bindot()` using `bins = 30`. Pick better value with `binwidth`.")
+ message("Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.")
}
params
},
diff --git a/R/stat-binhex.r b/R/stat-binhex.r
index 303384ca71..40d7025df5 100644
--- a/R/stat-binhex.r
+++ b/R/stat-binhex.r
@@ -49,7 +49,7 @@ StatBinhex <- ggproto("StatBinhex", Stat,
compute_group = function(data, scales, binwidth = NULL, bins = 30,
na.rm = FALSE) {
- try_require("hexbin", "stat_binhex")
+ check_installed("hexbin", reason = "for `stat_binhex()`")
binwidth <- binwidth %||% hex_binwidth(bins, scales)
wt <- data$weight %||% rep(1L, nrow(data))
diff --git a/R/stat-count.r b/R/stat-count.r
index 89d7999997..42f252c41b 100644
--- a/R/stat-count.r
+++ b/R/stat-count.r
@@ -64,6 +64,11 @@ StatCount <- ggproto("StatCount", Stat,
abort("stat_count() can only have an x or y aesthetic.")
}
+ if (is.null(params$width)) {
+ x <- if (params$flipped_aes) "y" else "x"
+ params$width <- resolution(data[[x]]) * 0.9
+ }
+
params
},
@@ -73,7 +78,6 @@ StatCount <- ggproto("StatCount", Stat,
data <- flip_data(data, flipped_aes)
x <- data$x
weight <- data$weight %||% rep(1, length(x))
- width <- width %||% (resolution(x) * 0.9)
count <- as.numeric(tapply(weight, x, sum, na.rm = TRUE))
count[is.na(count)] <- 0
diff --git a/R/stat-quantile.r b/R/stat-quantile.r
index d379fbc900..059f54ffb7 100644
--- a/R/stat-quantile.r
+++ b/R/stat-quantile.r
@@ -50,7 +50,7 @@ StatQuantile <- ggproto("StatQuantile", Stat,
compute_group = function(data, scales, quantiles = c(0.25, 0.5, 0.75),
formula = NULL, xseq = NULL, method = "rq",
method.args = list(), lambda = 1, na.rm = FALSE) {
- try_require("quantreg", "stat_quantile")
+ check_installed("quantreg", reason = "for `stat_quantile()`")
if (is.null(formula)) {
if (method == "rqss") {
diff --git a/R/stat-summary-hex.r b/R/stat-summary-hex.r
index 6ba1d6822b..6ebfb80ad1 100644
--- a/R/stat-summary-hex.r
+++ b/R/stat-summary-hex.r
@@ -43,7 +43,7 @@ StatSummaryHex <- ggproto("StatSummaryHex", Stat,
compute_group = function(data, scales, binwidth = NULL, bins = 30, drop = TRUE,
fun = "mean", fun.args = list()) {
- try_require("hexbin", "stat_summary_hex")
+ check_installed("hexbin", reason = "for `stat_summary_hex()`")
binwidth <- binwidth %||% hex_binwidth(bins, scales)
fun <- as_function(fun)
diff --git a/R/stat-summary.r b/R/stat-summary.r
index 8769c3c424..04eaa70b5f 100644
--- a/R/stat-summary.r
+++ b/R/stat-summary.r
@@ -245,8 +245,7 @@ NULL
wrap_hmisc <- function(fun) {
function(x, ...) {
- if (!requireNamespace("Hmisc", quietly = TRUE))
- abort("Hmisc package required for this function")
+ check_installed("Hmisc")
fun <- getExportedValue("Hmisc", fun)
result <- do.call(fun, list(x = quote(x), ...))
diff --git a/R/utilities.r b/R/utilities.r
index 40b117767b..a9587d2cc5 100644
--- a/R/utilities.r
+++ b/R/utilities.r
@@ -56,22 +56,6 @@ clist <- function(l) {
paste(paste(names(l), l, sep = " = ", collapse = ", "), sep = "")
}
-
-# Test whether package `package` is available. `fun` provides
-# the name of the ggplot2 function that uses this package, and is
-# used only to produce a meaningful error message if the
-# package is not available.
-try_require <- function(package, fun) {
- if (requireNamespace(package, quietly = TRUE)) {
- return(invisible())
- }
-
- abort(glue("
- Package `{package}` required for `{fun}`.
- Please install and try again.
- "))
-}
-
# Return unique columns
# This is used for figuring out which columns are constant within a group
#
diff --git a/R/zxx.r b/R/zxx.r
index cf379281fa..222238abc4 100644
--- a/R/zxx.r
+++ b/R/zxx.r
@@ -3,7 +3,15 @@
#' @export
#' @rdname scale_viridis
#' @usage NULL
-scale_colour_ordinal <- scale_colour_viridis_d
+scale_colour_ordinal <- function(..., type = getOption("ggplot2.ordinal.colour", getOption("ggplot2.ordinal.fill"))) {
+ type <- type %||% scale_colour_viridis_d
+ if (is.function(type)) {
+ type(...)
+ } else {
+ discrete_scale("colour", "ordinal", ordinal_pal(type), ...)
+ }
+}
+
#' @export
#' @rdname scale_viridis
@@ -62,7 +70,21 @@ scale_color_date <- scale_colour_date
#' @export
#' @rdname scale_viridis
#' @usage NULL
-scale_fill_ordinal <- scale_fill_viridis_d
+scale_fill_ordinal <- function(..., type = getOption("ggplot2.ordinal.fill", getOption("ggplot2.ordinal.colour"))) {
+ type <- type %||% scale_fill_viridis_d
+ if (is.function(type)) {
+ type(...)
+ } else {
+ discrete_scale("fill", "ordinal", ordinal_pal(type), ...)
+ }
+}
+
+ordinal_pal <- function(colours, na.color = "grey50", alpha = TRUE) {
+ pal <- scales::colour_ramp(colours, na.color = na.color, alpha = alpha)
+ function(n) {
+ pal(seq(0, 1, length.out = n))
+ }
+}
#' @export
#' @rdname scale_gradient
@@ -131,7 +153,7 @@ scale_color_continuous <- scale_colour_continuous
scale_color_binned <- scale_colour_binned
#' @export
-#' @rdname scale_hue
+#' @rdname scale_colour_discrete
#' @usage NULL
scale_color_discrete <- scale_colour_discrete
diff --git a/_pkgdown.yml b/_pkgdown.yml
index eff8524f98..5506b48ae0 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -243,6 +243,31 @@ reference:
- fortify
- map_data
+
+articles:
+- title: Building plots
+ navbar: ~
+ contents:
+ - ggplot2-specs
+
+- title: Developer
+ navbar: Developer
+ contents:
+ - extending-ggplot2
+ - ggplot2-in-packages
+ - profiling
+
+- title: FAQ
+ navbar: FAQ
+ contents:
+ - articles/faq-axes
+ - articles/faq-faceting
+ - articles/faq-customising
+ - articles/faq-annotation
+ - articles/faq-reordering
+ - articles/faq-bars
+
+
news:
releases:
- text: "Version 3.3.0"
@@ -264,7 +289,7 @@ news:
navbar:
structure:
- right: [extensions, github]
+ right: [reference, news, articles, extensions, github]
components:
home: ~
extensions:
diff --git a/man/geom_contour.Rd b/man/geom_contour.Rd
index e3b0ee8f03..1277d9b84b 100644
--- a/man/geom_contour.Rd
+++ b/man/geom_contour.Rd
@@ -102,9 +102,9 @@ to the paired geom/stat.}
\item{binwidth}{The width of the contour bins. Overridden by \code{breaks}.}
-\item{breaks}{Numeric vector to set the contour breaks.
-Overrides \code{binwidth} and \code{bins}. By default, this is a vector of
-length ten with \code{\link[=pretty]{pretty()}} breaks.}
+\item{breaks}{Numeric vector to set the contour breaks. Overrides \code{binwidth}
+and \code{bins}. By default, this is a vector of length ten with \code{\link[=pretty]{pretty()}}
+breaks.}
\item{lineend}{Line end style (round, butt, square).}
@@ -130,12 +130,16 @@ the default plot specification, e.g. \code{\link[=borders]{borders()}}.}
}
\description{
ggplot2 can not draw true 3D surfaces, but you can use \code{geom_contour()},
-\code{geom_contour_filled()}, and \code{\link[=geom_tile]{geom_tile()}} to visualise 3D surfaces in 2D.
-To specify a valid surface, the data must contain \code{x}, \code{y}, and \code{z} coordinates,
-and each unique combination of \code{x} and \code{y} can appear exactly once. Contouring
-tends to work best when \code{x} and \code{y} form a (roughly) evenly
-spaced grid. If your data is not evenly spaced, you may want to interpolate
-to a grid before visualising, see \code{\link[=geom_density_2d]{geom_density_2d()}}.
+\code{geom_contour_filled()}, and \code{\link[=geom_tile]{geom_tile()}} to visualise 3D surfaces in 2D. To
+specify a valid surface, the data must contain \code{x}, \code{y}, and \code{z} coordinates,
+and each unique combination of \code{x} and \code{y} can appear at most once.
+Contouring requires that the points can be rearranged so that the \code{z} values
+form a matrix, with rows corresponding to unique \code{x} values, and columns
+corresponding to unique \code{y} values. Missing entries are allowed, but contouring
+will only be done on cells of the grid with all four \code{z} values present. If
+your data is irregular, you can interpolate to a grid before visualising
+using the \code{\link[interp:interp]{interp::interp()}} function from the \code{interp} package
+(or one of the interpolating functions from the \code{akima} package.)
}
\section{Aesthetics}{
@@ -236,6 +240,22 @@ v + geom_contour(aes(colour = after_stat(level)))
v + geom_contour(colour = "red")
v + geom_raster(aes(fill = density)) +
geom_contour(colour = "white")
+
+# Irregular data
+if (requireNamespace("interp")) {
+ # Use a dataset from the interp package
+ data(franke, package = "interp")
+ origdata <- as.data.frame(interp::franke.data(1, 1, franke))
+ grid <- with(origdata, interp::interp(x, y, z))
+ griddf <- subset(data.frame(x = rep(grid$x, nrow(grid$z)),
+ y = rep(grid$y, each = ncol(grid$z)),
+ z = as.numeric(grid$z)),
+ !is.na(z))
+ ggplot(griddf, aes(x, y, z = z)) +
+ geom_contour_filled() +
+ geom_point(data = origdata)
+} else
+ message("Irregular data requires the 'interp' package")
}
}
\seealso{
diff --git a/man/geom_density_2d.Rd b/man/geom_density_2d.Rd
index c9c0ea7511..c0daf09211 100644
--- a/man/geom_density_2d.Rd
+++ b/man/geom_density_2d.Rd
@@ -99,9 +99,9 @@ a call to a position adjustment function.}
\describe{
\item{\code{bins}}{Number of contour bins. Overridden by \code{binwidth}.}
\item{\code{binwidth}}{The width of the contour bins. Overridden by \code{breaks}.}
- \item{\code{breaks}}{Numeric vector to set the contour breaks.
-Overrides \code{binwidth} and \code{bins}. By default, this is a vector of
-length ten with \code{\link[=pretty]{pretty()}} breaks.}
+ \item{\code{breaks}}{Numeric vector to set the contour breaks. Overrides \code{binwidth}
+and \code{bins}. By default, this is a vector of length ten with \code{\link[=pretty]{pretty()}}
+breaks.}
}}
\item{contour_var}{Character string identifying the variable to contour
diff --git a/man/geom_text.Rd b/man/geom_text.Rd
index e3b4ddf5c1..06ebb387ac 100644
--- a/man/geom_text.Rd
+++ b/man/geom_text.Rd
@@ -100,7 +100,8 @@ the default plot specification, e.g. \code{\link[=borders]{borders()}}.}
\item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the
same layer will not be plotted. \code{check_overlap} happens at draw time and in
the order of the data. Therefore data should be arranged by the label
-column before calling \code{geom_label()} or \code{geom_text()}.}
+column before calling \code{geom_text()}. Note that this argument is not
+supported by \code{geom_label()}.}
}
\description{
Text geoms are useful for labeling plots. They can be used by themselves as
@@ -150,9 +151,9 @@ Learn more about setting these aesthetics in \code{vignette("ggplot2-specs")}.
\section{\code{geom_label()}}{
-Currently \code{geom_label()} does not support the \code{angle} aesthetic and
-is considerably slower than \code{geom_text()}. The \code{fill} aesthetic
-controls the background colour of the label.
+Currently \code{geom_label()} does not support the \code{check_overlap} argument
+or the \code{angle} aesthetic. Also, it is considerably slower than \code{geom_text()}.
+The \code{fill} aesthetic controls the background colour of the label.
}
\section{Alignment}{
diff --git a/man/ggsave.Rd b/man/ggsave.Rd
index 8d6a6102b4..b72e12cad9 100644
--- a/man/ggsave.Rd
+++ b/man/ggsave.Rd
@@ -12,7 +12,7 @@ ggsave(
scale = 1,
width = NA,
height = NA,
- units = c("in", "cm", "mm"),
+ units = c("in", "cm", "mm", "px"),
dpi = 300,
limitsize = TRUE,
bg = NULL,
@@ -25,7 +25,7 @@ ggsave(
\item{plot}{Plot to save, defaults to last plot displayed.}
\item{device}{Device to use. Can either be a device function
-(e.g. \code{\link[=png]{png()}}), or one of "eps", "ps", "tex" (pictex),
+(e.g. \link{png}), or one of "eps", "ps", "tex" (pictex),
"pdf", "jpeg", "tiff", "png", "bmp", "svg" or "wmf" (windows only).}
\item{path}{Path of the directory to save plot to: \code{path} and \code{filename}
@@ -34,7 +34,7 @@ working directory.}
\item{scale}{Multiplicative scaling factor.}
-\item{width, height, units}{Plot size in \code{units} ("in", "cm", or "mm").
+\item{width, height, units}{Plot size in \code{units} ("in", "cm", "mm", or "px").
If not supplied, uses the size of current graphics device.}
\item{dpi}{Plot resolution. Also accepts a string input: "retina" (320),
diff --git a/man/ggsf.Rd b/man/ggsf.Rd
index 93c15928c6..4ef02e0bc8 100644
--- a/man/ggsf.Rd
+++ b/man/ggsf.Rd
@@ -250,7 +250,8 @@ when you really care about the exact locations.}
\item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the
same layer will not be plotted. \code{check_overlap} happens at draw time and in
the order of the data. Therefore data should be arranged by the label
-column before calling \code{geom_label()} or \code{geom_text()}.}
+column before calling \code{geom_text()}. Note that this argument is not
+supported by \code{geom_label()}.}
\item{geom}{The geometric object to use display the data}
}
diff --git a/man/position_jitterdodge.Rd b/man/position_jitterdodge.Rd
index 5e31830ae8..49d0ad55d0 100644
--- a/man/position_jitterdodge.Rd
+++ b/man/position_jitterdodge.Rd
@@ -35,7 +35,7 @@ This is primarily used for aligning points generated through
a fill aesthetic supplied).
}
\examples{
-dsub <- diamonds[ sample(nrow(diamonds), 1000), ]
+dsub <- diamonds[sample(nrow(diamonds), 1000), ]
ggplot(dsub, aes(x = cut, y = carat, fill = clarity)) +
geom_boxplot(outlier.size = 0) +
geom_point(pch = 21, position = position_jitterdodge())
diff --git a/man/scale_colour_discrete.Rd b/man/scale_colour_discrete.Rd
index c50c42400d..7f230ba19b 100644
--- a/man/scale_colour_discrete.Rd
+++ b/man/scale_colour_discrete.Rd
@@ -1,8 +1,9 @@
% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/scale-hue.r
+% Please edit documentation in R/scale-hue.r, R/zxx.r
\name{scale_colour_discrete}
\alias{scale_colour_discrete}
\alias{scale_fill_discrete}
+\alias{scale_color_discrete}
\title{Discrete colour scales}
\usage{
scale_colour_discrete(
diff --git a/man/scale_grey.Rd b/man/scale_grey.Rd
index 420c809068..a6812aafc7 100644
--- a/man/scale_grey.Rd
+++ b/man/scale_grey.Rd
@@ -4,6 +4,8 @@
\alias{scale_colour_grey}
\alias{scale_fill_grey}
\alias{scale_color_grey}
+\alias{scale_color_gray}
+\alias{scale_fill_gray}
\title{Sequential grey colour scales}
\usage{
scale_colour_grey(
diff --git a/man/scale_hue.Rd b/man/scale_hue.Rd
index 3299bc4e69..dc0699eceb 100644
--- a/man/scale_hue.Rd
+++ b/man/scale_hue.Rd
@@ -3,7 +3,6 @@
\name{scale_colour_hue}
\alias{scale_colour_hue}
\alias{scale_fill_hue}
-\alias{scale_color_discrete}
\alias{scale_color_hue}
\title{Evenly spaced colours for discrete data}
\usage{
diff --git a/tests/figs/coord-sf/sf-polygons.svg b/tests/figs/coord-sf/sf-polygons.svg
index a3b1b6928c..62c30c94df 100644
--- a/tests/figs/coord-sf/sf-polygons.svg
+++ b/tests/figs/coord-sf/sf-polygons.svg
@@ -39,25 +39,25 @@
36.25
°
N
-36.3
+36.30
°
N
36.35
°
N
-36.4
+36.40
°
N
36.45
°
N
-36.5
+36.50
°
N
36.55
°
N
-36.6
+36.60
°
N
diff --git a/tests/figs/deps.txt b/tests/figs/deps.txt
index 0f64e23e67..1fcd925731 100644
--- a/tests/figs/deps.txt
+++ b/tests/figs/deps.txt
@@ -1,3 +1,3 @@
- vdiffr-svg-engine: 1.0
-- vdiffr: 0.3.1
-- freetypeharfbuzz: 0.2.5
+- vdiffr: 0.3.3
+- freetypeharfbuzz: 0.2.6
diff --git a/tests/figs/geom-dotplot/bin-y-dodging-3-stackgroups-histodot-currently-broken.svg b/tests/figs/geom-dotplot/bin-y-dodging-3-stackgroups-histodot.svg
similarity index 83%
rename from tests/figs/geom-dotplot/bin-y-dodging-3-stackgroups-histodot-currently-broken.svg
rename to tests/figs/geom-dotplot/bin-y-dodging-3-stackgroups-histodot.svg
index ef53f1c0f0..b008396488 100644
--- a/tests/figs/geom-dotplot/bin-y-dodging-3-stackgroups-histodot-currently-broken.svg
+++ b/tests/figs/geom-dotplot/bin-y-dodging-3-stackgroups-histodot.svg
@@ -19,96 +19,96 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
-
-
-
+
+
+
+
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
-
-
+
-
+
-
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
+
-
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
-
-
+
@@ -141,5 +141,5 @@
A
B
-bin y, dodging, 3 stackgroups, histodot (currently broken)
+bin y, dodging, 3 stackgroups, histodot
diff --git a/tests/figs/geom-sf/north-carolina-county-boundaries.svg b/tests/figs/geom-sf/north-carolina-county-boundaries.svg
index 805823d951..429a3d8696 100644
--- a/tests/figs/geom-sf/north-carolina-county-boundaries.svg
+++ b/tests/figs/geom-sf/north-carolina-county-boundaries.svg
@@ -39,25 +39,25 @@
36.25
°
N
-36.3
+36.30
°
N
36.35
°
N
-36.4
+36.40
°
N
36.45
°
N
-36.5
+36.50
°
N
36.55
°
N
-36.6
+36.60
°
N
diff --git a/tests/testthat/test-facet-.r b/tests/testthat/test-facet-.r
index a010256953..e54eeb7797 100644
--- a/tests/testthat/test-facet-.r
+++ b/tests/testthat/test-facet-.r
@@ -52,13 +52,25 @@ test_that("facets reject aes()", {
test_that("wrap_as_facets_list() returns a quosures object with compacted", {
expect_identical(wrap_as_facets_list(vars(foo)), quos(foo = foo))
expect_identical(wrap_as_facets_list(~foo + bar), quos(foo = foo, bar = bar))
- expect_identical(wrap_as_facets_list(vars(foo, NULL, bar)), quos(foo = foo, bar = bar))
+
+ f <- function(x) {
+ expect_identical(wrap_as_facets_list(vars(foo, {{ x }}, bar)), quos(foo = foo, bar = bar))
+ }
+
+ f(NULL)
+ f()
})
test_that("grid_as_facets_list() returns a list of quosures objects with compacted", {
expect_identical(grid_as_facets_list(vars(foo), NULL), list(rows = quos(foo = foo), cols = quos()))
expect_identical(grid_as_facets_list(~foo, NULL), list(rows = quos(), cols = quos(foo = foo)))
- expect_identical(grid_as_facets_list(vars(foo, NULL, bar), NULL), list(rows = quos(foo = foo, bar = bar), cols = quos()))
+
+ f <- function(x) {
+ expect_identical(grid_as_facets_list(vars(foo, {{ x }}, bar), NULL), list(rows = quos(foo = foo, bar = bar), cols = quos()))
+ }
+
+ f(NULL)
+ f()
})
test_that("wrap_as_facets_list() and grid_as_facets_list() accept empty specs", {
diff --git a/tests/testthat/test-geom-dotplot.R b/tests/testthat/test-geom-dotplot.R
index e35bca06c7..1368d781c4 100644
--- a/tests/testthat/test-geom-dotplot.R
+++ b/tests/testthat/test-geom-dotplot.R
@@ -207,7 +207,7 @@ test_that("geom_dotplot draws correctly", {
# This one is currently broken but it would be a really rare case, and it
# probably requires a really ugly hack to fix
- expect_doppelganger("bin y, dodging, 3 stackgroups, histodot (currently broken)",
+ expect_doppelganger("bin y, dodging, 3 stackgroups, histodot",
ggplot(dat2, aes(x, y, fill = g)) +
geom_dotplot(binaxis = "y", binwidth = .25, stackgroups = TRUE, method = "histodot",
alpha = 0.5, stackdir = "centerwhole")
diff --git a/tests/testthat/test-ggsave.R b/tests/testthat/test-ggsave.R
index b4ee2cc4dc..10069b4004 100644
--- a/tests/testthat/test-ggsave.R
+++ b/tests/testthat/test-ggsave.R
@@ -85,10 +85,6 @@ test_that("scale multiplies height & width", {
# plot_dev ---------------------------------------------------------------------
-test_that("function is passed back unchanged", {
- expect_equal(plot_dev(png), png)
-})
-
test_that("unknown device triggers error", {
expect_error(plot_dev("xyz"), "Unknown graphics device")
expect_error(plot_dev(NULL, "test.xyz"), "Unknown graphics device")
@@ -96,12 +92,12 @@ test_that("unknown device triggers error", {
test_that("text converted to function", {
- expect_identical(body(plot_dev("png"))[[1]], quote(grDevices::png))
+ expect_identical(body(plot_dev("png"))[[1]], quote(png_dev))
expect_identical(body(plot_dev("pdf"))[[1]], quote(grDevices::pdf))
})
test_that("if device is NULL, guess from extension", {
- expect_identical(body(plot_dev(NULL, "test.png"))[[1]], quote(grDevices::png))
+ expect_identical(body(plot_dev(NULL, "test.png"))[[1]], quote(png_dev))
})
# parse_dpi ---------------------------------------------------------------
diff --git a/tests/testthat/test-labels.r b/tests/testthat/test-labels.r
index dd13f07f78..b760b393e7 100644
--- a/tests/testthat/test-labels.r
+++ b/tests/testthat/test-labels.r
@@ -50,6 +50,18 @@ test_that("setting guide labels works", {
)
})
+test_that("Labels from default stat mapping are overwritten by default labels", {
+ p <- ggplot(mpg, aes(displ, hwy)) +
+ geom_density2d()
+
+ expect_equal(p$labels$colour[1], "colour")
+ expect_true(attr(p$labels$colour, "fallback"))
+
+ p <- p + geom_smooth(aes(color = drv))
+
+ expect_equal(p$labels$colour, "drv")
+})
+
# Visual tests ------------------------------------------------------------
diff --git a/tests/testthat/test-position-dodge2.R b/tests/testthat/test-position-dodge2.R
index 7c7d748022..08aa70082f 100644
--- a/tests/testthat/test-position-dodge2.R
+++ b/tests/testthat/test-position-dodge2.R
@@ -107,3 +107,11 @@ test_that("width of groups is computed per facet", {
expect_true(all(width == (0.9 / 3) * 0.9))
})
+
+test_that("NA values are given their own group", {
+ df <- data.frame(
+ xmin = c(1, 2, NA, NA),
+ xmax = c(1, 2, NA, NA)
+ )
+ expect_equal(find_x_overlaps(df), seq_len(4))
+})
diff --git a/tests/testthat/test-conditions.R b/tests/testthat/test-prohibited-functions.R
similarity index 52%
rename from tests/testthat/test-conditions.R
rename to tests/testthat/test-prohibited-functions.R
index e676212106..9ee7e1d736 100644
--- a/tests/testthat/test-conditions.R
+++ b/tests/testthat/test-prohibited-functions.R
@@ -10,6 +10,27 @@ get_n_warning <- function(f) {
sum(d$token == "SYMBOL_FUNCTION_CALL" & d$text == "warning")
}
+get_n_data.frame <- function(f) {
+ d <- getParseData(parse(f, keep.source = TRUE))
+ sum(d$token == "SYMBOL_FUNCTION_CALL" & d$text == "data.frame")
+}
+
+test_that("`get_n_*() detects number of calls properly", {
+ withr::local_file("tmp.R")
+ writeLines(
+ c(
+ 'stop("foo!")',
+ 'warning("bar!")',
+ "data.frame(x = 1)"
+ ),
+ "tmp.R"
+ )
+
+ expect_equal(get_n_stop("tmp.R"), 1)
+ expect_equal(get_n_warning("tmp.R"), 1)
+ expect_equal(get_n_data.frame("tmp.R"), 1)
+})
+
# Pattern is needed filter out files such as ggplot2.rdb, which is created when running covr::package_coverage()
R_files <- list.files("../../R", pattern = ".*\\.(R|r)$", full.names = TRUE)
@@ -22,3 +43,8 @@ test_that("do not use warning()", {
warnings <- vapply(R_files, get_n_warning, integer(1))
expect_equal(sum(warnings), 0)
})
+
+test_that("do not use data.frame(), use `data_frame()` or `new_data_frame()`", {
+ data.frames <- vapply(R_files, get_n_data.frame, integer(1))
+ expect_equal(sum(data.frames), 0)
+})
diff --git a/tests/testthat/test-theme.r b/tests/testthat/test-theme.r
index 19c28a5157..dd06008505 100644
--- a/tests/testthat/test-theme.r
+++ b/tests/testthat/test-theme.r
@@ -562,8 +562,8 @@ test_that("axes ticks can have independent lengths", {
axis.ticks.length.x.bottom = unit(-.25, "cm"),
axis.ticks.length.y.left = unit(.25, "cm"),
axis.ticks.length.y.right = unit(.5, "cm"),
- axis.text.x.bottom = element_text(margin = margin(t = .5, unit = "cm")),
- axis.text.x.top = element_text(margin = margin(b = .75, unit = "cm"))
+ axis.text.x.bottom = element_text(margin = margin(t = .25, unit = "cm")),
+ axis.text.x.top = element_text(margin = margin(b = .25, unit = "cm"))
)
expect_doppelganger("ticks_length", plot)
})
diff --git a/vignettes/articles/faq-annotation.Rmd b/vignettes/articles/faq-annotation.Rmd
new file mode 100644
index 0000000000..8a3560a309
--- /dev/null
+++ b/vignettes/articles/faq-annotation.Rmd
@@ -0,0 +1,214 @@
+---
+title: "FAQ: Annotation"
+---
+
+```{=html}
+
+```
+```{r, include = FALSE}
+library(ggplot2)
+library(dplyr)
+knitr::opts_chunk$set(
+ fig.dpi = 300,
+ collapse = TRUE,
+ comment = "#>",
+ fig.asp = 0.618,
+ fig.width = 6,
+ out.width = "80%"
+ )
+```
+
+
+
+### Why is annotation created with `geom_text()` pixellated? How can I make it more crisp?
+
+You should use `annotate(geom = "text")` instead of `geom_text()` for annotation.
+
+
+
+See example
+
+In the following visualisation we have annotated a histogram with a red line and red text to mark the mean. Note that both the line and the text appears pixellated/fuzzy.
+
+```{r}
+mean_hwy <- round(mean(mpg$hwy), 2)
+
+ggplot(mpg, aes(x = hwy)) +
+ geom_histogram(binwidth = 2) +
+ geom_segment(
+ x = mean_hwy, xend = mean_hwy,
+ y = 0, yend = 35,
+ color = "red"
+ ) +
+ geom_text(
+ x = mean_hwy, y = 40,
+ label = paste("mean\n", mean_hwy),
+ color = "red"
+ )
+```
+
+This is because `geom_text()` draws the geom once per each row of the data frame, and plotting these on top of each other. For annotation (as opposed to plotting the data using text as geometric objects to represent each observation) use `annotate()` instead.
+
+
+```{r}
+ggplot(mpg, aes(x = hwy)) +
+ geom_histogram(binwidth = 2) +
+ annotate("segment",
+ x = mean_hwy, xend = mean_hwy, y = 0, yend = 35,
+ color = "red"
+ ) +
+ annotate("text",
+ x = mean_hwy, y = 40,
+ label = paste("mean =", mean_hwy),
+ color = "red"
+ )
+```
+
+
+
+### How can I make sure all annotation created with `geom_text()` fits in the bounds of the plot?
+
+Set `vjust = "inward"` and `hjust = "inward"` in `geom_text()`.
+
+
+
+See example
+
+Suppose you have the following data frame and visualization. The labels at the edges of the plot are cut off slightly.
+
+```{r}
+df <- tibble::tribble(
+ ~x, ~y, ~name,
+ 2, 2, "two",
+ 3, 3, "three",
+ 4, 4, "four"
+)
+
+ggplot(df, aes(x = x, y = y, label = name)) +
+ geom_text(size = 10)
+```
+
+You could manually extend axis limits to avoid this, but a more straightforward approach is to set `vjust = "inward"` and `hjust = "inward"` in `geom_text()`.
+
+```{r}
+ggplot(df, aes(x = x, y = y, label = name)) +
+ geom_text(size = 10, vjust = "inward", hjust = "inward")
+```
+
+
+
+### How can I annotate my bar plot to display counts for each bar?
+
+Either calculate the counts ahead of time and place them on bars using `geom_text()` or let `ggplot()` calculate them for you and then add them to the plot using `stat_coun()` with `geom = "text"`.
+
+
+
+See example
+
+Suppose you have the following bar plot and you want to add the number of cars that fall into each `drv` level on their respective bars.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar()
+```
+
+One option is to calculate the counts with `dplyr::count()` and then pass them to the `label` mapping in `geom_text()`.
+Note that we expanded the y axis limit to get the numbers to fit on the plot.
+
+```{r}
+mpg %>%
+ dplyr::count(drv) %>%
+ ggplot(aes(x = drv, y = n)) +
+ geom_col() +
+ geom_text(aes(label = n), vjust = -0.5) +
+ coord_cartesian(ylim = c(0, 110))
+```
+
+Another option is to let `ggplot()` do the counting for you, and access these counts with `..count..` that is mapped to the labels to be placed on the plot with `stat_count()`.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar() +
+ stat_count(geom = "text", aes(label = ..count..), vjust = -0.5) +
+ coord_cartesian(ylim = c(0, 110))
+```
+
+
+
+### How can I annotate my stacked bar plot to display counts for each segment?
+
+First calculate the counts for each segment (e.g. with `dplyr::count()`) and then place them on the bars with `geom_text()` using `position_stack(vjust = 0.5)` in the `position` argument to place the values in the middle of the segments.
+
+
+
+See example
+
+Suppose you have the following stacked bar plot.
+
+```{r}
+ggplot(mpg, aes(x = class, fill = drv)) +
+ geom_bar()
+```
+
+You can first calculate the counts for each segment with `dplyr::count()`, which will place these values in a column called `n`.
+
+```{r}
+mpg %>%
+ count(class, drv)
+```
+
+You can then pass this result directly to `ggplot()`, draw the segments with appropriate heights with `y = n` in the `aes`thetic mapping and `geom_col()` to draw the bars, and finally place the counts on the plot with `geom_text()`.
+
+```{r}
+mpg %>%
+ count(class, drv) %>%
+ ggplot(aes(x = class, fill = drv, y = n)) +
+ geom_col() +
+ geom_text(aes(label = n), size = 3, position = position_stack(vjust = 0.5))
+```
+
+
+
+### How can I display proportions (relative frequencies) instead of counts on a bar plot?
+
+Either calculate the prpportions ahead of time and place them on bars using `geom_text()` or let `ggplot()` calculate them for you and then add them to the plot using `stat_coun()` with `geom = "text"`.
+
+
+
+See example
+
+Suppose you have the following bar plot but you want to display the proportion of cars that fall into each `drv` level, instead of the count.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar()
+```
+
+One option is to calculate the proportions with `dplyr::count()` and then use `geom_col()` to draw the bars
+
+```{r}
+mpg %>%
+ dplyr::count(drv) %>%
+ mutate(prop = n / sum(n)) %>%
+ ggplot(aes(x = drv, y = prop)) +
+ geom_col()
+```
+
+Another option is to let `ggplot()` do the calculation of proportions for you, and access these counts with `..prop..`.
+Note that we also need to the `group = 1` mapping for this option.
+
+```{r}
+ggplot(mpg, aes(x = drv, y = ..prop.., group = 1)) +
+ geom_bar()
+```
+
+
+
diff --git a/vignettes/articles/faq-axes.Rmd b/vignettes/articles/faq-axes.Rmd
new file mode 100644
index 0000000000..ba718d7d79
--- /dev/null
+++ b/vignettes/articles/faq-axes.Rmd
@@ -0,0 +1,487 @@
+---
+title: "FAQ: Axes"
+---
+
+```{=html}
+
+```
+```{r, include = FALSE}
+library(ggplot2)
+knitr::opts_chunk$set(
+ fig.dpi = 300,
+ collapse = TRUE,
+ comment = "#>",
+ fig.asp = 0.618,
+ fig.width = 6,
+ out.width = "80%")
+```
+
+## Label placement
+
+### How can I rotate the axis tick labels in ggplot2 so that tick labels that are long character strings don't overlap?
+
+Set the angle of the text in the `axis.text.x` or `axis.text.y` components of the `theme()`, e.g. `theme(axis.text.x = element_text(angle = 90))`.
+
+
+
+See example
+
+In the following plot the labels on the x-axis are overlapping.
+
+```{r msleep-order-sleep-total}
+ggplot(msleep, aes(x = order, y = sleep_total)) +
+ geom_boxplot()
+```
+
+- 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}
+ggplot(msleep, aes(x = order, y = sleep_total)) +
+ geom_boxplot() +
+ theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))
+```
+
+- Flip the axes: Use the y-axis for long labels.
+
+```{r msleep-order-sleep-total-flip}
+ggplot(msleep, aes(y = order, x = sleep_total)) +
+ geom_boxplot()
+```
+
+- 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}
+ggplot(msleep, aes(x = order, y = sleep_total)) +
+ geom_boxplot() +
+ scale_x_discrete(guide = guide_axis(n.dodge = 3))
+```
+
+- 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}
+ggplot(msleep, aes(x = order, y = sleep_total)) +
+ geom_boxplot() +
+ scale_x_discrete(guide = guide_axis(check.overlap = TRUE))
+```
+
+
+
+### How can I remove axis labels in ggplot2?
+
+Add a `theme()` layer and set relevant arguments, e.g. `axis.title.x`, `axis.text.x`, etc. to `element_blank()`.
+
+
+
+See example
+
+Suppose we want to remove the axis labels entirely.
+
+```{r ref.label="msleep-order-sleep-total"}
+```
+
+- Remove x or y axis labels: If you want to modify just one of the axes, you can do so by modifying the components of the `theme()`, setting the elements you want to remove to `element_blank()`. You would replace `x` with `y` for applying the same update to the y-axis. Note the distinction between `axis.title` and `axis.ticks` -- `axis.title` is the name of the variable and `axis.text` is the text accompanying each of the ticks.
+
+```{r}
+ggplot(msleep, aes(x = order, y = sleep_total)) +
+ geom_boxplot() +
+ theme(
+ axis.title.x = element_blank(),
+ axis.text.x = element_blank(),
+ axis.ticks.x = element_blank()
+ )
+```
+
+- Remove all axis labels: You can use `theme_void()` to remove all theming elements. Note that this might remove more features than you like. For finer control over the theme, see below.
+
+```{r}
+ggplot(msleep, aes(x = order, y = sleep_total)) +
+ geom_boxplot() +
+ theme_void()
+```
+
+
+
+### How can I add multi-row axis labels with a grouping variable?
+
+You can do this by either by using `interaction()` to map the interaction of the variable you're plotting and the grouping variable to the `x` or `y` aesthetic.
+
+
+
+See example
+
+Suppose you have the following data on sales for each quarter across two years.
+
+```{r}
+library(tibble)
+
+sales <- tribble(
+ ~value, ~quarter, ~year,
+ 10, "Q1", 2020,
+ 15, "Q2", 2020,
+ 15, "Q3", 2020,
+ 20, "Q4", 2020,
+ 10, "Q1", 2021,
+ 25, "Q2", 2021,
+ 30, "Q3", 2021,
+ 30, "Q4", 2021
+)
+```
+
+You can create a line plot of these data and facet by `year` to group the quarters for each year together.
+
+```{r}
+ggplot(sales, aes(x = quarter, y = value, group = 1)) +
+ geom_line() +
+ facet_wrap(~year)
+```
+
+However it might be preferable to plot all points in a single plot and indicate on the x-axis that the first Q1 to Q4 are in 2020 and the second are in 2021.
+
+To achieve this, map the `interaction()` of `quarter` and `year` to the `x` aesthetic.
+
+```{r}
+ggplot(sales, aes(x = interaction(quarter, year), y = value, group = 1)) +
+ geom_line()
+```
+
+This achieves the desired result for the line, however the labeling in the x-axis is very busy and difficult to read.
+To clean this up (1) clip the plotting area with `coord_cartesian()`, (2) remove the axis labels and add a wider margin at the bottom of the plot with `theme()`, (3) place axis labels indicating quarters underneath the plot, and (4) underneath those labels, place annotation indicating years.
+Note that the x-coordinates of the year labels are manually assigned here, but if you had many more years, you might write some logic to calculate their placement.
+
+```{r}
+ggplot(sales, aes(x = interaction(quarter, year), y = value, group = 1)) +
+ geom_line() +
+ coord_cartesian(ylim = c(9, 32), expand = FALSE, clip = "off") +
+ theme(
+ plot.margin = unit(c(1, 1, 3, 1), "lines"),
+ axis.title.x = element_blank(),
+ axis.text.x = element_blank()
+ ) +
+ annotate(geom = "text", x = seq_len(nrow(sales)), y = 8, label = sales$quarter, size = 3) +
+ annotate(geom = "text", x = c(2.5, 6.5), y = 6, label = unique(sales$year), size = 4)
+```
+
+This approach works with other geoms as well.
+For example, you might can create a bar plot representing the same data using the following.
+
+```{r}
+ggplot(sales, aes(x = interaction(quarter, year), y = value)) +
+ geom_col() +
+ coord_cartesian(ylim = c(0, 32), expand = FALSE, clip = "off") +
+ annotate(geom = "text", x = seq_len(nrow(sales)), y = -1, label = sales$quarter, size = 3) +
+ annotate(geom = "text", x = c(2.5, 6.5), y = -3, label = unique(sales$year), size = 4) +
+ theme(
+ plot.margin = unit(c(1, 1, 3, 1), "lines"),
+ axis.title.x = element_blank(),
+ axis.text.x = element_blank()
+ )
+```
+
+If it's undesirable to have the bars flush against the edges of the plot, a similar result can be achieved by leveraging faceting and removing the space between facets to create the appearance of a single plot.
+However note that the space between the bars for 2020 Q4 and 2021 Q1 is greater than the space between the other bars.
+
+```{r}
+ggplot(sales, aes(x = quarter, y = value)) +
+ geom_col() +
+ facet_wrap(~year, strip.position = "bottom") +
+ theme(
+ panel.spacing = unit(0, "lines"),
+ strip.background = element_blank(),
+ strip.placement = "outside"
+ ) +
+ labs(x = NULL)
+```
+
+
+
+## Label formatting and customization
+
+### How can I customize the text shown on the axis labels?
+
+Add a `scale_*()` layer, e.g. `scale_x_continuous()`, `scale_y_discrete()`, etc., and add custom labels to the `labels` argument.
+
+
+
+See example
+
+Suppose you want to give more informative labels for the type of drive train.
+
+```{r}
+ggplot(mpg, aes(y = drv)) +
+ geom_bar()
+```
+
+- Use the `labels` argument in the appropriate `scale_*()` function. You can find a list of these functions [here](https://ggplot2.tidyverse.org/reference/index.html#section-scales). Type of drive train (`drv`) is a discrete variable on the y-axis, so we'll adjust the labels in `scale_y_discrete()`. One option is to list the labels in the same order as the levels. Note that we start from the bottom and go up, just like we would if the variable was numeric/continuous.
+
+```{r}
+ggplot(mpg, aes(y = drv)) +
+ geom_bar() +
+ scale_y_discrete(
+ labels = c("Front wheel drive", "Rear wheel drive", "Four wheel drive")
+ )
+```
+
+- Another approach is to use a named list. This approach not only makes the relabelling more explicit, but it also means you don't need to worry about the order of the levels.
+
+```{r}
+ggplot(mpg, aes(y = drv)) +
+ geom_bar() +
+ scale_y_discrete(
+ labels = c(
+ "f" = "Front wheel drive",
+ "r" = "Rear wheel drive",
+ "4" = "Four wheel drive"
+ )
+ )
+```
+
+
+
+### How can I stop R from using scientific notation on axis labels?
+
+Use `scales::label_number()` to force decimal display of numbers.
+You will first need to add a `scale_*()` layer (e.g. `scale_x_continuous()`, `scale_y_discrete()`, etc.) and customise the `labels` argument within this layer with this function.
+
+
+
+See example
+
+By default, large numbers on the axis labels in the following plot are shown in scientific notation.
+
+```{r}
+ggplot(txhousing, aes(x = median, y = volume)) +
+ geom_point()
+```
+
+The [**scales**](https://scales.r-lib.org/) package offers a large number of functions to control the formatting of axis labels and legend keys.
+Use `scales::label_number()` to force decimal display of numbers rather than using scientific notation or use `scales::label_comma()` to insert a comma every three digits.
+
+```{r}
+library(scales)
+ggplot(txhousing, aes(x = median, y = volume)) +
+ geom_point() +
+ scale_x_continuous(labels = label_number()) +
+ scale_y_continuous(labels = label_comma())
+```
+
+
+
+### How can I change the number of decimal places on axis labels?
+
+Set the `accuracy` in `scales::label_number()` to the desired level of decimal places, e.g. 0.1 to show 1 decimal place, 0.0001 to show 4 decimal places, etc.
+You will first need to add a `scale_*()` layer (e.g. `scale_x_continuous()`, `scale_y_discrete()`, etc.) and customise the `labels` argument within this layer with this function.
+
+
+
+See example
+
+Suppose you want to increase/decrease the number of decimal spaces shown in the axis text in the following plot.
+
+```{r}
+ggplot(seals, aes(x = delta_long, y = delta_lat)) +
+ geom_point()
+```
+
+The [**scales**](https://scales.r-lib.org/) package offers a large number of functions to control the formatting of axis labels and legend keys.
+Use `scales::label_number()` where the `accuracy` argument indicates the number to round to, e.g. 0.1 to show 1 decimal place, 0.0001 to show 4 decimal places, etc.
+
+```{r}
+library(scales)
+ggplot(seals, aes(x = delta_long, y = delta_lat)) +
+ geom_point() +
+ scale_x_continuous(labels = label_number(accuracy = 0.1)) +
+ scale_y_continuous(labels = label_number(accuracy = 0.0001))
+```
+
+
+
+### How can I add percentage symbols (%) to axis labels?
+
+Use `scales::label_percent()`, which will place a `%` *after* the number, by default.
+You can customise where `%` is placed using the `prefix` and `suffix` arguments, and also `scale` the numbers if needed.
+You will first need to add a `scale_*()` layer (e.g. `scale_x_continuous()`, `scale_y_discrete()`, etc.) and customise the `labels` argument within this layer with this function.
+
+
+
+See example
+
+The variable on the y-axis of the following line plot (`psavert`) indicates the personal savings rate, which is in percentages.
+
+```{r}
+ggplot(economics, aes(x = date, y = psavert, group = 1)) +
+ geom_line()
+```
+
+With `scales::label_percent()` you can add `%`s after the numbers shown on the axis to make the units more clear.
+
+```{r}
+ggplot(economics, aes(x = date, y = psavert, group = 1)) +
+ geom_line() +
+ scale_y_continuous(labels = scales::label_percent(scale = 1, accuracy = 1))
+```
+
+where the `accuracy` argument indicates the number to round to, e.g. 0.1 to show 1 decimal place, 0.0001 to show 4 decimal places, etc.
+
+```{r}
+library(scales)
+ggplot(seals, aes(x = delta_long, y = delta_lat)) +
+ geom_point() +
+ scale_x_continuous(labels = label_number(accuracy = 0.1)) +
+ scale_y_continuous(labels = label_number(accuracy = 0.0001))
+```
+
+
+
+### How can I add superscripts and subscripts to axis labels?
+
+You can either use `bquote()` to parse mathematical expressions or use the [**ggtext**](https://wilkelab.org/ggtext/) package to write the expression using Markdown or HTML syntax.
+
+
+
+See example
+
+In the following plot `cty` is squared and `hwy` is log transformed.
+
+```{r}
+ggplot(mpg, aes(x = cty^2, y = log(hwy))) +
+ geom_point()
+```
+
+- Use `bquote()` function to parse mathematical expressions.
+
+```{r}
+ggplot(mpg, aes(x = cty^2, y = log(hwy, base = 10))) +
+ geom_point() +
+ labs(
+ x = bquote(cty^2),
+ y = bquote(paste(log[10], "(hwy)"))
+ )
+```
+
+- If you're already familiar with Markdown and HTML, you might prefer using the [ggtext](https://wilkelab.org/ggtext/) package instead. In Markdown we can write the axis labels as `cty2` and `log10(hwy)` for x and y axes, respectively. Then, we tell ggplot2 to interpret the axis labels as Markdown and not as plain text by setting `axis.title.x` and `axis.title.y` to `ggtext::element_markdown()`.
+
+```{r}
+ggplot(mpg, aes(x = cty^2, y = log(hwy, base = 10))) +
+ geom_point() +
+ labs(
+ x = "cty2",
+ y = "log10(hwy)"
+ ) +
+ theme(
+ axis.title.x = ggtext::element_markdown(),
+ axis.title.y = ggtext::element_markdown()
+ )
+```
+
+
+
+## Custom breaks
+
+### How can I increase / decrease the number of axis ticks?
+
+Customise the `breaks` and `minor_breaks` in `scale_x_continuous()`, `scale_y_continuous()`, etc.
+
+
+
+See example
+
+Suppose you want to customise the major and minor grid lines on both the x and the y axes of the following plot.
+
+```{r}
+ggplot(mpg, aes(x = cty, y = hwy)) +
+ geom_point()
+```
+
+You can set `breaks` and `minor_breaks` in `scale_x_continuous()` and `scale_y_continuous()` as desired.
+For example, on the x-axis we have major and minor grid breaks defined as a sequence and on the y-axis we have explicitly stated where major breaks should appear as a vector (the value stated are randomly selected for illustrative purposes only, they don't follow a best practice) and we have completely turned off minor grid lines by setting `minor_breaks` to `NULL`.
+
+```{r}
+ggplot(mpg, aes(x = cty, y = hwy)) +
+ geom_point() +
+ scale_x_continuous(
+ breaks = seq(9, 35, 3),
+ minor_breaks = seq(8.5, 35.5, 1)
+ ) +
+ scale_y_continuous(
+ breaks = c(12, 23, 36, 41),
+ minor_breaks = NULL
+ )
+```
+
+
+
+### How can I control the number of major and minor grid lines shown on the plot?
+
+Customise the `breaks` and `minor_breaks` in `scale_x_continuous()`, scale_y\_continuous()\`, etc.
+See [How can I increase / decrease the number of axis ticks?](#how-can-i-increase-decrease-the-number-of-axis-ticks-)
+for more detail.
+
+
+
+See example
+
+Note that the question was about grid lines but we answered it using breaks.
+This is because ggplot2 will place major grid lines at each break supplied to `breaks` and minor grid lines at each break supplied to `minor_breaks`.
+
+
+
+### How can I remove the space between the plot and the axis?
+
+Remove the padding around the data entirely using by setting `expand = c(0, 0)` within the `scale_x_continuous()`, `scale_y_discrete()`, etc. layers.
+
+
+
+See example
+
+- Remove all padding: Suppose you want to remove the padding around the heat map so it's flush against the axes.
+
+```{r}
+ggplot(faithfuld, aes(waiting, eruptions)) +
+ geom_raster(aes(fill = density))
+```
+
+Since both x and y variables are continuous, we set `expand = c(0, 0)` in both `scale_x_continuous()` and `scale_y_continuous()`.
+
+```{r}
+ggplot(faithfuld, aes(waiting, eruptions)) +
+ geom_raster(aes(fill = density)) +
+ scale_x_continuous(expand = c(0, 0)) +
+ scale_y_continuous(expand = c(0, 0))
+```
+
+- Remove some of the padding: Suppose you want to remove the padding below the bars and the x-axis only.
+
+```{r}
+ggplot(mpg, aes(drv)) +
+ geom_bar()
+```
+
+You would make this adjustment on `scale_y_continuous()` since that padding is in the vertical direction.
+
+```{r}
+ggplot(mpg, aes(drv)) +
+ geom_bar() +
+ scale_y_continuous(expand = c(0, 0))
+```
+
+However note that this removes the padding at the bottom of the bars as well as on top.
+By default, ggplot2 The expands the scale by 5% on each side for continuous variables and by 0.6 units on each side for discrete variables.
+To keep the default expansion on top while removing it at the bottom, you can use the following.
+The `mult` argument in `expansion()` takes a multiplicative range expansion factors.
+Given a vector of length 2, the lower limit is expanded by `mult[1]` (in this case 0) and the upper limit is expanded by `mult[2]` (in this case 0.05).
+
+```{r}
+ggplot(mpg, aes(drv)) +
+ geom_bar() +
+ scale_y_continuous(expand = expansion(mult = c(0, 0.05)))
+```
+
+
diff --git a/vignettes/articles/faq-bars.Rmd b/vignettes/articles/faq-bars.Rmd
new file mode 100644
index 0000000000..3fbd501ce9
--- /dev/null
+++ b/vignettes/articles/faq-bars.Rmd
@@ -0,0 +1,374 @@
+---
+title: "FAQ: Barplots"
+---
+
+```{=html}
+
+```
+```{r, include = FALSE}
+library(ggplot2)
+library(dplyr)
+library(tidyr)
+
+knitr::opts_chunk$set(
+ fig.dpi = 300,
+ collapse = TRUE,
+ comment = "#>",
+ fig.asp = 0.618,
+ fig.width = 6,
+ out.width = "80%"
+ )
+```
+
+## Colors
+
+### How can I change the color of the bars in my bar plot?
+
+If using the same color for all bars, define the `fill` argument in `geom_bar()` (or `geom_col()`).
+If assigning color based on another variable, map the variable to the `fill` `aes`thetic, and if needed, use one of the `scale_fill_*()` functions to set colors.
+
+
+
+See example
+
+You can set all bars to be a given color with the `fill` argument of `geom_bar()`.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar(fill = "blue")
+```
+
+Alternatively, if the colors should be based on a variable, this should be should happen in the `aes()` mapping.
+
+```{r}
+ggplot(mpg, aes(x = drv, fill = drv)) +
+ geom_bar()
+```
+
+And if you want to then customize the colors, one option is `scale_fill_manual()`, which allows you to manually assign colors to each bar.
+See other `scale_fill_*()` functions for more options for color choices.
+
+```{r}
+ggplot(mpg, aes(x = drv, fill = drv)) +
+ geom_bar() +
+ scale_fill_manual(values = c("purple", "orange", "darkblue"))
+```
+
+
+
+## Spacing and widths
+
+### How can I increase the space between the bars in my bar plot?
+
+Set the `width` of `geom_bar()` to a small value to obtain narrower bars with more space between them.
+
+
+
+See example
+
+By default, the `width` of bars is `0.9` (90% of the resolution of the data).
+You can set this argument to a lower value to get bars that are narrower with more space between them.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar(width = 0.5)
+
+ggplot(mpg, aes(x = drv)) +
+ geom_bar(width = 0.1)
+```
+
+
+
+### How can I remove the space between the bars and the x-axis?
+
+Adjust the `expand` argument in `scale_y_continuous()`, e.g. add `scale_y_continuous(expand = expansion(mult = c(0, 0.05)))` to remove the expansion on the lower end of the y-axis but keep the expansion on the upper end of the y-axis at 0.05 (the default expansion for continuous scales).
+
+
+
+See example
+
+By default ggplot2 expands the axes so the geoms aren't flush against the edges of the plot.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar()
+```
+
+To remove the spacing between the bars and the x-axis, but keep the spacing between the bars and the top of the plot, use the following.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar() +
+ scale_y_continuous(expand = expansion(mult = c(0, 0.05)))
+```
+
+To achieve the opposite, switch the values in `mult`.
+Note that the tallest bar is now flush against top of the plot.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar() +
+ scale_y_continuous(expand = expansion(mult = c(0.05, 0)))
+```
+
+To adjust spacing around the x-axis, adjust the `expand` argument in `scale_x_discrete()`.
+Note that this places the bars flush against the left side and leaves some space on the right side.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar() +
+ scale_x_discrete(expand = expansion(add = c(0, 0.6)))
+```
+
+The default look of a bar plot can be achieved with the following.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar() +
+ scale_x_discrete(expand = expansion(add = 0.6)) +
+ scale_y_continuous(expand = expansion(mult = 0.05))
+```
+
+
+
+### How do I ensure that bars on a dodged bar plot have the same width?
+
+Set `position = position_dodge2(preserve = "single")` in `geom_bar()`.
+
+
+
+See example
+
+In the following plot the bars have differing widths within each level of `drv` as there are differing levels of `class` represented.
+
+```{r}
+ggplot(mpg, aes(x = drv, fill = class)) +
+ geom_bar(position = "dodge")
+```
+
+You can use `position_dodge2()` with `preserve = "single"` to address this.
+
+```{r}
+ggplot(mpg, aes(x = drv, fill = class)) +
+ geom_bar(position = position_dodge2(preserve = "single"))
+```
+
+
+
+## Stacked bar plots
+
+### How can I create a stacked bar plot displaying a conditional distribution where each stack is scaled to sum to 100%?
+
+Use `position = "fill"` in `geom_bar()` or `geom_col()`.
+If you also want to show percentages on the axis, use `scales::label_percent()`.
+
+
+
+See example
+
+The following plot is useful for comparing counts but not as useful for comparing proportions, which is what you need if you want to be able to make statements like "in this sample, it's more likely to have a two-seater car that has rear-wheel drive than an SUV that has rear-wheel drive".
+
+```{r}
+ggplot(mpg, aes(y = class, fill = drv)) +
+ geom_bar()
+```
+
+`position = "fill"` will generate a bar plot with bars of equal length and the stacks in each bar will show the proportion of `drv` for that particular `class`.
+
+```{r}
+ggplot(mpg, aes(y = class, fill = drv)) +
+ geom_bar(position = "fill")
+```
+
+If you want to show percentages instead of proportions on the x-axis, you can define this in `scale_x_continuous()` with `scales::label_percent()`.
+
+```{r}
+ggplot(mpg, aes(y = class, fill = drv)) +
+ geom_bar(position = "fill") +
+ scale_x_continuous(name = "percentage", labels = scales::label_percent(accuracy = 1))
+```
+
+
+
+### How can I create a stacked bar plot based on data from a contingency table of to categorical variables?
+
+First reshape the data (e.g. with `tidyr::pivot_longer()`) so that there is one row per each combination of the levels of the categorical variables, then use `geom_col()` to draw the bars.
+
+
+
+See example
+
+Suppose you have the following data from an opinion poll, where the numbers in the cells represent the number of responses for each party/opinion combination.
+
+```{r}
+poll <- tribble(
+ ~party, ~agree, ~disagree, ~no_opinion,
+ "Democrat", 20, 30, 20,
+ "Republican", 15, 20, 10,
+ "Independent", 10, 5, 0
+)
+```
+
+You can first pivot the data longer to obtain a data frame with one row per party/opinion combination and a new column, `n`, for the number of responses that fall into that category.
+
+```{r}
+poll_longer <- poll %>%
+ pivot_longer(
+ cols = -party,
+ names_to = "opinion",
+ values_to = "n"
+ )
+
+poll_longer
+```
+
+Then, you can pass this result to `ggplot()` and create a bar for each `party` on the `y` (or `x`, if you prefer vertical bars) axis and fill the bars in with number of responses for each `opinion`.
+
+```{r}
+ggplot(poll_longer, aes(y = party, fill = opinion, x = n)) +
+ geom_col()
+```
+
+To plot proportions (relative frequencies) instead of counts, use `position = "fill"` in `geom_col()`.
+
+```{r}
+ggplot(poll_longer, aes(y = party, fill = opinion, x = n)) +
+ geom_col(position = "fill") +
+ xlab("proportion")
+```
+
+
+
+### How can I make a grouped bar plot?
+
+Map the variable you want to group by to the `x` or `y` `aes`thetic, map the variable you want to color the vars by to the `fill` aesthetic, and set `position = "dodge"` in `geom_bar()`.
+
+
+
+See example
+
+Suppose you have data from a survey with three questions, where respondents select "Agree" or "Disagree" for each question.
+
+```{r}
+survey <- tibble::tribble(
+ ~respondent, ~q1, ~q2, ~q3,
+ 1, "Agree", "Agree", "Disagree",
+ 2, "Disagree", "Agree", "Disagree",
+ 3, "Agree", "Agree", "Disagree",
+ 4, "Disagree", "Disagree", "Agree"
+)
+```
+
+You'll first want to reshape these data so that each row represents a respondent / question pair.
+You can do this with `tidyr::pivot_longer()`.
+Then, pass the resulting longer data frame to `ggplot()` group responses for each question together.
+
+```{r}
+survey %>%
+ tidyr::pivot_longer(
+ cols = -respondent,
+ names_to = "question",
+ values_to = "response"
+ ) %>%
+ ggplot(aes(x = question, fill = response)) +
+ geom_bar(position = "dodge")
+```
+
+
+
+### How can I make a bar plot of group means?
+
+Either calculate the group means first and use `geom_col()` to draw the bars or let ggplot2 calculate the means with `stat_summary()` with `fun = "mean"` and `geom = "bar"`.
+
+
+
+See example
+
+One option for calculating group means is using `dplyr::group_by()` followed by `dplyr::summarise()`.
+Then, you can pass the resulting data frame to `ggplot()` and plot bars using `geom_col()`.
+
+```{r}
+mpg %>%
+ group_by(drv) %>%
+ summarise(mean_hwy = mean(hwy)) %>%
+ ggplot(aes(x = drv, y = mean_hwy)) +
+ geom_col()
+```
+
+Alternatively, you can use `stat_summary()` to let ggplot2 calculate and plot the means.
+
+```{r}
+ggplot(mpg, aes(x = drv, y = hwy)) +
+ stat_summary(fun = "mean", geom = "bar")
+```
+
+
+
+## Axes and axis limits
+
+### Why do the bars on my plot disappear when I specify an axis range with `ylim()`? How can I get the bars to show up within a given axis range?
+
+`ylim()` is a shortcut for supplying the `limits` argument to individual scales.
+When either of these is set, any values outside the limits specified are replaced with `NA`.
+Since the bars naturally start at `y = 0`, replacing part of the bars with `NA`s results in the bars entirely disappearing from the plot.
+For changing axis limits without dropping data observations, set limits in `coord_cartesian()` instead.
+Also note that this will result in a deceiving bar plot, which should be avoided in general.
+
+
+
+See example
+
+In the following plot the y-axis is limited to 20 to 120, and hence the bars are not showing up.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar() +
+ ylim(c(20, 120))
+```
+
+In order to obtain a bar plot with limited y-axis, you need to instead set the limits in `coord_cartesian()`.
+
+```{r}
+ggplot(mpg, aes(x = drv)) +
+ geom_bar() +
+ coord_cartesian(ylim = c(20,110))
+```
+
+This is, indeed, a deceiving plot.
+If you're using a bar plot to display values that could not take the value of 0, you might choose a different geom instead.
+For example, if you have the following data and plot.
+
+```{r}
+df <- tibble::tribble(
+ ~x, ~y,
+ "A", 1050,
+ "B", 1100,
+ "C", 1150
+)
+
+ggplot(df, aes(x = x, y = y)) +
+ geom_col()
+```
+
+Also suppose that you want to cut off the bars at `y = 1000` since you know that the variable you're plotting cannot take a value less than 1000, you might use `geom_point()` instead.
+
+```{r}
+# don't do this
+ggplot(df, aes(x = x, y = y)) +
+ geom_col() +
+ coord_cartesian(ylim = c(1000, 1150))
+
+# do this
+ggplot(df, aes(x = x, y = y)) +
+ geom_point(size = 3)
+```
+
+
diff --git a/vignettes/articles/faq-customising.Rmd b/vignettes/articles/faq-customising.Rmd
new file mode 100644
index 0000000000..a8780e3ad7
--- /dev/null
+++ b/vignettes/articles/faq-customising.Rmd
@@ -0,0 +1,450 @@
+---
+title: "FAQ: Customising"
+---
+
+```{=html}
+
+```
+```{r, include = FALSE}
+library(ggplot2)
+library(tibble)
+knitr::opts_chunk$set(
+ fig.dpi = 300,
+ collapse = TRUE,
+ comment = "#>",
+ fig.asp = 0.618,
+ fig.width = 6,
+ out.width = "80%"
+ )
+```
+
+## Legends
+
+### How can I change the legend title?
+
+Change the label for the aesthetic the legend is drawn for in `labs()`.
+
+
+
+See example
+
+By default your legend label will be the name of the variable that is mapped to the aesthetic the legend is drawn for.
+You can change the title of your legend using `labs()`.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = drv)) +
+ geom_point() +
+ labs(color = "Drive train")
+```
+
+If a legend is drawn for multiple aesthetics, you'll want to update the title for all of them.
+
+```{r}
+# not this
+ggplot(mpg, aes(x = hwy, y = cty, color = drv, shape = drv)) +
+ geom_point() +
+ labs(color = "Drive train")
+
+# but this
+ggplot(mpg, aes(x = hwy, y = cty, color = drv, shape = drv)) +
+ geom_point() +
+ labs(color = "Drive train", shape = "Drive train")
+```
+
+
+
+### How can I increase the spacing between legend keys?
+
+Increase the horizontal space between legend keys with `legend.spacing.x` in `theme()`.
+This argument takes a unit object created with `grid::unit()`.
+
+
+
+See example
+
+If you have a horizontal legend, generally placed on top or bottom of the plot with `legend.position = "top"` or `"bottom"`, you can change the spacing between legend keys with `legend.spacing.x`.
+You can supply a unit object to this argument, e.g. `unit(1.0, "cm")` for 1 cm space between legend keys.
+See the documentation for `grid::unit()` for more options for units.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = drv)) +
+ geom_point() +
+ theme(
+ legend.position = "bottom",
+ legend.spacing.x = unit(1.0, "cm")
+ )
+```
+
+For vertical legends changing `legend.spacing.y` changes the space between the legend title and the keys, but not between the keys, e.g. see the large space between the legend title and keys.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = drv)) +
+ geom_point() +
+ theme(legend.spacing.y = unit(3.0, "cm"))
+```
+
+In order to change the space between the legend keys, you can first make the key size bigger with `legend.key.size` and then remove the grey background color with `legend.key`.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = drv)) +
+ geom_point() +
+ theme(
+ legend.key.size = unit(1.5, "cm"),
+ legend.key = element_rect(color = NA, fill = NA)
+ )
+```
+
+Note that the legend title is no longer aligned with the keys with this approach.
+You can also shift it over with `legend.title.align`.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = drv)) +
+ geom_point() +
+ theme(
+ legend.key.size = unit(1.5, "cm"),
+ legend.key = element_rect(color = NA, fill = NA),
+ legend.title.align = 0.5
+ )
+```
+
+
+
+### How can I change the key labels in the legend?
+
+If you don't want to change the levels of the variable the legend is being drawn for, you can change the key labels at the time of drawing the plot using the `labels` argument in the appropriate `scale_*()` function, e.g. `scale_colour_discrete()` if the legend is for a discrete variable mapped to the fill aesthetic.
+
+
+
+See example
+
+The `labels` argument of `scale_*` functions takes named vectors, which what we would recommend using for relabeling keys in a legend.
+Using named lists allows you to declare explicitly which label is assigned to which level, without having to keep track of level order.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = drv)) +
+ geom_point() +
+ scale_color_discrete(
+ labels = c("4" = "4-wheel drive",
+ "f" = "Front-wheel drive",
+ "r" = "Rear-wheel drive")
+ )
+```
+
+
+
+### How can I change the font sizes in the legend?
+
+Set your preference in `legend.text` for key labels and `legend.title` in `theme()`.
+In both cases, set font size in the `size` argument of `element_text()`, e.g. `legend.text = element_text(size = 14)`.
+
+
+
+See example
+
+Font characteristics of a legend can be controlled with the `legend.text` and `legend.title` elements of `theme()`.
+You can use the following for 14 pts text for legend key labels and 10 pts text for legend title.
+(Note that this doesn't result in a visually pleasing legend, by default ggplot2 uses a larger font size for the legend title than the legend text.)
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = class)) +
+ geom_point() +
+ theme(
+ legend.text = element_text(size = 14),
+ legend.title = element_text(size = 10)
+ )
+```
+
+For further customization of legend text, see the documentation for `element_text()`, e.g. you can change font colors or font face as well.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = class)) +
+ geom_point() +
+ theme(
+ legend.text = element_text(size = 14, color = "red"),
+ legend.title = element_text(size = 10, face = "bold.italic")
+ )
+```
+
+
+
+## Colours
+
+### How can I change the background colour of plot?
+
+Set the color in `panel.background` element of `theme()` with `element_rect()`, which takes arguments like `fill` (for background fill color) and `colour` (for background border color.
+
+
+
+See example
+
+You can set the background colour of the plot with `panel.backgroun` in `theme()`.
+In the following example the border is made thicker with `size = 3` to
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ theme(panel.background = element_rect(fill = "lightblue", colour = "red", size = 3))
+```
+
+If you want to change the colour of the plotting area but not the panel where the panel, you can so the same thing with `plot.background`.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ theme(plot.background = element_rect(fill = "lightblue", colour = "red", size = 3))
+```
+
+Note that ggplot2 has a variety of [complete themes](https://ggplot2.tidyverse.org/reference/ggtheme.html) that might already do what you're hoping to accomplish.
+For example, if you prefer a more minimal look to your plots, without the grey background, you might try `theme_minimal()`.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ theme_minimal()
+```
+
+And you can continue customization based on one of these themes.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ theme_minimal() +
+ theme(plot.background = element_rect(colour = "red", size = 3))
+```
+
+You might also find the [**thematic**](https://rstudio.github.io/thematic/) package useful for simplified theming of your plots.
+
+
+
+### How can I change the colour NAs are represented with in a plot?
+
+You can set the color of `NA` with the `na.value` argument in the appropriate `scale_*()` function, e.g. `scale_fill_discrete(na.value = "purple")` to make `NA`s purple.
+
+
+
+See example
+
+Suppose you have the following data frame with two discrete variables, one of which has an `NA`.
+
+```{r}
+df <- tibble::tribble(
+ ~group, ~outcome,
+ 1, "yes",
+ 1, "no",
+ 2, "yes",
+ 2, "no",
+ 2, "no",
+ 2, NA
+)
+```
+
+By default, ggplot2 uses grey to represent `NA`s.
+
+```{r}
+ggplot(df, aes(x = group, fill = outcome)) +
+ geom_bar()
+```
+
+You can change the color of `NA` with `scale_fill_discrete()` in this case, e.g. make it purple.
+
+```{r}
+ggplot(df, aes(x = group, fill = outcome)) +
+ geom_bar() +
+ scale_fill_discrete(na.value = "purple")
+```
+
+You can also set the color to `"transparent"`.
+In the plot below this is shown with `theme_minimal()` to demonstrate how that looks on a plot with a transparent background.
+Note that while this is possible, setting the colour to transparent as such wouldn't be recommended in this particular case as it gives the appearance of a floating bar.
+
+```{r}
+ggplot(df, aes(x = group, fill = outcome)) +
+ geom_bar() +
+ scale_fill_discrete(na.value = "transparent") +
+ theme_minimal()
+```
+
+
+
+## Fonts
+
+### How can I change the default font size in ggplot2?
+
+Set `base_size` in the theme you're using, which is `theme_gray()` by default.
+
+
+
+See example
+
+The base font size is 11 pts by default.
+You can change it with the `base_size` argument in the theme you're using.
+See the [complete theme documentation](https://ggplot2.tidyverse.org/reference/ggtheme.html) for more high level options you can set.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty, color = class)) +
+ geom_point() +
+ theme_gray(base_size = 18)
+```
+
+If you would like all plots within a session/document to use a particular base size, you can set it with `theme_set()`.
+Run the following at the beginning of your session or include on top of your R Markdown document.
+
+```{r eval = FALSE}
+theme_set(theme_gray(base_size = 18))
+```
+
+
+
+### How can I change the font size of the plot title and subtitle?
+
+Set your preference in `plot.title` and `plot.subtitle` in `theme()`.
+In both cases, set font size in the `size` argument of `element_text()`, e.g. `plot.title = element_text(size = 20)`.
+
+
+
+See example
+
+Font characteristics of plot titles and subtitles can be controlled with the `plot.title` and `plot.subtitle` elements of `theme()`.
+You can use the following for 20 pts text for the plot title and 15 pts text for the plot subtitle.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ labs(
+ title = "This is the plot title",
+ subtitle = "And this is the subtitle"
+ ) +
+ theme(
+ plot.title = element_text(size = 20),
+ plot.subtitle = element_text(size = 15)
+ )
+```
+
+For further customization of plot title and subtitle, see the documentation for `element_text()`, e.g. you can change font colors or font face as well.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ labs(
+ title = "This is the plot title",
+ subtitle = "And this is the subtitle"
+ ) +
+ theme(
+ plot.title = element_text(size = 20, color = "red"),
+ plot.subtitle = element_text(size = 15, face = "bold.italic")
+ )
+```
+
+
+
+### How can I change the font size of axis labels?
+
+Set your preference in `axis.title`.
+`axis.title.x`, or `axis.title.y` in `theme()`.
+In both cases, set font size in the `size` argument of `element_text()`, e.g. `axis.text = element_text(size = 14)`.
+
+
+
+See example
+
+Font characteristics of axis labels can be controlled with `axis.title.x` or `axis.title.y` (or `axis.title` if you the same settings for both axes).
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ labs(
+ x = "This is HUGE",
+ y = "This is small"
+ ) +
+ theme(
+ axis.title.x = element_text(size = 20),
+ axis.title.y = element_text(size = 10)
+ )
+```
+
+For further customization of plot title and subtitle, see the documentation for `element_text()`, e.g. you can change font colors or font face as well.
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ labs(
+ x = "This is HUGE",
+ y = "This is tiny"
+ ) +
+ theme(
+ axis.title.x = element_text(size = 20, color = "red"),
+ axis.title.y = element_text(size = 10, face = "bold.italic")
+ )
+```
+
+You can also change the size of the axis text (e.g. numbers at the axis ticks) using `axis.text` (or `axis.text.x` and `axis.text.y` if you want to set different sizes).
+
+```{r}
+ggplot(mpg, aes(x = hwy, y = cty)) +
+ geom_point() +
+ labs(
+ x = "The axis labels are the same size",
+ y = "The axis labels are the same size"
+ ) +
+ theme(
+ axis.title = element_text(size = 16),
+ axis.text = element_text(size = 20, color = "blue")
+ )
+```
+
+
+
+### What is the default size of `geom_text()` and how can I change the font size of `geom_text()`?
+
+The default font size of `geom_text()` is 3.88.
+
+```{r}
+GeomLabel$default_aes$size
+```
+
+You can change the size using the `size` argument in `geom_text()` for a single plot. If you want to use the same updated size, you can set this with `update_geom_defaults()`, e.g. `update_geom_defaults("text", list(size = 6))`.
+
+
+
+See example
+
+Suppose you have the following data frame and visualization.
+
+```{r}
+df <- tibble::tribble(
+ ~x, ~y, ~name,
+ 2, 2, "two",
+ 3, 3, "three",
+ 4, 4, "four"
+)
+
+ggplot(df, aes(x = x, y = y, label = name)) +
+ geom_text()
+```
+
+You can set the size of the text with the following.
+
+```{r}
+ggplot(df, aes(x = x, y = y, label = name)) +
+ geom_text(size = 6)
+```
+
+Or you can map it to the `size` `aes`thetic. In the following size is determined by the `x` value with `scale_size_identity()`.
+
+```{r}
+ggplot(df, aes(x = x, y = y, label = name)) +
+ geom_text(aes(size = x)) +
+ scale_size_identity()
+```
+
+If you want to use the same updated size for `geom_text()` in a series of plots in a session/R Markdown document, you can set use `update_geom_defaults()` to update the default size, e.g. if you want the size for all `geom_text()` to be 6, use `update_geom_defaults("text", list(size = 6))`.
+
+
diff --git a/vignettes/articles/faq-faceting.Rmd b/vignettes/articles/faq-faceting.Rmd
new file mode 100644
index 0000000000..d84d1da19a
--- /dev/null
+++ b/vignettes/articles/faq-faceting.Rmd
@@ -0,0 +1,268 @@
+---
+title: "FAQ: Faceting"
+---
+
+```{=html}
+
+```
+```{r, include = FALSE}
+library(ggplot2)
+knitr::opts_chunk$set(
+ fig.dpi = 300,
+ collapse = TRUE,
+ comment = "#>",
+ fig.asp = 0.618,
+ fig.width = 6,
+ out.width = "80%")
+```
+
+## Panes
+
+### What is the difference between `facet_wrap()` and `facet_grid()`?
+
+The simplest answer is that you should use `facet_wrap()` when faceting by a single variable and `facet_grid()` when faceting by two variables and want to create a grid of panes.
+
+
+
+See example
+
+`facet_wrap()` is most commonly used to facet by a plot by a single categorical variable.
+
+```{r}
+ggplot(mpg, aes(x = cty)) +
+ geom_histogram() +
+ facet_wrap(~ drv)
+```
+
+And `facet_grid()` is commonly used to facet by a plot by two categorical variables.
+
+```{r}
+ggplot(mpg, aes(x = cty)) +
+ geom_histogram() +
+ facet_grid(cyl ~ drv)
+```
+
+Notice that this results in some empty panes (e.g. 4-wheel drive and 5 cylinders) as there are no cars in the `mpg` dataset that fall into such categories.
+
+You can also use `facet_wrap()` with to facet by two categorical variables.
+This will only create facets for combinations of the levels of variables for which data exists.
+
+```{r}
+ggplot(mpg, aes(x = cty)) +
+ geom_histogram() +
+ facet_wrap(cyl ~ drv)
+```
+
+In `facet_wrap()` you can control the number of rows and/or columns of the resulting plot layout using the `nrow` and `ncol` arguments, respectively.
+In `facet_grid()` these values are determined by the number of levels of the variables you're faceting by.
+
+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%"}
+ggplot(mpg, aes(x = cty)) +
+ geom_histogram() +
+ facet_grid(cyl ~ .)
+
+ggplot(mpg, aes(x = cty)) +
+ geom_histogram() +
+ facet_grid(. ~ cyl)
+```
+
+
+
+### How can I place a vertical lines (`geom_vline()`) in each pane of a faceted plot?
+
+First, calculate where the lines should be placed and save this information in a separate data frame.
+Then, add a `geom_vline()` layer to your plot that uses the summarized data.
+
+
+
+See example
+
+Suppose you have the following plot, and you want to add a vertical line at the mean value of `hwy` (highway mileage) for each pane.
+
+```{r}
+ggplot(mpg, aes(x = hwy)) +
+ geom_histogram(binwidth = 5) +
+ facet_wrap(~ drv)
+```
+
+First, calculate these means and save them in a new data frame.
+
+```{r}
+library(dplyr)
+
+mpg_summary <- mpg %>%
+ group_by(drv) %>%
+ summarise(hwy_mean = mean(hwy))
+
+mpg_summary
+```
+
+Then, add a `geom_vline()` layer to your plot that uses the summary data.
+
+```{r}
+ggplot(mpg, aes(x = hwy)) +
+ geom_histogram(binwidth = 5) +
+ facet_wrap(~ drv) +
+ geom_vline(data = mpg_summary, aes(xintercept = hwy_mean))
+```
+
+
+
+## Axes
+
+### How can I set individual axis limits for facets?
+
+Either let ggplot2 determine custom axis limits for the facets based on the range of the data you're plotting using the `scales` argument in `facet_wrap()` or `facet_grid()` or, if that is not sufficient, use `expand_limits()` to ensure limits include a single value or a range of values.
+
+
+
+See example
+
+Suppose you have the following faceted plot.
+By default, both x and y scales are shared across the facets.
+
+```{r}
+ggplot(mpg, aes(x = cty, y = hwy)) +
+ geom_point() +
+ facet_grid(cyl ~ drv)
+```
+
+You can control this behaviour with the `scales` argument of faceting functions: varying scales across rows (`"free_x"`), columns (`"free_y"`), or both rows and columns (`"free"`), e.g.
+
+```{r}
+ggplot(mpg, aes(x = cty, y = hwy)) +
+ geom_point() +
+ facet_grid(cyl ~ drv, scales = "free")
+```
+
+If you also want to make sure that a particular value or range is included in each of the facets, you can set this with `expand_limits()`, e.g. ensure that 10 is included in the x-axis and values between 20 to 25 are included in the y-axis:
+
+```{r}
+ggplot(mpg, aes(x = cty, y = hwy)) +
+ geom_point() +
+ facet_grid(cyl ~ drv, scales = "free") +
+ expand_limits(x = 10, y = c(20, 25))
+```
+
+
+
+## Facet labels
+
+### How can I remove the facet labels entirely?
+
+Set the `strip.text` element in `theme()` to `element_blank()`.
+
+
+
+See example
+
+Setting `strip.text` to `element_blank()` will remove all facet labels.
+
+```{r}
+ggplot(mpg, aes(x = cty, y = hwy)) +
+ geom_point() +
+ facet_grid(cyl ~ drv) +
+ theme(strip.text = element_blank())
+```
+
+You can also remove the labels across rows only with `strip.x.text` or across columns only with `strip.y.text`.
+
+```{r}
+ggplot(mpg, aes(x = cty, y = hwy)) +
+ geom_point() +
+ facet_grid(cyl ~ drv) +
+ theme(strip.text.x = element_blank())
+```
+
+
+
+### The facet labels in my plot are too long so they get cut off. How can I wrap facet label text so that long labels are spread across two rows?
+
+Use `label_wrap_gen()` in the `labeller` argument of your faceting function and set a `width` (number of characters) for the maximum number of characters before wrapping the strip.
+
+
+
+See example
+
+In the data frame below we have 100 observations, 50 of them come from one group and 50 from another.
+These groups have very long names, and so when you facet the ploy by group, the facet labels (strips) get cut off.
+
+```{r}
+df <- data.frame(
+ x = rnorm(100),
+ group = c(rep("A long group name for the first group", 50),
+ rep("A muuuuuuuuuuuuuch longer group name for the second group", 50))
+)
+
+ggplot(df, aes(x = x)) +
+ geom_histogram(binwidth = 0.5) +
+ facet_wrap(~ group)
+```
+
+You can control the maximum width of the facet label by setting the `width` in the `label_wrap_gen()` function, which is then passed to the `labeller` argument of your faceting function.
+
+```{r}
+ggplot(df, aes(x = x)) +
+ geom_histogram(binwidth = 0.5) +
+ facet_wrap(~ group, labeller = labeller(group = label_wrap_gen(width = 25)))
+```
+
+
+
+### How can I set different axis labels for facets?
+
+Use `as_labeller()` in the `labeller` argument of your faceting function and then set `strip.background` and `strip.placement` elements in the `theme()` to place the facet labels where axis labels would go.
+This is a particularly useful solution for plotting data on different scales without the use of double y-axes.
+
+
+
+See example
+
+Suppose you have data price data on a given item over a few years from two countries with very different currency scales.
+
+```{r}
+df <- data.frame(
+ year = rep(2016:2021, 2),
+ price = c(10, 10, 13, 12, 14, 15, 1000, 1010, 1200, 1050, 1105, 1300),
+ country = c(rep("US", 6), rep("Japan", 6))
+)
+
+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}
+ggplot(df, aes(x = year, y = price)) +
+ geom_smooth() +
+ facet_wrap(~ country, ncol = 1, scales = "free_y") +
+ scale_x_continuous(breaks = 2011:2020)
+```
+
+With the following you can customize the facet labels first with `as_labeller()`, turn off the default y-axis label, and then place the facet labels where the y-axis label goes (`"outside"` and on the `"left"`).
+
+```{r}
+ggplot(df, aes(x = year, y = price)) +
+ geom_smooth() +
+ facet_wrap(~ country, ncol = 1, scales = "free_y",
+ labeller = as_labeller(
+ c(US = "US Dollars (USD)", Japan = "Japanese Yens (JPY)")),
+ strip.position = "left"
+ ) +
+ scale_x_continuous(breaks = 2011:2020) +
+ labs(y = NULL) +
+ theme(strip.background = element_blank(), strip.placement = "outside")
+```
+
+
diff --git a/vignettes/articles/faq-reordering.Rmd b/vignettes/articles/faq-reordering.Rmd
new file mode 100644
index 0000000000..fc2a9a4a38
--- /dev/null
+++ b/vignettes/articles/faq-reordering.Rmd
@@ -0,0 +1,220 @@
+---
+title: "FAQ: Reordering"
+---
+
+```{=html}
+
+```
+```{r, include = FALSE}
+library(ggplot2)
+library(dplyr)
+library(tibble)
+
+knitr::opts_chunk$set(
+ fig.dpi = 300,
+ collapse = TRUE,
+ comment = "#>",
+ fig.asp = 0.618,
+ fig.width = 6,
+ out.width = "80%"
+ )
+```
+
+## Bar plots
+
+### How can I reorder the bars in a bar plot by their value?
+
+Change the order of the levels of the factor variable you're creating the bar plot for in the `aes`thetic `mapping`.
+The forcats package offers a variety of options for doing this, such as `forcats::fct_infreq()` for ordering by the number of observations within each level.
+
+
+
+See example
+
+The following bar plot shows the number of cars that fall into each `class` category.
+Classes are ordered alphabetically.
+You might prefer them to be ordered by the number of cars in each class.
+
+```{r}
+ggplot(mpg, aes(y = class)) +
+ geom_bar()
+```
+
+To do this, you can use `forcats::fct_infreq()`.
+
+```{r}
+ggplot(mpg, aes(y = forcats::fct_infreq(class))) +
+ geom_bar()
+```
+
+If you'd like to plot the highest value first, you can also reverse the order with `forcats::fct_rev()`. You might also want to simplify the axis label.
+
+```{r}
+ggplot(mpg, aes(y = forcats::fct_rev(forcats::fct_infreq(class)))) +
+ geom_bar() +
+ labs(y = "class")
+```
+
+
+
+### How can I reorder the stacks in a stacked bar plot?
+
+Change the order of the levels of the factor variable you're creating the stacks with in the `aes`thetic `mapping`.
+The forcats package offers a variety of options for doing this, such as `forcats::fct_reorder()` to reorder the levels or `forcats::fct_rev()` to reverse their order.
+
+
+
+See example
+
+Suppose you have the following stacked bar plot of `clarity` of `diamonds` by their `cut`.
+
+```{r}
+ggplot(diamonds, aes(x = cut, fill = clarity)) +
+ geom_bar()
+```
+
+You can revers the order `clarity` levels are displayed in the bars with `forcats::fct_rev()`.
+This will also change the order they're presented in the legend so the two orders match.
+
+```{r}
+ggplot(diamonds, aes(x = cut, fill = forcats::fct_rev(clarity))) +
+ geom_bar() +
+ labs(fill = "clarity")
+```
+
+
+
+## Box plots
+
+### How can I control the order of boxes in a side-by-side box plot?
+
+Change the order of the levels of the factor variable you're faceting by.
+The forcats package offers a variety of options for doing this, such as `forcats::fct_relevel()` for manual reordering or `forcats::fct_reorder()` for ordering by a particular value, e.g. group median.
+
+
+
+See example
+
+The order of the boxes is determined by the order of the levels of the variable you're grouping by.
+If the faceting variable is character, this order is alphabetical by default.
+
+```{r}
+ggplot(mpg, aes(x = class, y = hwy)) +
+ geom_boxplot()
+```
+
+Suppose you'd like the boxes to be ordered in ascending order of their medians.
+You can do this in a data transformation step prior to plotting (e.g. with `dplyr::mutate()`) or you can do it directly in the plotting code as shown below.
+You might then want to customize the x-axis label as well.
+
+```{r}
+ggplot(mpg, aes(x = forcats::fct_reorder(class, hwy, .fun = median), y = hwy)) +
+ geom_boxplot() +
+ labs(x = "class")
+```
+
+
+
+## Facets
+
+### How can I control the order of panes created with `facet_wrap()` or `facet_grid()`?
+
+Change the order of the levels of the factor variable you're faceting by.
+The forcats package offers a variety of options for doing this, such as `forcats::fct_relevel()`.
+
+
+
+See example
+
+The order of the panes is determined by the order of the levels of the variable you're faceting by.
+If the faceting variable is character, this order is alphabetical by default.
+
+```{r}
+ggplot(mpg, aes(x = displ, y = hwy)) +
+ geom_point() +
+ facet_wrap(~drv)
+```
+
+Suppose you'd like the panes to be in the order `"r"`, `"f"` , `"4"`.
+You can use `forcats::fct_relevel()` to reorder the levels of `drv`.
+You can do this in a data transformation step prior to plotting (e.g. with `dplyr::mutate()`) or you can do it directly in the plotting code as shown below.
+
+```{r}
+ggplot(mpg, aes(x = displ, y = hwy)) +
+ geom_point() +
+ facet_wrap(~forcats::fct_relevel(drv, "r", "f", "4"))
+```
+
+
+
+## Overplotting
+
+### How can I control the order of the points plotted?
+
+If there is a specific point (or group of points) you want to make sure is plotted on top of others, subset the data for those observations and add as a new layer to your plot.
+
+
+
+See example
+
+Suppose you have the following data frame.
+
+```{r}
+df <- tibble::tribble(
+ ~id, ~x, ~y, ~shape, ~fill,
+ 1, 0.01, 0, "circle filled", "blue",
+ 2, 1, 0, "square filled", "red",
+ 3, 0.99, 0, "asterisk", "black",
+ 4, 0, 0, "triangle filled", "yellow"
+)
+```
+
+By default, this is how a scatterplot of these looks.
+Note that the blue circle is partially covered by the yellow triangle since that observation comes later in the dataset.
+Similarly the black asterisk appears on top of the red square.
+
+```{r}
+ggplot(df, aes(x = x, y = y, fill = fill, shape = shape)) +
+ geom_point(size = 8) +
+ scale_shape_identity() +
+ scale_fill_identity()
+```
+
+Suppose you arranged your data in ascending order of the x-coordinates and plotted again.
+Now the blue circle is over the yellow triangle since 0.01 comes after 0 and similarly the red square is over the black asterisk since 1 comes after 0.99.
+
+```{r}
+df_arranged <- df %>% dplyr::arrange(x)
+
+df_arranged %>%
+ ggplot(aes(x = x, y = y, fill = fill, shape = shape)) +
+ geom_point(size = 8) +
+ scale_shape_identity() +
+ scale_fill_identity()
+```
+
+If you wanted to make sure that the observation identified with an asterisk is always plotted on top, regardless of how the data are arranged in the data frame, you can create an additional layer for that observation.
+
+```{r}
+ggplot(mapping = aes(x = x, y = y, fill = fill, shape = shape)) +
+ geom_point(data = df %>% filter(shape != "asterisk"), size = 8) +
+ geom_point(data = df %>% filter(shape == "asterisk"), size = 8) +
+ scale_shape_identity() +
+ scale_fill_identity()
+
+ggplot(mapping = aes(x = x, y = y, fill = fill, shape = shape)) +
+ geom_point(data = df_arranged %>% filter(shape != "asterisk"), size = 8) +
+ geom_point(data = df_arranged %>% filter(shape == "asterisk"), size = 8) +
+ scale_shape_identity() +
+ scale_fill_identity()
+```
+
+
diff --git a/vignettes/extending-ggplot2.Rmd b/vignettes/extending-ggplot2.Rmd
index 90583f2451..6bb38183c0 100644
--- a/vignettes/extending-ggplot2.Rmd
+++ b/vignettes/extending-ggplot2.Rmd
@@ -1,6 +1,8 @@
---
title: "Extending ggplot2"
output: rmarkdown::html_vignette
+description: |
+ Official extension mechanism provided in ggplot2.
vignette: >
%\VignetteIndexEntry{Extending ggplot2}
%\VignetteEngine{knitr::rmarkdown}
diff --git a/vignettes/ggplot2-in-packages.Rmd b/vignettes/ggplot2-in-packages.Rmd
index 20a17adde9..5d10efb7cd 100644
--- a/vignettes/ggplot2-in-packages.Rmd
+++ b/vignettes/ggplot2-in-packages.Rmd
@@ -1,6 +1,8 @@
---
title: "Using ggplot2 in packages"
output: rmarkdown::html_vignette
+description: |
+ Customising how aesthetic specifications are represented on your plot.
vignette: >
%\VignetteIndexEntry{Using ggplot2 in packages}
%\VignetteEngine{knitr::rmarkdown}
diff --git a/vignettes/ggplot2-specs.Rmd b/vignettes/ggplot2-specs.Rmd
index e65a6d61c2..fa6d235d01 100644
--- a/vignettes/ggplot2-specs.Rmd
+++ b/vignettes/ggplot2-specs.Rmd
@@ -1,6 +1,8 @@
---
title: "Aesthetic specifications"
output: rmarkdown::html_vignette
+description: |
+ Customising how aesthetic specifications are represented on your plot.
vignette: >
%\VignetteIndexEntry{Aesthetic specifications}
%\VignetteEngine{knitr::rmarkdown}
diff --git a/vignettes/profiling.Rmd b/vignettes/profiling.Rmd
index 57e085e8a5..a0a77340df 100644
--- a/vignettes/profiling.Rmd
+++ b/vignettes/profiling.Rmd
@@ -2,6 +2,8 @@
title: "Profiling Performance"
author: "Thomas Lin Pedersen"
output: rmarkdown::html_vignette
+description: |
+ Monitoring the performance of your plots.
vignette: >
%\VignetteIndexEntry{Profiling Performance}
%\VignetteEngine{knitr::rmarkdown}