51
51
# ' (`bg`), foreground (`fg`), and accent (`accent`) colors inherit from the
52
52
# ' plot's containing HTML element(s)' CSS styling. When `autoTheme` is `TRUE`
53
53
# ' (or a list options), default theming rules are applied ggplot2, lattice, and
54
- # ' base graphics. In addition, a `qualitative` color palette is set for each
55
- # ' plotting framework to ensure a consistent and colour-blind safe palette.
56
- # ' For `qualitative`, as well as (`fg`/`bg`/`accent`), you may supply your own
57
- # ' color codes to override the defaults, or supply `NA` to prevent auto-theming
58
- # ' logic from being applied
59
- # ' (e.g., `autoTheme = list(accent="red", qualitative=NA)`).
54
+ # ' base graphics. Additionally, under certain conditions, `sequential` and
55
+ # ' `qualitative` color palettes are also set. The default `sequential` palette
56
+ # ' derives from the `accent` color, whereas the `qualitative` palette is based
57
+ # ' on the Okabe-Ito scale. To control auto-theming defaults, pass a list of
58
+ # ' options with the desired color codes (and/or `NA` to use plotting framework's
59
+ # ' defaults instead of the auto-theming defaults). For example,
60
+ # ' `autoTheme = list(accent="red", sequential=NA)` sets the `accent` to `"red"`,
61
+ # ' but also ensures ggplot2's sequential colorscale defaults still apply.
60
62
# ' @param outputArgs A list of arguments to be passed through to the implicit
61
63
# ' call to [plotOutput()] when `renderPlot` is used in an
62
64
# ' interactive R Markdown document.
@@ -287,10 +289,10 @@ drawPlot <- function(name, session, func, width, height, pixelratio, res, theme
287
289
# NULL is the normal case, but in case any of the param setting calls
288
290
# threw an error; in that case, not all of these four may have been
289
291
# performed.
290
- if (! is.null(base_params )) { do.call(par , base_params ) }
292
+ if (! is.null(base_params )) { do.call(graphics :: par , base_params ) }
291
293
if (! is.null(grid_params )) { do.call(grid :: gpar , grid_params ) }
292
294
if (! is.null(lattice_params )) { lattice_set_par_list(lattice_params ) }
293
- if (! is.null(old_palette )) { palette(old_palette ) }
295
+ if (! is.null(old_palette )) { grDevices :: palette(old_palette ) }
294
296
295
297
grDevices :: dev.off(device )
296
298
}
@@ -336,11 +338,11 @@ base_set_params <- function(theme) {
336
338
params <- list ()
337
339
bg <- theme $ bg
338
340
if (! is.null(bg )) {
339
- params <- c(params , par(bg = bg ))
341
+ params <- c(params , graphics :: par(bg = bg ))
340
342
}
341
343
fg <- theme $ fg
342
344
if (! is.null(fg )) {
343
- params <- c(params , par(
345
+ params <- c(params , graphics :: par(
344
346
fg = fg ,
345
347
col.axis = fg ,
346
348
col.lab = fg ,
@@ -358,18 +360,23 @@ grid_set_params <- function(theme) {
358
360
359
361
lattice_set_params <- function (theme ) {
360
362
if (system.file(package = " lattice" ) == " " ) return ()
361
- old_par <- lattice :: trellis.par.get()
363
+ old_par <- utils :: getFromNamespace( " trellis.par.get" , " lattice " ) ()
362
364
bg <- theme $ bg
363
365
fg <- theme $ fg
364
366
365
- lattice :: trellis.par.set(
367
+ par_set <- utils :: getFromNamespace(" trellis.par.set" , " lattice" )
368
+ par_set(
366
369
# See figure 9.3 for an example of where grid gpar matters
367
370
# http://lmdvr.r-forge.r-project.org/figures/figures.html
368
371
grid.pars = list (col = fg ),
369
372
background = list (col = bg ),
370
373
reference.line = list (col = bg ),
371
- panel.background = list (col = setAlpha(fg , 0.1 )),
372
- strip.background = list (col = setAlpha(fg , 0.2 )),
374
+ panel.background = list (
375
+ col = mix_colors(theme $ bg , theme $ fg , 0.1 )
376
+ ),
377
+ strip.background = list (
378
+ col = mix_colors(theme $ bg , theme $ fg , 0.2 )
379
+ ),
373
380
strip.border = list (col = fg ),
374
381
axis.line = list (col = fg ),
375
382
axis.text = list (col = fg ),
@@ -384,14 +391,16 @@ lattice_set_params <- function(theme) {
384
391
plot.polygon = list (border = fg ),
385
392
superpose.polygon = list (border = fg ),
386
393
box.dot = list (col = fg ),
387
- dot.line = list (col = setAlpha(fg , 0.2 ))
394
+ dot.line = list (
395
+ col = mix_colors(theme $ bg , theme $ fg , 0.2 )
396
+ )
388
397
)
389
398
390
399
# For lattice, accent can be of length 2, one to specify
391
400
# 'stroke' accent and one for fill accent
392
401
accent <- rep(theme $ accent , length.out = 2 )
393
402
if (sum(is.na(accent )) == 0 ) {
394
- lattice :: trellis.par.set (
403
+ par_set (
395
404
plot.line = list (col = accent [[1 ]]),
396
405
plot.symbol = list (col = accent [[1 ]]),
397
406
dot.symbol = list (col = accent [[1 ]]),
@@ -405,8 +414,8 @@ lattice_set_params <- function(theme) {
405
414
qualitative <- getQualitativeCodes(theme , 7 )
406
415
if (sum(is.na(qualitative )) == 0 ) {
407
416
# I'm not in love with the idea of this; but alas, it's consistent with lattice's default
408
- region_pal <- colorRampPalette(c(qualitative [[1 ]], " white" , qualitative [[2 ]]))
409
- lattice :: trellis.par.set (
417
+ region_pal <- grDevices :: colorRampPalette(c(qualitative [[1 ]], " white" , qualitative [[2 ]]))
418
+ par_set (
410
419
strip.shingle = list (col = qualitative ),
411
420
regions = list (col = region_pal(100 )),
412
421
superpose.line = list (col = qualitative ),
@@ -420,33 +429,56 @@ lattice_set_params <- function(theme) {
420
429
421
430
lattice_set_par_list <- function (params ) {
422
431
if (system.file(package = " lattice" ) == " " ) return ()
423
- lattice :: trellis.par.set(theme = params )
432
+ utils :: getFromNamespace( " trellis.par.set" , " lattice " ) (theme = params )
424
433
}
425
434
426
435
base_set_palette <- function (theme ) {
427
436
codes <- getQualitativeCodes(theme )
428
- if (isTRUE(is.na(codes ))) palette() else palette(codes )
437
+ if (isTRUE(is.na(codes ))) grDevices :: palette() else grDevices :: palette(codes )
429
438
}
430
439
431
440
getQualitativeCodes <- function (theme , n = NULL ) {
432
441
qualitative <- theme $ qualitative
433
442
if (isTRUE(is.na(qualitative )) || is.character(qualitative )) {
434
443
return (qualitative )
435
444
}
445
+ # https://jfly.uni-koeln.de/color/
436
446
# TODO: use another colorscale in dark mode?
447
+ okabeIto <- c(" #E69F00" , " #009E73" , " #0072B2" , " #CC79A7" , " #999999" , " #D55E00" , " #F0E442" , " #56B4E9" )
437
448
if (is.null(n )) okabeIto else okabeIto [seq_len(n )]
438
449
}
439
450
440
- # https://jfly.uni-koeln.de/color/
441
- okabeIto <- c(" #E69F00" , " #009E73" , " #0072B2" , " #CC79A7" , " #999999" , " #D55E00" , " #F0E442" , " #56B4E9" )
451
+ # Currently only used for ggplot2
452
+ getSequentialCodes <- function (theme , n = 8 ) {
453
+ sequential <- theme $ sequential
454
+ if (isTRUE(is.na(sequential )) || is.character(sequential )) {
455
+ return (sequential )
456
+ }
457
+ # This shouldn't really happen since ggplot2 depends on scales
458
+ # (and this is only called in the ggplot2 case)
459
+ if (system.file(package = " farver" ) == " " ) {
460
+ warning(" Computing default sequential codes (for autoTheme) requires the farver package." )
461
+ return (NA )
462
+ }
463
+ decode_colour <- utils :: getFromNamespace(" decode_colour" , " farver" )
464
+ if (system.file(package = " colorspace" ) == " " ) {
465
+ warning(" Computing default sequential codes (for autoTheme) requires the colorspace package." )
466
+ return (NA )
467
+ }
468
+ sequential_hcl <- utils :: getFromNamespace(" sequential_hcl" , " colorspace" )
469
+ hcl <- as.list(decode_colour(theme $ accent , to = " hcl" )[1 , ])
470
+ l <- c(hcl $ l - 20 , hcl $ l + 20 )
471
+ c <- c(hcl $ c + 20 , hcl $ c - 20 )
472
+ sequential_hcl(n = n , h = hcl $ h , c = c , l = l )
473
+ }
442
474
443
475
# A modified version of print.ggplot which returns the built ggplot object
444
476
# as well as the gtable grob. This overrides the ggplot::print.ggplot
445
477
# method, but only within the context of renderPlot. The reason this needs
446
478
# to be a (pseudo) S3 method is so that, if an object has a class in
447
479
# addition to ggplot, and there's a print method for that class, that we
448
480
# won't override that method. https://github.com/rstudio/shiny/issues/841
449
- custom_print.ggplot <- function (theme ) {
481
+ custom_print.ggplot <- function (theme = list () ) {
450
482
function (x ) {
451
483
build <- ggplot_build_with_theme(x , theme )
452
484
gtable <- ggplot2 :: ggplot_gtable(build )
@@ -464,7 +496,7 @@ custom_print.ggplot <- function(theme) {
464
496
# use this function with a custom ggplot_build function (e.g. plotly) can do so
465
497
# and geom defaults will still be restored after building
466
498
ggplot_build_with_theme <- function (p , theme , ggplot_build = ggplot2 :: ggplot_build , newpage = TRUE ) {
467
- if (is.null (theme )) return (ggplot_build(p ))
499
+ if (! length (theme )) return (ggplot_build(p ))
468
500
fg <- theme $ fg
469
501
bg <- theme $ bg
470
502
# Accent can be of length 2 because lattice
@@ -482,7 +514,10 @@ ggplot_build_with_theme <- function(p, theme, ggplot_build = ggplot2::ggplot_bui
482
514
# from 'lower-level' geoms, like GeomPoint, GeomLine, GeomPolygon
483
515
geoms <- c(
484
516
lapply(p $ layers , function (x ) x $ geom ),
485
- lapply(c(" GeomPoint" , " GeomLine" , " GeomPolygon" ), getFromNamespace , " ggplot2" )
517
+ lapply(
518
+ c(" GeomPoint" , " GeomLine" , " GeomPolygon" ),
519
+ utils :: getFromNamespace , " ggplot2"
520
+ )
486
521
)
487
522
488
523
# Remember defaults
@@ -520,30 +555,30 @@ ggplot_build_with_theme <- function(p, theme, ggplot_build = ggplot2::ggplot_bui
520
555
}
521
556
522
557
ggtheme_auto <- function (bg , fg ) {
523
- text <- element_text(colour = fg )
524
- line <- element_line(colour = fg )
525
- themeGray <- theme_gray()
558
+ text <- ggplot2 :: element_text(colour = fg )
559
+ line <- ggplot2 :: element_line(colour = fg )
560
+ themeGray <- ggplot2 :: theme_gray()
526
561
527
- theme(
562
+ ggplot2 :: theme(
528
563
line = line ,
529
564
text = text ,
530
565
axis.title = text ,
531
566
axis.text = text ,
532
567
axis.ticks = line ,
533
- plot.background = element_rect(fill = bg , colour = " transparent" ),
534
- panel.background = element_rect(
568
+ plot.background = ggplot2 :: element_rect(fill = bg , colour = " transparent" ),
569
+ panel.background = ggplot2 :: element_rect(
535
570
fill = adjust_color(themeGray $ panel.background $ fill , bg , fg )
536
571
),
537
- panel.grid = element_line(colour = bg ),
538
- legend.background = element_rect(fill = " transparent" ),
539
- legend.box.background = element_rect(
572
+ panel.grid = ggplot2 :: element_line(colour = bg ),
573
+ legend.background = ggplot2 :: element_rect(fill = " transparent" ),
574
+ legend.box.background = ggplot2 :: element_rect(
540
575
fill = " transparent" , colour = " transparent"
541
576
),
542
- legend.key = element_rect(
577
+ legend.key = ggplot2 :: element_rect(
543
578
fill = adjust_color(themeGray $ legend.key $ fill , bg , fg ),
544
579
colour = bg
545
580
),
546
- strip.background = element_rect(
581
+ strip.background = ggplot2 :: element_rect(
547
582
fill = adjust_color(themeGray $ strip.background $ fill , bg , fg )
548
583
),
549
584
strip.text = text
@@ -561,11 +596,9 @@ adjust_color <- function(color, bg, fg, accent = NA) {
561
596
562
597
# If a gray scale color, then the degree of gray determines
563
598
# the mixing between fg (aka black) and bg (aka white)
564
- rgbs <- col2rgb(color , alpha = TRUE )[1 : 3 ,1 ]
599
+ rgbs <- grDevices :: col2rgb(color , alpha = TRUE )[1 : 3 ,1 ]
565
600
if (sum(diff(rgbs )) == 0 ) {
566
601
return (mix_colors(bg , fg , 1 - (rgbs [1 ] / 255 )))
567
- # IDEA: instead of mixing colors with a colorRamp, perhaps it's better to adjust luminance?
568
- # return(scales::col2hcl(bg, l = luminance(color)))
569
602
}
570
603
571
604
# At this point we should be dealing with an accent color...
@@ -576,7 +609,7 @@ adjust_color <- function(color, bg, fg, accent = NA) {
576
609
577
610
mix_colors <- function (bg , fg , amount ) {
578
611
if (! length(bg ) || ! length(fg )) return (NULL )
579
- mid_color <- colorRamp(c(bg , fg ), alpha = TRUE )(amount )
612
+ mid_color <- grDevices :: colorRamp(c(bg , fg ), alpha = TRUE )(amount )
580
613
sprintf(
581
614
" #%02X%02X%02X%02X" ,
582
615
round(mid_color [1 ,1 ]),
@@ -589,9 +622,6 @@ mix_colors <- function(bg, fg, amount) {
589
622
add_scale_defaults <- function (p , aesthetic = " colour" , theme ) {
590
623
# If user has specified this scale type, then do nothing
591
624
if (p $ scales $ has_scale(aesthetic )) return (p )
592
- # If palette is explicit NA, do nothing
593
- codes <- getQualitativeCodes(theme )
594
- if (isTRUE(is.na(codes ))) return (p )
595
625
596
626
# Obtain the input values to the scale
597
627
values <- lapply(p $ layers , function (x ) {
@@ -600,15 +630,27 @@ add_scale_defaults <- function(p, aesthetic = "colour", theme) {
600
630
rlang :: eval_tidy(aes_map [[aesthetic ]], data )
601
631
})
602
632
603
- # At the moment, we only set a default for qualitative scales
604
- isQualitative <- all(vapply(values , function (x ) is_discrete(x ) && ! is.ordered(x ), logical (1 )))
605
- if (! isQualitative ) return (p )
633
+ # Apply sequential default, if relevant
634
+ isSequential <- all(vapply(values , is.numeric , logical (1 )))
635
+ if (isSequential ) {
636
+ seqCodes <- getSequentialCodes(theme )
637
+ if (! isTRUE(is.na(seqCodes ))) {
638
+ f <- match.fun(paste0(" scale_" , aesthetic , " _gradientn" ))
639
+ p <- p + f(colors = seqCodes )
640
+ }
641
+ }
606
642
607
- # Only apply scale if we have enough codes for it
608
- n <- length(unique(unlist(values )))
609
- if (n < = length(codes )) {
610
- f <- match.fun(paste0(" scale_" , aesthetic , " _manual" ))
611
- p <- p + f(values = codes )
643
+ # Apply qualitative default, if relevant (and we have enough codes)
644
+ isQualitative <- all(vapply(values , function (x ) is_discrete(x ) && ! is.ordered(x ), logical (1 )))
645
+ if (isQualitative ) {
646
+ qualCodes <- getQualitativeCodes(theme )
647
+ if (! isTRUE(is.na(qualCodes ))) {
648
+ n <- length(unique(unlist(values )))
649
+ if (n < = length(qualCodes )) {
650
+ f <- match.fun(paste0(" scale_" , aesthetic , " _manual" ))
651
+ p <- p + f(values = qualCodes )
652
+ }
653
+ }
612
654
}
613
655
614
656
p
@@ -622,7 +664,7 @@ is_discrete <- function(x) {
622
664
# ala Bootstrap's color-yiq()
623
665
# https://getbootstrap.com/docs/4.4/getting-started/theming/#color-contrast
624
666
color_yiq <- function (color ) {
625
- rgb <- col2rgb(color )
667
+ rgb <- grDevices :: col2rgb(color )
626
668
unname(
627
669
(rgb [" red" , ] * 299 + rgb [" green" , ] * 587 + rgb [" blue" , ] * 114 ) / 1000
628
670
)
@@ -791,6 +833,7 @@ color_yiq_islight <- function(color, threshold = 150) {
791
833
792
834
getCoordmap <- function (x , width , height , res ) {
793
835
if (inherits(x , " ggplot_build_gtable" )) {
836
+
794
837
getGgplotCoordmap(x , width , height , res )
795
838
} else {
796
839
getPrevPlotCoordmap(width , height )
@@ -849,7 +892,6 @@ getPrevPlotCoordmap <- function(width, height) {
849
892
getGgplotCoordmap <- function (p , width , height , res ) {
850
893
if (! inherits(p , " ggplot_build_gtable" ))
851
894
return (NULL )
852
-
853
895
tryCatch({
854
896
# Get info from built ggplot object
855
897
panel_info <- find_panel_info(p $ build )
0 commit comments