diff --git a/NEWS.md b/NEWS.md index 67c07b0b05..80d4dd7d02 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # ggplot2 (development version) +* (internal) rearranged the code of `Facet$draw_paensl()` method (@teunbrand). * `geom_rug()` prints a warning when `na.rm = FALSE`, as per documentation (@pn317, #5905) * `position_dodge(preserve = "single")` now handles multi-row geoms better, such as `geom_violin()` (@teunbrand based on @clauswilke's work, #2801). diff --git a/R/facet-.R b/R/facet-.R index abdd373d05..f985d84afc 100644 --- a/R/facet-.R +++ b/R/facet-.R @@ -133,8 +133,32 @@ Facet <- ggproto("Facet", NULL, draw_front = function(data, layout, x_scales, y_scales, theme, params) { rep(list(zeroGrob()), vec_unique_count(layout$PANEL)) }, - draw_panels = function(panels, layout, x_scales, y_scales, ranges, coord, data, theme, params) { - cli::cli_abort("Not implemented.") + draw_panels = function(self, panels, layout, x_scales = NULL, y_scales = NULL, + ranges, coord, data = NULL, theme, params) { + + free <- params$free %||% list(x = FALSE, y = FALSE) + space <- params$space_free %||% list(x = FALSE, y = FALSE) + + if ((free$x || free$y) && !coord$is_free()) { + cli::cli_abort( + "{.fn {snake_class(self)}} can't use free scales with \\ + {.fn {snake_class(coord)}}." + ) + } + + aspect_ratio <- theme$aspect.ratio + if (!is.null(aspect_ratio) && (space$x || space$y)) { + cli::cli_abort("Free scales cannot be mixed with a fixed aspect ratio.") + } + + table <- self$init_gtable( + panels, layout, theme, ranges, params, + aspect_ratio = aspect_ratio %||% coord$aspect(ranges[[1]]), + clip = coord$clip + ) + + table <- self$attach_axes(table, layout, ranges, coord, theme, params) + self$attach_strips(table, layout, params, theme) }, draw_labels = function(panels, layout, x_scales, y_scales, ranges, coord, data, theme, labels, params) { panel_dim <- find_panel(panels) @@ -173,6 +197,64 @@ Facet <- ggproto("Facet", NULL, finish_data = function(data, layout, x_scales, y_scales, params) { data }, + init_gtable = function(panels, layout, theme, ranges, params, + aspect_ratio = NULL, clip = "on") { + + # Initialise matrix of panels + dim <- c(max(layout$ROW), max(layout$COL)) + table <- matrix(list(zeroGrob()), dim[1], dim[2]) + table[cbind(layout$ROW, layout$COL)] <- panels + + # Set initial sizes + widths <- unit(rep(1, dim[2]), "null") + heights <- unit(rep(1 * abs(aspect_ratio %||% 1), dim[1]), "null") + + # When space are free, let panel parameter limits determine size of panel + space <- params$space_free %||% list(x = FALSE, y = FALSE) + if (space$x) { + idx <- layout$PANEL[layout$ROW == 1] + widths <- vapply(idx, function(i) diff(ranges[[i]]$x.range), numeric(1)) + widths <- unit(widths, "null") + } + + if (space$y) { + idx <- layout$PANEL[layout$COL == 1] + heights <- vapply(idx, function(i) diff(ranges[[i]]$y.range), numeric(1)) + heights <- unit(heights, "null") + } + + # Build gtable + table <- gtable_matrix( + "layout", table, + widths = widths, heights = heights, + respect = !is.null(aspect_ratio), + clip = clip, z = matrix(1, dim[1], dim[2]) + ) + + # Set panel names + table$layout$name <- paste( + "panel", + rep(seq_len(dim[2]), dim[1]), + rep(seq_len(dim[1]), each = dim[2]), + sep = "-" + ) + + # Add spacing between panels + spacing <- lapply( + c(x = "panel.spacing.x", y = "panel.spacing.y"), + calc_element, theme = theme + ) + + table <- gtable_add_col_space(table, spacing$x) + table <- gtable_add_row_space(table, spacing$y) + table + }, + attach_axes = function(table, layout, ranges, coord, theme, params) { + table + }, + attach_strips = function(table, layout, params, theme) { + table + }, vars = function() { character(0) } diff --git a/R/facet-grid-.R b/R/facet-grid-.R index 0854b5299b..4d4f12f345 100644 --- a/R/facet-grid-.R +++ b/R/facet-grid-.R @@ -335,175 +335,101 @@ FacetGrid <- ggproto("FacetGrid", Facet, } data }, - draw_panels = function(panels, layout, x_scales, y_scales, ranges, coord, data, theme, params) { - if ((params$free$x || params$free$y) && !coord$is_free()) { - cli::cli_abort("{.fn {snake_class(coord)}} doesn't support free scales.") - } - # Fill missing parameters for backward compatibility - params$draw_axes <- params$draw_axes %||% list(x = FALSE, y = FALSE) - params$axis_labels <- params$axis_labels %||% list(x = TRUE, y = TRUE) + attach_axes = function(table, layout, ranges, coord, theme, params) { + + # Setup parameters + draw_axes <- params$draw_axes %||% list(x = FALSE, y = FALSE) + axis_labels <- params$axis_labels %||% list(x = TRUE, y = TRUE) - if (!params$axis_labels$x) { - cols <- seq_len(nrow(layout)) - x_axis_order <- as.integer(layout$PANEL[order(layout$ROW, layout$COL)]) + dim <- c(max(layout$ROW), max(layout$COL)) + if (!axis_labels$x) { + cols <- seq_len(nrow(layout)) + x_order <- as.integer(layout$PANEL[order(layout$ROW, layout$COL)]) } else { - cols <- which(layout$ROW == 1) - x_axis_order <- layout$COL + cols <- which(layout$ROW == 1) + x_order <- layout$COL } - if (!params$axis_labels$y) { - rows <- seq_len(nrow(layout)) - y_axis_order <- as.integer(layout$PANEL[order(layout$ROW, layout$COL)]) + if (!axis_labels$y) { + rows <- seq_len(nrow(layout)) + y_order <- as.integer(layout$PANEL[order(layout$ROW, layout$COL)]) } else { - rows <- which(layout$COL == 1) - y_axis_order <- layout$ROW + rows <- which(layout$COL == 1) + y_order <- layout$ROW } - ranges <- censor_labels(ranges, layout, params$axis_labels) - axes <- render_axes(ranges[cols], ranges[rows], coord, theme, transpose = TRUE) - - col_vars <- unique0(layout[names(params$cols)]) - row_vars <- unique0(layout[names(params$rows)]) - # Adding labels metadata, useful for labellers - attr(col_vars, "type") <- "cols" - attr(col_vars, "facet") <- "grid" - attr(row_vars, "type") <- "rows" - attr(row_vars, "facet") <- "grid" - strips <- render_strips(col_vars, row_vars, params$labeller, theme) + # Render individual axes + ranges <- censor_labels(ranges, layout, axis_labels) + axes <- render_axes(ranges[cols], ranges[rows], coord, theme, transpose = TRUE) + mtx <- function(x, o) matrix(x[o], dim[1], dim[2], byrow = TRUE) - aspect_ratio <- theme$aspect.ratio - if (!is.null(aspect_ratio) && (params$space_free$x || params$space_free$y)) { - cli::cli_abort("Free scales cannot be mixed with a fixed aspect ratio.") - } - aspect_ratio <- aspect_ratio %||% coord$aspect(ranges[[1]]) - if (is.null(aspect_ratio)) { - aspect_ratio <- 1 - respect <- FALSE - } else { - respect <- TRUE - } - ncol <- max(layout$COL) - nrow <- max(layout$ROW) - mtx <- function(x) matrix(x, nrow = nrow, ncol = ncol, byrow = TRUE) - panel_table <- mtx(panels) - - # @kohske - # Now size of each panel is calculated using PANEL$ranges, which is given by - # coord_train called by train_range. - # So here, "scale" need not to be referred. - # - # In general, panel has all information for building facet. - if (params$space_free$x) { - ps <- layout$PANEL[layout$ROW == 1] - widths <- vapply(ps, function(i) diff(ranges[[i]]$x.range), numeric(1)) - panel_widths <- unit(widths, "null") + if (draw_axes$x) { + table <- weave_axes(table, lapply(axes$x, mtx, o = x_order)) } else { - panel_widths <- rep(unit(1, "null"), ncol) + table <- seam_table(table, axes$x$top, side = "top", name = "axis-t", z = 3) + table <- seam_table(table, axes$x$bottom, side = "bottom", name = "axis-b", z = 3) } - if (params$space_free$y) { - ps <- layout$PANEL[layout$COL == 1] - heights <- vapply(ps, function(i) diff(ranges[[i]]$y.range), numeric(1)) - panel_heights <- unit(heights, "null") + + if (draw_axes$y) { + table <- weave_axes(table, lapply(axes$y, mtx, o = y_order)) } else { - panel_heights <- rep(unit(1 * abs(aspect_ratio), "null"), nrow) + table <- seam_table(table, axes$y$left, side = "left", name = "axis-l", z = 3) + table <- seam_table(table, axes$y$right, side = "right", name = "axis-r", z = 3) } - panel_table <- gtable_matrix("layout", panel_table, - panel_widths, panel_heights, respect = respect, clip = coord$clip, z = mtx(1)) - panel_table$layout$name <- paste0('panel-', rep(seq_len(nrow), ncol), '-', rep(seq_len(ncol), each = nrow)) + table + }, - spacing_x <- calc_element("panel.spacing.x", theme) - spacing_y <- calc_element("panel.spacing.y", theme) - panel_table <- gtable_add_col_space(panel_table, spacing_x) - panel_table <- gtable_add_row_space(panel_table, spacing_y) + attach_strips = function(table, layout, params, theme) { - # Add axes - if (params$draw_axes$x) { - axes$x <- lapply(axes$x, function(x) mtx(x[x_axis_order])) - panel_table <- weave_axes(panel_table, axes$x)$panels - } else { - panel_table <- gtable_add_rows(panel_table, max_height(axes$x$top), 0) - panel_table <- gtable_add_rows(panel_table, max_height(axes$x$bottom), -1) - panel_pos_col <- panel_cols(panel_table) - panel_table <- gtable_add_grob(panel_table, axes$x$top, 1, panel_pos_col$l, clip = "off", name = paste0("axis-t-", seq_along(axes$x$top)), z = 3) - panel_table <- gtable_add_grob(panel_table, axes$x$bottom, -1, panel_pos_col$l, clip = "off", name = paste0("axis-b-", seq_along(axes$x$bottom)), z = 3) - } + col_vars <- unique0(layout[names(params$cols)]) + row_vars <- unique0(layout[names(params$rows)]) + attr(col_vars, "type") <- "cols" + attr(row_vars, "type") <- "rows" + attr(col_vars, "facet") <- "grid" + attr(row_vars, "facet") <- "grid" - if (params$draw_axes$y) { - axes$y <- lapply(axes$y, function(y) mtx(y[y_axis_order])) - panel_table <- weave_axes(panel_table, axes$y)$panels - } else { - panel_table <- gtable_add_cols(panel_table, max_width(axes$y$left), 0) - panel_table <- gtable_add_cols(panel_table, max_width(axes$y$right), -1) - panel_pos_rows <- panel_rows(panel_table) - panel_table <- gtable_add_grob(panel_table, axes$y$left, panel_pos_rows$t, 1, clip = "off", name = paste0("axis-l-", seq_along(axes$y$left)), z = 3) - panel_table <- gtable_add_grob(panel_table, axes$y$right, panel_pos_rows$t, -1, clip = "off", name = paste0("axis-r-", seq_along(axes$y$right)), z= 3) - } + strips <- render_strips(col_vars, row_vars, params$labeller, theme) + padding <- convertUnit(calc_element("strip.switch.pad.grid", theme), "cm") - # Add strips switch_x <- !is.null(params$switch) && params$switch %in% c("both", "x") - switch_y <- !is.null(params$switch) && params$switch %in% c("both", "y") - inside_x <- (theme$strip.placement.x %||% theme$strip.placement %||% "inside") == "inside" - inside_y <- (theme$strip.placement.y %||% theme$strip.placement %||% "inside") == "inside" - strip_padding <- convertUnit(calc_element("strip.switch.pad.grid", theme), "cm") - panel_pos_col <- panel_cols(panel_table) + inside_x <- (calc_element("strip.placement.x", theme) %||% "inside") == "inside" + shift_x <- if (inside_x) 1 else 2 + if (switch_x) { - if (!is.null(strips$x$bottom)) { - if (inside_x) { - panel_table <- gtable_add_rows(panel_table, max_height(strips$x$bottom), -2) - panel_table <- gtable_add_grob(panel_table, strips$x$bottom, -2, panel_pos_col$l, clip = "on", name = paste0("strip-b-", seq_along(strips$x$bottom)), z = 2) - } else { - if (!all(vapply(axes$x$bottom, is.zero, logical(1)))) { - panel_table <- gtable_add_rows(panel_table, strip_padding, -1) - } - panel_table <- gtable_add_rows(panel_table, max_height(strips$x$bottom), -1) - panel_table <- gtable_add_grob(panel_table, strips$x$bottom, -1, panel_pos_col$l, clip = "on", name = paste0("strip-b-", seq_along(strips$x$bottom)), z = 2) - } - } + space <- if (!inside_x & table_has_grob(table, "axis-b")) padding + table <- seam_table( + table, strips$x$bottom, side = "bottom", name = "strip-b", + shift = shift_x, z = 2, clip = "on", spacing = space + ) } else { - if (!is.null(strips$x$top)) { - if (inside_x) { - panel_table <- gtable_add_rows(panel_table, max_height(strips$x$top), 1) - panel_table <- gtable_add_grob(panel_table, strips$x$top, 2, panel_pos_col$l, clip = "on", name = paste0("strip-t-", seq_along(strips$x$top)), z = 2) - } else { - if (!all(vapply(axes$x$top, is.zero, logical(1)))) { - panel_table <- gtable_add_rows(panel_table, strip_padding, 0) - } - panel_table <- gtable_add_rows(panel_table, max_height(strips$x$top), 0) - panel_table <- gtable_add_grob(panel_table, strips$x$top, 1, panel_pos_col$l, clip = "on", name = paste0("strip-t-", seq_along(strips$x$top)), z = 2) - } - } + space <- if (!inside_x & table_has_grob(table, "axis-t")) padding + table <- seam_table( + table, strips$x$top, side = "top", name = "strip-t", + shift = shift_x, z = 2, clip = "on", spacing = space + ) } - panel_pos_rows <- panel_rows(panel_table) + + switch_y <- !is.null(params$switch) && params$switch %in% c("both", "y") + inside_y <- (calc_element("strip.placement.y", theme) %||% "inside") == "inside" + shift_y <- if (inside_y) 1 else 2 + if (switch_y) { - if (!is.null(strips$y$left)) { - if (inside_y) { - panel_table <- gtable_add_cols(panel_table, max_width(strips$y$left), 1) - panel_table <- gtable_add_grob(panel_table, strips$y$left, panel_pos_rows$t, 2, clip = "on", name = paste0("strip-l-", seq_along(strips$y$left)), z = 2) - } else { - if (!all(vapply(axes$y$left, is.zero, logical(1)))) { - panel_table <- gtable_add_cols(panel_table, strip_padding, 0) - } - panel_table <- gtable_add_cols(panel_table, max_width(strips$y$left), 0) - panel_table <- gtable_add_grob(panel_table, strips$y$left, panel_pos_rows$t, 1, clip = "on", name = paste0("strip-l-", seq_along(strips$y$left)), z = 2) - } - } + space <- if (!inside_y & table_has_grob(table, "axis-l")) padding + table <- seam_table( + table, strips$y$left, side = "left", name = "strip-l", + shift = shift_y, z = 2, clip = "on", spacing = space + ) } else { - if (!is.null(strips$y$right)) { - if (inside_y) { - panel_table <- gtable_add_cols(panel_table, max_width(strips$y$right), -2) - panel_table <- gtable_add_grob(panel_table, strips$y$right, panel_pos_rows$t, -2, clip = "on", name = paste0("strip-r-", seq_along(strips$y$right)), z = 2) - } else { - if (!all(vapply(axes$y$right, is.zero, logical(1)))) { - panel_table <- gtable_add_cols(panel_table, strip_padding, -1) - } - panel_table <- gtable_add_cols(panel_table, max_width(strips$y$right), -1) - panel_table <- gtable_add_grob(panel_table, strips$y$right, panel_pos_rows$t, -1, clip = "on", name = paste0("strip-r-", seq_along(strips$y$right)), z = 2) - } - } + space <- if (!inside_y & table_has_grob(table, "axis-r")) padding + table <- seam_table( + table, strips$y$right, side = "right", name = "strip-r", + shift = shift_y, z = 2, clip = "on", spacing = space + ) } - panel_table + table }, + vars = function(self) { names(c(self$params$rows, self$params$cols)) } @@ -519,3 +445,52 @@ ulevels <- function(x, na.last = TRUE) { sort(unique0(x), na.last = na.last) } } + +table_has_grob <- function(table, pattern) { + grobs <- table$grobs[grep(pattern, table$layout$name)] + !all(vapply(grobs, is.zero, logical(1))) +} + +seam_table <- function(table, grobs = NULL, side, shift = 1, name, z = 1, + clip = "off", spacing = NULL) { + if (is.null(grobs)) { + return(table) + } + + panel_col <- panel_cols(table) + panel_row <- panel_rows(table) + + row <- switch( + side, + bottom = max(panel_row$b) + shift - 1L, + top = min(panel_row$t) - shift, + panel_row$t + ) + + col <- switch( + side, + right = max(panel_col$r) + shift - 1L, + left = min(panel_col$l) - shift, + panel_col$l + ) + + if (!is.null(spacing)) { + table <- switch( + side, + bottom = , top = gtable_add_rows(table, spacing, row), + left = , right = gtable_add_cols(table, spacing, col) + ) + row <- row + as.numeric(side == "bottom") + col <- col + as.numeric(side == "right") + } + + table <- switch( + side, + bottom = , top = gtable_add_rows(table, max_height(grobs), row), + left = , right = gtable_add_cols(table, max_width(grobs), col) + ) + name <- paste(name, seq_along(grobs), sep = "-") + row <- row + as.numeric(side %in% c("top", "bottom")) + col <- col + as.numeric(side %in% c("left", "right")) + gtable_add_grob(table, grobs, t = row, l = col, name = name, z = z, clip = clip) +} diff --git a/R/facet-wrap.R b/R/facet-wrap.R index dfe487a3f8..4f07736f7d 100644 --- a/R/facet-wrap.R +++ b/R/facet-wrap.R @@ -257,227 +257,203 @@ FacetWrap <- ggproto("FacetWrap", Facet, data$PANEL <- layout$PANEL[match(keys$x, keys$y)] data }, - draw_panels = function(self, panels, layout, x_scales, y_scales, ranges, coord, data, theme, params) { - if ((params$free$x || params$free$y) && !coord$is_free()) { - cli::cli_abort("{.fn {snake_class(self)}} can't use free scales with {.fn {snake_class(coord)}}.") + + attach_axes = function(table, layout, ranges, coord, theme, params) { + + # Setup parameters + draw_axes <- params$draw_axes %||% list(x = FALSE, y = FALSE) + axis_labels <- params$axis_labels %||% list(x = TRUE, y = TRUE) + free <- params$free %||% list(x = FALSE, y = FALSE) + + # Render individual axes + ranges <- censor_labels(ranges, layout, axis_labels) + original <- render_axes(ranges, ranges, coord, theme, transpose = TRUE) + + # Sort axes + x_order <- if (axis_labels$x) layout$SCALE_X else seq_len(nrow(layout)) + y_order <- if (axis_labels$y) layout$SCALE_Y else seq_len(nrow(layout)) + original$x <- lapply(original$x, `[`, i = x_order) + original$y <- lapply(original$y, `[`, i = y_order) + + # Setup matrices for axes + dim <- c(max(layout$ROW), max(layout$COL)) + index <- convertInd(layout$ROW, layout$COL, dim[1]) + empty <- matrix(list(zeroGrob()), dim[1], dim[2]) + top <- bottom <- left <- right <- empty + + # Fill axis matrices + top[index] <- original$x$top + bottom[index] <- original$x$bottom + left[index] <- original$y$left + right[index] <- original$y$right + + # Suppress interior axes + if (!(free$x || draw_axes$x)) { + top[-1, ] <- list(zeroGrob()) + bottom[-dim[1], ] <- list(zeroGrob()) + } + if (!(free$y || draw_axes$y)) { + left[, -1] <- list(zeroGrob()) + right[, -dim[2]] <- list(zeroGrob()) } - if (inherits(coord, "CoordFlip")) { - if (params$free$x) { - layout$SCALE_X <- seq_len(nrow(layout)) - } else { - layout$SCALE_X <- 1L + # Check for empty panels and exit early if there are none + empty <- matrix(TRUE, dim[1], dim[2]) + empty[index] <- FALSE + if (!any(empty)) { + axes <- list(top = top, bottom = bottom, left = left, right = right) + return(weave_axes(table, axes, empty)) + } + + # Match empty table to layout + matched <- vec_match( + data_frame0(ROW = as.vector(row(empty)), COL = as.vector(col(empty))), + layout[, c("ROW", "COL")] + ) + + # Figure out where axes should be added back + empty_bottom <- which( apply(empty, 2, function(x) c(diff(x) == 1, FALSE))) + empty_top <- which( apply(empty, 2, function(x) c(FALSE, diff(x) == -1))) + empty_right <- which(t(apply(empty, 1, function(x) c(diff(x) == 1, FALSE)))) + empty_left <- which(t(apply(empty, 1, function(x) c(FALSE, diff(x) == -1)))) + + # Keep track of potential clashes between strips and axes + inside <- (theme$strip.placement %||% "inside") == "inside" + strip <- params$strip.position %||% "top" + clash <- c(top = FALSE, bottom = FALSE, left = FALSE, right = FALSE) + + # Go through every position and place back axes + if (length(empty_bottom) > 0) { + x_axes <- original$x$bottom[matched[empty_bottom]] + clash["bottom"] <- strip == "bottom" && !inside && !free$x && + !all(vapply(x_axes, is.zero, logical(1))) + if (!clash["bottom"]) { + bottom[empty_bottom] <- x_axes } - if (params$free$y) { - layout$SCALE_Y <- seq_len(nrow(layout)) - } else { - layout$SCALE_Y <- 1L + } + + if (length(empty_top) > 0) { + x_axes <- original$x$top[matched[empty_top]] + clash["top"] <- strip == "top" && !inside && !free$x && + !all(vapply(x_axes, is.zero, logical(1))) + if (!clash["top"]) { + top[empty_top] <- x_axes } } - ncol <- max(layout$COL) - nrow <- max(layout$ROW) - n <- nrow(layout) - panel_order <- order(layout$ROW, layout$COL) - layout <- layout[panel_order, ] - panels <- panels[panel_order] - panel_pos <- convertInd(layout$ROW, layout$COL, nrow) + if (length(empty_right) > 0) { + y_axes <- original$y$right[matched[empty_right]] + clash["right"] <- strip == "right" && !inside && !free$y && + !all(vapply(y_axes, is.zero, logical(1))) + if (!clash["right"]) { + right[empty_right] <- y_axes + } + } - # Fill missing parameters for backward compatibility - params$draw_axes <- params$draw_axes %||% list(x = FALSE, y = FALSE) - params$axis_labels <- params$axis_labels %||% list(x = TRUE, y = TRUE) + if (length(empty_left) > 0) { + y_axes <- original$y$left[matched[empty_left]] + clash["left"] <- strip == "left" && !inside && !free$y && + !all(vapply(y_axes, is.zero, logical(1))) + if (!clash["left"]) { + left[empty_left] <- y_axes + } + } - x_axis_order <- if (params$axis_labels$x) layout$SCALE_X else seq(n) - y_axis_order <- if (params$axis_labels$y) layout$SCALE_Y else seq(n) + if (any(clash)) { + cli::cli_warn( + "Suppressing axis rendering when \\ + {.code strip.position =\"{strip}\"} and \\ + {.code strip.placement = \"outside\".}" + ) + } + + axes <- list(top = top, bottom = bottom, left = left, right = right) + weave_axes(table, axes, empty) + }, - ranges <- censor_labels(ranges, layout, params$axis_labels) - axes <- render_axes(ranges, ranges, coord, theme, transpose = TRUE) + attach_strips = function(table, layout, params, theme) { + # Format labels if (length(params$facets) == 0) { - # Add a dummy label - labels_df <- data_frame0("(all)" = "(all)", .size = 1) + labels <- data_frame0("(all)" = "(all)", .size = 1) } else { - labels_df <- layout[names(params$facets)] + labels <- layout[names(params$facets)] } - attr(labels_df, "facet") <- "wrap" - strips <- render_strips( - structure(labels_df, type = "rows"), - structure(labels_df, type = "cols"), - params$labeller, theme) + attr(labels, "facet") <- "wrap" - # If user hasn't set aspect ratio, ask the coordinate system if - # it wants to specify one - aspect_ratio <- theme$aspect.ratio %||% coord$aspect(ranges[[1]]) + # Render individual strips + strips <- render_strips( + x = structure(labels, type = "rows"), + y = structure(labels, type = "cols"), + params$labeller, theme + ) - if (is.null(aspect_ratio)) { - aspect_ratio <- 1 - respect <- FALSE + # Set position invariant parameters + padding <- convertUnit(calc_element("strip.switch.pad.wrap", theme), "cm") + position <- params$strip.position %||% "top" + pos <- substr(position, 1, 1) + prefix <- paste0("strip-", pos) + + # Setup weaving table + dim <- c(max(layout$ROW), max(layout$COL)) + index <- convertInd(layout$ROW, layout$COL, dim[1]) + mat <- matrix(list(zeroGrob()), dim[1], dim[2]) + mat[index] <- unlist(unname(strips), recursive = FALSE)[[position]] + + # Setup orientation dependent parameters + if (position %in% c("top", "bottom")) { + inside <- "strip.placement.x" + size <- apply(mat, 1, max_height, value_only = TRUE) + weave <- weave_tables_row } else { - respect <- TRUE + inside <- "strip.placement.y" + size <- apply(mat, 2, max_width, value_only = TRUE) + weave <- weave_tables_col } - empty_table <- matrix(list(zeroGrob()), nrow = nrow, ncol = ncol) - panel_table <- empty_table - panel_table[panel_pos] <- panels - empties <- apply(panel_table, c(1,2), function(x) is.zero(x[[1]])) - panel_table <- gtable_matrix("layout", panel_table, - widths = unit(rep(1, ncol), "null"), - heights = unit(rep(abs(aspect_ratio), nrow), "null"), respect = respect, clip = coord$clip, z = matrix(1, ncol = ncol, nrow = nrow)) - panel_table$layout$name <- paste0('panel-', rep(seq_len(ncol), nrow), '-', rep(seq_len(nrow), each = ncol)) - - - panel_table <- gtable_add_col_space(panel_table, calc_element("panel.spacing.x", theme)) - panel_table <- gtable_add_row_space(panel_table, calc_element("panel.spacing.y", theme)) - - # Add axes - axis_mat_x_top <- empty_table - axis_mat_x_top[panel_pos] <- axes$x$top[x_axis_order] - axis_mat_x_bottom <- empty_table - axis_mat_x_bottom[panel_pos] <- axes$x$bottom[x_axis_order] - axis_mat_y_left <- empty_table - axis_mat_y_left[panel_pos] <- axes$y$left[y_axis_order] - axis_mat_y_right <- empty_table - axis_mat_y_right[panel_pos] <- axes$y$right[y_axis_order] - if (!(params$free$x || params$draw_axes$x)) { - axis_mat_x_top[-1,]<- list(zeroGrob()) - axis_mat_x_bottom[-nrow,]<- list(zeroGrob()) - } - if (!(params$free$y || params$draw_axes$y)) { - axis_mat_y_left[, -1] <- list(zeroGrob()) - axis_mat_y_right[, -ncol] <- list(zeroGrob()) + inside <- (calc_element(inside, theme) %||% "inside") == "inside" + shift <- switch(position, top = , left = c(-1, -2), c(0, 1)) + shift <- if (inside) shift[1] else shift[2] + size <- unit(size, "cm") + + table <- weave(table, mat, shift, size, name = prefix, z = 2, clip = "on") + + if (!inside) { + axes <- grepl(paste0("axis-", pos), table$layout$name) + has_axes <- !vapply(table$grobs[axes], is.zero, logical(1)) + has_axes <- split(has_axes, table$layout[[pos]][axes]) + has_axes <- vapply(has_axes, sum, numeric(1)) > 0 + padding <- rep(padding, length(has_axes)) + padding[!has_axes] <- unit(0, "cm") + table <- weave(table, , shift, padding) } + table + }, - # Add back missing axes - if (any(empties)) { - row_ind <- row(empties) - col_ind <- col(empties) - inside <- (theme$strip.placement %||% "inside") == "inside" - empty_bottom <- apply(empties, 2, function(x) c(diff(x) == 1, FALSE)) - if (any(empty_bottom)) { - pos <- which(empty_bottom) - panel_loc <- data_frame0( - ROW = row_ind[pos], - COL = col_ind[pos], - .size = length(pos) - ) - panels <- vec_match(panel_loc, layout[, c("ROW", "COL")]) - x_axes <- axes$x$bottom[x_axis_order[panels]] - if (params$strip.position == "bottom" && - !inside && - any(!vapply(x_axes, is.zero, logical(1))) && - !params$free$x) { - cli::cli_warn("Suppressing axis rendering when {.code strip.position = \"bottom\"} and {.code strip.placement == \"outside\"}") - } else { - axis_mat_x_bottom[pos] <- x_axes - } - } - empty_top <- apply(empties, 2, function(x) c(FALSE, diff(x) == -1)) - if (any(empty_top)) { - pos <- which(empty_top) - panel_loc <- data_frame0( - ROW = row_ind[pos], - COL = col_ind[pos], - .size = length(pos) - ) - panels <- vec_match(panel_loc, layout[, c("ROW", "COL")]) - x_axes <- axes$x$top[x_axis_order[panels]] - if (params$strip.position == "top" && - !inside && - any(!vapply(x_axes, is.zero, logical(1))) && - !params$free$x) { - cli::cli_warn("Suppressing axis rendering when {.code strip.position = \"top\"} and {.code strip.placement == \"outside\"}") - } else { - axis_mat_x_top[pos] <- x_axes - } - } - empty_right <- t(apply(empties, 1, function(x) c(diff(x) == 1, FALSE))) - if (any(empty_right)) { - pos <- which(empty_right) - panel_loc <- data_frame0( - ROW = row_ind[pos], - COL = col_ind[pos], - .size = length(pos) - ) - panels <- vec_match(panel_loc, layout[, c("ROW", "COL")]) - y_axes <- axes$y$right[y_axis_order[panels]] - if (params$strip.position == "right" && - !inside && - any(!vapply(y_axes, is.zero, logical(1))) && - !params$free$y) { - cli::cli_warn("Suppressing axis rendering when {.code strip.position = \"right\"} and {.code strip.placement == \"outside\"}") - } else { - axis_mat_y_right[pos] <- y_axes - } - } - empty_left <- t(apply(empties, 1, function(x) c(FALSE, diff(x) == -1))) - if (any(empty_left)) { - pos <- which(empty_left) - panel_loc <- data_frame0( - ROW = row_ind[pos], - COL = col_ind[pos], - .size = length(pos) - ) - panels <- vec_match(panel_loc, layout[, c("ROW", "COL")]) - y_axes <- axes$y$left[y_axis_order[panels]] - if (params$strip.position == "left" && - !inside && - any(!vapply(y_axes, is.zero, logical(1))) && - !params$free$y) { - cli::cli_warn("Suppressing axis rendering when {.code strip.position = \"left\"} and {.code strip.placement == \"outside\"}") - } else { - axis_mat_y_left[pos] <- y_axes - } - } - } - panel_table <- weave_axes( - panel_table, - axes = list( - top = axis_mat_x_top, bottom = axis_mat_x_bottom, - left = axis_mat_y_left, right = axis_mat_y_right - ), - empty = empties - ) - axis_size <- panel_table$sizes - panel_table <- panel_table$panels - - strip_padding <- convertUnit(calc_element("strip.switch.pad.wrap", theme), "cm") - strip_name <- paste0("strip-", substr(params$strip.position, 1, 1)) - strip_mat <- empty_table - strip_mat[panel_pos] <- unlist(unname(strips), recursive = FALSE)[[params$strip.position]] - if (params$strip.position %in% c("top", "bottom")) { - inside_x <- (theme$strip.placement.x %||% theme$strip.placement %||% "inside") == "inside" - if (params$strip.position == "top") { - placement <- if (inside_x) -1 else -2 - strip_pad <- axis_size$top + draw_panels = function(self, panels, layout, x_scales, y_scales, ranges, coord, data, theme, params) { + if (inherits(coord, "CoordFlip")) { + if (params$free$x) { + layout$SCALE_X <- seq_len(nrow(layout)) } else { - placement <- if (inside_x) 0 else 1 - strip_pad <- axis_size$bottom - } - strip_height <- unit(apply(strip_mat, 1, max_height, value_only = TRUE), "cm") - panel_table <- weave_tables_row(panel_table, strip_mat, placement, strip_height, strip_name, 2, coord$clip) - if (!inside_x) { - strip_pad[as.numeric(strip_pad) != 0] <- strip_padding - panel_table <- weave_tables_row(panel_table, row_shift = placement, row_height = strip_pad) + layout$SCALE_X <- 1L } - } else { - inside_y <- (theme$strip.placement.y %||% theme$strip.placement %||% "inside") == "inside" - if (params$strip.position == "left") { - placement <- if (inside_y) -1 else -2 - strip_pad <- axis_size$left + if (params$free$y) { + layout$SCALE_Y <- seq_len(nrow(layout)) } else { - placement <- if (inside_y) 0 else 1 - strip_pad <- axis_size$right - } - strip_pad[as.numeric(strip_pad) != 0] <- strip_padding - strip_width <- unit(apply(strip_mat, 2, max_width, value_only = TRUE), "cm") - panel_table <- weave_tables_col(panel_table, strip_mat, placement, strip_width, strip_name, 2, coord$clip) - if (!inside_y) { - strip_pad[as.numeric(strip_pad) != 0] <- strip_padding - panel_table <- weave_tables_col(panel_table, col_shift = placement, col_width = strip_pad) + layout$SCALE_Y <- 1L } } - panel_table + + panel_order <- order(layout$ROW, layout$COL) + layout <- layout[panel_order, ] + panels <- panels[panel_order] + + ggproto_parent(Facet, self)$draw_panels( + panels = panels, layout = layout, + ranges = ranges, coord = coord, + theme = theme, params = params + ) }, vars = function(self) { names(self$params$facets) @@ -559,7 +535,7 @@ weave_axes <- function(panels, axes, empty = NULL, z = 3L) { for (i in seq_along(axes)) { panels <- weave[[i]](panels, axes[[i]], shift[i], sizes[[i]], names[i], z = z) } - list(panels = panels, sizes = sizes) + panels } # Measures the size of axes while ignoring those bordering empty panels diff --git a/R/stat-ecdf.R b/R/stat-ecdf.R index fc55dc2e04..85287b9130 100644 --- a/R/stat-ecdf.R +++ b/R/stat-ecdf.R @@ -183,7 +183,7 @@ wecdf <- function(x, weights = NULL) { ) # Like `ecdf(x)`, we return an approx function - approxfun( + stats::approxfun( vals, cumsum(agg_weights) / total, method = "constant", diff --git a/tests/testthat/_snaps/coord-cartesian.md b/tests/testthat/_snaps/coord-cartesian.md index 7da67ba9c9..e7ed10569a 100644 --- a/tests/testthat/_snaps/coord-cartesian.md +++ b/tests/testthat/_snaps/coord-cartesian.md @@ -1,6 +1,6 @@ # cartesian coords throws error when limits are badly specified - `xlim` must be a vector of length 2, not a object. + `xlim` must be a vector of length 2, not a object. --- diff --git a/tests/testthat/_snaps/coord-flip.md b/tests/testthat/_snaps/coord-flip.md index b7717a7381..99806717ba 100644 --- a/tests/testthat/_snaps/coord-flip.md +++ b/tests/testthat/_snaps/coord-flip.md @@ -1,6 +1,6 @@ # flip coords throws error when limits are badly specified - `xlim` must be a vector of length 2, not a object. + `xlim` must be a vector of length 2, not a object. --- diff --git a/tests/testthat/_snaps/coord-map.md b/tests/testthat/_snaps/coord-map.md index 372d54df39..011a6dd41f 100644 --- a/tests/testthat/_snaps/coord-map.md +++ b/tests/testthat/_snaps/coord-map.md @@ -1,6 +1,6 @@ # coord map throws error when limits are badly specified - `xlim` must be a vector of length 2, not a object. + `xlim` must be a vector of length 2, not a object. --- diff --git a/tests/testthat/_snaps/coord-transform.md b/tests/testthat/_snaps/coord-transform.md index 14be4bd125..def35a0f27 100644 --- a/tests/testthat/_snaps/coord-transform.md +++ b/tests/testthat/_snaps/coord-transform.md @@ -1,6 +1,6 @@ # coord_trans() throws error when limits are badly specified - `xlim` must be a vector of length 2, not a object. + `xlim` must be a vector of length 2, not a object. --- diff --git a/tests/testthat/_snaps/coord_sf.md b/tests/testthat/_snaps/coord_sf.md index 486763d781..bb43424d33 100644 --- a/tests/testthat/_snaps/coord_sf.md +++ b/tests/testthat/_snaps/coord_sf.md @@ -21,7 +21,7 @@ # coord_sf() throws error when limits are badly specified - `xlim` must be a vector of length 2, not a object. + `xlim` must be a vector of length 2, not a object. --- diff --git a/tests/testthat/_snaps/facet-layout.md b/tests/testthat/_snaps/facet-layout.md index 03cdcbe8b3..142bde22fe 100644 --- a/tests/testthat/_snaps/facet-layout.md +++ b/tests/testthat/_snaps/facet-layout.md @@ -33,7 +33,7 @@ # facet_grid throws errors at bad layout specs - `coord_fixed()` doesn't support free scales. + `facet_grid()` can't use free scales with `coord_fixed()`. --- diff --git a/tests/testthat/test-facet-strips.R b/tests/testthat/test-facet-strips.R index 1ee8792e99..ece67935a4 100644 --- a/tests/testthat/test-facet-strips.R +++ b/tests/testthat/test-facet-strips.R @@ -135,38 +135,54 @@ test_that("strips can be removed", { expect_true(all(sapply(strip_grobs, inherits, 'zeroGrob'))) }) -test_that("strips can be removed", { - dat <- data_frame(a = rep(LETTERS[1:10], 10), x = rnorm(100), y = rnorm(100)) - g <- ggplot(dat, aes(x = x, y = y)) + - geom_point() + - facet_wrap(~a) + - theme(strip.background = element_blank(), strip.text = element_blank()) - g_grobs <- ggplotGrob(g) - strip_grobs <- g_grobs$grobs[grepl('strip-', g_grobs$layout$name)] - expect_true(all(sapply(strip_grobs, inherits, 'zeroGrob'))) -}) - test_that("padding is only added if axis is present", { p <- ggplot(data = mpg, aes(x = displ, y = hwy)) + - facet_grid(. ~ drv) + + facet_grid(year ~ drv) + theme( strip.placement = "outside", strip.switch.pad.grid = unit(10, "mm") ) pg <- ggplotGrob(p) - expect_equal(length(pg$heights), 17) + expect_equal(length(pg$heights), 19) + expect_equal(length(pg$widths), 18) - pg <- ggplotGrob(p + scale_x_continuous(position = "top")) - expect_equal(length(pg$heights), 18) + pg <- ggplotGrob( + p + scale_x_continuous(position = "top") + + scale_y_continuous(position = "right") + ) + expect_equal(length(pg$heights), 20) expect_equal(as.character(pg$heights[9]), "1cm") + expect_equal(length(pg$widths), 19) + expect_equal(as.character(pg$widths[13]), "1cm") # Also add padding with negative ticks and no text (#5251) pg <- ggplotGrob( p + scale_x_continuous(labels = NULL, position = "top") + theme(axis.ticks.length.x.top = unit(-2, "mm")) ) - expect_equal(length(pg$heights), 18) + expect_equal(length(pg$heights), 20) expect_equal(as.character(pg$heights[9]), "1cm") + + # Inverse should be true when strips are switched + p <- ggplot(data = mpg, aes(x = displ, y = hwy)) + + facet_grid(year ~ drv, switch = "both") + + theme( + strip.placement = "outside", + strip.switch.pad.grid = unit(10, "mm") + ) + + pg <- ggplotGrob(p) + expect_equal(length(pg$heights), 20) + expect_equal(as.character(pg$heights[13]), "1cm") + expect_equal(length(pg$widths), 19) + expect_equal(as.character(pg$widths[7]), "1cm") + + pg <- ggplotGrob( + p + scale_x_continuous(position = "top") + + scale_y_continuous(position = "right") + ) + expect_equal(length(pg$heights), 19) + expect_equal(length(pg$widths), 18) }) test_that("y strip labels are rotated when strips are switched", {