diff --git a/NAMESPACE b/NAMESPACE index becf512278..8f4f9c5856 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -94,6 +94,7 @@ S3method(makeContext,dotstackGrob) S3method(merge_element,default) S3method(merge_element,element) S3method(merge_element,element_blank) +S3method(merge_element,margin) S3method(pattern_alpha,GridPattern) S3method(pattern_alpha,GridTilingPattern) S3method(pattern_alpha,default) @@ -494,6 +495,8 @@ export(layer_sf) export(lims) export(map_data) export(margin) +export(margin_auto) +export(margin_part) export(max_height) export(max_width) export(mean_cl_boot) diff --git a/NEWS.md b/NEWS.md index ebbcf92e02..b0c04550c1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # ggplot2 (development version) +* Theme margins can have NA-units to inherit from parent elements. The new + function `margin_part()` has NA-units as default (@teunbrand, #6115) +* New `margin_auto()` specification for theme margins. * New argument `labs(dictionary)` to label based on variable name rather than based on aesthetic (@teunbrand, #5178) * Fixed bug in out-of-bounds binned breaks (@teunbrand, #6054) diff --git a/R/geom-rug.R b/R/geom-rug.R index d675474f43..ffc761b91c 100644 --- a/R/geom-rug.R +++ b/R/geom-rug.R @@ -45,7 +45,7 @@ #' p + #' geom_rug(outside = TRUE, sides = "tr") + #' coord_cartesian(clip = "off") + -#' theme(plot.margin = margin(1, 1, 1, 1, "cm")) +#' theme(plot.margin = margin_auto(1, unit = "cm")) #' #' # increase the line length and #' # expand axis to avoid overplotting diff --git a/R/legend-draw.R b/R/legend-draw.R index 0f734f8fea..d08c6c6c93 100644 --- a/R/legend-draw.R +++ b/R/legend-draw.R @@ -320,7 +320,7 @@ draw_key_text <- function(data, params, size) { fontface = data$fontface %||% 1, fontsize = (data$size %||% 3.88) * .pt ), - margin = margin(0.1, 0.1, 0.1, 0.1, unit = "lines"), + margin = margin_auto(0.1, unit = "lines"), margin_x = TRUE, margin_y = TRUE ) attr(grob, "width") <- convertWidth(grobWidth(grob), "cm", valueOnly = TRUE) diff --git a/R/margins.R b/R/margins.R index 7104a7d330..61ea1dab43 100644 --- a/R/margins.R +++ b/R/margins.R @@ -9,6 +9,18 @@ margin <- function(t = 0, r = 0, b = 0, l = 0, unit = "pt") { u } +#' @rdname element +#' @export +margin_part <- function(t = NA, r = NA, b = NA, l = NA, unit = "pt") { + margin(t = t, r = r, b = b, l = l, unit = unit) +} + +#' @rdname element +#' @export +margin_auto <- function(t = 0, r = t, b = t, l = r, unit = "pt") { + margin(t = t, r = r, b = b, l = l, unit) +} + #' @export #' @rdname is_tests is.margin <- function(x) inherits(x, "margin") diff --git a/R/theme-defaults.R b/R/theme-defaults.R index ac8c5ecb51..e6efd2a783 100644 --- a/R/theme-defaults.R +++ b/R/theme-defaults.R @@ -143,7 +143,7 @@ theme_grey <- function(base_size = 11, base_family = "", title = element_text(family = header_family), spacing = unit(half_line, "pt"), - margins = margin(half_line, half_line, half_line, half_line), + margins = margin_auto(half_line), geom = element_geom( ink = ink, paper = paper, accent = "#3366FF", @@ -226,7 +226,7 @@ theme_grey <- function(base_size = 11, base_family = "", strip.text = element_text( colour = col_mix(ink, paper, 0.105), size = rel(0.8), - margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line) + margin = margin_auto(0.8 * half_line) ), strip.text.x = NULL, strip.text.y = element_text(angle = -90), @@ -341,7 +341,7 @@ theme_linedraw <- function(base_size = 11, base_family = "", strip.text = element_text( colour = paper, size = rel(0.8), - margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line) + margin = margin_auto(0.8 * half_line) ), complete = TRUE @@ -384,7 +384,7 @@ theme_light <- function(base_size = 11, base_family = "", strip.text = element_text( colour = paper, size = rel(0.8), - margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line) + margin = margin_auto(0.8 * half_line) ), complete = TRUE @@ -427,7 +427,7 @@ theme_dark <- function(base_size = 11, base_family = "", strip.text = element_text( colour = col_mix(ink, paper, 0.9), size = rel(0.8), - margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line) + margin = margin_auto(0.8 * half_line) ), complete = TRUE @@ -557,7 +557,7 @@ theme_void <- function(base_size = 11, base_family = "", ), title = element_text(family = header_family), spacing = unit(half_line, "pt"), - margins = margin(half_line, half_line, half_line, half_line), + margins = margin_auto(half_line), axis.text = element_blank(), axis.title = element_blank(), axis.ticks.length = rel(0), @@ -641,7 +641,7 @@ theme_test <- function(base_size = 11, base_family = "", ), title = element_text(family = header_family), spacing = unit(half_line, "pt"), - margins = margin(half_line, half_line, half_line, half_line), + margins = margin_auto(half_line), geom = element_geom( ink = ink, paper = paper, accent = "#3366FF", linewidth = base_line_size, borderwidth = base_line_size, @@ -690,7 +690,7 @@ theme_test <- function(base_size = 11, base_family = "", legend.spacing = rel(2), legend.spacing.x = NULL, legend.spacing.y = NULL, - legend.margin = margin(0, 0, 0, 0, "cm"), + legend.margin = margin_auto(0, unit = "cm"), legend.key = NULL, legend.key.size = unit(1.2, "lines"), legend.key.height = NULL, @@ -705,7 +705,7 @@ theme_test <- function(base_size = 11, base_family = "", legend.direction = NULL, legend.justification = "center", legend.box = NULL, - legend.box.margin = margin(0, 0, 0, 0, "cm"), + legend.box.margin = margin_auto(0, unit = "cm"), legend.box.background = element_blank(), legend.box.spacing = rel(2), @@ -726,7 +726,7 @@ theme_test <- function(base_size = 11, base_family = "", strip.text = element_text( colour = col_mix(ink, paper, 0.105), size = rel(0.8), - margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line) + margin = margin_auto(0.8 * half_line) ), strip.text.x = NULL, strip.text.y = element_text(angle = -90), diff --git a/R/theme-elements.R b/R/theme-elements.R index c51134216f..b1cb67b046 100644 --- a/R/theme-elements.R +++ b/R/theme-elements.R @@ -11,7 +11,8 @@ #' - `element_geom()`: defaults for drawing layers. #' #' `rel()` is used to specify sizes relative to the parent, -#' `margin()` is used to specify the margins of elements. +#' `margin()`, `margin_part()` and `margin_auto()` are all used to specify the +#' margins of elements. #' #' @param fill Fill colour. #' @param colour,color Line/border colour. Color is an alias for colour. @@ -42,7 +43,7 @@ #' #' plot + theme( #' panel.background = element_rect(fill = "white"), -#' plot.margin = margin(2, 2, 2, 2, "cm"), +#' plot.margin = margin_auto(2, unit = "cm"), #' plot.background = element_rect( #' fill = "grey90", #' colour = "black", diff --git a/R/theme.R b/R/theme.R index 2eecc13253..04ab2edf98 100644 --- a/R/theme.R +++ b/R/theme.R @@ -281,14 +281,14 @@ #' legend.position.inside = c(.95, .95), #' legend.justification = c("right", "top"), #' legend.box.just = "right", -#' legend.margin = margin(6, 6, 6, 6) +#' legend.margin = margin_auto(6) #' ) #' #' # The legend.box properties work similarly for the space around #' # all the legends #' p2 + theme( #' legend.box.background = element_rect(), -#' legend.box.margin = margin(6, 6, 6, 6) +#' legend.box.margin = margin_auto(6) #' ) #' #' # You can also control the display of the keys @@ -835,6 +835,18 @@ merge_element.element <- function(new, old) { new } +#' @rdname merge_element +#' @export +merge_element.margin <- function(new, old) { + if (is.null(old) || inherits(old, "element_blank")) { + return(new) + } + if (anyNA(new)) { + new[is.na(new)] <- old[is.na(new)] + } + new +} + #' Combine the properties of two elements #' #' @param e1 An element object @@ -868,6 +880,15 @@ combine_elements <- function(e1, e2) { return(e1) } + if (inherits(e1, "margin") && inherits(e2, "margin")) { + if (anyNA(e2)) { + e2[is.na(e2)] <- unit(0, "pt") + } + if (anyNA(e1)) { + e1[is.na(e1)] <- e2[is.na(e1)] + } + } + # If neither of e1 or e2 are element_* objects, return e1 if (!inherits(e1, "element") && !inherits(e2, "element")) { return(e1) @@ -897,6 +918,10 @@ combine_elements <- function(e1, e2) { e1$linewidth <- e2$linewidth * unclass(e1$linewidth) } + if (inherits(e1, "element_text")) { + e1$margin <- combine_elements(e1$margin, e2$margin) + } + # If e2 is 'richer' than e1, fill e2 with e1 parameters is_subclass <- !any(inherits(e2, class(e1), which = TRUE) == 0) is_subclass <- is_subclass && length(setdiff(class(e2), class(e1)) > 0) diff --git a/man/element.Rd b/man/element.Rd index adb1b7eb22..c43fadfed2 100644 --- a/man/element.Rd +++ b/man/element.Rd @@ -8,6 +8,8 @@ \alias{element_geom} \alias{rel} \alias{margin} +\alias{margin_part} +\alias{margin_auto} \title{Theme elements} \usage{ element_blank() @@ -66,6 +68,10 @@ element_geom( rel(x) margin(t = 0, r = 0, b = 0, l = 0, unit = "pt") + +margin_part(t = NA, r = NA, b = NA, l = NA, unit = "pt") + +margin_auto(t = 0, r = t, b = t, l = r, unit = "pt") } \arguments{ \item{fill}{Fill colour.} @@ -145,7 +151,8 @@ specify the display of how non-data components of the plot are drawn. } \code{rel()} is used to specify sizes relative to the parent, -\code{margin()} is used to specify the margins of elements. +\code{margin()}, \code{margin_part()} and \code{margin_auto()} are all used to specify the +margins of elements. } \examples{ plot <- ggplot(mpg, aes(displ, hwy)) + geom_point() @@ -165,7 +172,7 @@ plot + theme( plot + theme( panel.background = element_rect(fill = "white"), - plot.margin = margin(2, 2, 2, 2, "cm"), + plot.margin = margin_auto(2, unit = "cm"), plot.background = element_rect( fill = "grey90", colour = "black", diff --git a/man/geom_rug.Rd b/man/geom_rug.Rd index 1cc10e785a..b6508c7f9a 100644 --- a/man/geom_rug.Rd +++ b/man/geom_rug.Rd @@ -170,7 +170,7 @@ p + p + geom_rug(outside = TRUE, sides = "tr") + coord_cartesian(clip = "off") + - theme(plot.margin = margin(1, 1, 1, 1, "cm")) + theme(plot.margin = margin_auto(1, unit = "cm")) # increase the line length and # expand axis to avoid overplotting diff --git a/man/merge_element.Rd b/man/merge_element.Rd index 4071e6c69a..ca993eeec3 100644 --- a/man/merge_element.Rd +++ b/man/merge_element.Rd @@ -5,6 +5,7 @@ \alias{merge_element.default} \alias{merge_element.element_blank} \alias{merge_element.element} +\alias{merge_element.margin} \title{Merge a parent element into a child element} \usage{ merge_element(new, old) @@ -14,6 +15,8 @@ merge_element(new, old) \method{merge_element}{element_blank}(new, old) \method{merge_element}{element}(new, old) + +\method{merge_element}{margin}(new, old) } \arguments{ \item{new}{The child element in the theme hierarchy} diff --git a/man/theme.Rd b/man/theme.Rd index 829254ecdf..869e45ea71 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -469,14 +469,14 @@ p2 + theme( legend.position.inside = c(.95, .95), legend.justification = c("right", "top"), legend.box.just = "right", - legend.margin = margin(6, 6, 6, 6) + legend.margin = margin_auto(6) ) # The legend.box properties work similarly for the space around # all the legends p2 + theme( legend.box.background = element_rect(), - legend.box.margin = margin(6, 6, 6, 6) + legend.box.margin = margin_auto(6) ) # You can also control the display of the keys diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R index 2f7f7874f9..a1b656dbab 100644 --- a/tests/testthat/test-theme.R +++ b/tests/testthat/test-theme.R @@ -617,6 +617,22 @@ test_that("complete_theme completes a theme", { reset_theme_settings() }) +test_that("margin_part() mechanics work as expected", { + + t <- theme_gray() + + theme(plot.margin = margin_part(b = 11)) + + test <- calc_element("plot.margin", t) + expect_equal(as.numeric(test), c(5.5, 5.5, 11, 5.5)) + + t <- theme_gray() + + theme(margins = margin_part(b = 11)) + + test <- calc_element("plot.margin", t) + expect_equal(as.numeric(test), c(5.5, 5.5, 11, 5.5)) + +}) + # Visual tests ------------------------------------------------------------ test_that("aspect ratio is honored", {