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

527 lines
18KB

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