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

996 line
30KB

  1. #' A Plot Theme for ggplot2 by xaringanthemer
  2. #'
  3. #' @description
  4. #'
  5. #' **Lifecycle:** [Maturing](https://www.tidyverse.org/lifecycle/#maturing)
  6. #'
  7. #' Creates \pkg{ggplot2} themes to match the xaringanthemer theme used in the
  8. #' \pkg{xaringan} slides that seamlessly matches the "normal" slide colors and
  9. #' styles.
  10. #'
  11. #' @param text_color Color for text and foreground, inherits from `text_color`
  12. #' @param background_color Color for background, inherits from
  13. #' `background_color`
  14. #' @param accent_color Color for titles and accents, inherits from
  15. #' `header_color`
  16. #' @param accent_secondary_color Color for secondary accents, inherits from
  17. #' `text_bold_color`
  18. #' @param css_file Path to a \pkg{xaringanthemer} CSS file, from which the
  19. #' theme variables and values will be inferred. In general, if you use the
  20. #' \pkg{xaringathemer} defaults, you will not need to set this. This feature
  21. #' lets you create a \pkg{ggplot2} theme for your \pkg{xaringan} slides, even
  22. #' if you have only saved your theme CSS file and you aren't creating your
  23. #' CSS theme with \pkg{xaringanthemer} in your slides' source file.
  24. #' @inheritParams theme_xaringan_base
  25. #'
  26. #' @examples
  27. #' # Requires ggplot2
  28. #' has_ggplot2 <- requireNamespace("ggplot2", quietly = TRUE)
  29. #'
  30. #' if (has_ggplot2) {
  31. #' # Because this is an example, we'll save the CSS to a temp file
  32. #' path_to_css_file <- tempfile(fileext = ".css")
  33. #'
  34. #' # Create the xaringan theme: dark blue background with teal green accents
  35. #' style_duo(
  36. #' primary_color = "#002b36",
  37. #' secondary_color = "#31b09e",
  38. #' # Using basic fonts for this example, but the plot theme will
  39. #' # automatically use your theme font if you use Google fonts
  40. #' text_font_family = "sans",
  41. #' header_font_family = "serif",
  42. #' outfile = path_to_css_file
  43. #' )
  44. #'
  45. #' library(ggplot2)
  46. #' ggplot(iris) +
  47. #' aes(Petal.Length, Petal.Width) +
  48. #' geom_point() +
  49. #' ggtitle("Yet another Iris plot") +
  50. #' theme_xaringan()
  51. #' }
  52. #' @return A ggplot2 theme
  53. #' @family xaringanthemer ggplot2 themes
  54. #' @export
  55. theme_xaringan <- function(
  56. text_color = NULL,
  57. background_color = NULL,
  58. accent_color = NULL,
  59. accent_secondary_color = NULL,
  60. css_file = NULL,
  61. set_ggplot_defaults = TRUE,
  62. text_font = NULL,
  63. text_font_use_google = NULL,
  64. text_font_size = NULL,
  65. title_font = NULL,
  66. title_font_use_google = NULL,
  67. title_font_size = NULL,
  68. use_showtext = TRUE
  69. ) {
  70. requires_xaringanthemer_env(css_file = css_file, try_css = TRUE)
  71. requires_package(fn = "xaringan_theme")
  72. background_color <- background_color %||% xaringanthemer_env$background_color
  73. text_color <- text_color %||% xaringanthemer_env$text_color
  74. accent_color <- accent_color %||% xaringanthemer_env$header_color
  75. accent_secondary_color <- accent_secondary_color %||% xaringanthemer_env$text_bold_color %||% accent_color
  76. theme_xaringan_base(
  77. text_color,
  78. background_color,
  79. accent_color = accent_color,
  80. accent_secondary_color = accent_secondary_color,
  81. set_ggplot_defaults = set_ggplot_defaults,
  82. text_font = text_font,
  83. text_font_use_google = text_font_use_google,
  84. text_font_size = text_font_size,
  85. title_font = title_font,
  86. title_font_use_google = title_font_use_google,
  87. title_font_size = title_font_size,
  88. use_showtext = use_showtext
  89. )
  90. }
  91. #' An Inverse Plot Theme for ggplot2 by xaringanthemer
  92. #'
  93. #' @description
  94. #'
  95. #' **Lifecycle:** [Maturing](https://www.tidyverse.org/lifecycle/#maturing)
  96. #'
  97. #' A \pkg{ggplot2} xaringanthemer plot theme to seamlessly match the "inverse"
  98. #' \pkg{xaringan} slide colors and styles as styled by [xaringanthemer].
  99. #'
  100. #' @param text_color Color for text and foreground, inherits from `text_color`
  101. #' @param background_color Color for background, inherits from
  102. #' `background_color`
  103. #' @param accent_color Color for titles and accents, inherits from
  104. #' `header_color`
  105. #' @param accent_secondary_color Color for secondary accents, inherits from
  106. #' `text_bold_color`
  107. #' @inheritParams theme_xaringan
  108. #' @inheritParams theme_xaringan_base
  109. #'
  110. #' @examples
  111. #' # Requires ggplot2
  112. #' has_ggplot2 <- requireNamespace("ggplot2", quietly = TRUE)
  113. #'
  114. #' if (has_ggplot2) {
  115. #' # Because this is an example, we'll save the CSS to a temp file
  116. #' path_to_css_file <- tempfile(fileext = ".css")
  117. #'
  118. #' # Create the xaringan theme: dark blue background with teal green accents
  119. #' style_duo(
  120. #' primary_color = "#002b36",
  121. #' secondary_color = "#31b09e",
  122. #' # Using basic fonts for this example, but the plot theme will
  123. #' # automatically use your theme font if you use Google fonts
  124. #' text_font_family = "sans",
  125. #' header_font_family = "serif",
  126. #' outfile = path_to_css_file
  127. #' )
  128. #'
  129. #' library(ggplot2)
  130. #' ggplot(iris) +
  131. #' aes(Petal.Length, Petal.Width) +
  132. #' geom_point() +
  133. #' ggtitle("Yet another Iris plot") +
  134. #' # themed to match the inverse slides: teal background with dark blue text
  135. #' theme_xaringan_inverse()
  136. #' }
  137. #' @return A ggplot2 theme
  138. #' @family xaringanthemer ggplot2 themes
  139. #' @export
  140. theme_xaringan_inverse <- function(
  141. text_color = NULL,
  142. background_color = NULL,
  143. accent_color = NULL,
  144. accent_secondary_color = NULL,
  145. css_file = NULL,
  146. set_ggplot_defaults = TRUE,
  147. text_font = NULL,
  148. text_font_use_google = NULL,
  149. text_font_size = NULL,
  150. title_font = NULL,
  151. title_font_use_google = NULL,
  152. title_font_size = NULL,
  153. use_showtext = TRUE
  154. ) {
  155. requires_xaringanthemer_env(css_file = css_file, try_css = TRUE)
  156. requires_package(fn = "xaringan_theme")
  157. background_color <- background_color %||% xaringanthemer_env$inverse_background_color
  158. text_color <- text_color %||% xaringanthemer_env$inverse_text_color
  159. accent_color <- accent_color %||% xaringanthemer_env$inverse_header_color
  160. accent_secondary_color <- accent_secondary_color %||% accent_color
  161. theme_xaringan_base(
  162. text_color,
  163. background_color,
  164. accent_color = accent_color,
  165. accent_secondary_color = accent_secondary_color,
  166. set_ggplot_defaults = set_ggplot_defaults,
  167. text_font = text_font,
  168. text_font_use_google = text_font_use_google,
  169. text_font_size = text_font_size,
  170. title_font = title_font,
  171. title_font_use_google = title_font_use_google,
  172. title_font_size = title_font_size,
  173. use_showtext = use_showtext
  174. )
  175. }
  176. #' The ggplot2 xaringanthemer base plot theme
  177. #'
  178. #' @description
  179. #'
  180. #' **Lifecycle:** [Maturing](https://www.tidyverse.org/lifecycle/#maturing)
  181. #'
  182. #' Provides a base plot theme for \pkg{ggplot2} to match the \pkg{xaringan} slide theme
  183. #' created by [xaringanthemer]. The theme is designed to create a general plot
  184. #' style from two colors, a `background_color` and a `text_color` (or foreground
  185. #' color). Also accepts an `accent_color` and an `accent_secondary_color` that are
  186. #' [xaringanthemer] is not required for the base theme. Use
  187. #' [theme_xaringan()] or [theme_xaringan_inverse()] in xaringan slides styled by
  188. #' xaringanthemer for a plot theme that matches the slide style.
  189. #'
  190. #' @param text_color Color for text and foreground
  191. #' @param background_color Color for background
  192. #' @param accent_color Color for titles and accents, inherits from
  193. #' `header_color` or `text_color`. Used for the `title` base setting in
  194. #' [ggplot2::theme()], and additionally for setting the `color` or `fill` of
  195. #' \pkg{ggplot2} geom defaults.
  196. #' @param accent_secondary_color Color for secondary accents, inherits from
  197. #' `text_bold_color` or `accent_color`. Used only when setting \pkg{ggplot2} geom
  198. #' defaults.
  199. #' @param set_ggplot_defaults Should defaults be set for \pkg{ggplot2} _geoms_?
  200. #' Defaults to TRUE. To restore ggplot's defaults, or the previously set geom
  201. #' defaults, see [theme_xaringan_restore_defaults()].
  202. #' @param text_font Font to use for text elements, passed to
  203. #' [sysfonts::font_add_google()], if available and `text_font_use_google` is
  204. #' `TRUE`. Inherits from `text_font_family`.
  205. #' @param text_font_use_google Is `text_font` available on [Google
  206. #' Fonts](https://fonts.google.com)?
  207. #' @param text_font_size Base text font size, inherits from `text_font_size`, or
  208. #' defaults to 11.
  209. #' @param title_font Font to use for title elements, passed to
  210. #' [sysfonts::font_add_google()], if available and `title_font_use_google` is
  211. #' `TRUE`. Inherits from `title_font_family`.
  212. #' @param title_font_use_google Is `title_font` available on [Google
  213. #' Fonts](https://fonts.google.com)?
  214. #' @param title_font_size Base text font size, inherits from `title_font_size`,
  215. #' or defaults to 14.
  216. #' @param use_showtext If `TRUE` (default) the \pkg{showtext} package will be
  217. #' used to register Google fonts. Set to `FALSE` to disable this feature
  218. #' entirely, which may result in errors during plotting if the fonts used are
  219. #' not available locally.
  220. #' @param ... Ignored
  221. #'
  222. #' @examples
  223. #' # Requires ggplot2
  224. #' has_ggplot2 <- requireNamespace("ggplot2", quietly = TRUE)
  225. #'
  226. #' if (has_ggplot2) {
  227. #' library(ggplot2)
  228. #'
  229. #' plot1 <- ggplot(iris) +
  230. #' aes(Petal.Length, Petal.Width) +
  231. #' geom_point() +
  232. #' theme_xaringan_base(
  233. #' text_color = "#602f6b", # imperial
  234. #' background_color = "#f8f8f8", # light gray
  235. #' accent_color = "#317873", # myrtle green
  236. #' title_font = "sans",
  237. #' text_font = "serif",
  238. #' set_ggplot_defaults = TRUE
  239. #' ) +
  240. #' labs(
  241. #' title = "Basic Iris Plot",
  242. #' subtitle = "+ theme_xaringan_base()",
  243. #' caption = "xaringanthemer"
  244. #' )
  245. #'
  246. #' print(plot1)
  247. #'
  248. #' plot2 <- ggplot(iris) +
  249. #' aes(Sepal.Width) +
  250. #' geom_histogram(binwidth = 0.1) +
  251. #' theme_xaringan_base(
  252. #' text_color = "#a8a9c8", # light purple
  253. #' background_color = "#303163", # deep slate purple
  254. #' accent_color = "#ffff99", # canary yellow
  255. #' title_font = "sans",
  256. #' text_font = "serif",
  257. #' set_ggplot_defaults = TRUE
  258. #' ) +
  259. #' labs(
  260. #' title = "Basic Iris Plot",
  261. #' subtitle = "+ theme_xaringan_base()",
  262. #' caption = "xaringanthemer"
  263. #' )
  264. #'
  265. #' print(plot2)
  266. #' }
  267. #' @return A ggplot2 theme
  268. #' @family xaringanthemer ggplot2 themes
  269. #' @export
  270. theme_xaringan_base <- function(
  271. text_color,
  272. background_color,
  273. ...,
  274. set_ggplot_defaults = TRUE,
  275. accent_color = NULL,
  276. accent_secondary_color = NULL,
  277. text_font = NULL,
  278. text_font_use_google = NULL,
  279. text_font_size = NULL,
  280. title_font = NULL,
  281. title_font_use_google = NULL,
  282. title_font_size = NULL,
  283. use_showtext = TRUE
  284. ) {
  285. text_color <- full_length_hex(text_color)
  286. background_color <- full_length_hex(background_color)
  287. blend <- color_blender(text_color, background_color)
  288. text_font_size <- text_font_size %||% web_to_point(xaringanthemer_env$text_font_size, scale = 1.25) %||% 11
  289. title_font_size <- title_font_size %||% web_to_point(xaringanthemer_env$header_h3_font_size, scale = 0.8) %||% 14
  290. text_font <- if (!is.null(text_font)) {
  291. register_font(text_font, identical(text_font_use_google, TRUE) && use_showtext)
  292. } else {
  293. get_theme_font("text")
  294. }
  295. title_font <- if (!is.null(title_font)) {
  296. register_font(title_font, identical(title_font_use_google, TRUE) && use_showtext)
  297. } else {
  298. get_theme_font("header")
  299. }
  300. text_font %||% "sans"
  301. title_font %||% "sans"
  302. if (set_ggplot_defaults) {
  303. accent_color <- accent_color %||% xaringanthemer_env$header_color %||% text_color
  304. accent_secondary_color <- accent_secondary_color %||% xaringanthemer_env$text_bold_color %||% accent_color
  305. accent_color <- full_length_hex(accent_color)
  306. accent_secondary_color <- full_length_hex(accent_secondary_color)
  307. theme_xaringan_set_defaults(text_color, background_color, accent_color, accent_secondary_color)
  308. }
  309. ggplot2::theme(
  310. line = ggplot2::element_line(color = blend(0.2)),
  311. rect = ggplot2::element_rect(fill = background_color),
  312. text = ggplot2::element_text(
  313. color = blend(0.1),
  314. family = text_font,
  315. size = text_font_size
  316. ),
  317. title = ggplot2::element_text(
  318. color = accent_color,
  319. family = title_font,
  320. size = title_font_size
  321. ),
  322. plot.background = ggplot2::element_rect(
  323. fill = background_color,
  324. color = background_color
  325. ),
  326. panel.background = ggplot2::element_rect(
  327. fill = background_color,
  328. color = background_color
  329. ),
  330. panel.grid.major = ggplot2::element_line(
  331. color = blend(0.8),
  332. inherit.blank = TRUE
  333. ),
  334. panel.grid.minor = ggplot2::element_line(
  335. color = blend(0.9),
  336. inherit.blank = TRUE
  337. ),
  338. axis.title = ggplot2::element_text(size = title_font_size * 0.8),
  339. axis.ticks = ggplot2::element_line(color = blend(0.8)),
  340. axis.text = ggplot2::element_text(color = blend(0.4)),
  341. legend.key = ggplot2::element_rect(fill = "transparent", colour = NA),
  342. plot.caption = ggplot2::element_text(
  343. size = text_font_size * 0.8,
  344. color = blend(0.3)
  345. )
  346. )
  347. }
  348. #' Set and Restore ggplot2 geom Defaults
  349. #'
  350. #' @description
  351. #'
  352. #' **Lifecycle:** [Maturing](https://www.tidyverse.org/lifecycle/#maturing)
  353. #'
  354. #' Set \pkg{ggplot2} _geom_ defaults to match [theme_xaringan()] with
  355. #' `theme_xaringan_set_defaults()` and restore the standard or previously-set
  356. #' defaults with `theme_xaringan_restore_defaults()`. By default,
  357. #' `theme_xaringan_set_defaults()` is run with [theme_xaringan()] or
  358. #' [theme_xaringan_inverse()].
  359. #'
  360. #' @family xaringanthemer ggplot2 themes
  361. #' @inheritParams theme_xaringan
  362. #' @inheritParams theme_xaringan_base
  363. #' @return Invisibly returns a list of the current ggplot2 geom defaults
  364. #' @export
  365. theme_xaringan_set_defaults <- function(
  366. text_color = NULL,
  367. background_color = NULL,
  368. accent_color = text_color,
  369. accent_secondary_color = accent_color,
  370. text_font = NULL
  371. ) {
  372. requires_package("ggplot2")
  373. blend <- color_blender(text_color, background_color)
  374. xaringan_theme_defaults <- list(
  375. "line" = list(color = text_color),
  376. "vline" = list(color = accent_secondary_color),
  377. "hline" = list(color = accent_secondary_color),
  378. "abline" = list(color = accent_secondary_color),
  379. "segment" = list(color = text_color),
  380. "bar" = list(fill = accent_color),
  381. "col" = list(fill = accent_color),
  382. "boxplot" = list(color = text_color),
  383. "contour" = list(color = text_color),
  384. "density" = list(color = text_color,
  385. fill = text_color,
  386. alpha = 0.1),
  387. "dotplot" = list(color = accent_color),
  388. "errorbarh" = list(color = text_color),
  389. "crossbar" = list(color = text_color),
  390. "errorbar" = list(color = text_color),
  391. "linerange" = list(color = text_color),
  392. "pointrange" = list(color = text_color),
  393. "map" = list(color = text_color),
  394. "path" = list(color = text_color),
  395. "line" = list(color = text_color),
  396. "step" = list(color = text_color),
  397. "point" = list(color = accent_color),
  398. "polygon" = list(color = accent_color,
  399. fill = accent_color),
  400. "quantile" = list(color = text_color),
  401. "rug" = list(color = blend(0.5)),
  402. "segment" = list(color = text_color),
  403. "smooth" = list(fill = blend(0.75),
  404. color = accent_secondary_color),
  405. "spoke" = list(color = text_color),
  406. "label" = list(color = text_color,
  407. family= text_font %||% get_theme_font("text")),
  408. "text" = list(color = text_color,
  409. family= text_font %||% get_theme_font("text")),
  410. "rect" = list(fill = text_color),
  411. "tile" = list(fill = text_color),
  412. "violin" = list(fill = text_color),
  413. "sf" = list(color = text_color)
  414. )
  415. geom_names <- purrr::set_names(names(xaringan_theme_defaults))
  416. previous_defaults <- lapply(
  417. geom_names,
  418. function(geom) safely_set_geom(geom, xaringan_theme_defaults[[geom]])
  419. )
  420. if (is.null(xaringanthemer_env$old_ggplot_defaults)) {
  421. xaringanthemer_env$old_ggplot_defaults <- previous_defaults
  422. }
  423. invisible(previous_defaults)
  424. }
  425. #' @describeIn theme_xaringan_set_defaults Restore previous or standard
  426. #' \pkg{ggplot2} _geom_ defaults.
  427. #' @return Invisibly returns a list of the current ggplot2 geom defaults
  428. #' @export
  429. theme_xaringan_restore_defaults <- function() {
  430. requires_package("ggplot2")
  431. requires_xaringanthemer_env(try_css = FALSE, requires_theme = FALSE)
  432. if (is.null(xaringanthemer_env$old_ggplot_defaults)) {
  433. return(invisible())
  434. }
  435. old_default <- xaringanthemer_env$old_ggplot_defaults
  436. old_default_not_std <- vapply(old_default, function(x) length(x) > 0, logical(1))
  437. old_default <- old_default[old_default_not_std]
  438. restore_default <- utils::modifyList(xaringanthemer_env$std_ggplot_defaults, old_default)
  439. geom_names <- purrr::set_names(names(restore_default))
  440. previous_defaults <- lapply(
  441. geom_names,
  442. function(geom) safely_set_geom(geom, restore_default[[geom]])
  443. )
  444. invisible(previous_defaults)
  445. }
  446. safely_set_geom <- function(geom, new) {
  447. tryCatch(
  448. {
  449. ggplot2::update_geom_defaults(geom, new)
  450. },
  451. error = function(e) invisible(),
  452. warning = function(w) invisible()
  453. )
  454. }
  455. # Color Scales ------------------------------------------------------------
  456. #' Xaringan Themer ggplot2 Scales
  457. #'
  458. #' @description
  459. #'
  460. #' **Lifecycle:** [Maturing](https://www.tidyverse.org/lifecycle/#maturing)
  461. #'
  462. #' Color and fill single-color scales for discrete and continuous values,
  463. #' created using the primary accent color of the xaringanthemer styles.
  464. #'
  465. #' @param ... Arguments passed on to either the \pkg{colorspace} scale
  466. #' functions — one of [colorspace::scale_color_discrete_sequential],
  467. #' [colorspace::scale_color_continuous_sequential],
  468. #' [colorspace::scale_fill_discrete_sequential], or
  469. #' [colorspace::scale_fill_continuous_sequential] — or to
  470. #' [ggplot2::continuous_scale] or [ggplot2::discrete_scale].
  471. #' @param color A color value, in hex, to override the default color. Otherwise,
  472. #' the primary color of the resulting scale is chosen from the xaringanthemer
  473. #' slide styles.
  474. #' @param inverse If `color` is not supplied and `inverse = TRUE`, a primary
  475. #' color is chosen to work well with the inverse slide styles, namely the
  476. #' value of `inverse_header_color`
  477. #' @param direction Direction of the discrete scale. Use values less than 0 to
  478. #' reverse the direction, e.g. `direction = -1`.
  479. #' @inheritParams colorspace::scale_color_continuous_sequential
  480. #' @param aes_type The type of aesthetic to which the scale is being applied.
  481. #' One of "color", "colour", or "fill".
  482. #'
  483. #'
  484. #' @examples
  485. #' # Requires ggplot2
  486. #' has_ggplot2 <- requireNamespace("ggplot2", quietly = TRUE)
  487. #'
  488. #' if (has_ggplot2) {
  489. #' library(ggplot2)
  490. #' # Saving the theme to a temp file because this is an example
  491. #' path_to_css_file <- tempfile(fileext = ".css")
  492. #'
  493. #' # Create the xaringan theme: dark blue background with teal green accents
  494. #' style_duo(
  495. #' primary_color = "#002b36",
  496. #' secondary_color = "#31b09e",
  497. #' # Using basic fonts for this example, but the plot theme will
  498. #' # automatically use your theme font if you use Google fonts
  499. #' text_font_family = "sans",
  500. #' header_font_family = "serif",
  501. #' outfile = path_to_css_file
  502. #' )
  503. #'
  504. #' # Here's some very basic example data
  505. #' ex <- data.frame(
  506. #' name = c("Couple", "Few", "Lots", "Many"),
  507. #' n = c(2, 3, 5, 7)
  508. #' )
  509. #'
  510. #' # Fill color scales demo
  511. #' ggplot(ex) +
  512. #' aes(name, n, fill = n) +
  513. #' geom_col() +
  514. #' ggtitle("Matching fill scales") +
  515. #' # themed to match the slides: dark blue background with teal text
  516. #' theme_xaringan() +
  517. #' # Fill color matches teal text
  518. #' scale_xaringan_fill_continuous()
  519. #'
  520. #' # Color scales demo
  521. #' ggplot(ex) +
  522. #' aes(name, y = 1, color = name) +
  523. #' geom_point(size = 10) +
  524. #' ggtitle("Matching color scales") +
  525. #' # themed to match the slides: dark blue background with teal text
  526. #' theme_xaringan() +
  527. #' # Fill color matches teal text
  528. #' scale_xaringan_color_discrete(direction = -1)
  529. #' }
  530. #'
  531. #' @name scale_xaringan
  532. NULL
  533. #' @rdname scale_xaringan
  534. #' @export
  535. scale_xaringan_discrete <- function(
  536. aes_type = c("color", "colour", "fill"),
  537. ...,
  538. color = NULL,
  539. direction = 1,
  540. inverse = FALSE
  541. ) {
  542. requires_package("ggplot2", "scale_xaringan_discrete")
  543. aes_type <- match.arg(aes_type)
  544. color <- hex2HCL(get_theme_accent_color(color, inverse))
  545. pal <- function(n) {
  546. colors <- colorspace::sequential_hcl(
  547. n = n,
  548. c1 = color[1, "C"],
  549. l1 = color[1, "L"],
  550. h1 = color[1, "H"],
  551. rev = direction >= 1
  552. )
  553. }
  554. ggplot2::discrete_scale(aes_type, "manual", pal, ...)
  555. }
  556. #' @rdname scale_xaringan
  557. #' @export
  558. scale_xaringan_fill_discrete <- function(
  559. ...,
  560. color = NULL,
  561. direction = 1,
  562. inverse = FALSE
  563. ) {
  564. scale_xaringan_discrete(
  565. "fill",
  566. ...,
  567. color = color,
  568. direction = direction,
  569. inverse = inverse
  570. )
  571. }
  572. #' @rdname scale_xaringan
  573. #' @export
  574. scale_xaringan_color_discrete <- function(
  575. ...,
  576. color = NULL,
  577. direction = 1,
  578. inverse = FALSE
  579. ) {
  580. scale_xaringan_discrete(
  581. "color",
  582. ...,
  583. color = color,
  584. direction = direction,
  585. inverse = inverse
  586. )
  587. }
  588. #' @rdname scale_xaringan
  589. #' @export
  590. scale_xaringan_colour_discrete <- scale_xaringan_color_discrete
  591. #' @rdname scale_xaringan
  592. #' @export
  593. scale_xaringan_continuous <- function(
  594. aes_type = c("color", "colour", "fill"),
  595. ...,
  596. color = NULL,
  597. begin = 0,
  598. end = 1,
  599. inverse = FALSE
  600. ) {
  601. requires_package("ggplot2", "scale_xaringan_continuous")
  602. requires_package("scales", "scale_xaringan_continuous")
  603. aes_type <- match.arg(aes_type)
  604. color <- hex2HCL(get_theme_accent_color(color, inverse))
  605. colors <- colorspace::sequential_hcl(
  606. n = 12,
  607. c1 = color[1, "C"],
  608. l1 = color[1, "L"],
  609. h1 = color[1, "H"],
  610. rev = TRUE
  611. )
  612. rescaler <- function(x, ...) {
  613. scales::rescale(x, to = c(begin, end), from = range(x, na.rm = TRUE))
  614. }
  615. ggplot2::continuous_scale(
  616. aes_type,
  617. "continuous_sequential",
  618. palette = scales::gradient_n_pal(colors, values = NULL),
  619. rescaler = rescaler,
  620. oob = scales::censor,
  621. ...
  622. )
  623. }
  624. #' @rdname scale_xaringan
  625. #' @export
  626. scale_xaringan_fill_continuous <- function(
  627. ...,
  628. color = NULL,
  629. begin = 0,
  630. end = 1,
  631. inverse = FALSE
  632. ) {
  633. scale_xaringan_continuous(
  634. "fill",
  635. ...,
  636. color = color,
  637. begin = begin,
  638. end = end,
  639. inverse = inverse
  640. )
  641. }
  642. #' @rdname scale_xaringan
  643. #' @export
  644. scale_xaringan_color_continuous <- function(
  645. ...,
  646. color = NULL,
  647. begin = 0,
  648. end = 1,
  649. inverse = FALSE
  650. ) {
  651. scale_xaringan_continuous(
  652. "color",
  653. ...,
  654. color = color,
  655. begin = begin,
  656. end = end,
  657. inverse = inverse
  658. )
  659. }
  660. #' @rdname scale_xaringan
  661. #' @export
  662. scale_xaringan_colour_continuous <- scale_xaringan_color_continuous
  663. get_theme_accent_color <- function(color = NULL, inverse = FALSE) {
  664. color <-
  665. if (!inverse) {
  666. color %||%
  667. xaringanthemer_env[["header_color"]] %||%
  668. xaringanthemer_env[["text_color"]]
  669. } else {
  670. color %||% xaringanthemer_env[["inverse_header_color"]]
  671. }
  672. if (is.null(color)) {
  673. stop(
  674. call. = FALSE,
  675. "No color provided and no default available. ",
  676. "Have you forgotten to use a style function to set the xaringan theme?"
  677. )
  678. }
  679. color
  680. }
  681. blend_colors <- function(x, y, alpha = 0.5) {
  682. x <- colorspace::hex2RGB(x)
  683. y <- colorspace::hex2RGB(y)
  684. z <- colorspace::mixcolor(alpha, x, y)
  685. colorspace::hex(z)
  686. }
  687. color_blender <- function(x, y) function(alpha = 0.5) blend_colors(x, y, alpha)
  688. hex2HCL <- function(x) {
  689. colorspace::coords(methods::as(colorspace::hex2RGB(x), "polarLUV"))
  690. }
  691. # Fonts -------------------------------------------------------------------
  692. get_theme_font <- function(element = c("text", "header", "code"), use_showtext = TRUE) {
  693. element <- match.arg(element)
  694. element_family <- paste0(element, "_font_family")
  695. element_google <- paste0(element, "_font_google")
  696. element_is_google <- paste0(element, "_font_is_google")
  697. element_url <- paste0(element, "_font_url")
  698. family <- xaringanthemer_env[[element_family]]
  699. is_google_font <- xaringanthemer_env[[element_is_google]]
  700. if (is.null(is_google_font)) {
  701. is_google_font <- !is.null(xaringanthemer_env[[element_google]]) ||
  702. grepl("fonts.google", xaringanthemer_env[[element_url]], fixed = TRUE)
  703. }
  704. register_font(
  705. family,
  706. google = is_google_font,
  707. fn = sys.calls()[[max(1, sys.nframe() - 1)]][[1]],
  708. use_showtext = use_showtext
  709. )
  710. }
  711. register_font <- function(
  712. family,
  713. google = TRUE,
  714. fn = sys.calls()[[max(1, sys.nframe() - 1)]][[1]],
  715. ...,
  716. use_showtext = TRUE
  717. ) {
  718. if (is.null(family) || !use_showtext) {
  719. return(NULL)
  720. }
  721. family <- gsub("['\"]", "", family)
  722. if (!identical(xaringanthemer_env$showtext_auto, TRUE)) {
  723. if (!requires_package(pkg = "showtext", fn, required = FALSE)) {
  724. return(family)
  725. }
  726. showtext::showtext_auto()
  727. xaringanthemer_env$showtext_auto <- TRUE
  728. }
  729. if (family %in% xaringanthemer_env[["registered_font_families"]] %||% "") {
  730. return(family)
  731. }
  732. if (!requires_package(pkg = "sysfonts", fn, required = FALSE)) {
  733. return(family)
  734. } else if (family == "Droid Serif") {
  735. dstmp <- tempfile("droid-serif", fileext = "ttf")
  736. utils::download.file(
  737. "https://github.com/google/fonts/raw/feb15862e0c66ec0e7531ca4c3ef2607071ea700/apache/droidserif/DroidSerif-Regular.ttf",
  738. dstmp,
  739. quiet = TRUE
  740. )
  741. sysfonts::font_add(
  742. family = "Droid Serif",
  743. regular = dstmp
  744. )
  745. } else if (!family %in% sysfonts::font_families()) {
  746. is_default_font <- family %in% c(
  747. "Roboto",
  748. "Source Code Pro",
  749. "Yanone Kaffeesatz"
  750. )
  751. font_found <- family %in% sysfonts::font_families()
  752. is_google_font <- identical(google, TRUE) || (missing(google) && is_default_font)
  753. if (is_google_font) {
  754. tryCatch(
  755. {
  756. sysfonts::font_add_google(family, ...)
  757. font_found <- TRUE
  758. },
  759. error = function(e) {},
  760. warning = function(w) {}
  761. )
  762. }
  763. if (!font_found) { # warn user if font still not found
  764. msg <- if (is_google_font) glue::glue(
  765. "Font '{family}' not found in Google Fonts. ",
  766. "Please manually register the font using `sysfonts::font_add()`."
  767. ) else {
  768. glue::glue(
  769. "Font '{family}' must be manually registered using `sysfonts::font_add()`."
  770. )
  771. }
  772. warning(str_wrap(msg), call. = FALSE)
  773. } else {
  774. verify_fig_showtext(fn)
  775. }
  776. }
  777. xaringanthemer_env[["registered_font_families"]] <- c(
  778. xaringanthemer_env[["registered_font_families"]],
  779. family
  780. )
  781. family
  782. }
  783. verify_fig_showtext <- function(fn = "theme_xaringan_base") {
  784. if (is.null(knitr::current_input())) return()
  785. # Try to set fig.showtext automatically
  786. if (isTRUE(knitr::opts_current$get("fig.showtext"))) {
  787. return()
  788. }
  789. stop(str_wrap(
  790. "To use ", fn, "() with knitr, you need to set the chunk option ",
  791. "`fig.showtext = TRUE` for this chunk. Or you can set this option ",
  792. "globally with `knitr::opts_chunk$set(fig.showtext = TRUE)`."
  793. ))
  794. }
  795. requires_xaringanthemer_env <- function(
  796. css_file = NULL,
  797. try_css = TRUE,
  798. requires_theme = TRUE
  799. ) {
  800. reload <- !is.null(css_file) && isTRUE(try_css)
  801. pkg_env_exists <- exists("xaringanthemer_env")
  802. missing_theme <- requires_theme && pkg_env_exists && is.null(xaringanthemer_env$header_color)
  803. if (reload || !pkg_env_exists || missing_theme) {
  804. if (try_css) {
  805. css_vars <- read_css_vars(css_file)
  806. for (css_var in names(css_vars)) {
  807. xaringanthemer_env[[css_var]] <- css_vars[[css_var]]
  808. }
  809. return(requires_xaringanthemer_env(try_css = FALSE))
  810. } else {
  811. stop("Please call a xaringanthemer theme function first.")
  812. }
  813. }
  814. }
  815. #' Get the Value of xaringanthemer Style Setting
  816. #'
  817. #' A helper function to retrieve the value of style settings as set by a
  818. #' xaringanthemer style function, for use in plotting and other circumstances.
  819. #'
  820. #' @section Style Settings:
  821. #' Style settings used by xaringanthemer include:
  822. #'
  823. #' - `background_color`
  824. #' - `background_image`
  825. #' - `background_position`
  826. #' - `background_size`
  827. #' - `blockquote_left_border_color`
  828. #' - `code_font_family`
  829. #' - `code_font_family_fallback`
  830. #' - `code_font_google`
  831. #' - `code_font_is_google`
  832. #' - `code_font_size`
  833. #' - `code_font_url`
  834. #' - `code_highlight_color`
  835. #' - `code_inline_background_color`
  836. #' - `code_inline_color`
  837. #' - `code_inline_font_size`
  838. #' - `extra_css`
  839. #' - `extra_fonts`
  840. #' - `footnote_color`
  841. #' - `footnote_font_size`
  842. #' - `footnote_position_bottom`
  843. #' - `header_background_auto`
  844. #' - `header_background_color`
  845. #' - `header_background_content_padding_top`
  846. #' - `header_background_ignore_classes`
  847. #' - `header_background_padding`
  848. #' - `header_background_text_color`
  849. #' - `header_color`
  850. #' - `header_font_family`
  851. #' - `header_font_google`
  852. #' - `hedaer_font_is_google`
  853. #' - `header_font_url`
  854. #' - `header_font_weight`
  855. #' - `header_h1_font_size`
  856. #' - `header_h2_font_size`
  857. #' - `header_h3_font_size`
  858. #' - `inverse_background_color`
  859. #' - `inverse_header_color`
  860. #' - `inverse_text_color`
  861. #' - `inverse_text_shadow`
  862. #' - `left_column_selected_color`
  863. #' - `left_column_subtle_color`
  864. #' - `link_color`
  865. #' - `padding`
  866. #' - `table_border_color`
  867. #' - `table_row_border_color`
  868. #' - `table_row_even_background_color`
  869. #' - `text_bold_color`
  870. #' - `text_color`
  871. #' - `text_font_base`
  872. #' - `text_font_family`
  873. #' - `text_font_family_fallback`
  874. #' - `text_font_google`
  875. #' - `text_font_is_google`
  876. #' - `text_font_size`
  877. #' - `text_font_url`
  878. #' - `text_font_weight`
  879. #' - `text_slide_number_color`
  880. #' - `text_slide_number_font_size`
  881. #' - `title_slide_background_color`
  882. #' - `title_slide_background_image`
  883. #' - `title_slide_background_position`
  884. #' - `title_slide_background_size`
  885. #' - `title_slide_text_color`
  886. #'
  887. #' @param setting A xaringanthemer style setting
  888. #' @inheritParams theme_xaringan
  889. #' @export
  890. theme_xaringan_get_value <- function(setting, css_file = NULL) {
  891. requires_xaringanthemer_env(css_file = css_file)
  892. if (length(setting) > 1) {
  893. ret <- list()
  894. for (var in setting) {
  895. ret[[var]] <- xaringanthemer_env[[var]]
  896. }
  897. return(ret)
  898. }
  899. xaringanthemer_env[[setting]]
  900. }
  901. web_to_point <- function(x, px_per_em = NULL, scale = 0.75) {
  902. if (is.null(x)) {
  903. return(NULL)
  904. }
  905. px_per_em <- px_per_em %||% get_base_font_size()
  906. if (grepl("pt$", x)) {
  907. return(as.numeric(sub("pt$", "", x)))
  908. } else if (grepl("px$", x)) {
  909. x <- as.numeric(sub("px$", "", x))
  910. return(x * scale)
  911. } else if (grepl("r?em$", x)) {
  912. x <- as.numeric(sub("r?em$", "", x))
  913. return(x * px_per_em * scale)
  914. } else {
  915. return()
  916. }
  917. }
  918. get_base_font_size <- function() {
  919. base_size <- xaringanthemer_env[["base_font_size"]] %||%
  920. xaringanthemer_env[["text_font_size"]]
  921. if (!grepl("px", base_size)) {
  922. # assume 16px base font size
  923. 16
  924. } else {
  925. as.numeric(sub("px", "", base_size))
  926. }
  927. }