5151# ' (`bg`), foreground (`fg`), and accent (`accent`) colors inherit from the
5252# ' plot's containing HTML element(s)' CSS styling. When `autoTheme` is `TRUE`
5353# ' (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.
6062# ' @param outputArgs A list of arguments to be passed through to the implicit
6163# ' call to [plotOutput()] when `renderPlot` is used in an
6264# ' interactive R Markdown document.
@@ -287,10 +289,10 @@ drawPlot <- function(name, session, func, width, height, pixelratio, res, theme
287289 # NULL is the normal case, but in case any of the param setting calls
288290 # threw an error; in that case, not all of these four may have been
289291 # performed.
290- if (! is.null(base_params )) { do.call(par , base_params ) }
292+ if (! is.null(base_params )) { do.call(graphics :: par , base_params ) }
291293 if (! is.null(grid_params )) { do.call(grid :: gpar , grid_params ) }
292294 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 ) }
294296
295297 grDevices :: dev.off(device )
296298 }
@@ -336,11 +338,11 @@ base_set_params <- function(theme) {
336338 params <- list ()
337339 bg <- theme $ bg
338340 if (! is.null(bg )) {
339- params <- c(params , par(bg = bg ))
341+ params <- c(params , graphics :: par(bg = bg ))
340342 }
341343 fg <- theme $ fg
342344 if (! is.null(fg )) {
343- params <- c(params , par(
345+ params <- c(params , graphics :: par(
344346 fg = fg ,
345347 col.axis = fg ,
346348 col.lab = fg ,
@@ -358,18 +360,23 @@ grid_set_params <- function(theme) {
358360
359361lattice_set_params <- function (theme ) {
360362 if (system.file(package = " lattice" ) == " " ) return ()
361- old_par <- lattice :: trellis.par.get()
363+ old_par <- utils :: getFromNamespace( " trellis.par.get" , " lattice " ) ()
362364 bg <- theme $ bg
363365 fg <- theme $ fg
364366
365- lattice :: trellis.par.set(
367+ par_set <- utils :: getFromNamespace(" trellis.par.set" , " lattice" )
368+ par_set(
366369 # See figure 9.3 for an example of where grid gpar matters
367370 # http://lmdvr.r-forge.r-project.org/figures/figures.html
368371 grid.pars = list (col = fg ),
369372 background = list (col = bg ),
370373 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+ ),
373380 strip.border = list (col = fg ),
374381 axis.line = list (col = fg ),
375382 axis.text = list (col = fg ),
@@ -384,14 +391,16 @@ lattice_set_params <- function(theme) {
384391 plot.polygon = list (border = fg ),
385392 superpose.polygon = list (border = fg ),
386393 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+ )
388397 )
389398
390399 # For lattice, accent can be of length 2, one to specify
391400 # 'stroke' accent and one for fill accent
392401 accent <- rep(theme $ accent , length.out = 2 )
393402 if (sum(is.na(accent )) == 0 ) {
394- lattice :: trellis.par.set (
403+ par_set (
395404 plot.line = list (col = accent [[1 ]]),
396405 plot.symbol = list (col = accent [[1 ]]),
397406 dot.symbol = list (col = accent [[1 ]]),
@@ -405,8 +414,8 @@ lattice_set_params <- function(theme) {
405414 qualitative <- getQualitativeCodes(theme , 7 )
406415 if (sum(is.na(qualitative )) == 0 ) {
407416 # 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 (
410419 strip.shingle = list (col = qualitative ),
411420 regions = list (col = region_pal(100 )),
412421 superpose.line = list (col = qualitative ),
@@ -425,28 +434,51 @@ lattice_set_par_list <- function(params) {
425434
426435base_set_palette <- function (theme ) {
427436 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 )
429438}
430439
431440getQualitativeCodes <- function (theme , n = NULL ) {
432441 qualitative <- theme $ qualitative
433442 if (isTRUE(is.na(qualitative )) || is.character(qualitative )) {
434443 return (qualitative )
435444 }
445+ # https://jfly.uni-koeln.de/color/
436446 # TODO: use another colorscale in dark mode?
447+ okabeIto <- c(" #E69F00" , " #009E73" , " #0072B2" , " #CC79A7" , " #999999" , " #D55E00" , " #F0E442" , " #56B4E9" )
437448 if (is.null(n )) okabeIto else okabeIto [seq_len(n )]
438449}
439450
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+ }
442474
443475# A modified version of print.ggplot which returns the built ggplot object
444476# as well as the gtable grob. This overrides the ggplot::print.ggplot
445477# method, but only within the context of renderPlot. The reason this needs
446478# to be a (pseudo) S3 method is so that, if an object has a class in
447479# addition to ggplot, and there's a print method for that class, that we
448480# 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 () ) {
450482 function (x ) {
451483 build <- ggplot_build_with_theme(x , theme )
452484 gtable <- ggplot2 :: ggplot_gtable(build )
@@ -464,7 +496,7 @@ custom_print.ggplot <- function(theme) {
464496# use this function with a custom ggplot_build function (e.g. plotly) can do so
465497# and geom defaults will still be restored after building
466498ggplot_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 ))
468500 fg <- theme $ fg
469501 bg <- theme $ bg
470502 # Accent can be of length 2 because lattice
@@ -482,7 +514,10 @@ ggplot_build_with_theme <- function(p, theme, ggplot_build = ggplot2::ggplot_bui
482514 # from 'lower-level' geoms, like GeomPoint, GeomLine, GeomPolygon
483515 geoms <- c(
484516 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+ )
486521 )
487522
488523 # Remember defaults
@@ -520,30 +555,30 @@ ggplot_build_with_theme <- function(p, theme, ggplot_build = ggplot2::ggplot_bui
520555}
521556
522557ggtheme_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()
526561
527- theme(
562+ ggplot2 :: theme(
528563 line = line ,
529564 text = text ,
530565 axis.title = text ,
531566 axis.text = text ,
532567 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(
535570 fill = adjust_color(themeGray $ panel.background $ fill , bg , fg )
536571 ),
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(
540575 fill = " transparent" , colour = " transparent"
541576 ),
542- legend.key = element_rect(
577+ legend.key = ggplot2 :: element_rect(
543578 fill = adjust_color(themeGray $ legend.key $ fill , bg , fg ),
544579 colour = bg
545580 ),
546- strip.background = element_rect(
581+ strip.background = ggplot2 :: element_rect(
547582 fill = adjust_color(themeGray $ strip.background $ fill , bg , fg )
548583 ),
549584 strip.text = text
@@ -561,11 +596,9 @@ adjust_color <- function(color, bg, fg, accent = NA) {
561596
562597 # If a gray scale color, then the degree of gray determines
563598 # 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 ]
565600 if (sum(diff(rgbs )) == 0 ) {
566601 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)))
569602 }
570603
571604 # At this point we should be dealing with an accent color...
@@ -576,7 +609,7 @@ adjust_color <- function(color, bg, fg, accent = NA) {
576609
577610mix_colors <- function (bg , fg , amount ) {
578611 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 )
580613 sprintf(
581614 " #%02X%02X%02X%02X" ,
582615 round(mid_color [1 ,1 ]),
@@ -589,9 +622,6 @@ mix_colors <- function(bg, fg, amount) {
589622add_scale_defaults <- function (p , aesthetic = " colour" , theme ) {
590623 # If user has specified this scale type, then do nothing
591624 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 )
595625
596626 # Obtain the input values to the scale
597627 values <- lapply(p $ layers , function (x ) {
@@ -600,15 +630,27 @@ add_scale_defaults <- function(p, aesthetic = "colour", theme) {
600630 rlang :: eval_tidy(aes_map [[aesthetic ]], data )
601631 })
602632
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+ }
606642
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+ }
612654 }
613655
614656 p
@@ -622,7 +664,7 @@ is_discrete <- function(x) {
622664# ala Bootstrap's color-yiq()
623665# https://getbootstrap.com/docs/4.4/getting-started/theming/#color-contrast
624666color_yiq <- function (color ) {
625- rgb <- col2rgb(color )
667+ rgb <- grDevices :: col2rgb(color )
626668 unname(
627669 (rgb [" red" , ] * 299 + rgb [" green" , ] * 587 + rgb [" blue" , ] * 114 ) / 1000
628670 )
@@ -791,6 +833,7 @@ color_yiq_islight <- function(color, threshold = 150) {
791833
792834getCoordmap <- function (x , width , height , res ) {
793835 if (inherits(x , " ggplot_build_gtable" )) {
836+
794837 getGgplotCoordmap(x , width , height , res )
795838 } else {
796839 getPrevPlotCoordmap(width , height )
@@ -849,7 +892,6 @@ getPrevPlotCoordmap <- function(width, height) {
849892getGgplotCoordmap <- function (p , width , height , res ) {
850893 if (! inherits(p , " ggplot_build_gtable" ))
851894 return (NULL )
852-
853895 tryCatch({
854896 # Get info from built ggplot object
855897 panel_info <- find_panel_info(p $ build )
0 commit comments