@@ -471,7 +471,7 @@ Guides <- ggproto(
471
471
# for every position, collect all individual guides and arrange them
472
472
# into a guide box which will be inserted into the main gtable
473
473
# Combining multiple guides in a guide box
474
- assemble = function (self , theme ) {
474
+ assemble = function (self , theme , params = self $ params , guides = self $ guides ) {
475
475
476
476
if (length(self $ guides ) < 1 ) {
477
477
return (zeroGrob())
@@ -485,15 +485,61 @@ Guides <- ggproto(
485
485
return (zeroGrob())
486
486
}
487
487
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
+
488
495
# Populate key sizes
489
496
theme $ legend.key.width <- calc_element(" legend.key.width" , theme )
490
497
theme $ legend.key.height <- calc_element(" legend.key.height" , theme )
491
498
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 ]
493
502
if (length(grobs ) < 1 ) {
494
503
return (zeroGrob())
495
504
}
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 )
497
543
498
544
# Set spacing
499
545
theme $ legend.spacing <- theme $ legend.spacing %|| % unit(0.5 , " lines" )
@@ -502,27 +548,24 @@ Guides <- ggproto(
502
548
503
549
Map(
504
550
grobs = grobs ,
505
- position = names(grobs ),
551
+ position = positions ,
552
+ inside_position = inside_positions ,
506
553
self $ package_box ,
507
554
MoreArgs = list (theme = theme )
508
555
)
509
556
},
510
557
511
558
# 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 ,
515
560
params = self $ params ,
516
561
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 ))
524
562
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 ))
526
569
}
527
570
528
571
grobs <- vector(" list" , length(guides ))
@@ -531,41 +574,22 @@ Guides <- ggproto(
531
574
theme = theme , position = positions [i ],
532
575
direction = directions [i ], params = params [[i ]]
533
576
)
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
- }
543
577
}
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
554
579
},
555
580
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 ) {
558
584
if (is.zero(grobs ) || length(grobs ) == 0 ) {
559
585
return (zeroGrob())
560
586
}
561
587
562
588
# Determine default direction
563
589
direction <- switch (
564
590
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"
569
593
)
570
594
571
595
# Populate missing theme arguments
@@ -584,25 +608,24 @@ Guides <- ggproto(
584
608
stretch_x <- any(unlist(lapply(widths , unitType )) == " null" )
585
609
stretch_y <- any(unlist(lapply(heights , unitType )) == " null" )
586
610
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 ]
601
620
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
+ }
602
628
} 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 ))
606
629
x <- global_xjust <- global_just [1 ]
607
630
y <- global_yjust <- global_just [2 ]
608
631
# Legends to the side of the plot need a margin for justification
@@ -684,7 +707,7 @@ Guides <- ggproto(
684
707
685
708
# Set global justification
686
709
vp <- viewport(
687
- x = x , y = y , just = global_just ,
710
+ x = global_xjust , y = global_yjust , just = global_just ,
688
711
height = vp_height ,
689
712
width = max(widths )
690
713
)
0 commit comments