Skip to content

Commit

Permalink
Merge branch 'main' into boxplot_staples
Browse files Browse the repository at this point in the history
  • Loading branch information
teunbrand authored Aug 9, 2023
2 parents 36f4f6c + bde88f8 commit 532a043
Show file tree
Hide file tree
Showing 39 changed files with 575 additions and 188 deletions.
22 changes: 22 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
* `geom_boxplot()` gains a new argument, `staplewidth` that can draw staples
at the ends of whiskers (@teunbrand, #5126)

* The `size` argument in `annotation_logticks()` has been deprecated in favour
of the `linewidth` argument (#5292).

* `geom_boxplot()` gains an `outliers` argument to switch outliers on or off,
in a manner that does affects the scale range. For hiding outliers that does
not affect the scale range, you can continue to use `outlier.shape = NA`
(@teunbrand, #4892).

* Binned scales now treat `NA`s in limits the same way continuous scales do
(#5355).

* Binned scales work better with `trans = "reverse"` (#5355).

* The `legend.text.align` and `legend.title.align` arguments in `theme()` are
deprecated. The `hjust` setting of the `legend.text` and `legend.title`
elements continues to fulfil the role of text alignment (@teunbrand, #5347).

* Integers are once again valid input to theme arguments that expect numeric
input (@teunbrand, #5369)

* Nicer error messages for xlim/ylim arguments in coord-* functions
(@92amartins, #4601, #5297).

Expand Down Expand Up @@ -60,6 +80,8 @@
* `guide_coloursteps()` and `guide_bins()` sort breaks (#5152).
* `guide_axis()` gains a `cap` argument that can be used to trim the
axis line to extreme breaks (#4907).
* Fixed regression in `guide_legend()` where the `linewidth` key size
wasn't adapted to the width of the lines (#5160).

* `geom_label()` now uses the `angle` aesthetic (@teunbrand, #2785)
* 'lines' units in `geom_label()`, often used in the `label.padding` argument,
Expand Down
23 changes: 15 additions & 8 deletions R/annotation-logticks.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
#' using `scale_y_log10()`. It should be `FALSE` when using
#' `coord_trans(y = "log10")`.
#' @param colour Colour of the tick marks.
#' @param size Thickness of tick marks, in mm.
#' @param linewidth Thickness of tick marks, in mm.
#' @param linetype Linetype of tick marks (`solid`, `dashed`, etc.)
#' @param alpha The transparency of the tick marks.
#' @param color An alias for `colour`.
#' @param ... Other parameters passed on to the layer
#' @param size `r lifecycle::badge("deprecated")`
#'
#' @export
#' @seealso [scale_y_continuous()], [scale_y_log10()] for log scale
Expand Down Expand Up @@ -81,11 +82,17 @@
#' )
annotation_logticks <- function(base = 10, sides = "bl", outside = FALSE, scaled = TRUE,
short = unit(0.1, "cm"), mid = unit(0.2, "cm"), long = unit(0.3, "cm"),
colour = "black", size = 0.5, linetype = 1, alpha = 1, color = NULL, ...)
colour = "black", linewidth = 0.5, linetype = 1, alpha = 1, color = NULL, ...,
size = deprecated())
{
if (!is.null(color))
colour <- color

if (lifecycle::is_present(size)) {
deprecate_soft0("3.5.0", I("Using the `size` aesthetic in this geom"), I("`linewidth`"))
linewidth <- linewidth %||% size
}

layer(
data = dummy_data(),
mapping = NULL,
Expand All @@ -103,7 +110,7 @@ annotation_logticks <- function(base = 10, sides = "bl", outside = FALSE, scaled
mid = mid,
long = long,
colour = colour,
size = size,
linewidth = linewidth,
linetype = linetype,
alpha = alpha,
...
Expand Down Expand Up @@ -163,14 +170,14 @@ GeomLogticks <- ggproto("GeomLogticks", Geom,
ticks$x_b <- with(data, segmentsGrob(
x0 = unit(xticks$x, "native"), x1 = unit(xticks$x, "native"),
y0 = unit(xticks$start, "cm"), y1 = unit(xticks$end, "cm"),
gp = gpar(col = alpha(colour, alpha), lty = linetype, lwd = size * .pt)
gp = gpar(col = alpha(colour, alpha), lty = linetype, lwd = linewidth * .pt)
))
}
if (grepl("t", sides) && nrow(xticks) > 0) {
ticks$x_t <- with(data, segmentsGrob(
x0 = unit(xticks$x, "native"), x1 = unit(xticks$x, "native"),
y0 = unit(1, "npc") - unit(xticks$start, "cm"), y1 = unit(1, "npc") - unit(xticks$end, "cm"),
gp = gpar(col = alpha(colour, alpha), lty = linetype, lwd = size * .pt)
gp = gpar(col = alpha(colour, alpha), lty = linetype, lwd = linewidth * .pt)
))
}
}
Expand Down Expand Up @@ -201,22 +208,22 @@ GeomLogticks <- ggproto("GeomLogticks", Geom,
ticks$y_l <- with(data, segmentsGrob(
y0 = unit(yticks$y, "native"), y1 = unit(yticks$y, "native"),
x0 = unit(yticks$start, "cm"), x1 = unit(yticks$end, "cm"),
gp = gpar(col = alpha(colour, alpha), lty = linetype, lwd = size * .pt)
gp = gpar(col = alpha(colour, alpha), lty = linetype, lwd = linewidth * .pt)
))
}
if (grepl("r", sides) && nrow(yticks) > 0) {
ticks$y_r <- with(data, segmentsGrob(
y0 = unit(yticks$y, "native"), y1 = unit(yticks$y, "native"),
x0 = unit(1, "npc") - unit(yticks$start, "cm"), x1 = unit(1, "npc") - unit(yticks$end, "cm"),
gp = gpar(col = alpha(colour, alpha), lty = linetype, lwd = size * .pt)
gp = gpar(col = alpha(colour, alpha), lty = linetype, lwd = linewidth * .pt)
))
}
}

gTree(children = inject(gList(!!!ticks)))
},

default_aes = aes(colour = "black", size = 0.5, linetype = 1, alpha = 1)
default_aes = aes(colour = "black", linewidth = 0.5, linetype = 1, alpha = 1)
)


Expand Down
36 changes: 4 additions & 32 deletions R/coord-cartesian-.R
Original file line number Diff line number Diff line change
Expand Up @@ -147,37 +147,9 @@ view_scales_from_scale <- function(scale, coord_limits = NULL, expand = TRUE) {
}

panel_guides_grob <- function(guides, position, theme) {
pair <- guide_for_position(guides, position) %||%
list(guide = guide_none(), params = NULL)
pair$guide$draw(theme, pair$params)
}

guide_for_position <- function(guides, position) {
params <- guides$params
has_position <- vapply(
params, function(p) identical(p$position, position), logical(1)
)
if (!any(has_position)) {
return(NULL)
}

# Subset guides and parameters
guides <- guides$get_guide(has_position)
params <- params[has_position]
# Pair up guides with parameters
pairs <- Map(list, guide = guides, params = params)

# Early exit, nothing to merge
if (length(pairs) == 1) {
return(pairs[[1]])
if (!inherits(guides, "Guides")) {
return(zeroGrob())
}

# TODO: There must be a smarter way to merge these
order <- order(vapply(params, function(p) as.numeric(p$order), numeric(1)))
Reduce(
function(old, new) {
old$guide$merge(old$params, new$guide, new$params)
},
pairs[order]
)
pair <- guides$get_position(position)
pair$guide$draw(theme, pair$params)
}
22 changes: 15 additions & 7 deletions R/geom-boxplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,19 @@
#' @inheritParams geom_bar
#' @param geom,stat Use to override the default connection between
#' `geom_boxplot()` and `stat_boxplot()`.
#' @param outliers Whether to display (`TRUE`) or discard (`FALSE`) outliers
#' from the plot. Hiding or discarding outliers can be useful when, for
#' example, raw data points need to be displayed on top of the boxplot.
#' By discarding outliers, the axis limits will adapt to the box and whiskers
#' only, not the full data range. If outliers need to be hidden and the axes
#' needs to show the full data range, please use `outlier.shape = NA` instead.
#' @param outlier.colour,outlier.color,outlier.fill,outlier.shape,outlier.size,outlier.stroke,outlier.alpha
#' Default aesthetics for outliers. Set to `NULL` to inherit from the
#' aesthetics used for the box.
#'
#' In the unlikely event you specify both US and UK spellings of colour, the
#' US spelling will take precedence.
#'
#' Sometimes it can be useful to hide the outliers, for example when overlaying
#' the raw data points on top of the boxplot. Hiding the outliers can be achieved
#' by setting `outlier.shape = NA`. Importantly, this does not remove the outliers,
#' it only hides them, so the range calculated for the y-axis will be the
#' same with outliers shown and outliers hidden.
#'
#' @param notch If `FALSE` (default) make a standard box plot. If
#' `TRUE`, make a notched box plot. Notches are used to compare groups;
#' if the notches of two boxes do not overlap, this suggests that the medians
Expand Down Expand Up @@ -111,6 +111,7 @@
geom_boxplot <- function(mapping = NULL, data = NULL,
stat = "boxplot", position = "dodge2",
...,
outliers = TRUE,
outlier.colour = NULL,
outlier.color = NULL,
outlier.fill = NULL,
Expand All @@ -136,7 +137,9 @@ geom_boxplot <- function(mapping = NULL, data = NULL,
position$preserve <- "single"
}
}

check_number_decimal(staplewidth)
check_bool(outliers)

layer(
data = data,
Expand All @@ -147,6 +150,7 @@ geom_boxplot <- function(mapping = NULL, data = NULL,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list2(
outliers = outliers,
outlier.colour = outlier.color %||% outlier.colour,
outlier.fill = outlier.fill,
outlier.shape = outlier.shape,
Expand All @@ -172,7 +176,7 @@ GeomBoxplot <- ggproto("GeomBoxplot", Geom,

# need to declare `width` here in case this geom is used with a stat that
# doesn't have a `width` parameter (e.g., `stat_identity`).
extra_params = c("na.rm", "width", "orientation"),
extra_params = c("na.rm", "width", "orientation", "outliers"),

setup_params = function(data, params) {
params$flipped_aes <- has_flipped_aes(data, params)
Expand All @@ -185,6 +189,10 @@ GeomBoxplot <- ggproto("GeomBoxplot", Geom,
data$width <- data$width %||%
params$width %||% (resolution(data$x, FALSE) * 0.9)

if (isFALSE(params$outliers)) {
data$outliers <- NULL
}

if (!is.null(data$outliers)) {
suppressWarnings({
out_min <- vapply(data$outliers, min, numeric(1))
Expand Down
103 changes: 96 additions & 7 deletions R/guide-.R
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,79 @@ new_guide <- function(..., available_aes = "any", super) {
#' To create a new type of Guide object, you typically will want to override
#' one or more of the following:
#'
#' TODO: Fill this in properly
#' Properties:
#'
#' - `available_aes` A `character` vector with aesthetics that this guide
#' supports. The value `"any"` indicates all non-position aesthetics.
#'
#' - `params` A named `list` of parameters that the guide needs to function.
#' It has the following roles:
#'
#' - `params` provides the defaults for a guide.
#' - `names(params)` determines what are valid arguments to `new_guide()`.
#' Some parameters are *required* to render the guide. These are: `title`,
#' `name`, `position`, `direction`, `order` and `hash`.
#' - During build stages, `params` holds information about the guide.
#'
#' - `elements` A named list of `character`s, giving the name of theme elements
#' that should be retrieved automatically, for example `"legend.text"`.
#'
#' - `hashables` An `expression` that can be evaluated in the context of
#' `params`. The hash of the evaluated expression determines the merge
#' compatibility of guides, and is stored in `params$hash`.
#'
#' Methods:
#'
#' - `extract_key()` Returns a `data.frame` with (mapped) breaks and labels
#' extracted from the scale, which will be stored in `params$key`.
#'
#' - `extract_decor()` Returns a `data.frame` containing other structured
#' information extracted from the scale, which will be stored in
#' `params$decor`. The `decor` has a guide-specific meaning: it is the bar in
#' `guide_colourbar()`, but specifies the `axis.line` in `guide_axis()`.
#'
#' - `extract_params()` Updates the `params` with other, unstructured
#' information from the scale. An example of this is inheriting the guide's
#' title from the `scale$name` field.
#'
#' - `transform()` Updates the `params$key` based on the coordinates. This
#' applies to position guides, as it rescales the aesthetic to the \[0, 1\]
#' range.
#'
#' - `merge()` Combines information from multiple guides with the same
#' `params$hash`. This ensures that e.g. `guide_legend()` can display both
#' `shape` and `colour` in the same guide.
#'
#' - `get_layer_key()` Extract information from layers. This can be used to
#' check that the guide's aesthetic is actually in use, or to gather
#' information about how legend keys should be displayed.
#'
#' - `setup_params()` Set up parameters at the beginning of drawing stages.
#' It can be used to overrule user-supplied parameters or perform checks on
#' the `params` property.
#'
#' - `override_elements()` Take populated theme elements derived from the
#' `elements` property and allows overriding these theme settings.
#'
#' - `build_title()` Render the guide's title.
#'
#' - `build_labels()` Render the guide's labels.
#'
#' - `build_decor()` Render the `params$decor`, which is different for every
#' guide.
#'
#' - `build_ticks()` Render tick marks.
#'
#' - `measure_grobs()` Measure dimensions of the graphical objects produced
#' by the `build_*()` methods to be used in the layout or assembly.
#'
#' - `arrange_layout()` Set up a layout for how graphical objects produced by
#' the `build_*()` methods should be arranged.
#'
#' - `assemble_drawing()` Take the graphical objects produced by the `build_*()`
#' methods, the measurements from `measure_grobs()` and layout from
#' `arrange_layout()` to finalise the guide.
#'
#' @rdname ggplot2-ggproto
#' @format NULL
#' @usage NULL
Expand Down Expand Up @@ -117,14 +189,15 @@ Guide <- ggproto(
return(NULL)
}
params$decor <- inject(self$extract_decor(scale, !!!params))
self$extract_params(scale, params, self$hashables, ...)
params <- self$extract_params(scale, params, ...)
# Make hash
# TODO: Maybe we only need the hash on demand during merging?
params$hash <- hash(lapply(unname(self$hashables), eval_tidy, data = params))
params
},

# Setup parameters that are only available after training
# TODO: Maybe we only need the hash on demand during merging?
extract_params = function(scale, params, hashables, ...) {
# Make hash
params$hash <- hash(lapply(unname(hashables), eval_tidy, data = params))
extract_params = function(scale, params, ...) {
params
},

Expand All @@ -137,13 +210,18 @@ Guide <- ggproto(

mapped <- scale$map(breaks)
labels <- scale$get_labels(breaks)
# {vctrs} doesn't play nice with expressions, convert to list.
# see also https://github.com/r-lib/vctrs/issues/559
if (is.expression(labels)) {
labels <- as.list(labels)
}

key <- data_frame(mapped, .name_repair = ~ aesthetic)
key$.value <- breaks
key$.label <- labels

if (is.numeric(breaks)) {
key[is.finite(breaks), , drop = FALSE]
vec_slice(key, is.finite(breaks))
} else {
key
}
Expand Down Expand Up @@ -342,3 +420,14 @@ flip_names = c(
# Shortcut for position argument matching
.trbl <- c("top", "right", "bottom", "left")

# Ensure that labels aren't a list of expressions, but proper expressions
validate_labels <- function(labels) {
if (!is.list(labels)) {
return(labels)
}
if (any(vapply(labels, is.language, logical(1)))) {
do.call(expression, labels)
} else {
unlist(labels)
}
}
Loading

0 comments on commit 532a043

Please sign in to comment.