9
9
# ' about theme inheritance below.
10
10
# '
11
11
# ' @section Theme inheritance:
12
- # ' Theme elements inherit properties from other theme elements heirarchically .
12
+ # ' Theme elements inherit properties from other theme elements hierarchically .
13
13
# ' For example, `axis.title.x.bottom` inherits from `axis.title.x` which inherits
14
14
# ' from `axis.title`, which in turn inherits from `text`. All text elements inherit
15
15
# ' directly or indirectly from `text`; all lines inherit from
164
164
# ' `complete = TRUE` all elements will be set to inherit from blank
165
165
# ' elements.
166
166
# ' @param validate `TRUE` to run `validate_element()`, `FALSE` to bypass checks.
167
+ # ' @param element_tree optional addition or modification to the element tree,
168
+ # ' which specifies the inheritance relationship of the theme elements. The element
169
+ # ' tree should be provided as a list of named element definitions created with
170
+ # ' [`el_def()`]. See [`el_def()`] for more details.
167
171
# '
168
172
# ' @seealso
169
173
# ' [+.gg()] and \code{\link{\%+replace\%}},
@@ -358,9 +362,10 @@ theme <- function(line,
358
362
strip.switch.pad.wrap ,
359
363
... ,
360
364
complete = FALSE ,
361
- validate = TRUE
365
+ validate = TRUE ,
366
+ element_tree = NULL
362
367
) {
363
- elements <- find_args(... , complete = NULL , validate = NULL )
368
+ elements <- find_args(... , complete = NULL , validate = NULL , element_tree = NULL )
364
369
365
370
if (! is.null(elements $ axis.ticks.margin )) {
366
371
warning(" `axis.ticks.margin` is deprecated. Please set `margin` property " ,
@@ -392,11 +397,6 @@ theme <- function(line,
392
397
elements $ legend.margin <- margin()
393
398
}
394
399
395
- # Check that all elements have the correct class (element_text, unit, etc)
396
- if (validate ) {
397
- mapply(validate_element , elements , names(elements ))
398
- }
399
-
400
400
# If complete theme set all non-blank elements to inherit from blanks
401
401
if (complete ) {
402
402
elements <- lapply(elements , function (el ) {
@@ -410,21 +410,69 @@ theme <- function(line,
410
410
elements ,
411
411
class = c(" theme" , " gg" ),
412
412
complete = complete ,
413
- validate = validate
413
+ validate = validate ,
414
+ element_tree = element_tree
414
415
)
415
416
}
416
417
417
- is_theme_complete <- function (x ) isTRUE(attr(x , " complete" ))
418
+ # check whether theme is complete
419
+ is_theme_complete <- function (x ) isTRUE(attr(x , " complete" , exact = TRUE ))
418
420
421
+ # check whether theme should be validated
422
+ is_theme_validate <- function (x ) {
423
+ validate <- attr(x , " validate" , exact = TRUE )
424
+ if (is.null(validate ))
425
+ TRUE # we validate by default
426
+ else
427
+ isTRUE(validate )
428
+ }
429
+
430
+ # obtain the full element tree from a theme,
431
+ # substituting the defaults if needed
432
+ complete_element_tree <- function (theme ) {
433
+ element_tree <- attr(theme , " element_tree" , exact = TRUE )
434
+
435
+ # we fill in the element tree first from the current default theme,
436
+ # and then from the internal element tree if necessary
437
+ # this makes it easy for extension packages to provide modified
438
+ # default element trees
439
+ defaults(
440
+ defaults(
441
+ element_tree ,
442
+ attr(theme_get(), " element_tree" , exact = TRUE )
443
+ ),
444
+ ggplot_global $ element_tree
445
+ )
446
+ }
419
447
420
448
# Combine plot defaults with current theme to get complete theme for a plot
421
449
plot_theme <- function (x , default = theme_get()) {
422
450
theme <- x $ theme
451
+
452
+ # apply theme defaults appropriately if needed
423
453
if (is_theme_complete(theme )) {
424
- theme
454
+ # for complete themes, we fill in missing elements but don't do any element merging
455
+ # can't use `defaults()` because it strips attributes
456
+ missing <- setdiff(names(default ), names(theme ))
457
+ theme [missing ] <- default [missing ]
425
458
} else {
426
- defaults(theme , default )
459
+ # otherwise, we can just add the theme to the default theme
460
+ theme <- default + theme
427
461
}
462
+
463
+ # complete the element tree and save back to the theme
464
+ element_tree <- complete_element_tree(theme )
465
+ attr(theme , " element_tree" ) <- element_tree
466
+
467
+ # Check that all elements have the correct class (element_text, unit, etc)
468
+ if (is_theme_validate(theme )) {
469
+ mapply(
470
+ validate_element , theme , names(theme ),
471
+ MoreArgs = list (element_tree = element_tree )
472
+ )
473
+ }
474
+
475
+ theme
428
476
}
429
477
430
478
# ' Modify properties of an element in a theme object
@@ -435,7 +483,7 @@ plot_theme <- function(x, default = theme_get()) {
435
483
# ' informative error messages.
436
484
# ' @keywords internal
437
485
add_theme <- function (t1 , t2 , t2name ) {
438
- if (! is.theme (t2 )) {
486
+ if (! is.list (t2 )) { # in various places in the code base, simple lists are used as themes
439
487
stop(" Can't add `" , t2name , " ` to a theme object." ,
440
488
call. = FALSE )
441
489
}
@@ -457,6 +505,17 @@ add_theme <- function(t1, t2, t2name) {
457
505
# make sure the "complete" attribute is set; this can be missing
458
506
# when t1 is an empty list
459
507
attr(t1 , " complete" ) <- is_theme_complete(t1 )
508
+
509
+ # Only validate if both themes should be validated
510
+ attr(t1 , " validate" ) <-
511
+ is_theme_validate(t1 ) && is_theme_validate(t2 )
512
+
513
+ # Merge element trees if provided
514
+ attr(t1 , " element_tree" ) <- defaults(
515
+ attr(t2 , " element_tree" , exact = TRUE ),
516
+ attr(t1 , " element_tree" , exact = TRUE )
517
+ )
518
+
460
519
t1
461
520
}
462
521
@@ -484,30 +543,31 @@ add_theme <- function(t1, t2, t2name) {
484
543
calc_element <- function (element , theme , verbose = FALSE ) {
485
544
if (verbose ) message(element , " --> " , appendLF = FALSE )
486
545
487
- # if theme is not complete, merge element with theme defaults,
488
- # otherwise take it as is. This fills in theme defaults if no
489
- # explicit theme is set for the plot.
490
- if (! is_theme_complete(theme )) {
491
- el_out <- merge_element(theme [[element ]], theme_get()[[element ]])
492
- } else {
493
- el_out <- theme [[element ]]
494
- }
546
+ el_out <- theme [[element ]]
495
547
496
548
# If result is element_blank, don't inherit anything from parents
497
549
if (inherits(el_out , " element_blank" )) {
498
550
if (verbose ) message(" element_blank (no inheritance)" )
499
551
return (el_out )
500
552
}
501
553
554
+ # Obtain the element tree and check that the element is in it
555
+ # If not, try to retrieve the complete element tree. This is
556
+ # needed for backwards compatibility and certain unit tests.
557
+ element_tree <- attr(theme , " element_tree" , exact = TRUE )
558
+ if (! element %in% names(element_tree )) {
559
+ element_tree <- complete_element_tree(theme )
560
+ }
561
+
502
562
# If the element is defined (and not just inherited), check that
503
- # it is of the class specified in . element_tree
563
+ # it is of the class specified in element_tree
504
564
if (! is.null(el_out ) &&
505
- ! inherits(el_out , ggplot_global $ element_tree [[element ]]$ class )) {
506
- stop(element , " should have class " , ggplot_global $ element_tree [[element ]]$ class )
565
+ ! inherits(el_out , element_tree [[element ]]$ class )) {
566
+ stop(element , " should have class " , element_tree [[element ]]$ class )
507
567
}
508
568
509
569
# Get the names of parents from the inheritance tree
510
- pnames <- ggplot_global $ element_tree [[element ]]$ inherit
570
+ pnames <- element_tree [[element ]]$ inherit
511
571
512
572
# If no parents, this is a "root" node. Just return this element.
513
573
if (is.null(pnames )) {
0 commit comments