diff --git a/DESCRIPTION b/DESCRIPTION index 1481517272..fe830898e9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -173,6 +173,7 @@ Collate: 'grob-dotstack.R' 'grob-null.R' 'grouping.R' + 'theme-elements.R' 'guide-.R' 'guide-axis.R' 'guide-axis-logticks.R' @@ -267,7 +268,6 @@ Collate: 'stat-ydensity.R' 'summarise-plot.R' 'summary.R' - 'theme-elements.R' 'theme.R' 'theme-defaults.R' 'theme-current.R' diff --git a/NEWS.md b/NEWS.md index b848c4ce16..db9d477402 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,12 @@ # ggplot2 (development version) +* `guide_*()` functions get a new `theme` argument to style individual guides. + The `theme()` function has gained additional arguments for styling guides: + `legend.key.spacing{.x/.y}`, `legend.frame`, `legend.axis.line`, + `legend.ticks`, `legend.ticks.length`, `legend.text.position` and + `legend.title.position`. Previous style arguments in the `guide_*()` functions + have been soft-deprecated. + * When legend titles are larger than the legend, title justification extends to the placement of keys and labels (#1903). diff --git a/R/guide-.R b/R/guide-.R index 85fb5ee942..b90c7b93f4 100644 --- a/R/guide-.R +++ b/R/guide-.R @@ -1,3 +1,6 @@ +#' @include theme-elements.R +NULL + #' Guide constructor #' #' A constructor function for guides, which performs some standard compatibility @@ -25,13 +28,8 @@ new_guide <- function(..., available_aes = "any", super) { params <- intersect(names(args), param_names) params <- defaults(args[params], super$params) - # Set elements - elems_names <- names(super$elements) - elems <- intersect(names(args), elems_names) - elems <- defaults(args[elems], super$elements) - # Warn about extra arguments - extra_args <- setdiff(names(args), union(param_names, elems_names)) + extra_args <- setdiff(names(args), param_names) if (length(extra_args) > 0) { cli::cli_warn(paste0( "Ignoring unknown {cli::qty(extra_args)} argument{?s} to ", @@ -50,14 +48,20 @@ new_guide <- function(..., available_aes = "any", super) { )) } + # Validate theme settings + if (!is.null(params$theme)) { + check_object(params$theme, is.theme, what = "a {.cls theme} object") + validate_theme(params$theme) + params$direction <- params$direction %||% params$theme$legend.direction + } + # Ensure 'order' is length 1 integer params$order <- vec_cast(params$order, 0L, x_arg = "order", call = pf) vec_assert(params$order, 0L, size = 1L, arg = "order", call = pf) ggproto( NULL, super, - params = params, - elements = elems, + params = params, available_aes = available_aes ) } @@ -162,6 +166,7 @@ Guide <- ggproto( # `GuidesList` class. params = list( title = waiver(), + theme = NULL, name = character(), position = waiver(), direction = NULL, @@ -275,6 +280,7 @@ Guide <- ggproto( # Converts the `elements` field to proper elements to be accepted by # `element_grob()`. String-interpolates aesthetic/position dependent elements. setup_elements = function(params, elements, theme) { + theme <- add_theme(theme, params$theme) is_char <- vapply(elements, is.character, logical(1)) elements[is_char] <- lapply(elements[is_char], calc_element, theme = theme) elements @@ -294,8 +300,7 @@ Guide <- ggproto( key <- params$key # Setup parameters and theme - params$position <- params$position %||% position - params$direction <- params$direction %||% direction + params <- replace_null(params, position = position, direction = direction) params <- self$setup_params(params) elems <- self$setup_elements(params, self$elements, theme) elems <- self$override_elements(params, elems, theme) diff --git a/R/guide-axis-logticks.R b/R/guide-axis-logticks.R index 699b52aee2..d7742c8cc6 100644 --- a/R/guide-axis-logticks.R +++ b/R/guide-axis-logticks.R @@ -72,6 +72,7 @@ guide_axis_logticks <- function( short_theme = element_line(), expanded = TRUE, cap = "none", + theme = NULL, ... ) { if (is.logical(cap)) { @@ -108,6 +109,7 @@ guide_axis_logticks <- function( cap = cap, minor.ticks = TRUE, short_theme = short_theme, + theme = theme, ..., super = GuideAxisLogticks ) diff --git a/R/guide-axis-stack.R b/R/guide-axis-stack.R index 2fdd73b34e..1e0f765898 100644 --- a/R/guide-axis-stack.R +++ b/R/guide-axis-stack.R @@ -29,7 +29,7 @@ NULL #' #' # A normal axis first, then a capped axis #' p + guides(x = guide_axis_stack("axis", guide_axis(cap = "both"))) -guide_axis_stack <- function(first = "axis", ..., title = waiver(), +guide_axis_stack <- function(first = "axis", ..., title = waiver(), theme = NULL, spacing = NULL, order = 0, position = waiver()) { check_object(spacing, is.unit, "{.cls unit}", allow_null = TRUE) @@ -63,6 +63,7 @@ guide_axis_stack <- function(first = "axis", ..., title = waiver(), new_guide( title = title, + theme = theme, guides = axes, guide_params = params, available_aes = c("x", "y", "theta", "r"), @@ -88,6 +89,7 @@ GuideAxisStack <- ggproto( # Standard guide stuff name = "stacked_axis", title = waiver(), + theme = NULL, angle = waiver(), hash = character(), position = waiver(), @@ -142,6 +144,7 @@ GuideAxisStack <- ggproto( draw = function(self, theme, position = NULL, direction = NULL, params = self$params) { + theme <- add_theme(theme, params$theme) position <- params$position %||% position direction <- params$direction %||% direction diff --git a/R/guide-axis-theta.R b/R/guide-axis-theta.R index c8c8fa3619..bf3d07281a 100644 --- a/R/guide-axis-theta.R +++ b/R/guide-axis-theta.R @@ -23,7 +23,7 @@ NULL #' #' # The `angle` argument can be used to set relative angles #' p + guides(theta = guide_axis_theta(angle = 0)) -guide_axis_theta <- function(title = waiver(), angle = waiver(), +guide_axis_theta <- function(title = waiver(), theme = NULL, angle = waiver(), minor.ticks = FALSE, cap = "none", order = 0, position = waiver()) { diff --git a/R/guide-axis.R b/R/guide-axis.R index efca81c08e..ac59ef41b4 100644 --- a/R/guide-axis.R +++ b/R/guide-axis.R @@ -46,9 +46,9 @@ #' #' # can also be used to add a duplicate guide #' p + guides(x = guide_axis(n.dodge = 2), y.sec = guide_axis()) -guide_axis <- function(title = waiver(), check.overlap = FALSE, angle = waiver(), - n.dodge = 1, minor.ticks = FALSE, cap = "none", - order = 0, position = waiver()) { +guide_axis <- function(title = waiver(), theme = NULL, check.overlap = FALSE, + angle = waiver(), n.dodge = 1, minor.ticks = FALSE, + cap = "none", order = 0, position = waiver()) { check_bool(minor.ticks) if (is.logical(cap)) { check_bool(cap) @@ -58,6 +58,7 @@ guide_axis <- function(title = waiver(), check.overlap = FALSE, angle = waiver() new_guide( title = title, + theme = theme, # customisations check.overlap = check.overlap, @@ -86,6 +87,7 @@ GuideAxis <- ggproto( params = list( title = waiver(), + theme = NULL, name = "axis", hash = character(), position = waiver(), @@ -225,17 +227,14 @@ GuideAxis <- ggproto( }, setup_elements = function(params, elements, theme) { - axis_elem <- c("line", "text", "ticks", "minor", "major_length", "minor_length") - is_char <- vapply(elements[axis_elem], is.character, logical(1)) - axis_elem <- axis_elem[is_char] - elements[axis_elem] <- lapply( - paste( - unlist(elements[axis_elem]), - params$aes, params$position, sep = "." - ), - calc_element, theme = theme + is_char <- vapply(elements, is.character, logical(1)) + suffix <- paste(params$aes, params$position, sep = ".") + elements[is_char] <- vapply( + elements[is_char], + function(x) paste(x, suffix, sep = "."), + character(1) ) - elements + Guide$setup_elements(params, elements, theme) }, override_elements = function(params, elements, theme) { diff --git a/R/guide-bins.R b/R/guide-bins.R index 54676378bb..c13447eb32 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -11,26 +11,11 @@ NULL #' guide if they are mapped in the same way. #' #' @inheritParams guide_legend -#' @param axis A theme object for rendering a small axis along the guide. -#' Usually, the object of `element_line()` is expected (default). If -#' `element_blank()`, no axis is drawn. For backward compatibility, can also -#' be a logical which translates `TRUE` to `element_line()` and `FALSE` to -#' `element_blank()`. -#' @param axis.colour,axis.linewidth Graphic specifications for the look of the -#' axis. -#' @param axis.arrow A call to `arrow()` to specify arrows at the end of the -#' axis line, thus showing an open interval. #' @param show.limits Logical. Should the limits of the scale be shown with #' labels and ticks. Default is `NULL` meaning it will take the value from the #' scale. This argument is ignored if `labels` is given as a vector of #' values. If one or both of the limits is also given in `breaks` it will be #' shown irrespective of the value of `show.limits`. -#' @param ticks A theme object for rendering tick marks at the colourbar. -#' Usually, the object of `element_line()` is expected. If `element_blank()`, -#' no tick marks are drawn. If `NULL` (default), the `axis` argument is -#' re-used as `ticks` argument (without arrow). -#' @param ticks.length A numeric or a [grid::unit()] object specifying the -#' length of tick marks between the keys. #' #' @section Use with discrete scale: #' This guide is intended to show binned data and work together with ggplot2's @@ -57,12 +42,15 @@ NULL #' p #' #' # Remove the axis or style it -#' p + guides(size = guide_bins(axis = FALSE)) +#' p + guides(size = guide_bins( +#' theme = theme(legend.axis.line = element_blank()) +#' )) #' #' p + guides(size = guide_bins(show.limits = TRUE)) #' +#' my_arrow <- arrow(length = unit(1.5, "mm"), ends = "both") #' p + guides(size = guide_bins( -#' axis.arrow = arrow(length = unit(1.5, 'mm'), ends = 'both') +#' theme = theme(legend.axis.line = element_line(arrow = my_arrow)) #' )) #' #' # Guides are merged together if possible @@ -74,35 +62,11 @@ NULL guide_bins <- function( # title title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - # label - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - - # key - keywidth = NULL, - keyheight = NULL, - - # ticks - axis = TRUE, - axis.colour = "black", - axis.linewidth = NULL, - axis.arrow = NULL, - - ticks = NULL, - ticks.length = unit(0.2, "npc"), + theme = NULL, # general position = NULL, direction = NULL, - default.unit = "line", override.aes = list(), reverse = FALSE, order = 0, @@ -110,67 +74,15 @@ guide_bins <- function( ... ) { - if (!(is.null(keywidth) || is.unit(keywidth))) { - keywidth <- unit(keywidth, default.unit) - } - if (!(is.null(keyheight) || is.unit(keyheight))) { - keyheight <- unit(keyheight, default.unit) - } - if (!is.unit(ticks.length)) { - ticks.length <- unit(ticks.length, default.unit) - } - if (!is.null(title.position)) { - title.position <- arg_match0(title.position, .trbl) - } + theme <- deprecated_guide_args(theme, ...) if (!is.null(position)) { position <- arg_match0(position, c(.trbl, "inside")) } - if (!is.null(direction)) { - direction <- arg_match0(direction, c("horizontal", "vertical")) - } - if (!is.null(label.position)) { - label.position <- arg_match0(label.position, .trbl) - } - - if (is.logical(axis)) { - axis <- if (axis) element_line() else element_rect() - } - if (inherits(axis, "element_line")) { - axis$colour <- axis.colour %||% axis$colour %||% "black" - axis$linewidth <- axis.linewidth %||% axis$linewidth %||% (0.5 / .pt) - axis$arrow <- axis.arrow %||% axis$arrow - } else { - axis <- element_blank() - } - - if (is.null(ticks)) { - ticks <- axis - ticks$arrow <- NULL - } new_guide( # title title = title, - title.position = title.position, - title.theme = title.theme, - title.hjust = title.hjust, - title.vjust = title.vjust, - - # label - label = label, - label.position = label.position, - label.theme = label.theme, - label.hjust = label.hjust, - label.vjust = label.vjust, - - # key - keywidth = keywidth, - keyheight = keyheight, - - # ticks - line = axis, - ticks = ticks, - ticks_length = ticks.length, + theme = theme, # general position = position, @@ -197,19 +109,12 @@ GuideBins <- ggproto( params = list( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - - keywidth = NULL, - keyheight = NULL, + # theming + theme = NULL, + default_axis = element_line("black", linewidth = (0.5 / .pt)), + default_ticks = element_line(inherit.blank = TRUE), + default_tick_length = unit(0.2, "npc"), direction = NULL, override.aes = list(), @@ -226,9 +131,9 @@ GuideBins <- ggproto( elements = c( GuideLegend$elements, list( - line = "line", - ticks = "line", - ticks_length = unit(0.2, "npc") + axis_line = "legend.axis.line", + ticks_length = "legend.ticks.length", + ticks = "legend.ticks" ) ), @@ -301,43 +206,45 @@ GuideBins <- ggproto( key$.value <- 1 - key$.value } - params$title <- scale$make_title( - params$title %|W|% scale$name %|W|% title - ) + params$title <- scale$make_title(params$title %|W|% scale$name %|W|% title) params$key <- key params }, setup_params = function(params) { - params$direction <- arg_match0( - params$direction, - c("horizontal", "vertical"), arg_nm = "direction" - ) - valid_label_pos <- switch( + params <- GuideLegend$setup_params(params) + params$nrow <- params$ncol <- params$n_breaks <- params$n_key_layers <- 1 + params + }, + + setup_elements = function(params, elements, theme) { + valid_position <- switch( params$direction, "horizontal" = c("bottom", "top"), "vertical" = c("right", "left") ) - params$label.position <- params$label.position %||% valid_label_pos[1] - if (!params$label.position %in% valid_label_pos) { + + # Set defaults + theme <- replace_null( + theme, + legend.text.position = valid_position[1], + legend.ticks.length = params$default_tick_length, + legend.axis.line = params$default_axis, + legend.ticks = params$default_ticks + ) + + # Let the legend guide handle the rest + elements <- GuideLegend$setup_elements(params, elements, theme) + + # Check text position + if (!elements$text_position %in% valid_position) { cli::cli_abort(paste0( - "When {.arg direction} is {.val {params$direction}}, ", - "{.arg label.position} must be one of {.or {.val {valid_label_pos}}}, ", - "not {.val {params$label.position}}." + "When {.arg direction} is {.val {params$direction}, ", + "{.arg legend.text.position} must be one of ", + "{.or {.val {valid_position}}}, not {.val {elements$text.position}}." )) } - params <- GuideLegend$setup_params(params) - params$byrow <- FALSE - params$rejust_labels <- FALSE - params$nrow <- params$ncol <- params$n_breaks <- params$n_key_layers <- 1 - params$multikey_decor <- FALSE - params - }, - - override_elements = function(params, elements, theme) { - elements$ticks <- combine_elements(elements$ticks, theme$line) - elements$line <- combine_elements(elements$line, theme$line) - GuideLegend$override_elements(params, elements, theme) + elements }, build_labels = function(key, elements, params) { @@ -366,7 +273,7 @@ GuideBins <- ggproto( key$.value <- 1 - key$.value } key$.value[c(1, nrow(key))[!params$show.limits]] <- NA - Guide$build_ticks(key$.value, elements, params, params$label.position) + Guide$build_ticks(key$.value, elements, params, elements$text_position) }, build_decor = function(decor, grobs, elements, params) { @@ -378,8 +285,8 @@ GuideBins <- ggproto( sizes <- measure_legend_keys( decor, nkeys, dim, byrow = FALSE, - default_width = elements$key.width, - default_height = elements$key.height + default_width = elements$width_cm, + default_height = elements$height_cm ) sizes <- lapply(sizes, function(x) rep_len(max(x), length(x))) @@ -401,13 +308,13 @@ GuideBins <- ggproto( name = key_nm, clip = "off") axis <- switch( - params$label.position, + elements$text_position, "top" = list(x = c(0, 1), y = c(1, 1)), "bottom" = list(x = c(0, 1), y = c(0, 0)), "left" = list(x = c(0, 0), y = c(0, 1)), "right" = list(x = c(1, 1), y = c(0, 1)) ) - axis <- element_grob(elements$line, x = axis$x, y = axis$y) + axis <- element_grob(elements$axis_line, x = axis$x, y = axis$y) list(keys = gt, axis_line = axis, ticks = grobs$ticks) }, diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index df2362b717..9fdc92c437 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -15,45 +15,18 @@ NULL #' see [guides()]. #' #' @inheritParams guide_legend -#' @param barwidth,barheight A numeric or [grid::unit()] object specifying the -#' width and height of the bar respectively. Default value is derived from -#' `legend.key.width`, `legend.key.height` or `legend.key` in [theme()].\cr -#' `r lifecycle::badge("experimental")`: optionally a `"null"` unit to stretch -#' the bar to the available space. -#' @param frame A theme object for rendering a frame drawn around the bar. -#' Usually, the object of `element_rect()` is expected. If `element_blank()` -#' (default), no frame is drawn. -#' @param frame.colour A string specifying the colour of the frame -#' drawn around the bar. For backward compatibility, if this argument is -#' not `NULL`, the `frame` argument will be set to `element_rect()`. -#' @param frame.linewidth A numeric specifying the width of the frame -#' drawn around the bar in millimetres. -#' @param frame.linetype A numeric specifying the linetype of the frame -#' drawn around the bar. #' @param nbin A numeric specifying the number of bins for drawing the #' colourbar. A smoother colourbar results from a larger value. #' @param raster A logical. If `TRUE` then the colourbar is rendered as a #' raster object. If `FALSE` then the colourbar is rendered as a set of #' rectangles. Note that not all graphics devices are capable of rendering #' raster image. -#' @param ticks A theme object for rendering tick marks at the colourbar. -#' Usually, the object of `element_line()` is expected (default). If -#' `element_blank()`, no tick marks are drawn. For backward compatibility, -#' can also be a logical which translates `TRUE` to `element_line()` and -#' `FALSE` to `element_blank()`. -#' @param ticks.colour A string specifying the colour of the tick marks. -#' @param ticks.linewidth A numeric specifying the width of the tick marks in -#' millimetres. -#' @param ticks.length A numeric or a [grid::unit()] object specifying the -#' length of tick marks at the colourbar. #' @param draw.ulim A logical specifying if the upper limit tick marks should #' be visible. #' @param draw.llim A logical specifying if the lower limit tick marks should #' be visible. #' @param direction A character string indicating the direction of the guide. #' One of "horizontal" or "vertical." -#' @param default.unit A character string indicating [grid::unit()] -#' for `barwidth` and `barheight`. #' @param reverse logical. If `TRUE` the colourbar is reversed. By default, #' the highest value is on the top and the lowest value is on the bottom #' @param available_aes A vector of character strings listing the aesthetics @@ -77,19 +50,31 @@ NULL #' # Control styles #' #' # bar size -#' p1 + guides(fill = guide_colourbar(barwidth = 0.5, barheight = 10)) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.key.width = unit(0.5, "lines"), +#' legend.key.height = unit(10, "lines") +#' ))) +#' #' #' # no label -#' p1 + guides(fill = guide_colourbar(label = FALSE)) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.text = element_blank() +#' ))) #' #' # no tick marks -#' p1 + guides(fill = guide_colourbar(ticks = FALSE)) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.ticks = element_blank() +#' ))) #' #' # label position -#' p1 + guides(fill = guide_colourbar(label.position = "left")) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.text.position = "left" +#' ))) #' #' # label theme -#' p1 + guides(fill = guide_colourbar(label.theme = element_text(colour = "blue", angle = 0))) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.text = element_text(colour = "blue", angle = 0) +#' ))) #' #' # small number of bins #' p1 + guides(fill = guide_colourbar(nbin = 3)) @@ -102,7 +87,7 @@ NULL #' scale_fill_continuous( #' limits = c(0,20), breaks = c(0, 5, 10, 15, 20), #' guide = guide_colourbar(nbin = 100, draw.ulim = FALSE, draw.llim = FALSE) -#' ) +#' ) #' #' # guides can be controlled independently #' p2 + @@ -111,148 +96,45 @@ NULL #' p2 + guides(fill = "colourbar", size = "legend") #' #' p2 + -#' scale_fill_continuous(guide = guide_colourbar(direction = "horizontal")) + -#' scale_size(guide = guide_legend(direction = "vertical")) +#' scale_fill_continuous(guide = guide_colourbar(theme = theme( +#' legend.direction = "horizontal" +#' ))) + +#' scale_size(guide = guide_legend(theme = theme( +#' legend.direction = "vertical" +#' ))) guide_colourbar <- function( - - # title title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - # label - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - - # bar - barwidth = NULL, - barheight = NULL, + theme = NULL, nbin = 300, raster = TRUE, - - # frame - frame = element_blank(), - frame.colour = NULL, - frame.linewidth = NULL, - frame.linetype = NULL, - - # ticks - ticks = element_line(), - ticks.colour = NULL, - ticks.linewidth = NULL, - ticks.length = unit(0.2, "npc"), draw.ulim = TRUE, draw.llim = TRUE, - - # general position = NULL, direction = NULL, - default.unit = "line", reverse = FALSE, order = 0, available_aes = c("colour", "color", "fill"), ... ) { - if (!(is.null(barwidth) || is.unit(barwidth))) { - barwidth <- unit(barwidth, default.unit) - } - if (!(is.null(barheight) || is.unit(barheight))) { - barheight <- unit(barheight, default.unit) - } - if (!is.unit(ticks.length)) { - ticks.length <- unit(ticks.length, default.unit) - } - if (!is.null(title.position)) { - title.position <- arg_match0(title.position, .trbl) - } + theme <- deprecated_guide_args(theme, ...) if (!is.null(position)) { position <- arg_match0(position, c(.trbl, "inside")) } - if (!is.null(direction)) { - direction <- arg_match0(direction, c("horizontal", "vertical")) - } - if (!is.null(label.position)) { - label.position <- arg_match0(label.position, .trbl) - } - - if (!is.null(frame.colour) && !inherits(frame, "element_rect")) { - # For backward compatibility, frame should not be element_blank when - # colour is not NULL - cli::cli_inform(c(paste0( - "If {.arg frame.colour} is set, {.arg frame} should not be ", - "{.cls {class(frame)[[1]]}}." - ), "i" = "{.arg frame} has been converted to {.cls element_rect}.")) - frame <- element_rect() - } - if (inherits(frame, "element_rect")) { - frame$colour <- frame.colour %||% frame$colour - frame$linewidth <- frame.linewidth %||% frame$linewidth %||% (0.5 / .pt) - frame$linetype <- frame.linetype %||% frame$linetype %||% 1 - } else { - frame <- element_blank() - } - - if (is.logical(ticks)) { - # Also for backward compatibility. `ticks = FALSE` used to mean: don't draw - # the ticks - ticks <- if (ticks) element_line() else element_blank() - } - if (inherits(ticks, "element_line")) { - ticks$colour <- ticks.colour %||% ticks$colour %||% "white" - ticks$linewidth <- ticks.linewidth %||% ticks$linewidth %||% (0.5 / .pt) - } - - # Trick to re-use this constructor in `guide_coloursteps()`. - args <- list2(...) - super <- args$super %||% GuideColourbar - args$super <- NULL new_guide( - # title title = title, - title.position = title.position, - title.theme = title.theme, - title.hjust = title.hjust, - title.vjust = title.vjust, - - # label - label = label, - label.position = label.position, - label.theme = label.theme, - label.hjust = label.hjust, - label.vjust = label.vjust, - - # bar - keywidth = barwidth, - keyheight = barheight, + theme = theme, nbin = nbin, raster = raster, - - # frame - frame = frame, - - # ticks - ticks = ticks, - ticks_length = ticks.length, draw_lim = c(isTRUE(draw.llim), isTRUE(draw.ulim)), - - # general position = position, direction = direction, reverse = reverse, order = order, - - # parameter available_aes = available_aes, name = "colourbar", - !!!args, - super = super + super = GuideColourbar ) } @@ -270,21 +152,14 @@ GuideColourbar <- ggproto( params = list( # title title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - # label - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, + + # theming + theme = NULL, + default_ticks = element_line(colour = "white", linewidth = 0.5 / .pt), + default_frame = element_blank(), + default_tick_length = unit(0.2, "npc"), # bar - keywidth = NULL, - keyheight = NULL, nbin = 300, raster = TRUE, @@ -306,16 +181,19 @@ GuideColourbar <- ggproto( hashables = exprs(title, key$.label, decor, name), elements = list( - frame = "rect", - ticks = "line", - ticks_length = unit(0.2, "npc"), - background = "legend.background", - margin = "legend.margin", - key = "legend.key", - key.height = "legend.key.height", - key.width = "legend.key.width", - text = "legend.text", - theme.title = "legend.title" + background = "legend.background", + margin = "legend.margin", + key = "legend.key", + key_height = "legend.key.height", + key_width = "legend.key.width", + text = "legend.text", + theme.title = "legend.title", + text_position = "legend.text.position", + title_position = "legend.title.position", + axis_line = "legend.axis.line", + ticks = "legend.ticks", + ticks_length = "legend.ticks.length", + frame = "legend.frame" ), extract_key = function(scale, aesthetic, ...) { @@ -346,11 +224,9 @@ GuideColourbar <- ggproto( extract_params = function(scale, params, title = waiver(), ...) { - params$title <- scale$make_title( - params$title %|W|% scale$name %|W|% title - ) + params$title <- scale$make_title(params$title %|W|% scale$name %|W|% title) - limits <- c(params$decor$value[1], params$decor$value[nrow(params$decor)]) + limits <- params$decor$value[c(1L, nrow(params$decor))] params$key$.value <- rescale( params$key$.value, c(0.5, params$nbin - 0.5) / params$nbin, @@ -374,38 +250,41 @@ GuideColourbar <- ggproto( params$direction, c("horizontal", "vertical"), arg_nm = "direction" ) - valid_label_pos <- switch( - params$direction, - "horizontal" = c("bottom", "top"), - "vertical" = c("right", "left") - ) - params$label.position <- params$label.position %||% valid_label_pos[1] - if (!params$label.position %in% valid_label_pos) { - cli::cli_abort(paste0( - "When {.arg direction} is {.val {params$direction}}, ", - "{.arg label.position} must be one of {.or {.val {valid_label_pos}}}, ", - "not {.val {params$label.position}}." - )) - } - params$title.position <- arg_match0( - params$title.position %||% - switch(params$direction, vertical = "top", horizontal = "left"), - .trbl, arg_nm = "title.position" - ) - params$rejust_labels <- FALSE params }, - override_elements = function(params, elements, theme) { - # These key sizes are the defaults, the GuideLegend method may overrule this + setup_elements = function(params, elements, theme) { + # We set the defaults in `theme` so that the `params$theme` can still + # overrule defaults given here if (params$direction == "horizontal") { - elements$key.width <- elements$key.width * 5 + theme$legend.key.width <- theme$legend.key.width * 5 + valid_position <- c("bottom", "top") } else { - elements$key.height <- elements$key.height * 5 + theme$legend.key.height <- theme$legend.key.height * 5 + valid_position <- c("right", "left") + } + + # Set defaults + theme <- replace_null( + theme, + legend.text.position = valid_position[1], + legend.ticks.length = params$default_tick_length, + legend.ticks = params$default_ticks, + legend.frame = params$default_frame + ) + + # Let the legend guide handle the rest + elements <- GuideLegend$setup_elements(params, elements, theme) + + # Check text position + if (!elements$text_position %in% valid_position) { + cli::cli_abort(paste0( + "When {.arg direction} is {.val {params$direction}}, ", + "{.arg legend.text.position} must be one of ", + "{.or {.val {valid_position}}}, not {.val {elements$text_position}}." + )) } - elements$ticks <- combine_elements(elements$ticks, theme$line) - elements$frame <- combine_elements(elements$frame, theme$rect) - GuideLegend$override_elements(params, elements, theme) + elements }, build_labels = function(key, elements, params) { @@ -485,8 +364,8 @@ GuideColourbar <- ggproto( measure_grobs = function(grobs, params, elements) { params$sizes <- list( - widths = elements$key.width, - heights = elements$key.height + widths = elements$width_cm, + heights = elements$height_cm ) GuideLegend$measure_grobs(grobs, params, elements) } diff --git a/R/guide-colorsteps.R b/R/guide-colorsteps.R index 7206a4c19e..45d0bed2d7 100644 --- a/R/guide-colorsteps.R +++ b/R/guide-colorsteps.R @@ -11,12 +11,7 @@ #' scale. This argument is ignored if `labels` is given as a vector of #' values. If one or both of the limits is also given in `breaks` it will be #' shown irrespective of the value of `show.limits`. -#' @param ticks A theme object for rendering tick marks at the colourbar. -#' Usually, the object of `element_line()` is expected. If `element_blank()` -#' (default), no tick marks are drawn. For backward compatibility, can also -#' be a logical which translates `TRUE` to `element_line()` and `FALSE` to -#' `element_blank()`. -#' @inheritDotParams guide_colourbar -nbin -raster -ticks -available_aes +#' @inheritParams guide_colourbar #' #' @inheritSection guide_bins Use with discrete scale #' @@ -49,17 +44,28 @@ #' # (can also be set in the scale) #' p + scale_fill_binned(show.limits = TRUE) guide_coloursteps <- function( + title = waiver(), + theme = NULL, even.steps = TRUE, show.limits = NULL, - ticks = element_blank(), + direction = NULL, + reverse = FALSE, + order = 0, + available_aes = c("colour", "color", "fill"), ... ) { - guide_colourbar( + + theme <- deprecated_guide_args(theme, ...) + + new_guide( + title = title, + theme = theme, even.steps = even.steps, show.limits = show.limits, - ticks = ticks, - ..., - super = GuideColoursteps + direction = direction, + reverse = reverse, + order = order, + super = GuideColoursteps ) } @@ -76,7 +82,7 @@ GuideColoursteps <- ggproto( params = c( list(even.steps = TRUE, show.limits = NULL), - GuideColourbar$params + vec_assign(GuideColourbar$params, "default_ticks", list(element_blank())) ), extract_key = function(scale, aesthetic, even.steps, ...) { @@ -94,7 +100,7 @@ GuideColoursteps <- ggproto( limits <- parsed$limits breaks <- parsed$breaks - key <- data_frame(scale$map(breaks), .name_repair = ~ aesthetic) + key <- data_frame0(!!aesthetic := scale$map(breaks)) key$.value <- seq_along(breaks) key$.label <- scale$get_labels(breaks) diff --git a/R/guide-legend.R b/R/guide-legend.R index 324e5b81f7..26a0b401b0 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -13,50 +13,17 @@ #' If `NULL`, the title is not shown. By default #' ([waiver()]), the name of the scale object or the name #' specified in [labs()] is used for the title. -#' @param title.position A character string indicating the position of a -#' title. One of "top" (default for a vertical guide), "bottom", "left" -#' (default for a horizontal guide), or "right." -#' @param title.theme A theme object for rendering the title text. Usually the -#' object of [element_text()] is expected. By default, the theme is -#' specified by `legend.title` in [theme()] or theme. -#' @param title.hjust A number specifying horizontal justification of the -#' title text. -#' @param title.vjust A number specifying vertical justification of the title -#' text. -#' @param label logical. If `TRUE` then the labels are drawn. If -#' `FALSE` then the labels are invisible. -#' @param label.position A character string indicating the position of a -#' label. One of "top", "bottom" (default for horizontal guide), "left", or -#' "right" (default for vertical guide). -#' @param label.theme A theme object for rendering the label text. Usually the -#' object of [element_text()] is expected. By default, the theme is -#' specified by `legend.text` in [theme()]. -#' @param label.hjust A numeric specifying horizontal justification of the -#' label text. The default for standard text is 0 (left-aligned) and 1 -#' (right-aligned) for expressions. -#' @param label.vjust A numeric specifying vertical justification of the label -#' text. -#' @param keywidth,keyheight A numeric or [grid::unit()] object specifying the -#' width and height of the legend key respectively. Default value is -#' `legend.key.width`, `legend.key.height` or `legend.key` in [theme()].\cr -#' `r lifecycle::badge("experimental")`: optionally a `"null"` unit to stretch -#' keys to the available space. -#' @param key.spacing,key.spacing.x,key.spacing.y A numeric or [grid::unit()] -#' object specifying the distance between key-label pairs in the horizontal -#' direction (`key.spacing.x`), vertical direction (`key.spacing.y`) or both -#' (`key.spacing`). +#' @param theme A [`theme`][theme()] object to style the guide individually or +#' differently from the plot's theme settings. The `theme` argument in the +#' guide overrides, and is combined with, the plot's theme. #' @param position A character string indicating where the legend should be #' placed relative to the plot panels. #' @param direction A character string indicating the direction of the guide. #' One of "horizontal" or "vertical." -#' @param default.unit A character string indicating [grid::unit()] -#' for `keywidth` and `keyheight`. #' @param override.aes A list specifying aesthetic parameters of legend key. #' See details and examples. -#' @param nrow The desired number of rows of legends. -#' @param ncol The desired number of column of legends. -#' @param byrow logical. If `FALSE` (the default) the legend-matrix is -#' filled by columns, otherwise the legend-matrix is filled by rows. +#' @param nrow,ncol The desired number of rows and column of legends +#' respectively. #' @param reverse logical. If `TRUE` the order of legends is reversed. #' @param order positive integer less than 99 that specifies the order of #' this guide among multiple guides. This controls the order in which @@ -79,36 +46,32 @@ #' # Control styles #' #' # title position -#' p1 + guides(fill = guide_legend(title = "LEFT", title.position = "left")) +#' p1 + guides(fill = guide_legend( +#' title = "LEFT", theme(legend.title.position = "left") +#' )) #' #' # title text styles via element_text -#' p1 + guides(fill = -#' guide_legend( -#' title.theme = element_text( -#' size = 15, -#' face = "italic", -#' colour = "red", -#' angle = 0 -#' ) -#' ) -#' ) +#' p1 + guides(fill = guide_legend(theme = theme( +#' legend.title = element_text(size = 15, face = "italic", colour = "red") +#' ))) #' #' # label position -#' p1 + guides(fill = guide_legend(label.position = "left", label.hjust = 1)) +#' p1 + guides(fill = guide_legend(theme = theme( +#' legend.text.position = "left", +#' legend.text = element_text(hjust = 1) +#' ))) #' #' # label styles #' p1 + #' scale_fill_continuous( #' breaks = c(5, 10, 15), #' labels = paste("long", c(5, 10, 15)), -#' guide = guide_legend( -#' direction = "horizontal", -#' title.position = "top", -#' label.position = "bottom", -#' label.hjust = 0.5, -#' label.vjust = 1, -#' label.theme = element_text(angle = 90) -#' ) +#' guide = guide_legend(theme = theme( +#' legend.direction = "horizontal", +#' legend.title.position = "top", +#' legend.text.position = "bottom", +#' legend.text = element_text(hjust = 0.5, vjust = 1, angle = 90) +#' )) #' ) #' #' # Set aesthetic of legend key @@ -125,70 +88,31 @@ #' geom_point(aes(colour = color)) #' p + guides(col = guide_legend(nrow = 8)) #' p + guides(col = guide_legend(ncol = 8)) -#' p + guides(col = guide_legend(nrow = 8, byrow = TRUE)) +#' p + guides(col = guide_legend(nrow = 8, theme = theme(legend.byrow = TRUE))) #' #' # reversed order legend #' p + guides(col = guide_legend(reverse = TRUE)) #' } guide_legend <- function( # Title - title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - # Label - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, + title = waiver(), - # Key size - keywidth = NULL, - keyheight = NULL, - key.spacing = NULL, - key.spacing.x = NULL, - key.spacing.y = NULL, + # Theme + theme = NULL, # General position = NULL, direction = NULL, - default.unit = "line", override.aes = list(), nrow = NULL, ncol = NULL, - byrow = FALSE, reverse = FALSE, order = 0, ... ) { - # Resolve key sizes - if (!(is.null(keywidth) || is.unit(keywidth))) { - keywidth <- unit(keywidth, default.unit) - } - if (!(is.null(keyheight) || is.unit(keyheight))) { - keyheight <- unit(keyheight, default.unit) - } - # Resolve spacing - key.spacing.x <- key.spacing.x %||% key.spacing - if (!is.null(key.spacing.x) || is.unit(key.spacing.x)) { - key.spacing.x <- unit(key.spacing.x, default.unit) - } - key.spacing.y <- key.spacing.y %||% key.spacing - if (!is.null(key.spacing.y) || is.unit(key.spacing.y)) { - key.spacing.y <- unit(key.spacing.y, default.unit) - } + theme <- deprecated_guide_args(theme, ...) - - if (!is.null(title.position)) { - title.position <- arg_match0(title.position, .trbl) - } - if (!is.null(label.position)) { - label.position <- arg_match0(label.position, .trbl) - } if (!is.null(position)) { position <- arg_match0(position, c(.trbl, "inside")) } @@ -196,30 +120,13 @@ guide_legend <- function( new_guide( # Title title = title, - title.position = title.position, - title.theme = title.theme, - title.hjust = title.hjust, - title.vjust = title.vjust, - - # Label - label = label, - label.position = label.position, - label.theme = label.theme, - label.hjust = label.hjust, - label.vjust = label.vjust, - - # Key size - keywidth = keywidth, - keyheight = keyheight, - key.spacing.x = key.spacing.x, - key.spacing.y = key.spacing.y, + theme = theme, # General direction = direction, override.aes = rename_aes(override.aes), nrow = nrow, ncol = ncol, - byrow = byrow, reverse = reverse, order = order, position = position, @@ -240,27 +147,12 @@ GuideLegend <- ggproto( params = list( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - - keywidth = NULL, - keyheight = NULL, - key.spacing.x = NULL, - key.spacing.y = NULL, + theme = NULL, # General override.aes = list(), nrow = NULL, ncol = NULL, - byrow = FALSE, reverse = FALSE, order = 0, @@ -275,20 +167,23 @@ GuideLegend <- ggproto( hashables = exprs(title, key$.label, name), elements = list( - background = "legend.background", - margin = "legend.margin", - key = "legend.key", - key.height = "legend.key.height", - key.width = "legend.key.width", - text = "legend.text", - theme.title = "legend.title" + background = "legend.background", + margin = "legend.margin", + key = "legend.key", + key_height = "legend.key.height", + key_width = "legend.key.width", + text = "legend.text", + theme.title = "legend.title", + spacing_x = "legend.key.spacing.x", + spacing_y = "legend.key.spacing.y", + text_position = "legend.text.position", + title_position = "legend.title.position", + byrow = "legend.byrow" ), extract_params = function(scale, params, title = waiver(), ...) { - params$title <- scale$make_title( - params$title %|W|% scale$name %|W|% title - ) + params$title <- scale$make_title(params$title %|W|% scale$name %|W|% title) if (isTRUE(params$reverse %||% FALSE)) { params$key <- params$key[nrow(params$key):1, , drop = FALSE] } @@ -374,25 +269,9 @@ GuideLegend <- ggproto( setup_params = function(params) { params$direction <- arg_match0( - params$direction %||% direction, + params$direction, c("horizontal", "vertical"), arg_nm = "direction" ) - - if ("title.position" %in% names(params)) { - params$title.position <- arg_match0( - params$title.position %||% - switch(params$direction, vertical = "top", horizontal = "left"), - .trbl, arg_nm = "title.position" - ) - } - if ("label.position" %in% names(params)) { - params$label.position <- arg_match0( - params$label.position %||% "right", - .trbl, arg_nm = "label.position" - ) - params$rejust_labels <- TRUE - } - params$n_breaks <- n_breaks <- nrow(params$key) params$n_key_layers <- length(params$decor) + 1 # +1 is key background @@ -416,91 +295,86 @@ GuideLegend <- ggproto( params }, - override_elements = function(params, elements, theme) { + setup_elements = function(params, elements, theme) { + theme <- add_theme(theme, params$theme) + params$theme <- NULL - # Title - title <- combine_elements(params$title.theme, elements$theme.title) - title$hjust <- params$title.hjust %||% title$hjust %||% 0 - title$vjust <- params$title.vjust %||% title$vjust %||% 0.5 - elements$title <- title - - # Labels - if (!is.null(elements$text)) { - label <- combine_elements(params$label.theme, elements$text) - if (!params$label || is.null(params$key$.label)) { - label <- element_blank() - } else { - hjust <- unname(label_hjust_defaults[params$label.position]) - vjust <- unname(label_vjust_defaults[params$label.position]) - # Expressions default to right-justified - if (hjust == 0 && any(is.expression(params$key$.label))) { - hjust <- 1 - } - # Breaking justification inheritance for intuition purposes. - if (is.null(params$label.theme$hjust) && - is.null(theme$legend.text$hjust)) { - label$hjust <- NULL - } - if (is.null(params$label.theme$vjust) && - is.null(theme$legend.text$vjust)) { - label$vjust <- NULL - } - label$hjust <- params$label.hjust %||% label$hjust %||% hjust - label$vjust <- params$label.vjust %||% label$vjust %||% vjust - } - elements$text <- label - } - - # Keys - if (any(c("key.width", "key.height") %in% names(elements))) { - elements$key.width <- width_cm( params$keywidth %||% elements$key.width) - elements$key.height <- height_cm(params$keyheight %||% elements$key.height) - } + # Resolve text positions + text_position <- theme$legend.text.position %||% "right" + title_position <- theme$legend.title.position %||% switch( + params$direction, + vertical = "top", horizontal = "left" + ) + theme$legend.text.position <- + arg_match0(text_position, .trbl, arg_nm = "legend.text.position") + theme$legend.title.position <- + arg_match0(title_position, .trbl, arg_nm = "legend.title.position") - # Spacing - gap <- title$size %||% elements$theme.title$size %||% - elements$text$size %||% 11 - gap <- unit(gap * 0.5, "pt") - # Should maybe be elements$spacing.{x/y} instead of the theme's spacing? + # Set default spacing + theme$legend.key.spacing <- theme$legend.key.spacing %||% unit(5.5, "pt") + gap <- calc_element("legend.key.spacing", theme) + # For backward compatibility, default vertical spacing is no spacing if (params$direction == "vertical") { - # For backward compatibility, vertical default is no spacing - vgap <- params$key.spacing.y %||% unit(0, "pt") - } else { - vgap <- params$key.spacing.y %||% gap + theme$legend.key.spacing.y <- theme$legend.key.spacing.y %||% + unit(0, "pt") } - elements$hgap <- width_cm( params$key.spacing.x %||% gap) - elements$vgap <- height_cm(vgap) - elements$padding <- convertUnit( - elements$margin %||% margin(), - "cm", valueOnly = TRUE + # Resolve title. The trick here is to override the main text element, so + # that any settings declared in `legend.title` will be honoured but we have + # custom defaults for the guide. + margin <- calc_element("text", theme)$margin + title <- theme(text = element_text( + hjust = 0, vjust = 0.5, + margin = position_margin(title_position, margin, gap) + )) + elements$title <- calc_element("legend.title", add_theme(theme, title)) + + # Resolve text, setting default justification and margins. Again, the + # trick here is to set the main text element to propagate defaults while + # honouring the `legend.text` settings. + margin <- position_margin(text_position, margin, gap) + text <- theme( + text = switch( + text_position, + top = element_text(hjust = 0.5, vjust = 0.0, margin = margin), + bottom = element_text(hjust = 0.5, vjust = 1.0, margin = margin), + left = element_text(hjust = 1.0, vjust = 0.5, margin = margin), + right = element_text(hjust = 0.0, vjust = 0.5, margin = margin) + ) ) + elements$text <- calc_element("legend.text", add_theme(theme, text)) + Guide$setup_elements(params, elements, theme) + }, - # When no explicit margin has been set, either in this guide or in the - # theme, we set a default text margin to leave a small gap in between - # the label and the key. - if (is.null(params$label.theme$margin %||% theme$legend.text$margin) && - !inherits(elements$text, "element_blank")) { - i <- match(params$label.position, .trbl[c(3, 4, 1, 2)]) - elements$text$margin[i] <- elements$text$margin[i] + gap + override_elements = function(params, elements, theme) { + + if (any(c("key_width", "key_height") %in% names(elements))) { + # Determine if the key is stretched + elements$stretch_x <- unitType(elements$key_width) == "null" + elements$stretch_y <- unitType(elements$key_height) == "null" + # Convert key sizes to cm + elements$width_cm <- width_cm(elements$key_width) + elements$height_cm <- height_cm(elements$key_height) } - if (is.null(params$title.theme$margin %||% theme$legend.title$margin) && - !inherits(elements$title, "element_blank")) { - i <- match(params$title.position, .trbl[c(3, 4, 1, 2)]) - elements$title$margin[i] <- elements$title$margin[i] + gap + + # Convert padding and spacing to cm + if (any(c("spacing_x", "spacing_y") %in% names(elements))) { + elements$spacing_x <- width_cm(elements$spacing_x) + elements$spacing_y <- height_cm(elements$spacing_y) } + elements$padding <- + convertUnit(elements$margin %||% margin(), "cm", valueOnly = TRUE) + # Evaluate backgrounds early if (!is.null(elements$background)) { - elements$background <- ggname( - "legend.background", element_grob(elements$background) - ) + elements$background <- + ggname("legend.background", element_grob(elements$background)) } if (!is.null(elements$key)) { - elements$key <- ggname( - "legend.key", element_grob(elements$key) - ) + elements$key <- + ggname("legend.key", element_grob(elements$key)) } elements @@ -512,7 +386,7 @@ GuideLegend <- ggproto( build_decor = function(decor, grobs, elements, params) { - key_size <- c(elements$key.width, elements$key.height) * 10 + key_size <- c(elements$width_cm, elements$height_cm) * 10 draw <- function(i) { bg <- elements$key @@ -550,16 +424,17 @@ GuideLegend <- ggproto( }, measure_grobs = function(grobs, params, elements) { - byrow <- params$byrow %||% FALSE + + byrow <- elements$byrow %||% FALSE n_breaks <- params$n_breaks %||% 1L - dim <- c(params$nrow %||% 1L, params$ncol %||% 1L) + dim <- c(params$nrow %||% 1L, params$ncol %||% 1L) # A guide may have already specified the size of the decoration, only # measure when it hasn't already. sizes <- params$sizes %||% measure_legend_keys( grobs$decor, n = n_breaks, dim = dim, byrow = byrow, - default_width = elements$key.width, - default_height = elements$key.height + default_width = elements$width_cm, + default_height = elements$height_cm ) widths <- sizes$widths heights <- sizes$heights @@ -578,18 +453,18 @@ GuideLegend <- ggproto( # Interleave gaps between keys and labels, which depends on the label # position. For unclear reasons, we need to adjust some gaps based on the # `byrow` parameter (see also #4352). - hgap <- elements$hgap %||% 0 + hgap <- elements$spacing_x %||% 0 widths <- switch( - params$label.position, + elements$text_position, "left" = list(label_widths, widths, hgap), "right" = list(widths, label_widths, hgap), list(pmax(label_widths, widths), hgap) ) widths <- head(vec_interleave(!!!widths), -1) - vgap <- elements$vgap %||% 0 + vgap <- elements$spacing_y %||% 0 heights <- switch( - params$label.position, + elements$text_position, "top" = list(label_heights, heights, vgap), "bottom" = list(heights, label_heights, vgap), list(pmax(label_heights, heights), vgap) @@ -604,33 +479,25 @@ GuideLegend <- ggproto( title_height <- height_cm(grobs$title) # Titles are assumed to have sufficient size when keys are null units - if (is.unit(params$keywidth) && unitType(params$keywidth) == "null") { - extra_width <- 0 - } else { - extra_width <- max(0, title_width - sum(widths)) - } - if (is.unit(params$keyheight) && unitType(params$keyheight) == "null") { - extra_height <- 0 - } else { - extra_height <- max(0, title_height - sum(heights)) - } + extra_width <- + if (isTRUE(elements$stretch_x)) 0 else max(0, title_width - sum(widths)) + extra_height <- + if (isTRUE(elements$stretch_y)) 0 else max(0, title_height - sum(heights)) - just <- with(elements$title, rotate_just(angle, hjust, vjust)) - hjust <- just$hjust - vjust <- just$vjust + just <- with(elements$title, rotate_just(angle, hjust, vjust)) # Combine title with rest of the sizes based on its position widths <- switch( - params$title.position, + elements$title_position, "left" = c(title_width, widths), "right" = c(widths, title_width), - c(extra_width * hjust, widths, extra_width * (1 - hjust)) + c(extra_width * just$hjust, widths, extra_width * (1 - just$hjust)) ) heights <- switch( - params$title.position, + elements$title_position, "top" = c(title_height, heights), "bottom" = c(heights, title_height), - c(extra_height * (1 - vjust), heights, extra_height * vjust) + c(extra_height * (1 - just$vjust), heights, extra_height * just$vjust) ) } @@ -638,7 +505,9 @@ GuideLegend <- ggproto( widths = widths, heights = heights, padding = elements$padding, - has_title = has_title + has_title = has_title, + label_position = elements$text_position, + title_position = elements$title_position ) }, @@ -649,43 +518,43 @@ GuideLegend <- ggproto( # Find rows / columns of legend items if (params$byrow %||% FALSE) { - df <- data_frame0( - R = ceiling(break_seq / dim[2]), - C = (break_seq - 1) %% dim[2] + 1 - ) + row <- ceiling(break_seq / dim[2L]) + col <- (break_seq - 1L) %% dim[2L] + 1L } else { df <- mat_2_df(arrayInd(break_seq, dim), c("R", "C")) + row <- df$R + col <- df$C } # Make spacing for padding / gaps. For example: because first gtable cell # will be padding, first item will be at [2, 2] position. Then the # second item-row will be [4, 2] because [3, 2] will be a gap cell. - key_row <- label_row <- df$R * 2 - key_col <- label_col <- df$C * 2 + key_row <- label_row <- row * 2 + key_col <- label_col <- col * 2 # Make gaps for key-label spacing depending on label position switch( - params$label.position, + sizes$label_position, "top" = { - key_row <- key_row + df$R + key_row <- key_row + row label_row <- key_row - 1 }, "bottom" = { - key_row <- key_row + df$R - 1 + key_row <- key_row + row - 1 label_row <- key_row + 1 }, "left" = { - key_col <- key_col + df$C + key_col <- key_col + col label_col <- key_col - 1 }, "right" = { - key_col <- key_col + df$C - 1 + key_col <- key_col + col - 1 label_col <- key_col + 1 } ) # Offset layout based on title position if (sizes$has_title) { - position <- params$title.position + position <- sizes$title_position if (position != "right") { key_col <- key_col + 1 label_col <- label_col + 1 @@ -710,15 +579,13 @@ GuideLegend <- ggproto( assemble_drawing = function(grobs, layout, sizes, params, elements) { widths <- unit(c(sizes$padding[4], sizes$widths, sizes$padding[2]), "cm") - if (is.unit(params$keywidth) && unitType(params$keywidth) == "null") { - i <- unique(layout$layout$key_col) - widths[i] <- params$keywidth + if (isTRUE(elements$stretch_x)) { + widths[unique(layout$layout$key_col)] <- elements$key_width } heights <- unit(c(sizes$padding[1], sizes$heights, sizes$padding[3]), "cm") - if (is.unit(params$keyheight) && unitType(params$keyheight) == "null") { - i <- unique(layout$layout$key_row) - heights[i] <- params$keyheight + if (isTRUE(elements$stretch_y)) { + heights[unique(layout$layout$key_row)] <- elements$key_height } gt <- gtable(widths = widths, heights = heights) @@ -735,14 +602,7 @@ GuideLegend <- ggproto( # Add title if (!is.zero(grobs$title)) { gt <- gtable_add_grob( - gt, - justify_grobs( - grobs$title, - hjust = elements$title$hjust, - vjust = elements$title$vjust, - int_angle = elements$title$angle, - debug = elements$title$debug - ), + gt, grobs$title, name = "title", clip = "off", t = min(layout$title_row), r = max(layout$title_col), b = max(layout$title_row), l = min(layout$title_col) @@ -770,18 +630,8 @@ GuideLegend <- ggproto( } if (!is.zero(grobs$labels)) { - labels <- if (params$rejust_labels %||% TRUE) { - justify_grobs( - grobs$labels, - hjust = elements$text$hjust, vjust = elements$text$vjust, - int_angle = elements$text$angle, debug = elements$text$debug - ) - } else { - grobs$labels - } - gt <- gtable_add_grob( - gt, labels, + gt, grobs$labels, name = names(labels) %||% paste("label", layout$label_row, layout$label_col, sep = "-"), clip = "off", @@ -794,8 +644,6 @@ GuideLegend <- ggproto( } ) -label_hjust_defaults <- c(top = 0.5, bottom = 0.5, left = 1, right = 0) -label_vjust_defaults <- c(top = 0, bottom = 1, left = 0.5, right = 0.5) measure_legend_keys <- function(keys, n, dim, byrow = FALSE, default_width = 1, default_height = 1) { @@ -880,3 +728,129 @@ keep_key_data <- function(key, data, aes, show) { } keep } + +position_margin <- function(position, margin = margin(), gap = unit(0, "pt")) { + switch( + position, + top = replace(margin, 3, margin[3] + gap), + bottom = replace(margin, 1, margin[1] + gap), + left = replace(margin, 2, margin[2] + gap), + right = replace(margin, 4, margin[4] + gap) + ) +} + +# Function implementing backward compatibility with the old way of specifying +# guide styling +deprecated_guide_args <- function( + theme = NULL, + title.position = NULL, + title.theme = NULL, title.hjust = NULL, title.vjust = NULL, + label = NULL, + label.position = NULL, + label.theme = NULL, label.hjust = NULL, label.vjust = NULL, + keywidth = NULL, keyheight = NULL, barwidth = NULL, barheight = NULL, + byrow = NULL, + frame.colour = NULL, frame.linewidth = NULL, frame.linetype = NULL, + ticks = NULL, ticks.colour = NULL, ticks.linewidth = NULL, + axis = NULL, axis.colour = NULL, axis.linewidth = NULL, axis.arrow = NULL, + default.unit = "line", + ..., + .call = caller_call()) { + + args <- names(formals(deprecated_guide_args)) + args <- setdiff(args, c("theme", "default.unit", "...", ".call")) + vals <- compact(mget(args, current_env())) + + # Early exit when no old arguments have been supplied + if (length(vals) == 0) { + return(theme) + } + fun_name <- call_name(.call) + replacement <- paste0(fun_name, "(theme)") + for (arg_name in names(vals)) { + deprecate_soft0( + when = "3.5.0", + what = paste0(fun_name, "(", arg_name, ")"), + with = replacement + ) + } + def_unit <- function(x) { + if (is.null(x) || is.unit(x)) { + return(x) + } + unit(x, default.unit) + } + + theme <- theme %||% list() + + # Resolve straightforward arguments + theme <- replace_null( + theme, + legend.title.position = title.position, + legend.text.position = label.position, + legend.byrow = byrow, + legend.key.width = def_unit(keywidth %||% barwidth), + legend.key.height = def_unit(keyheight %||% barheight) + ) + + # Set legend.text + if (isFALSE(label)) { + label.theme <- element_blank() + } else if (!is.null(label.theme %||% label.hjust %||% label.vjust)) { + label.theme <- label.theme %||% element_text() + label.theme <- replace_null( + label.theme, + hjust = label.hjust %||% label.theme$hjust, + vjust = label.vjust %||% label.theme$vjust + ) + } + theme$legend.text <- theme$legend.text %||% label.theme + + # Set legend.title + if (!is.null(title.hjust %||% title.vjust)) { + title.theme <- title.theme %||% element_text() + title.theme <- replace_null( + title.theme, + hjust = title.hjust %||% title.theme$hjust, + vjust = title.vjust %||% title.theme$vjust + ) + } + theme$legend.title <- theme$legend.title %||% title.theme + + # Set legend.frame + if (!is.null(frame.colour %||% frame.linewidth %||% frame.linetype)) { + frame <- theme$legend.frame %||% element_rect( + colour = frame.colour, + linewidth = frame.linewidth, + linetype = frame.linetype + ) + theme$legend.frame <- theme$legend.frame %||% frame + } + + # Set legend.ticks + if (isFALSE(ticks)) { + ticks <- element_blank() + } else if (!is.null(ticks.colour %||% ticks.linewidth)) { + ticks <- element_line(colour = ticks.colour, linewidth = ticks.linewidth) + theme$legend.ticks <- theme$legend.ticks %||% ticks + } + + # Set legend.axis + if (isFALSE(axis)) { + axis <- element_blank() + } else if (!is.null(axis.colour %||% axis.linewidth %||% axis.arrow)) { + axis <- element_line( + colour = axis.colour, + linewidth = axis.linewidth, + arrow = axis.arrow + ) + theme$legend.axis.line <- theme$legend.axis.line %||% axis + } + + # Set as theme + theme <- compact(theme) + if (!is.theme(theme)) { + theme <- inject(theme(!!!theme)) + } + theme +} diff --git a/R/theme-defaults.R b/R/theme-defaults.R index 7a062409a0..e9436acfb9 100644 --- a/R/theme-defaults.R +++ b/R/theme-defaults.R @@ -176,6 +176,7 @@ theme_grey <- function(base_size = 11, base_family = "", legend.key.size = unit(1.2, "lines"), legend.key.height = NULL, legend.key.width = NULL, + legend.key.spacing = unit(half_line, "pt"), legend.text = element_text(size = rel(0.8)), legend.title = element_text(hjust = 0), legend.position = "right", @@ -474,6 +475,7 @@ theme_void <- function(base_size = 11, base_family = "", legend.position = "right", legend.text = element_text(size = rel(0.8)), legend.title = element_text(hjust = 0), + legend.key.spacing = unit(half_line, "pt"), strip.clip = "inherit", strip.text = element_text(size = rel(0.8)), strip.switch.pad.grid = unit(half_line / 2, "pt"), @@ -579,6 +581,9 @@ theme_test <- function(base_size = 11, base_family = "", legend.key.size = unit(1.2, "lines"), legend.key.height = NULL, legend.key.width = NULL, + legend.key.spacing = unit(half_line, "pt"), + legend.key.spacing.x = NULL, + legend.key.spacing.y = NULL, legend.text = element_text(size = rel(0.8)), legend.title = element_text(hjust = 0), legend.position = "right", diff --git a/R/theme-elements.R b/R/theme-elements.R index 448aa4763a..d90d11ae11 100644 --- a/R/theme-elements.R +++ b/R/theme-elements.R @@ -500,8 +500,18 @@ el_def <- function(class = NULL, inherit = NULL, description = NULL) { legend.key = el_def("element_rect", "panel.background"), legend.key.height = el_def(c("unit", "rel"), "legend.key.size"), legend.key.width = el_def(c("unit", "rel"), "legend.key.size"), + legend.key.spacing = el_def("unit"), + legend.key.spacing.x = el_def(c("unit", "rel"), "legend.key.spacing"), + legend.key.spacing.y = el_def(c("unit", "rel"), "legend.key.spacing"), + legend.frame = el_def("element_rect", "rect"), + legend.axis.line = el_def("element_line", "line"), + legend.ticks = el_def("element_line", "legend.axis.line"), + legend.ticks.length = el_def("unit"), legend.text = el_def("element_text", "text"), + legend.text.position = el_def("character"), legend.title = el_def("element_text", "title"), + legend.title.position = el_def("character"), + legend.byrow = el_def("logical"), legend.position = el_def("character"), legend.position.inside = el_def(c("numeric", "integer")), legend.direction = el_def("character"), diff --git a/R/theme.R b/R/theme.R index 6def5d7ab5..1e012d13ad 100644 --- a/R/theme.R +++ b/R/theme.R @@ -74,16 +74,31 @@ #' @param legend.key.size,legend.key.height,legend.key.width #' size of legend keys (`unit`); key background height & width inherit from #' `legend.key.size` or can be specified separately +#' @param legend.key.spacing,legend.key.spacing.x,legend.key.spacing.y spacing +#' between legend keys given as a `unit`. Spacing in the horizontal (x) and +#' vertical (y) direction inherit from `legend.key.spacing` or can be +#' specified separately. +#' @param legend.frame frame drawn around the bar ([element_rect()]). +#' @param legend.ticks tick marks shown along bars or axes ([element_line()]) +#' @param legend.ticks.length length of tick marks in legend (`unit`) +#' @param legend.axis.line lines along axes in legends ([element_line()]) #' @param legend.text legend item labels ([element_text()]; inherits from #' `text`) +#' @param legend.text.position placement of legend text relative to legend keys +#' or bars ("top", "right", "bottom" or "left"). The legend text placement +#' might be incompatible with the legend's direction for some guides. #' @param legend.title title of legend ([element_text()]; inherits from #' `title`) +#' @param legend.title.position placement of legend title relative to the main +#' legend ("top", "right", "bottom" or "left"). #' @param legend.position the default position of legends ("none", "left", #' "right", "bottom", "top", "inside") #' @param legend.position.inside A numeric vector of length two setting the #' placement of legends that have the `"inside"` position. #' @param legend.direction layout of items in legends ("horizontal" or #' "vertical") +#' @param legend.byrow whether the legend-matrix is filled by columns +#' (`FALSE`, the default) or by rows (`TRUE`). #' @param legend.justification anchor point for positioning legend inside plot #' ("center" or two-element numeric vector) or the justification according to #' the plot area when positioned outside the plot @@ -349,11 +364,21 @@ theme <- function(..., legend.key.size, legend.key.height, legend.key.width, + legend.key.spacing, + legend.key.spacing.x, + legend.key.spacing.y, + legend.frame, + legend.ticks, + legend.ticks.length, + legend.axis.line, legend.text, + legend.text.position, legend.title, + legend.title.position, legend.position, legend.position.inside, legend.direction, + legend.byrow, legend.justification, legend.justification.top, legend.justification.bottom, @@ -501,10 +526,17 @@ is_theme_complete <- function(x) isTRUE(attr(x, "complete", exact = TRUE)) # check whether theme should be validated is_theme_validate <- function(x) { validate <- attr(x, "validate", exact = TRUE) - if (is.null(validate)) - TRUE # we validate by default - else - isTRUE(validate) + isTRUE(validate %||% TRUE) +} + +validate_theme <- function(theme, tree = get_element_tree()) { + if (!is_theme_validate(theme)) { + return() + } + mapply( + validate_element, theme, names(theme), + MoreArgs = list(element_tree = tree) + ) } # Combine plot defaults with current theme to get complete theme for a plot @@ -527,12 +559,7 @@ plot_theme <- function(x, default = theme_get()) { theme[missing] <- ggplot_global$theme_default[missing] # Check that all elements have the correct class (element_text, unit, etc) - if (is_theme_validate(theme)) { - mapply( - validate_element, theme, names(theme), - MoreArgs = list(element_tree = get_element_tree()) - ) - } + validate_theme(theme) theme } diff --git a/R/utilities.R b/R/utilities.R index 5888423cea..127765dafb 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -773,6 +773,25 @@ vec_rbind0 <- function(..., .error_call = current_env(), .call = caller_env()) { ) } +# This function is used to vectorise the following pattern: +# +# obj$name1 <- obj$name1 %||% value +# obj$name2 <- obj$name2 %||% value +# +# and express this pattern as: +# +# replace_null(obj, name1 = value, name2 = value) +replace_null <- function(obj, ..., env = caller_env()) { + # Collect dots without evaluating + dots <- enexprs(...) + # Select arguments that are null in `obj` + nms <- names(dots) + nms <- nms[vapply(obj[nms], is.null, logical(1))] + # Replace those with the evaluated dots + obj[nms] <- inject(list(!!!dots[nms]), env = env) + obj +} + attach_plot_env <- function(env) { old_env <- getOption("ggplot2_plot_env") options(ggplot2_plot_env = env) diff --git a/man/element.Rd b/man/element.Rd index 3102d9a7d4..a3c27a259c 100644 --- a/man/element.Rd +++ b/man/element.Rd @@ -1,16 +1,14 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/margins.R, R/theme-elements.R -\name{margin} -\alias{margin} +% Please edit documentation in R/theme-elements.R, R/margins.R +\name{element} \alias{element_blank} \alias{element_rect} \alias{element_line} \alias{element_text} \alias{rel} +\alias{margin} \title{Theme elements} \usage{ -margin(t = 0, r = 0, b = 0, l = 0, unit = "pt") - element_blank() element_rect( @@ -50,13 +48,10 @@ element_text( ) rel(x) + +margin(t = 0, r = 0, b = 0, l = 0, unit = "pt") } \arguments{ -\item{t, r, b, l}{Dimensions of each margin. (To remember order, think trouble).} - -\item{unit}{Default units of dimensions. Defaults to "pt" so it -can be most easily scaled with the text.} - \item{fill}{Fill colour.} \item{colour, color}{Line/border colour. Color is an alias for colour.} @@ -101,6 +96,11 @@ rectangle behind the complete text area, and a point where each label is anchored.} \item{x}{A single number specifying size relative to parent element.} + +\item{t, r, b, l}{Dimensions of each margin. (To remember order, think trouble).} + +\item{unit}{Default units of dimensions. Defaults to "pt" so it +can be most easily scaled with the text.} } \value{ An S3 object of class \code{element}, \code{rel}, or \code{margin}. diff --git a/man/guide_axis.Rd b/man/guide_axis.Rd index fa09421300..4d4ba4f166 100644 --- a/man/guide_axis.Rd +++ b/man/guide_axis.Rd @@ -6,6 +6,7 @@ \usage{ guide_axis( title = waiver(), + theme = NULL, check.overlap = FALSE, angle = waiver(), n.dodge = 1, @@ -21,6 +22,10 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide overrides, and is combined with, the plot's theme.} + \item{check.overlap}{silently remove overlapping labels, (recursively) prioritizing the first, last, and middle labels.} diff --git a/man/guide_axis_logticks.Rd b/man/guide_axis_logticks.Rd index 60ebaa8b12..3b8fcb5478 100644 --- a/man/guide_axis_logticks.Rd +++ b/man/guide_axis_logticks.Rd @@ -13,6 +13,7 @@ guide_axis_logticks( short_theme = element_line(), expanded = TRUE, cap = "none", + theme = NULL, ... ) } @@ -45,6 +46,10 @@ be \code{"none"} (default) to draw the axis line along the whole panel, or \code{"both"} to only draw the line in between the most extreme breaks. \code{TRUE} and \code{FALSE} are shorthand for \code{"both"} and \code{"none"} respectively.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide overrides, and is combined with, the plot's theme.} + \item{...}{ Arguments passed on to \code{\link[=guide_axis]{guide_axis}} \describe{ diff --git a/man/guide_axis_stack.Rd b/man/guide_axis_stack.Rd index 63ae75b003..a001a35ac2 100644 --- a/man/guide_axis_stack.Rd +++ b/man/guide_axis_stack.Rd @@ -8,6 +8,7 @@ guide_axis_stack( first = "axis", ..., title = waiver(), + theme = NULL, spacing = NULL, order = 0, position = waiver() @@ -27,6 +28,10 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide overrides, and is combined with, the plot's theme.} + \item{spacing}{A \code{\link[=unit]{unit()}} objects that determines how far separate guides are spaced apart.} diff --git a/man/guide_axis_theta.Rd b/man/guide_axis_theta.Rd index 16a8e89cf1..6e18e57a60 100644 --- a/man/guide_axis_theta.Rd +++ b/man/guide_axis_theta.Rd @@ -6,6 +6,7 @@ \usage{ guide_axis_theta( title = waiver(), + theme = NULL, angle = waiver(), minor.ticks = FALSE, cap = "none", @@ -19,6 +20,10 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide overrides, and is combined with, the plot's theme.} + \item{angle}{Compared to setting the angle in \code{\link[=theme]{theme()}} / \code{\link[=element_text]{element_text()}}, this also uses some heuristics to automatically pick the \code{hjust} and \code{vjust} that you probably want. Can be one of the following: diff --git a/man/guide_bins.Rd b/man/guide_bins.Rd index 59faaa8050..8633915f2d 100644 --- a/man/guide_bins.Rd +++ b/man/guide_bins.Rd @@ -6,26 +6,9 @@ \usage{ guide_bins( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - keywidth = NULL, - keyheight = NULL, - axis = TRUE, - axis.colour = "black", - axis.linewidth = NULL, - axis.arrow = NULL, - ticks = NULL, - ticks.length = unit(0.2, "npc"), + theme = NULL, position = NULL, direction = NULL, - default.unit = "line", override.aes = list(), reverse = FALSE, order = 0, @@ -39,63 +22,9 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} -\item{title.position}{A character string indicating the position of a -title. One of "top" (default for a vertical guide), "bottom", "left" -(default for a horizontal guide), or "right."} - -\item{title.theme}{A theme object for rendering the title text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.title} in \code{\link[=theme]{theme()}} or theme.} - -\item{title.hjust}{A number specifying horizontal justification of the -title text.} - -\item{title.vjust}{A number specifying vertical justification of the title -text.} - -\item{label}{logical. If \code{TRUE} then the labels are drawn. If -\code{FALSE} then the labels are invisible.} - -\item{label.position}{A character string indicating the position of a -label. One of "top", "bottom" (default for horizontal guide), "left", or -"right" (default for vertical guide).} - -\item{label.theme}{A theme object for rendering the label text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.text} in \code{\link[=theme]{theme()}}.} - -\item{label.hjust}{A numeric specifying horizontal justification of the -label text. The default for standard text is 0 (left-aligned) and 1 -(right-aligned) for expressions.} - -\item{label.vjust}{A numeric specifying vertical justification of the label -text.} - -\item{keywidth, keyheight}{A numeric or \code{\link[grid:unit]{grid::unit()}} object specifying the -width and height of the legend key respectively. Default value is -\code{legend.key.width}, \code{legend.key.height} or \code{legend.key} in \code{\link[=theme]{theme()}}.\cr -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}: optionally a \code{"null"} unit to stretch -keys to the available space.} - -\item{axis}{A theme object for rendering a small axis along the guide. -Usually, the object of \code{element_line()} is expected (default). If -\code{element_blank()}, no axis is drawn. For backward compatibility, can also -be a logical which translates \code{TRUE} to \code{element_line()} and \code{FALSE} to -\code{element_blank()}.} - -\item{axis.colour, axis.linewidth}{Graphic specifications for the look of the -axis.} - -\item{axis.arrow}{A call to \code{arrow()} to specify arrows at the end of the -axis line, thus showing an open interval.} - -\item{ticks}{A theme object for rendering tick marks at the colourbar. -Usually, the object of \code{element_line()} is expected. If \code{element_blank()}, -no tick marks are drawn. If \code{NULL} (default), the \code{axis} argument is -re-used as \code{ticks} argument (without arrow).} - -\item{ticks.length}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the -length of tick marks between the keys.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide overrides, and is combined with, the plot's theme.} \item{position}{A character string indicating where the legend should be placed relative to the plot panels.} @@ -103,9 +32,6 @@ placed relative to the plot panels.} \item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} -\item{default.unit}{A character string indicating \code{\link[grid:unit]{grid::unit()}} -for \code{keywidth} and \code{keyheight}.} - \item{override.aes}{A list specifying aesthetic parameters of legend key. See details and examples.} @@ -158,12 +84,15 @@ p <- ggplot(mtcars) + p # Remove the axis or style it -p + guides(size = guide_bins(axis = FALSE)) +p + guides(size = guide_bins( + theme = theme(legend.axis.line = element_blank()) +)) p + guides(size = guide_bins(show.limits = TRUE)) +my_arrow <- arrow(length = unit(1.5, "mm"), ends = "both") p + guides(size = guide_bins( - axis.arrow = arrow(length = unit(1.5, 'mm'), ends = 'both') + theme = theme(legend.axis.line = element_line(arrow = my_arrow)) )) # Guides are merged together if possible diff --git a/man/guide_colourbar.Rd b/man/guide_colourbar.Rd index 045396216e..2078bc13db 100644 --- a/man/guide_colourbar.Rd +++ b/man/guide_colourbar.Rd @@ -7,32 +7,13 @@ \usage{ guide_colourbar( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - barwidth = NULL, - barheight = NULL, + theme = NULL, nbin = 300, raster = TRUE, - frame = element_blank(), - frame.colour = NULL, - frame.linewidth = NULL, - frame.linetype = NULL, - ticks = element_line(), - ticks.colour = NULL, - ticks.linewidth = NULL, - ticks.length = unit(0.2, "npc"), draw.ulim = TRUE, draw.llim = TRUE, position = NULL, direction = NULL, - default.unit = "line", reverse = FALSE, order = 0, available_aes = c("colour", "color", "fill"), @@ -41,32 +22,13 @@ guide_colourbar( guide_colorbar( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - barwidth = NULL, - barheight = NULL, + theme = NULL, nbin = 300, raster = TRUE, - frame = element_blank(), - frame.colour = NULL, - frame.linewidth = NULL, - frame.linetype = NULL, - ticks = element_line(), - ticks.colour = NULL, - ticks.linewidth = NULL, - ticks.length = unit(0.2, "npc"), draw.ulim = TRUE, draw.llim = TRUE, position = NULL, direction = NULL, - default.unit = "line", reverse = FALSE, order = 0, available_aes = c("colour", "color", "fill"), @@ -79,43 +41,9 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} -\item{title.position}{A character string indicating the position of a -title. One of "top" (default for a vertical guide), "bottom", "left" -(default for a horizontal guide), or "right."} - -\item{title.theme}{A theme object for rendering the title text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.title} in \code{\link[=theme]{theme()}} or theme.} - -\item{title.hjust}{A number specifying horizontal justification of the -title text.} - -\item{title.vjust}{A number specifying vertical justification of the title -text.} - -\item{label}{logical. If \code{TRUE} then the labels are drawn. If -\code{FALSE} then the labels are invisible.} - -\item{label.position}{A character string indicating the position of a -label. One of "top", "bottom" (default for horizontal guide), "left", or -"right" (default for vertical guide).} - -\item{label.theme}{A theme object for rendering the label text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.text} in \code{\link[=theme]{theme()}}.} - -\item{label.hjust}{A numeric specifying horizontal justification of the -label text. The default for standard text is 0 (left-aligned) and 1 -(right-aligned) for expressions.} - -\item{label.vjust}{A numeric specifying vertical justification of the label -text.} - -\item{barwidth, barheight}{A numeric or \code{\link[grid:unit]{grid::unit()}} object specifying the -width and height of the bar respectively. Default value is derived from -\code{legend.key.width}, \code{legend.key.height} or \code{legend.key} in \code{\link[=theme]{theme()}}.\cr -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}: optionally a \code{"null"} unit to stretch -the bar to the available space.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide overrides, and is combined with, the plot's theme.} \item{nbin}{A numeric specifying the number of bins for drawing the colourbar. A smoother colourbar results from a larger value.} @@ -125,34 +53,6 @@ raster object. If \code{FALSE} then the colourbar is rendered as a set of rectangles. Note that not all graphics devices are capable of rendering raster image.} -\item{frame}{A theme object for rendering a frame drawn around the bar. -Usually, the object of \code{element_rect()} is expected. If \code{element_blank()} -(default), no frame is drawn.} - -\item{frame.colour}{A string specifying the colour of the frame -drawn around the bar. For backward compatibility, if this argument is -not \code{NULL}, the \code{frame} argument will be set to \code{element_rect()}.} - -\item{frame.linewidth}{A numeric specifying the width of the frame -drawn around the bar in millimetres.} - -\item{frame.linetype}{A numeric specifying the linetype of the frame -drawn around the bar.} - -\item{ticks}{A theme object for rendering tick marks at the colourbar. -Usually, the object of \code{element_line()} is expected (default). If -\code{element_blank()}, no tick marks are drawn. For backward compatibility, -can also be a logical which translates \code{TRUE} to \code{element_line()} and -\code{FALSE} to \code{element_blank()}.} - -\item{ticks.colour}{A string specifying the colour of the tick marks.} - -\item{ticks.linewidth}{A numeric specifying the width of the tick marks in -millimetres.} - -\item{ticks.length}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the -length of tick marks at the colourbar.} - \item{draw.ulim}{A logical specifying if the upper limit tick marks should be visible.} @@ -165,9 +65,6 @@ placed relative to the plot panels.} \item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} -\item{default.unit}{A character string indicating \code{\link[grid:unit]{grid::unit()}} -for \code{barwidth} and \code{barheight}.} - \item{reverse}{logical. If \code{TRUE} the colourbar is reversed. By default, the highest value is on the top and the lowest value is on the bottom} @@ -212,19 +109,31 @@ p1 + guides(fill = guide_colourbar()) # Control styles # bar size -p1 + guides(fill = guide_colourbar(barwidth = 0.5, barheight = 10)) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.key.width = unit(0.5, "lines"), + legend.key.height = unit(10, "lines") +))) + # no label -p1 + guides(fill = guide_colourbar(label = FALSE)) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.text = element_blank() +))) # no tick marks -p1 + guides(fill = guide_colourbar(ticks = FALSE)) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.ticks = element_blank() +))) # label position -p1 + guides(fill = guide_colourbar(label.position = "left")) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.text.position = "left" +))) # label theme -p1 + guides(fill = guide_colourbar(label.theme = element_text(colour = "blue", angle = 0))) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.text = element_text(colour = "blue", angle = 0) +))) # small number of bins p1 + guides(fill = guide_colourbar(nbin = 3)) @@ -237,7 +146,7 @@ p1 + scale_fill_continuous( limits = c(0,20), breaks = c(0, 5, 10, 15, 20), guide = guide_colourbar(nbin = 100, draw.ulim = FALSE, draw.llim = FALSE) - ) + ) # guides can be controlled independently p2 + @@ -246,8 +155,12 @@ p2 + p2 + guides(fill = "colourbar", size = "legend") p2 + - scale_fill_continuous(guide = guide_colourbar(direction = "horizontal")) + - scale_size(guide = guide_legend(direction = "vertical")) + scale_fill_continuous(guide = guide_colourbar(theme = theme( + legend.direction = "horizontal" + ))) + + scale_size(guide = guide_legend(theme = theme( + legend.direction = "vertical" + ))) } \seealso{ Other guides: diff --git a/man/guide_coloursteps.Rd b/man/guide_coloursteps.Rd index 4a8b42bdd9..3df628de34 100644 --- a/man/guide_coloursteps.Rd +++ b/man/guide_coloursteps.Rd @@ -6,20 +6,39 @@ \title{Discretized colourbar guide} \usage{ guide_coloursteps( + title = waiver(), + theme = NULL, even.steps = TRUE, show.limits = NULL, - ticks = element_blank(), + direction = NULL, + reverse = FALSE, + order = 0, + available_aes = c("colour", "color", "fill"), ... ) guide_colorsteps( + title = waiver(), + theme = NULL, even.steps = TRUE, show.limits = NULL, - ticks = element_blank(), + direction = NULL, + reverse = FALSE, + order = 0, + available_aes = c("colour", "color", "fill"), ... ) } \arguments{ +\item{title}{A character string or expression indicating a title of guide. +If \code{NULL}, the title is not shown. By default +(\code{\link[=waiver]{waiver()}}), the name of the scale object or the name +specified in \code{\link[=labs]{labs()}} is used for the title.} + +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide overrides, and is combined with, the plot's theme.} + \item{even.steps}{Should the rendered size of the bins be equal, or should they be proportional to their length in the data space? Defaults to \code{TRUE}} @@ -29,79 +48,21 @@ scale. This argument is ignored if \code{labels} is given as a vector of values. If one or both of the limits is also given in \code{breaks} it will be shown irrespective of the value of \code{show.limits}.} -\item{ticks}{A theme object for rendering tick marks at the colourbar. -Usually, the object of \code{element_line()} is expected. If \code{element_blank()} -(default), no tick marks are drawn. For backward compatibility, can also -be a logical which translates \code{TRUE} to \code{element_line()} and \code{FALSE} to -\code{element_blank()}.} - -\item{...}{ - Arguments passed on to \code{\link[=guide_colourbar]{guide_colourbar}} - \describe{ - \item{\code{barwidth,barheight}}{A numeric or \code{\link[grid:unit]{grid::unit()}} object specifying the -width and height of the bar respectively. Default value is derived from -\code{legend.key.width}, \code{legend.key.height} or \code{legend.key} in \code{\link[=theme]{theme()}}.\cr -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}: optionally a \code{"null"} unit to stretch -the bar to the available space.} - \item{\code{frame}}{A theme object for rendering a frame drawn around the bar. -Usually, the object of \code{element_rect()} is expected. If \code{element_blank()} -(default), no frame is drawn.} - \item{\code{frame.colour}}{A string specifying the colour of the frame -drawn around the bar. For backward compatibility, if this argument is -not \code{NULL}, the \code{frame} argument will be set to \code{element_rect()}.} - \item{\code{frame.linewidth}}{A numeric specifying the width of the frame -drawn around the bar in millimetres.} - \item{\code{frame.linetype}}{A numeric specifying the linetype of the frame -drawn around the bar.} - \item{\code{ticks.colour}}{A string specifying the colour of the tick marks.} - \item{\code{ticks.linewidth}}{A numeric specifying the width of the tick marks in -millimetres.} - \item{\code{ticks.length}}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the -length of tick marks at the colourbar.} - \item{\code{draw.ulim}}{A logical specifying if the upper limit tick marks should -be visible.} - \item{\code{draw.llim}}{A logical specifying if the lower limit tick marks should -be visible.} - \item{\code{direction}}{A character string indicating the direction of the guide. +\item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} - \item{\code{default.unit}}{A character string indicating \code{\link[grid:unit]{grid::unit()}} -for \code{barwidth} and \code{barheight}.} - \item{\code{reverse}}{logical. If \code{TRUE} the colourbar is reversed. By default, + +\item{reverse}{logical. If \code{TRUE} the colourbar is reversed. By default, the highest value is on the top and the lowest value is on the bottom} - \item{\code{title}}{A character string or expression indicating a title of guide. -If \code{NULL}, the title is not shown. By default -(\code{\link[=waiver]{waiver()}}), the name of the scale object or the name -specified in \code{\link[=labs]{labs()}} is used for the title.} - \item{\code{title.position}}{A character string indicating the position of a -title. One of "top" (default for a vertical guide), "bottom", "left" -(default for a horizontal guide), or "right."} - \item{\code{title.theme}}{A theme object for rendering the title text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.title} in \code{\link[=theme]{theme()}} or theme.} - \item{\code{title.hjust}}{A number specifying horizontal justification of the -title text.} - \item{\code{title.vjust}}{A number specifying vertical justification of the title -text.} - \item{\code{label}}{logical. If \code{TRUE} then the labels are drawn. If -\code{FALSE} then the labels are invisible.} - \item{\code{label.position}}{A character string indicating the position of a -label. One of "top", "bottom" (default for horizontal guide), "left", or -"right" (default for vertical guide).} - \item{\code{label.theme}}{A theme object for rendering the label text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.text} in \code{\link[=theme]{theme()}}.} - \item{\code{label.hjust}}{A numeric specifying horizontal justification of the -label text. The default for standard text is 0 (left-aligned) and 1 -(right-aligned) for expressions.} - \item{\code{label.vjust}}{A numeric specifying vertical justification of the label -text.} - \item{\code{position}}{A character string indicating where the legend should be -placed relative to the plot panels.} - \item{\code{order}}{positive integer less than 99 that specifies the order of + +\item{order}{positive integer less than 99 that specifies the order of this guide among multiple guides. This controls the order in which multiple guides are displayed, not the contents of the guide itself. If 0 (default), the order is determined by a secret algorithm.} - }} + +\item{available_aes}{A vector of character strings listing the aesthetics +for which a colourbar can be drawn.} + +\item{...}{ignored.} } \value{ A guide object diff --git a/man/guide_legend.Rd b/man/guide_legend.Rd index bfd498defc..952cc1d1d4 100644 --- a/man/guide_legend.Rd +++ b/man/guide_legend.Rd @@ -6,27 +6,12 @@ \usage{ guide_legend( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - keywidth = NULL, - keyheight = NULL, - key.spacing = NULL, - key.spacing.x = NULL, - key.spacing.y = NULL, + theme = NULL, position = NULL, direction = NULL, - default.unit = "line", override.aes = list(), nrow = NULL, ncol = NULL, - byrow = FALSE, reverse = FALSE, order = 0, ... @@ -38,48 +23,9 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} -\item{title.position}{A character string indicating the position of a -title. One of "top" (default for a vertical guide), "bottom", "left" -(default for a horizontal guide), or "right."} - -\item{title.theme}{A theme object for rendering the title text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.title} in \code{\link[=theme]{theme()}} or theme.} - -\item{title.hjust}{A number specifying horizontal justification of the -title text.} - -\item{title.vjust}{A number specifying vertical justification of the title -text.} - -\item{label}{logical. If \code{TRUE} then the labels are drawn. If -\code{FALSE} then the labels are invisible.} - -\item{label.position}{A character string indicating the position of a -label. One of "top", "bottom" (default for horizontal guide), "left", or -"right" (default for vertical guide).} - -\item{label.theme}{A theme object for rendering the label text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.text} in \code{\link[=theme]{theme()}}.} - -\item{label.hjust}{A numeric specifying horizontal justification of the -label text. The default for standard text is 0 (left-aligned) and 1 -(right-aligned) for expressions.} - -\item{label.vjust}{A numeric specifying vertical justification of the label -text.} - -\item{keywidth, keyheight}{A numeric or \code{\link[grid:unit]{grid::unit()}} object specifying the -width and height of the legend key respectively. Default value is -\code{legend.key.width}, \code{legend.key.height} or \code{legend.key} in \code{\link[=theme]{theme()}}.\cr -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}: optionally a \code{"null"} unit to stretch -keys to the available space.} - -\item{key.spacing, key.spacing.x, key.spacing.y}{A numeric or \code{\link[grid:unit]{grid::unit()}} -object specifying the distance between key-label pairs in the horizontal -direction (\code{key.spacing.x}), vertical direction (\code{key.spacing.y}) or both -(\code{key.spacing}).} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide overrides, and is combined with, the plot's theme.} \item{position}{A character string indicating where the legend should be placed relative to the plot panels.} @@ -87,18 +33,11 @@ placed relative to the plot panels.} \item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} -\item{default.unit}{A character string indicating \code{\link[grid:unit]{grid::unit()}} -for \code{keywidth} and \code{keyheight}.} - \item{override.aes}{A list specifying aesthetic parameters of legend key. See details and examples.} -\item{nrow}{The desired number of rows of legends.} - -\item{ncol}{The desired number of column of legends.} - -\item{byrow}{logical. If \code{FALSE} (the default) the legend-matrix is -filled by columns, otherwise the legend-matrix is filled by rows.} +\item{nrow, ncol}{The desired number of rows and column of legends +respectively.} \item{reverse}{logical. If \code{TRUE} the order of legends is reversed.} @@ -134,36 +73,32 @@ p1 + scale_fill_continuous(guide = guide_legend()) # Control styles # title position -p1 + guides(fill = guide_legend(title = "LEFT", title.position = "left")) +p1 + guides(fill = guide_legend( + title = "LEFT", theme(legend.title.position = "left") +)) # title text styles via element_text -p1 + guides(fill = - guide_legend( - title.theme = element_text( - size = 15, - face = "italic", - colour = "red", - angle = 0 - ) - ) -) +p1 + guides(fill = guide_legend(theme = theme( + legend.title = element_text(size = 15, face = "italic", colour = "red") +))) # label position -p1 + guides(fill = guide_legend(label.position = "left", label.hjust = 1)) +p1 + guides(fill = guide_legend(theme = theme( + legend.text.position = "left", + legend.text = element_text(hjust = 1) +))) # label styles p1 + scale_fill_continuous( breaks = c(5, 10, 15), labels = paste("long", c(5, 10, 15)), - guide = guide_legend( - direction = "horizontal", - title.position = "top", - label.position = "bottom", - label.hjust = 0.5, - label.vjust = 1, - label.theme = element_text(angle = 90) - ) + guide = guide_legend(theme = theme( + legend.direction = "horizontal", + legend.title.position = "top", + legend.text.position = "bottom", + legend.text = element_text(hjust = 0.5, vjust = 1, angle = 90) + )) ) # Set aesthetic of legend key @@ -180,7 +115,7 @@ p <- ggplot(df, aes(x, y)) + geom_point(aes(colour = color)) p + guides(col = guide_legend(nrow = 8)) p + guides(col = guide_legend(ncol = 8)) -p + guides(col = guide_legend(nrow = 8, byrow = TRUE)) +p + guides(col = guide_legend(nrow = 8, theme = theme(legend.byrow = TRUE))) # reversed order legend p + guides(col = guide_legend(reverse = TRUE)) diff --git a/man/theme.Rd b/man/theme.Rd index f0f7b179e1..8f4ea20015 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -66,11 +66,21 @@ theme( legend.key.size, legend.key.height, legend.key.width, + legend.key.spacing, + legend.key.spacing.x, + legend.key.spacing.y, + legend.frame, + legend.ticks, + legend.ticks.length, + legend.axis.line, legend.text, + legend.text.position, legend.title, + legend.title.position, legend.position, legend.position.inside, legend.direction, + legend.byrow, legend.justification, legend.justification.top, legend.justification.bottom, @@ -188,12 +198,32 @@ inherits from \code{rect})} \item{legend.key.size, legend.key.height, legend.key.width}{size of legend keys (\code{unit}); key background height & width inherit from \code{legend.key.size} or can be specified separately} +\item{legend.key.spacing, legend.key.spacing.x, legend.key.spacing.y}{spacing +between legend keys given as a \code{unit}. Spacing in the horizontal (x) and +vertical (y) direction inherit from \code{legend.key.spacing} or can be +specified separately.} + +\item{legend.frame}{frame drawn around the bar (\code{\link[=element_rect]{element_rect()}}).} + +\item{legend.ticks}{tick marks shown along bars or axes (\code{\link[=element_line]{element_line()}})} + +\item{legend.ticks.length}{length of tick marks in legend (\code{unit})} + +\item{legend.axis.line}{lines along axes in legends (\code{\link[=element_line]{element_line()}})} + \item{legend.text}{legend item labels (\code{\link[=element_text]{element_text()}}; inherits from \code{text})} +\item{legend.text.position}{placement of legend text relative to legend keys +or bars ("top", "right", "bottom" or "left"). The legend text placement +might be incompatible with the legend's direction for some guides.} + \item{legend.title}{title of legend (\code{\link[=element_text]{element_text()}}; inherits from \code{title})} +\item{legend.title.position}{placement of legend title relative to the main +legend ("top", "right", "bottom" or "left").} + \item{legend.position}{the default position of legends ("none", "left", "right", "bottom", "top", "inside")} @@ -203,6 +233,9 @@ placement of legends that have the \code{"inside"} position.} \item{legend.direction}{layout of items in legends ("horizontal" or "vertical")} +\item{legend.byrow}{whether the legend-matrix is filled by columns +(\code{FALSE}, the default) or by rows (\code{TRUE}).} + \item{legend.justification}{anchor point for positioning legend inside plot ("center" or two-element numeric vector) or the justification according to the plot area when positioned outside the plot} diff --git a/tests/testthat/_snaps/guides.md b/tests/testthat/_snaps/guides.md index 3703c52d09..8b3b7ed57f 100644 --- a/tests/testthat/_snaps/guides.md +++ b/tests/testthat/_snaps/guides.md @@ -18,7 +18,7 @@ --- - `title.position` must be one of "top", "right", "bottom", or "left", not "leftish". + `legend.title.position` must be one of "top", "right", "bottom", or "left", not "leftish". --- @@ -27,15 +27,15 @@ --- - When `direction` is "vertical", `label.position` must be one of "right" or "left", not "top". + When `direction` is "vertical", `legend.text.position` must be one of "right" or "left", not "top". --- - When `direction` is "horizontal", `label.position` must be one of "bottom" or "top", not "left". + When `direction` is "horizontal", `legend.text.position` must be one of "bottom" or "top", not "left". --- - `label.position` must be one of "top", "right", "bottom", or "left", not "test". + `legend.text.position` must be one of "top", "right", "bottom", or "left", not "test". i Did you mean "left"? --- diff --git a/tests/testthat/_snaps/guides/left-aligned-legend-key.svg b/tests/testthat/_snaps/guides/left-aligned-legend-key.svg index e71689c336..fc84b274ea 100644 --- a/tests/testthat/_snaps/guides/left-aligned-legend-key.svg +++ b/tests/testthat/_snaps/guides/left-aligned-legend-key.svg @@ -104,16 +104,16 @@ 400 disp mpg - + - - - - -4 -6 -8 + + + + +4 +6 +8 left aligned legend key diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index 2bee90f460..1614da03ea 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -233,20 +233,24 @@ test_that("guide specifications are properly checked", { expect_snapshot_warning(ggplotGrob(p)) - expect_snapshot_error(guide_legend(title.position = "leftish")) + p <- p + guides(shape = guide_legend(theme = theme(legend.title.position = "leftish"))) + expect_snapshot_error(ggplotGrob(p)) expect_snapshot_error(guide_colourbar()$transform()) p <- ggplot(mtcars) + geom_point(aes(mpg, disp, colour = gear)) + - guides(colour = guide_colourbar(label.position = "top")) + guides(colour = guide_colourbar(theme = theme(legend.text.position = "top"))) expect_snapshot_error(ggplotGrob(p)) p <- ggplot(mtcars) + geom_point(aes(mpg, disp, colour = gear)) + - guides(colour = guide_colourbar(direction = "horizontal", label.position = "left")) + guides(colour = guide_colourbar(direction = "horizontal", theme = theme(legend.text.position = "left"))) expect_snapshot_error(ggplotGrob(p)) - expect_snapshot_error(guide_legend(label.position = "test")) + p <- ggplot(mtcars) + + geom_point(aes(mpg, disp, colour = gear)) + + guides(colour = guide_legend(theme = theme(legend.text.position = "test"))) + expect_snapshot_error(ggplotGrob(p)) p <- ggplot(mtcars) + geom_point(aes(mpg, disp, colour = gear)) + guides(colour = guide_legend(nrow = 2, ncol = 2)) @@ -414,9 +418,12 @@ test_that("guide_axis_logticks calculates appropriate ticks", { test_that("guide_legend uses key.spacing correctly", { p <- ggplot(mtcars, aes(disp, mpg, colour = factor(carb))) + geom_point() + - guides(colour = guide_legend( - ncol = 2, key.spacing.y = 1, key.spacing.x = 2 - )) + guides(colour = guide_legend(ncol = 2)) + + theme_test() + + theme( + legend.key.spacing.x = unit(2, "lines"), + legend.key.spacing.y = unit(1, "lines") + ) expect_doppelganger("legend with widely spaced keys", p) }) @@ -810,8 +817,10 @@ test_that("guides title and text are positioned correctly", { scale_colour_continuous( name = "value", guide = guide_colorbar( - title.theme = element_text(size = 11, angle = 0, hjust = 0.5, vjust = 1), - label.theme = element_text(size = 0.8*11, angle = 270, hjust = 0.5, vjust = 1), + theme = theme( + legend.title = element_text(size = 11, angle = 0, hjust = 0.5, vjust = 1), + legend.text = element_text(size = 0.8 * 11, angle = 270, hjust = 0.5, vjust = 1) + ), order = 2 # set guide order to keep visual test stable ) ) + @@ -822,10 +831,12 @@ test_that("guides title and text are positioned correctly", { name = "fill value", guide = guide_legend( direction = "horizontal", - title.position = "top", - label.position = "bottom", - title.theme = element_text(size = 11, angle = 180, hjust = 0, vjust = 1), - label.theme = element_text(size = 0.8*11, angle = 90, hjust = 1, vjust = 0.5), + theme = theme( + legend.title.position = "top", + legend.text.position = "bottom", + legend.title = element_text(size = 11, angle = 180, hjust = 0, vjust = 1), + legend.text = element_text(size = 0.8 * 11, angle = 90, hjust = 1, vjust = 0.5) + ), order = 1 ) ) @@ -838,13 +849,32 @@ test_that("guides title and text are positioned correctly", { geom_point() + scale_alpha(breaks = 1:2) + guides( - colour = guide_legend("colour title with hjust = 0", title.hjust = 0, order = 1), - fill = guide_legend("fill title with hjust = 1", title.hjust = 1, order = 2, - title.position = "bottom", override.aes = list(shape = 21)), - alpha = guide_legend("Title\nfor\nalpha\nwith\nvjust=0", title.vjust = 0, - title.position = "left", order = 3), - shape = guide_legend("Title\nfor\nshape\nwith\nvjust=1", title.vjust = 1, - title.position = "right", order = 4) + colour = guide_legend( + "colour title with hjust = 0", order = 1, + theme = theme(legend.title = element_text(hjust = 0)) + ), + fill = guide_legend( + "fill title with hjust = 1", order = 2, + theme = theme( + legend.title = element_text(hjust = 1), + legend.title.position = "bottom" + ), + override.aes = list(shape = 21) + ), + alpha = guide_legend( + "Title\nfor\nalpha\nwith\nvjust=0", order = 3, + theme = theme( + legend.title = element_text(vjust = 0), + legend.title.position = "left" + ) + ), + shape = guide_legend( + "Title\nfor\nshape\nwith\nvjust=1", order = 4, + theme = theme( + legend.title = element_text(vjust = 1), + legend.title.position = "right" + ) + ) ) expect_doppelganger("legends with all title justifications", p) }) @@ -870,16 +900,16 @@ test_that("colorbar can be styled", { expect_doppelganger("white-to-red colorbar, long thick black ticks, green frame", p + scale_color_gradient( - low = 'white', high = 'red', - guide = guide_colorbar( - frame = element_rect(colour = "green"), - frame.linewidth = 1.5 / .pt, - ticks.colour = "black", - ticks.linewidth = 2.5 / .pt, - ticks.length = unit(0.4, "npc") - ) + low = 'white', high = 'red', + guide = guide_colorbar( + theme = theme( + legend.frame = element_rect(colour = "green", linewidth = 1.5 / .pt), + legend.ticks = element_line("black", linewidth = 2.5 / .pt), + legend.ticks.length = unit(0.4, "npc") ) + ) ) + ) }) test_that("guides can handle multiple aesthetics for one scale", { @@ -909,10 +939,21 @@ test_that("bin guide can be styled correctly", { p + guides(size = guide_bins(show.limits = TRUE)) ) expect_doppelganger("guide_bins can show arrows", - p + guides(size = guide_bins(axis.arrow = arrow(length = unit(1.5, "mm"), ends = "both"))) + p + guides(size = guide_bins()) + + theme_test() + + theme( + legend.axis.line = element_line( + linewidth = 0.5 / .pt, + arrow = arrow(length = unit(1.5, "mm"), ends = "both") + ) + ) ) expect_doppelganger("guide_bins can remove axis", - p + guides(size = guide_bins(axis = FALSE)) + p + guides(size = guide_bins()) + + theme_test() + + theme( + legend.axis.line = element_blank() + ) ) expect_doppelganger("guide_bins work horizontally", p + guides(size = guide_bins(direction = "horizontal")) @@ -935,7 +976,9 @@ test_that("coloursteps guide can be styled correctly", { p + guides(colour = guide_coloursteps(even.steps = FALSE)) ) expect_doppelganger("guide_bins can show ticks", - p + guides(colour = guide_coloursteps(ticks = TRUE)) + p + guides(colour = guide_coloursteps( + theme = theme(legend.ticks = element_line(linewidth = 0.5 / .pt, colour = "white")) + )) ) }) diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R index dcaba82966..146ad29fc8 100644 --- a/tests/testthat/test-theme.R +++ b/tests/testthat/test-theme.R @@ -806,8 +806,8 @@ test_that("legend margins are correct when using relative key sizes", { ) vertical <- p + guides( - colour = guide_colourbar(barheight = unit(1, "null")), - shape = guide_legend(keyheight = unit(1/3, "null")) + colour = guide_colourbar(theme = theme(legend.key.height = unit(1, "null"))), + shape = guide_legend(theme = theme(legend.key.height = unit(1/3, "null"))) ) + theme( legend.box.margin = margin(t = 5, b = 10, unit = "mm"), legend.margin = margin(t = 10, b = 5, unit = "mm") @@ -816,8 +816,8 @@ test_that("legend margins are correct when using relative key sizes", { expect_doppelganger("stretched vertical legends", vertical) horizontal <- p + guides( - colour = guide_colourbar(barwidth = unit(1, "null")), - shape = guide_legend(keywidth = unit(1/3, "null")) + colour = guide_colourbar(theme = theme(legend.key.width = unit(1, "null"))), + shape = guide_legend(theme = theme(legend.key.width = unit(1/3, "null"))) ) + theme( legend.position = "top", legend.box.margin = margin(l = 5, r = 10, unit = "mm"),