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