Skip to content

Commit b7754c8

Browse files
committed
manage position in Guides$assemble()
1 parent 51889d4 commit b7754c8

File tree

5 files changed

+113
-74
lines changed

5 files changed

+113
-74
lines changed

R/guide-colorbar.R

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,13 @@ guide_colourbar <- function(
146146

147147
theme <- deprecated_guide_args(theme, ...)
148148
if (!is.null(position)) {
149-
position <- arg_match0(position, c(.trbl, "inside"))
149+
if (is.numeric(position)) {
150+
if (length(position) != 2L) {
151+
cli::cli_abort("{.arg position} must be a numeric of length 2")
152+
}
153+
} else {
154+
position <- arg_match0(position, c(.trbl, "inside"))
155+
}
150156
}
151157
check_number_decimal(alpha, min = 0, max = 1, allow_na = TRUE)
152158

R/guide-custom.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ guide_custom <- function(
5050
cli::cli_abort("{.arg height} must be a single {.cls unit}, not a unit vector.")
5151
}
5252

53+
if (!is.null(position)) {
54+
if (is.numeric(position)) {
55+
if (length(position) != 2L) {
56+
cli::cli_abort("{.arg position} must be a numeric of length 2")
57+
}
58+
} else {
59+
position <- arg_match0(position, c(.trbl, "inside"))
60+
}
61+
}
62+
5363
new_guide(
5464
grob = grob,
5565
width = width,

R/guide-legend.R

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
#' differently from the plot's theme settings. The `theme` argument in the
1818
#' guide overrides, and is combined with, the plot's theme.
1919
#' @param position A character string indicating where the legend should be
20-
#' placed relative to the plot panels.
20+
#' placed relative to the plot panels, or a numeric value of length two
21+
#' setting the placement of legends.
2122
#' @param direction A character string indicating the direction of the guide.
2223
#' One of "horizontal" or "vertical".
2324
#' @param override.aes A list specifying aesthetic parameters of legend key.
@@ -116,7 +117,13 @@ guide_legend <- function(
116117
theme <- deprecated_guide_args(theme, ...)
117118

118119
if (!is.null(position)) {
119-
position <- arg_match0(position, c(.trbl, "inside"))
120+
if (is.numeric(position)) {
121+
if (length(position) != 2L) {
122+
cli::cli_abort("{.arg position} must be a numeric of length 2")
123+
}
124+
} else {
125+
position <- arg_match0(position, c(.trbl, "inside"))
126+
}
120127
}
121128

122129
new_guide(
@@ -180,8 +187,7 @@ GuideLegend <- ggproto(
180187
spacing_y = "legend.key.spacing.y",
181188
text_position = "legend.text.position",
182189
title_position = "legend.title.position",
183-
byrow = "legend.byrow",
184-
inside_position = "legend.position.inside"
190+
byrow = "legend.byrow"
185191
),
186192

187193
extract_params = function(scale, params,
@@ -564,12 +570,6 @@ GuideLegend <- ggproto(
564570
)
565571
}
566572

567-
# for inside guide legends, we also save the position values
568-
# in this way, we can identify legends with same position
569-
# and then merge them into same guide box in `Guides$draw()`
570-
if (identical(.subset2(params, "position"), "inside")) {
571-
attr(gt, "inside_position") <- .subset2(elements, "inside_position")
572-
}
573573
gt
574574
}
575575
)

R/guides-.R

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ Guides <- ggproto(
471471
# for every position, collect all individual guides and arrange them
472472
# into a guide box which will be inserted into the main gtable
473473
# Combining multiple guides in a guide box
474-
assemble = function(self, theme) {
474+
assemble = function(self, theme, params = self$params, guides = self$guides) {
475475

476476
if (length(self$guides) < 1) {
477477
return(zeroGrob())
@@ -485,15 +485,61 @@ Guides <- ggproto(
485485
return(zeroGrob())
486486
}
487487

488+
# extract the guide position
489+
positions <- vapply(
490+
params,
491+
function(p) p$position[1] %||% default_position,
492+
character(1), USE.NAMES = FALSE
493+
)
494+
488495
# Populate key sizes
489496
theme$legend.key.width <- calc_element("legend.key.width", theme)
490497
theme$legend.key.height <- calc_element("legend.key.height", theme)
491498

492-
grobs <- self$draw(theme, default_position, theme$legend.direction)
499+
grobs <- self$draw(theme, positions, theme$legend.direction)
500+
keep <- !vapply(grobs, is.zero, logical(1), USE.NAMES = FALSE)
501+
grobs <- grobs[keep]
493502
if (length(grobs) < 1) {
494503
return(zeroGrob())
495504
}
496-
grobs <- grobs[order(names(grobs))]
505+
506+
# prepare the position of inside legends
507+
default_inside_position <- calc_element(
508+
"legend.position.inside", theme
509+
) %||% valid.just(calc_element("legend.justification.inside", theme))
510+
inside_positions <- vector("list", length(positions))
511+
512+
# we'll merge inside legends with same coordinate into same guide box
513+
# we grouped the legends by the positions, for inside legends, they'll be
514+
# splitted by the actual inside coordinate
515+
groups <- positions
516+
for (i in seq_along(positions)) {
517+
if (identical(positions[i], "inside")) {
518+
# the actual inside position can be set in each guide by `theme`
519+
# argument
520+
inside_positions[[i]] <- calc_element(
521+
"legend.position.inside", params[[i]]$theme
522+
) %||% default_inside_position
523+
groups[i] <- paste0("inside_",
524+
paste(inside_positions[[i]], collapse = "_")
525+
)
526+
}
527+
}
528+
positions <- positions[keep]
529+
inside_positions <- inside_positions[keep]
530+
groups <- groups[keep]
531+
532+
# we group the guide legends
533+
locs <- vec_group_loc(groups)
534+
indices <- locs$loc
535+
grobs <- vec_chop(grobs, indices = indices)
536+
names(grobs) <- locs$key
537+
538+
# for each group, they share the same locations,
539+
# so we only extract the first one of `positions` and `inside_positions`
540+
first_indice <- lapply(indices, `[[`, 1L)
541+
positions <- vec_chop(positions, indices = first_indice)
542+
inside_positions <- vec_chop(inside_positions, indices = first_indice)
497543

498544
# Set spacing
499545
theme$legend.spacing <- theme$legend.spacing %||% unit(0.5, "lines")
@@ -502,27 +548,24 @@ Guides <- ggproto(
502548

503549
Map(
504550
grobs = grobs,
505-
position = names(grobs),
551+
position = positions,
552+
inside_position = inside_positions,
506553
self$package_box,
507554
MoreArgs = list(theme = theme)
508555
)
509556
},
510557

511558
# Render the guides into grobs
512-
draw = function(self, theme,
513-
default_position = "right",
514-
direction = NULL,
559+
draw = function(self, theme, positions, direction = NULL,
515560
params = self$params,
516561
guides = self$guides) {
517-
positions <- vapply(
518-
params,
519-
function(p) p$position[1] %||% default_position,
520-
character(1), USE.NAMES = FALSE
521-
)
522-
523-
directions <- rep(direction %||% "vertical", length(positions))
524562
if (is.null(direction)) {
525-
directions[positions %in% c("top", "bottom")] <- "horizontal"
563+
directions <- ifelse(
564+
positions %in% c("top", "bottom"),
565+
"horizontal", "vertical"
566+
)
567+
} else {
568+
directions <- rep(direction, length(positions))
526569
}
527570

528571
grobs <- vector("list", length(guides))
@@ -531,41 +574,22 @@ Guides <- ggproto(
531574
theme = theme, position = positions[i],
532575
direction = directions[i], params = params[[i]]
533576
)
534-
# we'll merge inside legends with same coordinate into same guide box
535-
# here, we define the groups of the inside legends
536-
if (identical(positions[i], "inside")) {
537-
positions[i] <- paste(
538-
"inside",
539-
paste(attr(.subset2(grobs, i), "inside_position"), collapse = "_"),
540-
sep = "_"
541-
)
542-
}
543577
}
544-
545-
# move inside legends to the last
546-
positions <- factor(positions,
547-
levels = c(.trbl, unique(positions[startsWith(positions, "inside")]))
548-
)
549-
keep <- !vapply(grobs, is.zero, logical(1), USE.NAMES = FALSE)
550-
551-
# we grouped the legends by the positions
552-
# for inside legends, they'll be splitted by the actual inside coordinate
553-
split(grobs[keep], positions[keep])
578+
grobs
554579
},
555580

556-
package_box = function(grobs, position, theme) {
557-
581+
# here, we put `inside_position` in the last, so that it won't break current
582+
# implement of patchwork
583+
package_box = function(grobs, position, theme, inside_position = NULL) {
558584
if (is.zero(grobs) || length(grobs) == 0) {
559585
return(zeroGrob())
560586
}
561587

562588
# Determine default direction
563589
direction <- switch(
564590
position,
565-
left = , right = "vertical",
566-
top = , bottom = "horizontal",
567-
# for all inside guide legends
568-
"vertical"
591+
inside = , left = , right = "vertical",
592+
top = , bottom = "horizontal"
569593
)
570594

571595
# Populate missing theme arguments
@@ -584,25 +608,24 @@ Guides <- ggproto(
584608
stretch_x <- any(unlist(lapply(widths, unitType)) == "null")
585609
stretch_y <- any(unlist(lapply(heights, unitType)) == "null")
586610

587-
if (startsWith(position, "inside")) {
588-
# Global justification of the complete legend box
589-
global_just <- valid.just(calc_element(
590-
"legend.justification.inside", theme
591-
))
592-
# for inside guide legends, the position was attached in
593-
# each grob of the input grobs (which should share the same position)
594-
inside_position <- attr(.subset2(grobs, 1L), "inside_position") %||%
595-
# fallback to original method of ggplot2 <=3.5.1
596-
.subset2(theme, "legend.position.inside") %||% global_just
597-
global_xjust <- global_just[1]
598-
global_yjust <- global_just[2]
599-
x <- inside_position[1]
600-
y <- inside_position[2]
611+
# Global justification of the complete legend box
612+
global_just <- paste0("legend.justification.", position)
613+
global_just <- valid.just(calc_element(global_just, theme))
614+
615+
if (position == "inside") {
616+
# The position of inside legends are set by their justification
617+
inside_just <- theme$legend.position.inside %||% global_just
618+
global_xjust <- inside_just[1]
619+
global_yjust <- inside_just[2]
601620
global_margin <- margin()
621+
if (is.null(inside_position)) { # for backward compatibility
622+
x <- global_xjust
623+
y <- global_yjust
624+
} else {
625+
x <- inside_position[1L]
626+
y <- inside_position[2L]
627+
}
602628
} else {
603-
# Global justification of the complete legend box
604-
global_just <- paste0("legend.justification.", position)
605-
global_just <- valid.just(calc_element(global_just, theme))
606629
x <- global_xjust <- global_just[1]
607630
y <- global_yjust <- global_just[2]
608631
# Legends to the side of the plot need a margin for justification
@@ -684,7 +707,7 @@ Guides <- ggproto(
684707

685708
# Set global justification
686709
vp <- viewport(
687-
x = x, y = y, just = global_just,
710+
x = global_xjust, y = global_yjust, just = global_just,
688711
height = vp_height,
689712
width = max(widths)
690713
)

R/plot-build.R

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ table_add_legends <- function(table, legends, theme) {
479479
table <- gtable_add_cols(table, spacing$right, pos = -1)
480480
table <- gtable_add_cols(table, widths$right, pos = -1)
481481
table <- gtable_add_grob(
482-
table, legends$right, clip = "off",
482+
table, legends$right %||% zeroGrob(), clip = "off",
483483
t = place$t, b = place$b, l = -1, r = -1,
484484
name = "guide-box-right"
485485
)
@@ -488,7 +488,7 @@ table_add_legends <- function(table, legends, theme) {
488488
table <- gtable_add_cols(table, spacing$left, pos = 0)
489489
table <- gtable_add_cols(table, widths$left, pos = 0)
490490
table <- gtable_add_grob(
491-
table, legends$left, clip = "off",
491+
table, legends$left %||% zeroGrob(), clip = "off",
492492
t = place$t, b = place$b, l = 1, r = 1,
493493
name = "guide-box-left"
494494
)
@@ -499,7 +499,7 @@ table_add_legends <- function(table, legends, theme) {
499499
table <- gtable_add_rows(table, spacing$bottom, pos = -1)
500500
table <- gtable_add_rows(table, heights$bottom, pos = -1)
501501
table <- gtable_add_grob(
502-
table, legends$bottom, clip = "off",
502+
table, legends$bottom %||% zeroGrob(), clip = "off",
503503
t = -1, b = -1, l = place$l, r = place$r,
504504
name = "guide-box-bottom"
505505
)
@@ -508,7 +508,7 @@ table_add_legends <- function(table, legends, theme) {
508508
table <- gtable_add_rows(table, spacing$top, pos = 0)
509509
table <- gtable_add_rows(table, heights$top, pos = 0)
510510
table <- gtable_add_grob(
511-
table, legends$top, clip = "off",
511+
table, legends$top %||% zeroGrob(), clip = "off",
512512
t = 1, b = 1, l = place$l, r = place$r,
513513
name = "guide-box-top"
514514
)
@@ -519,7 +519,7 @@ table_add_legends <- function(table, legends, theme) {
519519
if (length(inside_legends)) {
520520
for (i in seq_along(inside_legends)) {
521521
table <- gtable_add_grob(
522-
table, .subset2(inside_legends, i), clip = "off",
522+
table, inside_legends[[i]], clip = "off",
523523
t = place$t, b = place$b, l = place$l, r = place$r,
524524
name = paste("guide-box-inside", i, sep = "-")
525525
)

0 commit comments

Comments
 (0)