@@ -83,7 +83,7 @@ build_ggplot <- S7::method(ggplot_build, class_ggplot) <- function(plot, ...) {
8383 data <- by_layer(function (l , d ) l $ map_statistic(d , plot ), layers , data , " mapping stat to aesthetics" )
8484
8585 # Make sure missing (but required) aesthetics are added
86- plot @ scales $ add_missing(c(" x" , " y" ), plot @ plot_env )
86+ scales $ add_missing(c(" x" , " y" ), plot @ plot_env )
8787
8888 # Reparameterise geoms from (e.g.) y and width to ymin and ymax
8989 data <- by_layer(function (l , d ) l $ compute_geom_1(d ), layers , data , " setting up geom" )
@@ -190,146 +190,6 @@ get_layer_grob <- function(plot = get_last_plot(), i = 1L) {
190190# ' @rdname ggplot_build
191191layer_grob <- get_layer_grob
192192
193- # ' Build a plot with all the usual bits and pieces.
194- # '
195- # ' This function builds all grobs necessary for displaying the plot, and
196- # ' stores them in a special data structure called a [`gtable`][gtable::gtable].
197- # ' This object is amenable to programmatic manipulation, should you want
198- # ' to (e.g.) make the legend box 2 cm wide, or combine multiple plots into
199- # ' a single display, preserving aspect ratios across the plots.
200- # '
201- # ' The `ggplot_gtable()` function is vestigial and the `gtable_ggplot()` function
202- # ' should be used instead.
203- # '
204- # ' @seealso
205- # ' [print.ggplot()] and [benchplot()] for
206- # ' for functions that contain the complete set of steps for generating
207- # ' a ggplot2 plot.
208- # '
209- # ' The `r link_book("gtable step section", "internals#sec-ggplotgtable")`
210- # ' @return a `gtable` object
211- # ' @keywords internal
212- # ' @param data plot data generated by [ggplot_build()]
213- # ' @export
214- ggplot_gtable <- function (data ) {
215- # TODO: Swap to S7 generic once S7/#543 is resolved
216- attach_plot_env(data @ plot @ plot_env )
217- UseMethod(" ggplot_gtable" )
218- }
219-
220- S7 :: method(ggplot_gtable , class_ggplot_built ) <- function (data ) {
221- plot <- data @ plot
222- layout <- data @ layout
223- data <- data @ data
224- theme <- plot @ theme
225-
226- geom_grobs <- by_layer(function (l , d ) l $ draw_geom(d , layout ), plot @ layers , data , " converting geom to grob" )
227-
228- plot_table <- layout $ render(geom_grobs , data , theme , plot @ labels )
229-
230- # Legends
231- legend_box <- plot @ guides $ assemble(theme )
232- plot_table <- table_add_legends(plot_table , legend_box , theme )
233-
234- # Title
235- title <- element_render(
236- theme , " plot.title" , plot @ labels $ title ,
237- margin_y = TRUE , margin_x = TRUE
238- )
239- title_height <- grobHeight(title )
240-
241- # Subtitle
242- subtitle <- element_render(
243- theme , " plot.subtitle" , plot @ labels $ subtitle ,
244- margin_y = TRUE , margin_x = TRUE
245- )
246- subtitle_height <- grobHeight(subtitle )
247-
248- # whole plot annotation
249- caption <- element_render(
250- theme , " plot.caption" , plot @ labels $ caption ,
251- margin_y = TRUE , margin_x = TRUE
252- )
253- caption_height <- grobHeight(caption )
254-
255- # positioning of title and subtitle is governed by plot.title.position
256- # positioning of caption is governed by plot.caption.position
257- # "panel" means align to the panel(s)
258- # "plot" means align to the entire plot (except margins and tag)
259- title_pos <- arg_match0(
260- theme $ plot.title.position %|| % " panel" ,
261- c(" panel" , " plot" ),
262- arg_nm = " plot.title.position" ,
263- error_call = expr(theme())
264- )
265-
266- caption_pos <- arg_match0(
267- theme $ plot.caption.position %|| % " panel" ,
268- values = c(" panel" , " plot" ),
269- arg_nm = " plot.caption.position" ,
270- error_call = expr(theme())
271- )
272-
273- pans <- plot_table $ layout [grepl(" ^panel" , plot_table $ layout $ name ), , drop = FALSE ]
274- if (title_pos == " panel" ) {
275- title_l <- min(pans $ l )
276- title_r <- max(pans $ r )
277- } else {
278- title_l <- 1
279- title_r <- ncol(plot_table )
280- }
281- if (caption_pos == " panel" ) {
282- caption_l <- min(pans $ l )
283- caption_r <- max(pans $ r )
284- } else {
285- caption_l <- 1
286- caption_r <- ncol(plot_table )
287- }
288-
289- plot_table <- gtable_add_rows(plot_table , subtitle_height , pos = 0 )
290- plot_table <- gtable_add_grob(plot_table , subtitle , name = " subtitle" ,
291- t = 1 , b = 1 , l = title_l , r = title_r , clip = " off" )
292-
293- plot_table <- gtable_add_rows(plot_table , title_height , pos = 0 )
294- plot_table <- gtable_add_grob(plot_table , title , name = " title" ,
295- t = 1 , b = 1 , l = title_l , r = title_r , clip = " off" )
296-
297- plot_table <- gtable_add_rows(plot_table , caption_height , pos = - 1 )
298- plot_table <- gtable_add_grob(plot_table , caption , name = " caption" ,
299- t = - 1 , b = - 1 , l = caption_l , r = caption_r , clip = " off" )
300-
301- plot_table <- table_add_tag(plot_table , plot @ labels $ tag , theme )
302-
303- # Margins
304- plot_margin <- calc_element(" plot.margin" , theme ) %|| % margin()
305- plot_table <- gtable_add_padding(plot_table , plot_margin )
306-
307- if (is_theme_element(theme $ plot.background )) {
308- plot_table <- gtable_add_grob(plot_table ,
309- element_render(theme , " plot.background" ),
310- t = 1 , l = 1 , b = - 1 , r = - 1 , name = " background" , z = - Inf )
311- plot_table $ layout <- plot_table $ layout [c(nrow(plot_table $ layout ), 1 : (nrow(plot_table $ layout ) - 1 )),]
312- plot_table $ grobs <- plot_table $ grobs [c(nrow(plot_table $ layout ), 1 : (nrow(plot_table $ layout ) - 1 ))]
313- }
314-
315- # add alt-text as attribute
316- attr(plot_table , " alt-label" ) <- plot @ labels $ alt
317-
318- plot_table
319- }
320-
321- # ' Generate a ggplot2 plot grob.
322- # '
323- # ' @param x ggplot2 object
324- # ' @keywords internal
325- # ' @export
326- ggplotGrob <- function (x ) {
327- ggplot_gtable(ggplot_build(x ))
328- }
329-
330- S7 :: method(as.gtable , class_ggplot ) <- function (x , ... ) ggplotGrob(x )
331- S7 :: method(as.gtable , class_ggplot_built ) <- function (x , ... ) ggplot_gtable(x )
332-
333193# Apply function to layer and matching data
334194by_layer <- function (f , layers , data , step = NULL ) {
335195 ordinal <- label_ordinal()
@@ -349,209 +209,3 @@ by_layer <- function(f, layers, data, step = NULL) {
349209 )
350210 out
351211}
352-
353- # Add the tag element to the gtable
354- table_add_tag <- function (table , label , theme ) {
355- # Initialise the tag margins
356- table <- gtable_add_padding(table , unit(0 , " pt" ))
357-
358- # Early exit when label is absent or element is blank
359- if (length(label ) < 1 ) {
360- return (table )
361- }
362- element <- calc_element(" plot.tag" , theme )
363- if (is_theme_element(element , " blank" )) {
364- return (table )
365- }
366-
367- # Resolve position
368- position <- calc_element(" plot.tag.position" , theme ) %|| % " topleft"
369- location <- calc_element(" plot.tag.location" , theme ) %|| %
370- (if (is.numeric(position )) " plot" else " margin" )
371-
372- if (is.numeric(position )) {
373- if (location == " margin" ) {
374- cli :: cli_abort(paste0(
375- " A {.cls numeric} {.arg plot.tag.position} cannot be used with " ,
376- " `{.val margin}` as {.arg plot.tag.location}."
377- ),
378- call = expr(theme()))
379- }
380- check_length(
381- position , 2L , call = expr(theme()),
382- arg = I(" A {.cls numeric} {.arg plot.tag.position}" )
383- )
384- top <- left <- right <- bottom <- FALSE
385- } else {
386- # Break position into top/left/right/bottom
387- position <- arg_match0(
388- position [1 ],
389- c(" topleft" , " top" , " topright" , " left" ,
390- " right" , " bottomleft" , " bottom" , " bottomright" ),
391- arg_nm = " plot.tag.position" ,
392- error_call = expr(theme())
393- )
394- top <- position %in% c(" topleft" , " top" , " topright" )
395- left <- position %in% c(" topleft" , " left" , " bottomleft" )
396- right <- position %in% c(" topright" , " right" , " bottomright" )
397- bottom <- position %in% c(" bottomleft" , " bottom" , " bottomright" )
398- }
399-
400- # Resolve tag and sizes
401- tag <- element_grob(element , label = label , margin_y = TRUE , margin_x = TRUE )
402- height <- grobHeight(tag )
403- width <- grobWidth(tag )
404-
405- if (location %in% c(" plot" , " panel" )) {
406- if (! is.numeric(position )) {
407- hjust <- try_prop(element , " hjust" , default = 0.5 )
408- if (right || left ) {
409- x <- (1 - hjust ) * width
410- if (right ) {
411- x <- unit(1 , " npc" ) - x
412- }
413- } else {
414- x <- unit(hjust , " npc" )
415- }
416- if (top || bottom ) {
417- vjust <- try_prop(element , " vjust" , default = 0.5 )
418- y <- (1 - vjust ) * height
419- if (top ) {
420- y <- unit(1 , " npc" ) - y
421- }
422- } else {
423- y <- unit(vjust , " npc" )
424- }
425- } else {
426- x <- unit(position [1 ], " npc" )
427- y <- unit(position [2 ], " npc" )
428- }
429- # Re-render with manual positions
430- tag <- element_grob(
431- element , x = x , y = y , label = label ,
432- margin_y = TRUE , margin_x = TRUE
433- )
434- if (location == " plot" ) {
435- table <- gtable_add_grob(
436- table , tag , name = " tag" , clip = " off" ,
437- t = 1 , b = nrow(table ), l = 1 , r = ncol(table )
438- )
439- return (table )
440- }
441- }
442-
443- if (location == " panel" ) {
444- place <- find_panel(table )
445- } else {
446- n_col <- ncol(table )
447- n_row <- nrow(table )
448- # Actually fill margin with relevant units
449- if (top ) table $ heights <- unit.c(height , table $ heights [- 1 ])
450- if (left ) table $ widths <- unit.c(width , table $ widths [- 1 ])
451- if (right ) table $ widths <- unit.c(table $ widths [- n_col ], width )
452- if (bottom ) table $ heights <- unit.c(table $ heights [- n_row ], height )
453- place <- data_frame0(t = 1L , r = n_col , b = n_row , l = 1L )
454- }
455-
456- # Shrink placement to position
457- if (top ) place $ b <- place $ t
458- if (left ) place $ r <- place $ l
459- if (right ) place $ l <- place $ r
460- if (bottom ) place $ t <- place $ b
461-
462- gtable_add_grob(
463- table , tag , name = " tag" , clip = " off" ,
464- t = place $ t , l = place $ l , b = place $ b , r = place $ r
465- )
466- }
467-
468- # Add the legends to the gtable
469- table_add_legends <- function (table , legends , theme ) {
470-
471- if (is_zero(legends )) {
472- legends <- rep(list (zeroGrob()), 5 )
473- names(legends ) <- c(.trbl , " inside" )
474- }
475-
476- # Extract sizes
477- widths <- heights <- set_names(
478- rep(list (unit(0 , " cm" )), length(legends )),
479- names(legends )
480- )
481-
482- empty <- vapply(legends , is_zero , logical (1 ))
483- widths [! empty ] <- lapply(legends [! empty ], gtable_width )
484- heights [! empty ] <- lapply(legends [! empty ], gtable_height )
485- spacing <- calc_element(" legend.box.spacing" , theme ) %|| % unit(0.2 , " cm" )
486-
487- # If legend is missing, set spacing to zero for that legend
488- zero <- unit(0 , " pt" )
489- spacing <- lapply(empty , function (is_empty ) if (is_empty ) zero else spacing )
490-
491- location <- switch (
492- theme $ legend.location %|| % " panel" ,
493- " plot" = plot_extent ,
494- find_panel
495- )
496-
497- place <- location(table )
498-
499- # Add right legend
500- table <- gtable_add_cols(table , spacing $ right , pos = - 1 )
501- table <- gtable_add_cols(table , widths $ right , pos = - 1 )
502- table <- gtable_add_grob(
503- table , legends $ right , clip = " off" ,
504- t = place $ t , b = place $ b , l = - 1 , r = - 1 ,
505- name = " guide-box-right"
506- )
507-
508- # Add left legend
509- table <- gtable_add_cols(table , spacing $ left , pos = 0 )
510- table <- gtable_add_cols(table , widths $ left , pos = 0 )
511- table <- gtable_add_grob(
512- table , legends $ left , clip = " off" ,
513- t = place $ t , b = place $ b , l = 1 , r = 1 ,
514- name = " guide-box-left"
515- )
516-
517- place <- location(table )
518-
519- # Add bottom legend
520- table <- gtable_add_rows(table , spacing $ bottom , pos = - 1 )
521- table <- gtable_add_rows(table , heights $ bottom , pos = - 1 )
522- table <- gtable_add_grob(
523- table , legends $ bottom , clip = " off" ,
524- t = - 1 , b = - 1 , l = place $ l , r = place $ r ,
525- name = " guide-box-bottom"
526- )
527-
528- # Add top legend
529- table <- gtable_add_rows(table , spacing $ top , pos = 0 )
530- table <- gtable_add_rows(table , heights $ top , pos = 0 )
531- table <- gtable_add_grob(
532- table , legends $ top , clip = " off" ,
533- t = 1 , b = 1 , l = place $ l , r = place $ r ,
534- name = " guide-box-top"
535- )
536-
537- # Add manual legend
538- place <- find_panel(table )
539- table <- gtable_add_grob(
540- table , legends $ inside , clip = " off" ,
541- t = place $ t , b = place $ b , l = place $ l , r = place $ r ,
542- name = " guide-box-inside"
543- )
544-
545- table
546- }
547-
548- plot_extent <- function (table ) {
549- layout <- table $ layout
550- data_frame0(
551- t = min(layout [[" t" ]]),
552- r = max(layout [[" r" ]]),
553- b = max(layout [[" b" ]]),
554- l = min(layout [[" l" ]]),
555- .size = 1L
556- )
557- }
0 commit comments