@@ -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 )
0 commit comments