😎 Give your xaringan slides some style
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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. }