😎 Give your xaringan slides some style
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

474 lines
17KB

  1. #' A Plot Theme for ggplot2 by xaringanthemer
  2. #'
  3. #' Creates [ggplot2] themes to match the xaringanthemer theme used in the
  4. #' [xaringan] slides that seamlessly matches the "normal" slide colors and
  5. #' styles.
  6. #'
  7. #' @param text_color Color for text and foreground, inherits from `text_color`
  8. #' @param background_color Color for background, inherits from
  9. #' `background_color`
  10. #' @param accent_color Color for titles and accents, inherits from
  11. #' `header_color`
  12. #' @param accent_secondary_color Color for secondary accents, inherits from
  13. #' `text_bold_color`
  14. #' @inheritDotParams theme_xaringan_base
  15. #'
  16. #' @examples
  17. #' if (requireNamespace("ggplot2", quietly = TRUE)) {
  18. #' # Set xaringanthemer theme but save to tempfile
  19. #' duo_accent(outfile = tempfile())
  20. #'
  21. #' library(ggplot2)
  22. #' ggplot(iris) +
  23. #' aes(Petal.Length, Petal.Width) +
  24. #' geom_point() +
  25. #' theme_xaringan()
  26. #' }
  27. #'
  28. #' @family xaringanthemer ggplot2 themes
  29. #' @export
  30. theme_xaringan <- function(
  31. text_color = NULL,
  32. background_color = NULL,
  33. accent_color = NULL,
  34. accent_secondary_color = NULL,
  35. ...
  36. ) {
  37. requires_xaringanthemer_env()
  38. requires_package(fn = "xaringan_theme")
  39. background_color <- background_color %||% xaringanthemer_env$background_color
  40. text_color <- text_color %||% xaringanthemer_env$text_color
  41. accent_color <- accent_color %||% xaringanthemer_env$header_color
  42. accent_secondary_color <- accent_secondary_color %||% xaringanthemer_env$text_bold_color %||% accent_color
  43. theme_xaringan_base(text_color, background_color,
  44. accent_color = accent_color,
  45. accent_secondary_color = accent_secondary_color,
  46. ...)
  47. }
  48. #' An Inverse Plot Theme for ggplot2 by xaringanthemer
  49. #'
  50. #' A [ggplot2] xaringanthemer plot theme to seamlessly match the "inverse"
  51. #' [xaringan] slide colors and styles as styled by [xaringanthemer].
  52. #'
  53. #' @param text_color Color for text and foreground, inherits from `text_color`
  54. #' @param background_color Color for background, inherits from
  55. #' `background_color`
  56. #' @param accent_color Color for titles and accents, inherits from
  57. #' `header_color`
  58. #' @param accent_secondary_color Color for secondary accents, inherits from
  59. #' `text_bold_color`
  60. #' @inheritDotParams theme_xaringan_base
  61. #'
  62. #' @examples
  63. #' if (requireNamespace("ggplot2", quietly = TRUE)) {
  64. #' # Set xaringanthemer theme but save to tempfile
  65. #' duo_accent(outfile = tempfile())
  66. #'
  67. #' library(ggplot2)
  68. #' ggplot(iris) +
  69. #' aes(Petal.Length, Petal.Width) +
  70. #' geom_point() +
  71. #' theme_xaringan()
  72. #' }
  73. #'
  74. #' @family xaringanthemer ggplot2 themes
  75. #' @export
  76. theme_xaringan_inverse <- function(
  77. text_color = NULL,
  78. background_color = NULL,
  79. accent_color = NULL,
  80. accent_secondary_color = NULL,
  81. ...
  82. ) {
  83. requires_xaringanthemer_env()
  84. requires_package(fn = "xaringan_theme")
  85. background_color <- background_color %||% xaringanthemer_env$inverse_background_color
  86. text_color <- text_color %||% xaringanthemer_env$inverse_text_color
  87. accent_color <- accent_color %||% xaringanthemer_env$inverse_header_color
  88. accent_secondary_color <- accent_secondary_color %||% accent_color
  89. theme_xaringan_base(text_color, background_color,
  90. accent_color = accent_color,
  91. accent_secondary_color = accent_secondary_color,
  92. ...)
  93. }
  94. #' The ggplot2 xaringanthemer base plot theme
  95. #'
  96. #' Provides a base plot theme for [ggplot2] to match the [xaringan] slide theme
  97. #' created by [xaringanthemer]. The theme is designed to create a general plot
  98. #' style from two colors, a `background_color` and a `text_color` (or foreground
  99. #' color). Also accepts an `accent_color` and an `accent_secondary_color` that are
  100. #' [xaringanthemer] is not required for the base theme. Use
  101. #' [theme_xaringan()] or [theme_xaringan_inverse()] in xaringan slides styled by
  102. #' xaringanthemer for a plot theme that matches the slide style.
  103. #'
  104. #' @param text_color Color for text and foreground
  105. #' @param background_color Color for background
  106. #' @param accent_color Color for titles and accents, inherits from
  107. #' `header_color` or `text_color`. Used for the `title` base setting in
  108. #' [ggplot2::theme()], and additionally for setting the `color` or `fill` of
  109. #' [ggplot2] geom defaults.
  110. #' @param accent_secondary_color Color for secondary accents, inherits from
  111. #' `text_bold_color` or `accent_color`. Used only when setting [ggplot2] geom
  112. #' defaults.
  113. #' @param set_ggplot_defaults Should defaults be set for [ggplot2] _geoms_?
  114. #' Defaults to TRUE. To restore ggplot's defaults, or the previously set geom
  115. #' defaults, see [theme_xaringan_restore_defaults()].
  116. #' @param text_font Font to use for text elements, passed to
  117. #' [sysfonts::font_add_google()], if available and `text_font_use_google` is
  118. #' `TRUE`. Inherits from `text_font_family`.
  119. #' @param text_font_use_google Is `text_font` available on [Google
  120. #' Fonts](https://fonts.google.com)?
  121. #' @param text_font_size Base text font size, inherits from `text_font_size`, or
  122. #' defaults to 11.
  123. #' @param title_font Font to use for title elements, passed to
  124. #' [sysfonts::font_add_google()], if available and `title_font_use_google` is
  125. #' `TRUE`. Inherits from `title_font_family`.
  126. #' @param title_font_use_google Is `title_font` available on [Google
  127. #' Fonts](https://fonts.google.com)?
  128. #' @param title_font_size Base text font size, inherits from `title_font_size`,
  129. #' or defaults to 14.
  130. #' @param ... Ignored
  131. #'
  132. #' @examples
  133. #' if (requireNamespace("ggplot2", quietly = TRUE)) {
  134. #' library(ggplot2)
  135. #' ggplot(iris) +
  136. #' aes(Petal.Length, Petal.Width) +
  137. #' geom_point() +
  138. #' theme_xaringan_base(
  139. #' text_color = "#e1e5f2",
  140. #' background_color = "#021c25",
  141. #' accent_color = "#1f7a8c",
  142. #' set_ggplot_defaults = TRUE) +
  143. #' labs(title = "Basic Iris Plot",
  144. #' subtitle = "+ theme_xaringan_base()",
  145. #' caption = "{xaringanthemer}")
  146. #'
  147. #' ggplot(iris) +
  148. #' aes(Petal.Length, Petal.Width) +
  149. #' geom_point() +
  150. #' theme_xaringan_base(
  151. #' text_color = "#021c25",
  152. #' background_color = "#e1e5f2",
  153. #' accent_color = "#1f7a8c",
  154. #' set_ggplot_defaults = TRUE) +
  155. #' labs(title = "Basic Iris Plot",
  156. #' subtitle = "+ theme_xaringan_base()",
  157. #' caption = "{xaringanthemer}")
  158. #' }
  159. #'
  160. #' @family xaringanthemer ggplot2 themes
  161. #' @export
  162. theme_xaringan_base <- function(
  163. text_color,
  164. background_color,
  165. ...,
  166. set_ggplot_defaults = TRUE,
  167. accent_color = NULL,
  168. accent_secondary_color = NULL,
  169. text_font = NULL,
  170. text_font_use_google = NULL,
  171. text_font_size = NULL,
  172. title_font = NULL,
  173. title_font_use_google = NULL,
  174. title_font_size = NULL
  175. ) {
  176. blend <- color_blender(text_color, background_color)
  177. text_font_size <- text_font_size %||% web_to_point(xaringanthemer_env$text_font_size, scale = 1.25) %||% 11
  178. title_font_size <- title_font_size %||% web_to_point(xaringanthemer_env$header_h3_font_size, scale = 0.8) %||% 14
  179. text_font <- if (!is.null(text_font)) {
  180. register_font(text_font, identical(text_font_use_google, TRUE))
  181. } else get_theme_font("text")
  182. title_font <- if (!is.null(title_font)) {
  183. register_font(title_font, identical(title_font_use_google, TRUE))
  184. } else get_theme_font("header")
  185. text_font %||% "sans"
  186. title_font %||% "sans"
  187. if (set_ggplot_defaults) {
  188. accent_color <- accent_color %||% xaringanthemer_env$header_color %||% text_color
  189. accent_secondary_color <- accent_secondary_color %||% xaringanthemer_env$text_bold_color %||% accent_color
  190. theme_xaringan_set_defaults(text_color, background_color, accent_color, accent_secondary_color)
  191. }
  192. ggplot2::theme(
  193. line = ggplot2::element_line(color = blend(0.2)),
  194. rect = ggplot2::element_rect(fill = background_color),
  195. text = ggplot2::element_text(
  196. color = blend(0.1),
  197. family = text_font,
  198. size = text_font_size),
  199. title = ggplot2::element_text(
  200. color = accent_color,
  201. family = title_font,
  202. size = title_font_size),
  203. plot.background = ggplot2::element_rect(
  204. fill = background_color,
  205. color = background_color),
  206. panel.background = ggplot2::element_rect(
  207. fill = background_color,
  208. color = background_color),
  209. panel.grid.major = ggplot2::element_line(
  210. color = blend(0.8),
  211. inherit.blank = TRUE),
  212. panel.grid.minor = ggplot2::element_line(
  213. color = blend(0.9),
  214. inherit.blank = TRUE),
  215. axis.title = ggplot2::element_text(size = title_font_size * 0.8),
  216. axis.ticks = ggplot2::element_line(color = blend(0.8)),
  217. axis.text = ggplot2::element_text(color = blend(0.4)),
  218. plot.caption = ggplot2::element_text(
  219. size = text_font_size * 0.8,
  220. color = blend(0.3))
  221. )
  222. }
  223. #' Set and Restore ggplot2 geom Defaults
  224. #'
  225. #' Set [ggplot2] _geom_ defaults to match [theme_xaringan()] with
  226. #' `theme_xaringan_set_defaults()` and restore the standard or previously-set
  227. #' defaults with `theme_xaringan_restore_defaults()`. By default,
  228. #' `theme_xaringan_set_defaults()` is run with [theme_xaringan()] or
  229. #' [theme_xaringan_inverse()].
  230. #'
  231. #' @family xaringanthemer ggplot2 themes
  232. #' @inheritParams theme_xaringan
  233. #' @export
  234. theme_xaringan_set_defaults <- function(
  235. text_color = NULL,
  236. background_color = NULL,
  237. accent_color = text_color,
  238. accent_secondary_color = accent_color,
  239. text_family = NULL
  240. ) {
  241. requires_package("ggplot2")
  242. blend <- color_blender(text_color, background_color)
  243. xaringan_theme_defaults <- list(
  244. "line" = list(color = text_color),
  245. "vline" = list(color = accent_secondary_color),
  246. "hline" = list(color = accent_secondary_color),
  247. "abline" = list(color = accent_secondary_color),
  248. "segment" = list(color = text_color),
  249. "bar" = list(fill = accent_color),
  250. "col" = list(fill = accent_color),
  251. "boxplot" = list(color = text_color),
  252. "contour" = list(color = text_color),
  253. "density" = list(color = text_color,
  254. fill = text_color,
  255. alpha = 0.1),
  256. "dotplot" = list(color = accent_color),
  257. "errorbarh" = list(color = text_color),
  258. "crossbar" = list(color = text_color),
  259. "errorbar" = list(color = text_color),
  260. "linerange" = list(color = text_color),
  261. "pointrange" = list(color = text_color),
  262. "map" = list(color = text_color),
  263. "path" = list(color = text_color),
  264. "line" = list(color = text_color),
  265. "step" = list(color = text_color),
  266. "point" = list(color = accent_color),
  267. "polygon" = list(color = accent_color,
  268. fill = accent_color),
  269. "quantile" = list(color = text_color),
  270. "rug" = list(color = blend(0.5)),
  271. "segment" = list(color = text_color),
  272. "smooth" = list(fill = blend(0.75),
  273. color = accent_secondary_color),
  274. "spoke" = list(color = text_color),
  275. "label" = list(color = text_color,
  276. family = text_family %||% get_theme_font("text")),
  277. "text" = list(color = text_color,
  278. family = text_family %||% get_theme_font("text")),
  279. "rect" = list(fill = text_color),
  280. "tile" = list(fill = text_color),
  281. "violin" = list(fill = text_color),
  282. "sf" = list(color = text_color)
  283. )
  284. geom_names <- setNames(nm = names(xaringan_theme_defaults))
  285. previous_defaults <- lapply(
  286. geom_names,
  287. function(geom) safely_set_geom(geom, xaringan_theme_defaults[[geom]])
  288. )
  289. if (is.null(xaringanthemer_env$old_ggplot_defaults)) {
  290. xaringanthemer_env$old_ggplot_defaults <- previous_defaults
  291. }
  292. invisible(previous_defaults)
  293. }
  294. #' @describeIn theme_xaringan_set_defaults Restore previous or standard [ggplot2] _geom_ defaults.
  295. #' @inheritParams theme_xaringan
  296. #' @export
  297. xaringan_theme_restore_defaults <- function() {
  298. requires_package("ggplot2")
  299. requires_xaringanthemer_env()
  300. if (is.null(xaringanthemer_env$old_ggplot_defaults)) return(invisible())
  301. old_default <- xaringanthemer_env$old_ggplot_defaults
  302. old_default_not_std <- vapply(old_default, function(x) length(x) > 0, logical(1))
  303. old_default <- old_default[old_default_not_std]
  304. restore_default <- utils::modifyList(xaringanthemer_env$std_ggplot_defaults, old_default)
  305. geom_names <- setNames(nm = names(restore_default))
  306. previous_defaults <- lapply(
  307. geom_names,
  308. function(geom) safely_set_geom(geom, restore_default[[geom]])
  309. )
  310. invisible(previous_defaults)
  311. }
  312. safely_set_geom <- function(geom, new) {
  313. tryCatch({
  314. ggplot2::update_geom_defaults(geom, new)
  315. },
  316. error = function(e) invisible(),
  317. warning = function(w) invisible())
  318. }
  319. blend_colors <- function(x, y, alpha = 0.5) {
  320. x <- colorspace::hex2RGB(x)
  321. y <- colorspace::hex2RGB(y)
  322. z <- colorspace::mixcolor(alpha, x, y)
  323. colorspace::hex(z)
  324. }
  325. color_blender <- function(x, y) function(alpha = 0.5) blend_colors(x, y, alpha)
  326. get_theme_font <- function(element = c("text", "header", "code")) {
  327. element <- match.arg(element)
  328. element_family <- paste0(element, "_font_family")
  329. element_google <- paste0(element, "_font_google")
  330. element_url <- paste0(element, "_font_url")
  331. family <- xaringanthemer_env[[element_family]]
  332. is_google_font <- !is.null(xaringanthemer_env[[element_google]]) ||
  333. grepl("fonts.google", xaringanthemer_env[[element_url]], fixed = TRUE)
  334. register_font(family, google = is_google_font, fn = sys.calls()[[max(1, length(sys.calls()) - 1)]])
  335. }
  336. register_font <- function(
  337. family,
  338. google = TRUE,
  339. fn = sys.calls()[[max(1, length(sys.calls()) - 1)]][[1]],
  340. ...
  341. ) {
  342. if (is.null(family)) return(NULL)
  343. family <- gsub("['\"]", "", family)
  344. if (!identical(xaringanthemer_env$showtext_auto, TRUE)) {
  345. if (requires_package(pkg = "showtext", fn, required = FALSE)) {
  346. showtext::showtext_auto()
  347. } else return(family)
  348. xaringanthemer_env$showtext_auto <- TRUE
  349. }
  350. if (!requires_package(pkg = "sysfonts", fn, required = FALSE)) {
  351. return(family)
  352. } else if (!family %in% sysfonts::font_families()) {
  353. is_default_font <- family %in% c(
  354. "Roboto", "Source Code Pro", "Yanone Kaffeesatz"
  355. )
  356. if (identical(google, TRUE) || is_default_font) {
  357. sysfonts::font_add_google(family, ...)
  358. } else {
  359. warning(paste(
  360. "Please manually register fonts not served by Google Fonts.",
  361. "See `sysfonts::font_add()` for more information."))
  362. }
  363. }
  364. family
  365. }
  366. requires_package <- function(pkg = "ggplot2", fn = "", required = TRUE) {
  367. raise <- if (required) stop else warning
  368. if (!requireNamespace(pkg, quietly = TRUE)) {
  369. msg <- paste0(
  370. "`", pkg, "` is ",
  371. if (required) "required " else "suggested ",
  372. if (fn != "") paste0("by ", fn, "() ")[1],
  373. "but is not installed."
  374. )
  375. raise(msg, call. = FALSE)
  376. invisible(FALSE)
  377. }
  378. invisible(TRUE)
  379. }
  380. requires_xaringanthemer_env <- function() {
  381. if (!exists("xaringanthemer_env") || is.null(xaringanthemer_env$header_color)) {
  382. stop("Please call a xaringanthemer theme function first.")
  383. }
  384. }
  385. #' Get the Value of xaringanthemer Style Setting
  386. #'
  387. #' A helper function to retrieve the value of style settings as set by a
  388. #' xaringanthemer style function, for use in plotting and other circumstances.
  389. #'
  390. #' @param setting A xaringanthemer style setting
  391. #' @export
  392. get_xaringanthemer_value <- function(
  393. setting = c(
  394. "header_background_content_padding_top", "table_row_border_color",
  395. "text_bold_color", "code_highlight_color", "footnote_color",
  396. "text_slide_number_color", "table_row_even_background_color",
  397. "title_slide_text_color", "background_color", "extra_fonts",
  398. "header_background_ignore_classes", "header_font_weight", "title_slide_background_image",
  399. "background_size", "header_h2_font_size", "code_inline_font_size",
  400. "text_font_google", "header_h1_font_size", "header_background_padding",
  401. "header_font_family", "code_font_url", "text_font_url", "footnote_position_bottom",
  402. "title_slide_background_position", "code_inline_color", "link_color",
  403. "left_column_selected_color", "header_background_text_color",
  404. "inverse_text_color", "text_color", "code_inline_background_color",
  405. "extra_css", "outfile", "footnote_font_size", "header_h3_font_size",
  406. "text_font_base", "code_font_google", "code_font_size", "title_slide_background_size",
  407. "text_font_size", "padding", "text_font_family", "code_font_family",
  408. "text_font_family_fallback", "blockquote_left_border_color",
  409. "left_column_subtle_color", "table_border_color", "inverse_background_color",
  410. "header_color", "inverse_header_color", "title_slide_background_color",
  411. "header_background_color", "text_font_weight",
  412. "background_image", "header_font_google", "text_slide_number_font_size",
  413. "inverse_text_shadow", "code_font_family_fallback", "header_font_url",
  414. "background_position", "header_background_auto")
  415. ) {
  416. requires_xaringanthemer_env()
  417. setting <- match.arg(setting)
  418. xaringanthemer_env[[setting]]
  419. }
  420. web_to_point <- function(x, px_per_em = 16, scale = 1) {
  421. if (is.null(x)) return(NULL)
  422. if (grepl("pt$", x)) {
  423. return(as.numeric(sub("pt$", "", x)))
  424. } else if (grepl("px$", x)) {
  425. x <- as.numeric(sub("px$", "", x))
  426. return(x * 0.75)
  427. } else if (grepl("em$", x)) {
  428. x <- as.numeric(sub("em$", "", x))
  429. return(x * px_per_em * 0.75)
  430. } else {
  431. return()
  432. }
  433. }