Nothing
#' @title Set the panel width/height of a plot to a fixed value
#'
#' @description
#' The ggplot object, when stored, can only specify the height and width of the entire plot, not the panel.
#' The latter is obviously more important to control the final result of a plot.
#' This function can set the panel width/height of plot to a fixed value and rasterize it.
#'
#' @md
#' @inheritParams thisutils::log_message
#' @param x A ggplot object, a grob object, or a combined plot made by patchwork or cowplot package.
#' @param panel_index Specify the panel to be fixed.
#' If `NULL`, will fix all panels.
#' @param respect Whether row heights and column widths should respect each other.
#' @param width The desired width of the fixed panels.
#' @param height The desired height of the fixed panels.
#' @param margin The margin to add around each panel, in inches.
#' Default is `1`.
#' @param padding The padding to add around each panel, in inches.
#' Default is `0`.
#' @param units The units in which `height`, `width` and `margin` are given.
#' Can be `"mm"`, `"cm"`, `"in"`, etc. See [grid::unit].
#' @param raster Whether to rasterize the panel.
#' @param dpi Plot resolution.
#' @param return_grob Whether to return a grob object instead of a wrapped `patchwork` object.
#' Default is `FALSE`.
#' @param save `NULL` or the file name used to save the plot.
#' @param bg_color The background color of the plot.
#' @param ... Additional arguments passed to other functions.
#'
#' @return If `return_grob` is `TRUE`, returns a gtable object.
#' Otherwise, returns a patchwork object with fixed panel sizes.
#' The returned object has a `size` attribute containing width, height, and units.
#'
#' @export
#'
#' @examples
#' library(ggplot2)
#' p <- ggplot(
#' data = mtcars, aes(x = mpg, y = wt, colour = cyl)
#' ) +
#' geom_point() +
#' facet_wrap(~gear, nrow = 2)
#' # fix the size of panel
#' panel_fix(
#' p,
#' width = 5,
#' height = 3,
#' units = "cm"
#' )
#' # rasterize the panel
#' panel_fix(
#' p,
#' width = 5,
#' height = 3,
#' units = "cm",
#' raster = TRUE,
#' dpi = 90
#' )
#'
#' # `panel_fix` will build and render the plot when input a ggplot object.
#' # so after `panel_fix`, the size of the object will be changed.
#' object.size(p)
#' object.size(
#' panel_fix(
#' p,
#' width = 5,
#' height = 3,
#' units = "cm"
#' )
#' )
panel_fix <- function(
x = NULL,
panel_index = NULL,
respect = NULL,
width = NULL,
height = NULL,
margin = 1,
padding = 0,
units = "in",
raster = FALSE,
dpi = 300,
return_grob = FALSE,
bg_color = "white",
save = NULL,
verbose = FALSE,
...
) {
if (!inherits(x, "gtable")) {
tryCatch(
{
gtable <- as_gtable(x)
},
error = function(error) {
log_message(
error, "\nCannot convert the x to a gtable object",
message_type = "error"
)
}
)
} else {
gtable <- x
}
args <- as.list(match.call())[-1]
depth <- args[["depth"]]
if (is.null(depth)) {
depth <- 1
}
if (is.null(panel_index)) {
non_zero <- grep(
pattern = "zeroGrob",
vapply(gtable$grobs, as.character, character(1)),
invert = TRUE
)
panel_index <- grep(
pattern = "panel|full",
gtable[["layout"]][["name"]]
)
panel_index <- intersect(panel_index, non_zero)
}
if (length(panel_index) == 0 && length(gtable$grobs) == 1) {
panel_index <- 1
}
add_margin <- TRUE
for (i in panel_index) {
geom_index <- grep(
pattern = "GeomDrawGrob",
names(gtable$grobs[[i]][["children"]])
)
if (length(geom_index) > 0) {
log_message(
"panel {.val {i}} is detected as generated by plot_grid",
verbose = verbose
)
for (j in geom_index) {
subgrob <- gtable$grobs[[i]][["children"]][[j]][["children"]][[1]][["children"]][[1]]
if (length(subgrob$grobs[[1]][["children"]]) > 0 &&
all(sapply(subgrob$grobs[[1]][["children"]], function(x) inherits(x, "recordedGrob")))) {
subgrob <- panel_fix_overall(
x = subgrob$grobs[[1]][["children"]],
width = width,
height = height,
margin = padding,
units = units,
raster = raster,
dpi = dpi,
return_grob = TRUE
)
} else {
subgrob <- panel_fix(
x = subgrob,
width = width,
height = height,
margin = padding,
units = units,
raster = raster,
dpi = dpi,
return_grob = TRUE,
verbose = verbose,
depth = depth + 1
)
}
gtable$grobs[[i]][["children"]][[j]][["children"]][[1]][["children"]][[1]] <- subgrob
}
sum_width <- grid::convertWidth(
sum(subgrob[["widths"]]),
unitTo = units,
valueOnly = TRUE
) / as.numeric(gtable$grobs[[i]][["children"]][[j]]$vp$width)
sum_height <- grid::convertHeight(
sum(subgrob[["heights"]]),
unitTo = units,
valueOnly = TRUE
) / as.numeric(gtable$grobs[[i]][["children"]][[j]]$vp$height)
gtable <- panel_fix_overall(
gtable,
panel_index = i,
width = sum_width,
height = sum_height,
margin = ifelse(depth == 1, margin, 0),
units = units,
raster = FALSE,
return_grob = TRUE
)
} else if (gtable$grobs[[i]]$name == "layout" || inherits(x, "patchwork")) {
log_message(
"panel {.val {i}} is detected as generated by patchwork",
verbose = verbose
)
# if (i == panel_index[1] && length(panel_index) > 1 && isTRUE(verbose)) {
# log_message("More than 2 panels detected. panel_fix may not work as expected.")
# }
subgrob <- gtable$grobs[[i]]
if (length(subgrob[["children"]]) > 0 &&
all(sapply(subgrob[["children"]], function(x) inherits(x, "recordedGrob")))) {
subgrob <- panel_fix_overall(
subgrob[["children"]],
width = width,
height = height,
margin = 0,
units = units,
raster = raster,
dpi = dpi,
return_grob = TRUE
)
} else {
subgrob <- panel_fix(
subgrob,
width = width,
height = height,
margin = 0,
units = units,
raster = raster,
dpi = dpi,
return_grob = TRUE,
verbose = verbose,
depth = depth + 1
)
}
gtable$grobs[[i]] <- subgrob
layout <- gtable$layout
layout[["rowranges"]] <- lapply(
seq_len(nrow(layout)),
function(n) layout$t[n]:layout$b[n]
)
layout[["colranges"]] <- lapply(
seq_len(nrow(layout)),
function(n) layout$l[n]:layout$r[n]
)
p_row <- c(layout$t[i], layout$b[i])
p_col <- c(layout$l[i], layout$r[i])
background_index <- grep(
pattern = "background", layout$name
)
background_index <- background_index[order(layout$z[background_index], decreasing = TRUE)]
for (bgi in background_index) {
if (all(p_row %in% layout[["rowranges"]][[bgi]]) && all(p_col %in% layout[["colranges"]][[bgi]])) {
p_background_index <- bgi
break
}
}
gtable <- gtable::gtable_add_rows(
gtable,
heights = grid::unit(padding, units),
pos = layout$t[p_background_index] - 1
)
gtable <- gtable::gtable_add_rows(
gtable,
heights = grid::unit(padding, units),
pos = layout$b[p_background_index]
)
gtable <- gtable::gtable_add_cols(
gtable,
widths = grid::unit(padding, units),
pos = layout$l[p_background_index] - 1
)
gtable <- gtable::gtable_add_cols(
gtable,
widths = grid::unit(padding, units),
pos = layout$r[p_background_index]
)
sum_width <- grid::convertWidth(
sum(subgrob[["widths"]]),
unitTo = units,
valueOnly = TRUE
)
sum_height <- grid::convertHeight(
sum(subgrob[["heights"]]),
unitTo = units,
valueOnly = TRUE
)
gtable <- panel_fix_overall(
gtable,
panel_index = i,
width = sum_width,
height = sum_height,
margin = ifelse(depth == 1 & add_margin, margin, 0),
units = units,
raster = FALSE,
respect = TRUE,
return_grob = TRUE
)
if (depth == 1 & add_margin) {
add_margin <- FALSE
}
} else {
gtable <- panel_fix_overall(
gtable,
panel_index = i,
width = width,
height = height,
margin = margin,
units = units,
raster = raster,
dpi = dpi,
return_grob = TRUE
)
}
}
if (!is.null(respect)) {
gtable$respect <- respect
}
if (isTRUE(return_grob)) {
return(gtable)
} else {
p <- patchwork::wrap_plots(gtable) +
theme(
plot.background = element_rect(
fill = bg_color, color = bg_color
)
)
if (units != "null") {
plot_width <- grid::convertWidth(
sum(gtable[["widths"]]),
unitTo = units,
valueOnly = TRUE
)
plot_height <- grid::convertHeight(
sum(gtable[["heights"]]),
unitTo = units,
valueOnly = TRUE
)
attr(p, "size") <- list(
width = plot_width,
height = plot_height,
units = units
)
}
if (!is.null(save) && is.character(save) && nchar(save) > 0) {
if (units == "null") {
log_message(
"{.arg units} can not be 'null' if want to save the plot",
message_type = "error"
)
}
filename <- normalizePath(save)
log_message(
"Save the plot to the file: {.file {filename}}",
verbose = verbose
)
if (!dir.exists(dirname(filename))) {
dir.create(dirname(filename), recursive = TRUE, showWarnings = FALSE)
}
ggplot2::ggsave(
plot = p,
filename = filename,
width = plot_width,
height = plot_height,
units = units,
dpi = dpi,
limitsize = FALSE
)
}
return(p)
}
}
#' @rdname panel_fix
#' @export
panel_fix_overall <- function(
x,
panel_index = NULL,
respect = NULL,
width = NULL,
height = NULL,
margin = 1,
units = "in",
raster = FALSE,
dpi = 300,
return_grob = FALSE,
bg_color = "white",
save = NULL,
verbose = TRUE
) {
if (!inherits(x, "gtable")) {
if (inherits(x, "gTree")) {
x <- x[["children"]]
}
tryCatch(
{
gtable <- as_gtable(x)
},
error = function(error) {
log_message(
error, "\nCannot convert the x to a gtable object",
message_type = "error"
)
}
)
} else {
gtable <- x
}
if (is.null(panel_index)) {
non_zero <- grep(
pattern = "zeroGrob",
vapply(
gtable$grobs, as.character, character(1)
), invert = TRUE
)
panel_index <- grep("panel|full", gtable[["layout"]][["name"]])
panel_index <- intersect(panel_index, non_zero)
}
if (length(panel_index) == 0 && length(gtable$grobs) == 1) {
panel_index <- 1
}
if (!length(width) %in% c(0, 1, length(panel_index)) || !length(height) %in% c(0, 1, length(panel_index))) {
log_message(
"The length of 'width' and 'height' must be 1 or the length of panels.",
message_type = "error"
)
}
if (inherits(x, "gList")) {
panel_index <- 1
panel_index_h <- panel_index_w <- list(1)
w_comp <- h_comp <- list(grid::unit(1, "null"))
w <- h <- list(grid::unit(1, "null"))
} else if (length(panel_index) > 0) {
panel_index_w <- panel_index_h <- list()
w_comp <- h_comp <- list()
w <- h <- list()
for (i in seq_along(panel_index)) {
index <- panel_index[i]
panel_index_h[[i]] <- sort(
unique(c(
gtable[["layout"]][["t"]][index],
gtable[["layout"]][["b"]][index]
))
)
panel_index_w[[i]] <- sort(
unique(c(
gtable[["layout"]][["l"]][index],
gtable[["layout"]][["r"]][index]
))
)
w_comp[[i]] <- gtable[["widths"]][seq(min(panel_index_w[[i]]), max(panel_index_w[[i]]))]
h_comp[[i]] <- gtable[["heights"]][seq(min(panel_index_h[[i]]), max(panel_index_h[[i]]))]
if (length(w_comp[[i]]) == 1) {
w[[i]] <- w_comp[[i]]
} else if (length(w_comp[[i]]) > 1 && any(grid::unitType(w_comp[[i]]) == "null")) {
w[[i]] <- grid::unit(1, units = "null")
} else {
w[[i]] <- sum(w_comp[[i]])
}
if (length(h_comp[[i]]) == 1) {
h[[i]] <- h_comp[[i]]
} else if (length(h_comp[[i]]) > 1 && any(grid::unitType(h_comp[[i]]) == "null")) {
h[[i]] <- grid::unit(1, units = "null")
} else {
h[[i]] <- sum(h_comp[[i]])
}
}
} else {
log_message(
"No panel detected",
message_type = "error"
)
}
if (units != "null") {
raw_w <- sapply(
w, function(x) {
grid::convertWidth(x, unitTo = units, valueOnly = TRUE)
}
)
raw_h <- sapply(
h, function(x) {
grid::convertHeight(x, unitTo = units, valueOnly = TRUE)
}
)
for (i in seq_along(w)) {
if (grid::unitType(w[[i]]) == "null" || grid::convertUnit(w[[i]], unitTo = "cm", valueOnly = TRUE) < 1e-10) {
raw_w[i] <- 0
}
}
for (i in seq_along(h)) {
if (grid::unitType(h[[i]]) == "null" || grid::convertUnit(h[[i]], unitTo = "cm", valueOnly = TRUE) < 1e-10) {
raw_h[i] <- 0
}
}
if (isTRUE(gtable$respect)) {
raw_aspect <- sapply(h, as.vector) / sapply(w, as.vector)
} else {
if (all(raw_w != 0) && all(raw_h != 0)) {
raw_aspect <- raw_h / raw_w
} else {
raw_aspect <- grid::convertHeight(
grid::unit(1, "npc"), "cm",
valueOnly = TRUE
) / grid::convertWidth(grid::unit(1, "npc"), "cm", valueOnly = TRUE)
}
}
if (is.null(width) && is.null(height)) {
width <- raw_w
height <- raw_h
if (all(width == 0) && all(height == 0)) {
width <- grid::convertWidth(
grid::unit(1, "npc"), units,
valueOnly = TRUE
)
height <- grid::convertHeight(
grid::unit(1, "npc"), units,
valueOnly = TRUE
)
if (isTRUE(gtable$respect)) {
if (raw_aspect <= 1) {
height <- width * raw_aspect
} else {
width <- height / raw_aspect
}
}
}
}
for (i in seq_along(raw_aspect)) {
if (is.finite(raw_aspect[i]) && raw_aspect[i] != 0) {
if (is.null(width[i]) || is.na(width[i]) || width[i] == 0) {
width[i] <- height[i] / raw_aspect[i]
}
if (is.null(height[i]) || is.na(height[i]) || height[i] == 0) {
height[i] <- width[i] * raw_aspect[i]
}
}
}
for (i in seq_along(width)) {
if (inherits(width[i], "unit")) {
width[i] <- grid::convertWidth(
width[i],
unitTo = units,
valueOnly = TRUE
)
}
}
for (i in seq_along(height)) {
if (inherits(height[i], "unit")) {
height[i] <- grid::convertHeight(
height[i],
unitTo = units,
valueOnly = TRUE
)
}
}
}
if (length(width) == 1) {
width <- rep(width, length(panel_index))
}
if (length(height) == 1) {
height <- rep(height, length(panel_index))
}
for (i in seq_along(panel_index)) {
if (!is.null(width)) {
width_unit <- width[i] / length(w_comp[[i]])
gtable[["widths"]][seq(min(panel_index_w[[i]]), max(panel_index_w[[i]]))] <- rep(
grid::unit(width_unit, units = units), length(w_comp[[i]])
)
}
if (!is.null(height)) {
height_unit <- height[i] / length(h_comp[[i]])
gtable[["heights"]][seq(min(panel_index_h[[i]]), max(panel_index_h[[i]]))] <- rep(
grid::unit(height_unit, units = units), length(h_comp[[i]])
)
}
}
gtable <- gtable::gtable_add_padding(
gtable,
padding = grid::unit(margin, units = units)
)
if (isTRUE(raster)) {
for (i in seq_along(panel_index)) {
index <- panel_index[i]
g <- g_new <- gtable$grobs[[index]]
vp <- g$vp
children_order <- g$childrenOrder
if (is.null(g$vp)) {
g$vp <- grid::viewport()
}
for (j in seq_along(g[["children"]])) {
child <- g[["children"]][[j]]
child_nm <- names(g[["children"]])[j]
if (!is.null(child$vp) ||
any(grepl("(text)|(label)", child_nm)) ||
any(grepl("(text)|(segments)|(legend)", class(child$list[[1]])))) {
zero <- ggplot2::zeroGrob()
zero$name <- g[["children"]][[j]]$name
g[["children"]][[j]] <- zero
} else if (inherits(child$list[[1]], "grob") || is.null(child$list[[1]])) {
g_new[["children"]][[j]] <- ggplot2::zeroGrob()
}
}
temp <- tempfile(fileext = "png")
ragg::agg_png(
temp,
width = width[i],
height = height[i],
bg = "transparent",
res = dpi,
units = units
)
grid::grid.draw(g)
grDevices::dev.off()
g_ras <- grid::rasterGrob(
png::readPNG(temp, native = TRUE)
)
unlink(temp)
g <- grid::addGrob(g_new, g_ras)
g$vp <- vp
g$childrenOrder <- c(g_ras$name, children_order)
gtable$grobs[[index]] <- g
}
}
if (!is.null(respect)) {
gtable$respect <- respect
}
if (isTRUE(return_grob)) {
return(gtable)
} else {
p <- patchwork::wrap_plots(gtable) +
theme(
plot.background = element_rect(
fill = bg_color, color = bg_color
)
)
if (units != "null") {
plot_width <- grid::convertWidth(
sum(gtable[["widths"]]),
unitTo = units,
valueOnly = TRUE
)
plot_height <- grid::convertHeight(
sum(gtable[["heights"]]),
unitTo = units,
valueOnly = TRUE
)
attr(p, "size") <- list(
width = plot_width,
height = plot_height,
units = units
)
}
if (!is.null(save) && is.character(save) && nchar(save) > 0) {
if (units == "null") {
log_message(
"{.arg units} can not be 'null' if want to save the plot",
message_type = "error"
)
}
filename <- normalizePath(save)
log_message(
"Save plot to: {.file {filename}}",
verbose = verbose
)
if (!dir.exists(dirname(filename))) {
dir.create(
dirname(filename),
recursive = TRUE,
showWarnings = FALSE
)
}
ggplot2::ggsave(
plot = p,
filename = filename,
width = plot_width,
height = plot_height,
units = units,
dpi = dpi,
limitsize = FALSE
)
}
return(p)
}
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.