@@ -436,86 +436,31 @@ plot_theme <- function(x, default = theme_get()) {
436
436
# ' @keywords internal
437
437
add_theme <- function (t1 , t2 , t2name ) {
438
438
if (! is.theme(t2 )) {
439
- stop(" Don't know how to add RHS to a theme object" ,
439
+ stop(" Don't know how to add " , t2name , " to a theme object" ,
440
440
call. = FALSE )
441
441
}
442
442
443
+ # If t2 is a complete theme or t1 is NULL, just return t2
444
+ if (is_theme_complete(t2 ) || is.null(t1 ))
445
+ return (t2 )
446
+
443
447
# Iterate over the elements that are to be updated
444
448
for (item in names(t2 )) {
445
- x <- t1 [[item ]]
446
- y <- t2 [[item ]]
447
-
448
- if (is.null(x ) || inherits(x , " element_blank" )) {
449
- # If x is NULL or element_blank, then just assign it y
450
- x <- y
451
- } else if (is.null(y ) || is.character(y ) || is.numeric(y ) || is.unit(y ) ||
452
- is.logical(y ) || inherits(y , " element_blank" )) {
453
- # If y is NULL, or a string or numeric vector, or is element_blank, just replace x
454
- x <- y
455
- } else {
456
- # If x is not NULL, then merge into y
457
- x <- merge_element(y , x )
458
- }
449
+ x <- merge_element(t2 [[item ]], t1 [[item ]])
459
450
460
451
# Assign it back to t1
461
452
# This is like doing t1[[item]] <- x, except that it preserves NULLs.
462
453
# The other form will simply drop NULL values
463
454
t1 [item ] <- list (x )
464
455
}
465
456
466
- # If either theme is complete, then the combined theme is complete
467
- attr(t1 , " complete" ) <- is_theme_complete(t1 ) || is_theme_complete(t2 )
457
+ # make sure the "complete" attribute is set; this can be missing
458
+ # when t1 is an empty list
459
+ attr(t1 , " complete" ) <- is_theme_complete(t1 )
468
460
t1
469
461
}
470
462
471
463
472
- # Update a theme from a plot object
473
- #
474
- # This is called from add_ggplot.
475
- #
476
- # If newtheme is a *complete* theme, then it is meant to replace
477
- # oldtheme; this function just returns newtheme.
478
- #
479
- # Otherwise, it adds elements from newtheme to oldtheme:
480
- # If oldtheme doesn't already contain those elements,
481
- # it searches the current default theme, grabs the elements with the
482
- # same name as those from newtheme, and puts them in oldtheme. Then
483
- # it adds elements from newtheme to oldtheme.
484
- # This makes it possible to do things like:
485
- # ggplot(data.frame(x = 1:3, y = 1:3)) +
486
- # geom_point() + theme(text = element_text(colour = 'red'))
487
- # and have 'text' keep properties from the default theme. Otherwise
488
- # you would have to set all the element properties, like family, size,
489
- # etc.
490
- #
491
- # @param oldtheme an existing theme, usually from a plot object, like
492
- # plot$theme. This could be an empty list.
493
- # @param newtheme a new theme object to add to the existing theme
494
- update_theme <- function (oldtheme , newtheme ) {
495
- # If the newtheme is a complete one, don't bother searching
496
- # the default theme -- just replace everything with newtheme
497
- if (is_theme_complete(newtheme ))
498
- return (newtheme )
499
-
500
- # These are elements in newtheme that aren't already set in oldtheme.
501
- # They will be pulled from the default theme.
502
- newitems <- ! names(newtheme ) %in% names(oldtheme )
503
- newitem_names <- names(newtheme )[newitems ]
504
- oldtheme [newitem_names ] <- theme_get()[newitem_names ]
505
-
506
- # Update the theme elements with the things from newtheme
507
- # Turn the 'theme' list into a proper theme object first, and preserve
508
- # the 'complete' attribute. It's possible that oldtheme is an empty
509
- # list, and in that case, set complete to FALSE.
510
- old.validate <- isTRUE(attr(oldtheme , " validate" ))
511
- new.validate <- isTRUE(attr(newtheme , " validate" ))
512
- oldtheme <- do.call(theme , c(oldtheme ,
513
- complete = isTRUE(attr(oldtheme , " complete" )),
514
- validate = old.validate & new.validate ))
515
-
516
- oldtheme + newtheme
517
- }
518
-
519
464
# ' Calculate the element properties, by inheriting properties from its parents
520
465
# '
521
466
# ' @param element The name of the theme element to calculate
@@ -539,16 +484,25 @@ update_theme <- function(oldtheme, newtheme) {
539
484
calc_element <- function (element , theme , verbose = FALSE ) {
540
485
if (verbose ) message(element , " --> " , appendLF = FALSE )
541
486
542
- # If this is element_blank, don't inherit anything from parents
543
- if (inherits(theme [[element ]], " element_blank" )) {
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
+ }
495
+
496
+ # If result is element_blank, don't inherit anything from parents
497
+ if (inherits(el_out , " element_blank" )) {
544
498
if (verbose ) message(" element_blank (no inheritance)" )
545
- return (theme [[ element ]] )
499
+ return (el_out )
546
500
}
547
501
548
502
# If the element is defined (and not just inherited), check that
549
503
# it is of the class specified in .element_tree
550
- if (! is.null(theme [[ element ]] ) &&
551
- ! inherits(theme [[ element ]] , ggplot_global $ element_tree [[element ]]$ class )) {
504
+ if (! is.null(el_out ) &&
505
+ ! inherits(el_out , ggplot_global $ element_tree [[element ]]$ class )) {
552
506
stop(element , " should have class " , ggplot_global $ element_tree [[element ]]$ class )
553
507
}
554
508
@@ -557,23 +511,31 @@ calc_element <- function(element, theme, verbose = FALSE) {
557
511
558
512
# If no parents, this is a "root" node. Just return this element.
559
513
if (is.null(pnames )) {
514
+ if (verbose ) message(" nothing (top level)" )
515
+
560
516
# Check that all the properties of this element are non-NULL
561
- nullprops <- vapply(theme [[element ]], is.null , logical (1 ))
562
- if (any(nullprops )) {
563
- stop(" Theme element '" , element , " ' has NULL property: " ,
564
- paste(names(nullprops )[nullprops ], collapse = " , " ))
517
+ nullprops <- vapply(el_out , is.null , logical (1 ))
518
+ if (! any(nullprops )) {
519
+ return (el_out ) # no null properties, return element as is
565
520
}
566
521
567
- if (verbose ) message(" nothing (top level)" )
568
- return (theme [[element ]])
522
+ # if we have null properties, try to fill in from theme_grey()
523
+ el_out <- combine_elements(el_out , ggplot_global $ theme_grey [[element ]])
524
+ nullprops <- vapply(el_out , is.null , logical (1 ))
525
+ if (! any(nullprops )) {
526
+ return (el_out ) # no null properties remaining, return element
527
+ }
528
+
529
+ stop(" Theme element '" , element , " ' has NULL property without default: " ,
530
+ paste(names(nullprops )[nullprops ], collapse = " , " ))
569
531
}
570
532
571
533
# Calculate the parent objects' inheritance
572
534
if (verbose ) message(paste(pnames , collapse = " , " ))
573
535
parents <- lapply(pnames , calc_element , theme , verbose )
574
536
575
537
# Combine the properties of this element with all parents
576
- Reduce(combine_elements , parents , theme [[ element ]] )
538
+ Reduce(combine_elements , parents , el_out )
577
539
}
578
540
579
541
# ' Merge a parent element into a child element
@@ -597,17 +559,43 @@ calc_element <- function(element, theme, verbose = FALSE) {
597
559
merge_element <- function (new , old ) {
598
560
UseMethod(" merge_element" )
599
561
}
562
+
600
563
# ' @rdname merge_element
601
564
# ' @export
602
565
merge_element.default <- function (new , old ) {
566
+ if (is.null(old ) || inherits(old , " element_blank" )) {
567
+ # If old is NULL or element_blank, then just return new
568
+ return (new )
569
+ } else if (is.null(new ) || is.character(new ) || is.numeric(new ) || is.unit(new ) ||
570
+ is.logical(new )) {
571
+ # If new is NULL, or a string, numeric vector, unit, or logical, just return it
572
+ return (new )
573
+ }
574
+
575
+ # otherwise we can't merge
603
576
stop(" No method for merging " , class(new )[1 ], " into " , class(old )[1 ], call. = FALSE )
604
577
}
578
+
579
+ # ' @rdname merge_element
580
+ # ' @export
581
+ merge_element.element_blank <- function (new , old ) {
582
+ # If new is element_blank, just return it
583
+ new
584
+ }
585
+
605
586
# ' @rdname merge_element
606
587
# ' @export
607
588
merge_element.element <- function (new , old ) {
589
+ if (is.null(old ) || inherits(old , " element_blank" )) {
590
+ # If old is NULL or element_blank, then just return new
591
+ return (new )
592
+ }
593
+
594
+ # actual merging can only happen if classes match
608
595
if (! inherits(new , class(old )[1 ])) {
609
596
stop(" Only elements of the same class can be merged" , call. = FALSE )
610
597
}
598
+
611
599
# Override NULL properties of new with the values in old
612
600
# Get logical vector of NULL properties in new
613
601
idx <- vapply(new , is.null , logical (1 ))
0 commit comments