forked from ddsjoberg/gtsummary
-
Notifications
You must be signed in to change notification settings - Fork 0
/
set_gtsummary_theme.R
207 lines (184 loc) · 7.06 KB
/
set_gtsummary_theme.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#' Set a gtsummary theme
#'
#' @description
#' \lifecycle{maturing}
#' Functions to **set**, **reset**, **get**, and evaluate **with** gtsummary themes.
#'
#' - `set_gtsummary_theme()` set a theme
#' - `reset_gtsummary_theme()` reset themes
#' - `get_gtsummary_theme()` get a named list with all active theme elements
#' - `with_gtsummary_theme()` evaluate an expression with a theme temporarily set
#' - `check_gtsummary_theme()` checks if passed theme is valid
#'
#' @section Details:
#' The default formatting and styling throughout the gtsummary package are
#' taken from the published reporting guidelines of the top four urology
#' journals: European Urology, The Journal of Urology, Urology and
#' the British Journal of Urology International. Use this function to change
#' the default reporting style to match another journal, or your own
#' personal style.
#'
#' @param x A named list defining a gtsummary theme.
#' @param expr Expression to be evaluated with the theme specified in `x=` loaded
#' @param env The environment in which to evaluate `expr=`
#' @param msg_ignored_elements Default is NULL with no message printed. Pass a string
#' that will be printed with `cli::cli_alert_info()`. The `"{elements}"`
#' object contains vector of theme elements that will be overwritten and ignored.
#' @inheritParams add_global_p
#' @name set_gtsummary_theme
#' @export
#' @seealso [Themes vignette](https://www.danieldsjoberg.com/gtsummary/articles/themes.html)
#' @seealso Available [gtsummary themes][theme_gtsummary]
#' @examples
#' \donttest{
#' # Setting JAMA theme for gtsummary
#' set_gtsummary_theme(theme_gtsummary_journal("jama"))
#' # Themes can be combined by including more than one
#' set_gtsummary_theme(theme_gtsummary_compact())
#'
#' set_gtsummary_theme_ex1 <-
#' trial %>%
#' dplyr::select(age, grade, trt) %>%
#' tbl_summary(by = trt) %>%
#' add_stat_label() %>%
#' as_gt()
#'
#' # reset gtsummary theme
#' reset_gtsummary_theme()
#' }
#' @section Example Output:
#' \if{html}{Example}
#'
#' \if{html}{\out{
#' `r man_create_image_tag(file = "set_gtsummary_theme_ex1.png", width = "70")`
#' }}
NULL
# ------------------------------------------------------------------------------
#' @rdname set_gtsummary_theme
#' @export
set_gtsummary_theme <- function(x, quiet = NULL) {
# setting defaults -----------------------------------------------------------
quiet <- quiet %||% get_theme_element("pkgwide-lgl:quiet") %||% FALSE
# checking the input is a named list -----------------------------------------
if (rlang::is_empty(x)) {
return(invisible())
}
if (!inherits(x, "list") || is.null(names(x)) || "" %in% names(x)) {
stop("Argument `x=` must be named list.", call. = FALSE)
}
# check that all the names are proper names that set gtsummary attributes ----
if (!all(names(x) %in% df_theme_elements$name)) {
not_name <- names(x) %>% setdiff(df_theme_elements$name)
stop(paste(
"List elements", quoted_list(not_name), "are not accepted theme elements."
), call. = FALSE)
}
# print name of theme if present ---------------------------------------------
if (!is.null(x$`pkgwide-str:theme_name`) && !isTRUE(quiet)) {
rlang::inform(glue("Setting theme `{x$`pkgwide-str:theme_name`}`"))
}
# adding theme elements to environment ---------------------------------------
rlang::env_bind(.env = env_gtsummary_theme, !!!x)
}
# ------------------------------------------------------------------------------
#' @rdname set_gtsummary_theme
#' @export
reset_gtsummary_theme <- function() {
# deleting theme environment if it exists
rm(
list = ls(envir = env_gtsummary_theme),
envir = env_gtsummary_theme
)
invisible()
}
# ------------------------------------------------------------------------------
#' @rdname set_gtsummary_theme
#' @export
get_gtsummary_theme <- function() {
as.list(env_gtsummary_theme)
}
# ------------------------------------------------------------------------------
#' @rdname set_gtsummary_theme
#' @export
with_gtsummary_theme <- function(x, expr,
env = rlang::caller_env(),
msg_ignored_elements = NULL) {
# save current theme ---------------------------------------------------------
current_theme <- get_gtsummary_theme()
# on exit, restore theme -----------------------------------------------------
on.exit(reset_gtsummary_theme(), add = TRUE)
on.exit(set_gtsummary_theme(current_theme, quiet = TRUE), add = TRUE)
# message ignored theme elements ---------------------------------------------
.msg_ignored_elements(x, current_theme, msg_ignored_elements)
# add specified theme --------------------------------------------------------
set_gtsummary_theme(suppressMessages(x), quiet = TRUE)
# evaluate expression
rlang::eval_tidy({{ expr }}, env = env)
}
.msg_ignored_elements <- function(x, current_theme, msg) {
# if no message, dont print one!
if (is.null(msg)) {
return(invisible())
}
# save vector of theme element names that will be ignored
elements <-
x %>%
imap(
function(.x, .y) {
# if theme element not currently set, return NULL
if (is.null(current_theme[[.y]])) {
return(NULL)
}
# if theme element is already set and is different, return the name of the element
if (!identical(.x, current_theme[[.y]])) {
return(.y)
}
return(NULL)
}
) %>%
purrr::compact() %>%
unlist()
# print message of ignored elements
if (!rlang::is_empty(elements)) {
cli::cli_alert_info(msg)
}
return(invisible())
}
# ------------------------------------------------------------------------------
#' @rdname set_gtsummary_theme
#' @export
check_gtsummary_theme <- function(x) {
# themes must be named lists
if (!inherits(x, "list") || !rlang::is_named(x)) {
cli::cli_alert_danger("{.code x=} must be a named list.")
return(invisible())
}
# check all names are true theme elements
if (any(!names(x) %in% df_theme_elements$name)) {
paste(
"Theme element(s) {.val {setdiff(names(x), df_theme_elements$name)}}",
"are not valid."
) %>%
cli::cli_alert_danger()
return(invisible())
}
cli::cli_alert_success("Looks good!")
invisible()
}
# ------------------------------------------------------------------------------
# this function grabs a gtsummary theme element if it exists
# otherwise returns the default value
get_theme_element <- function(x, default = NULL, eval = TRUE) {
# checking input REMOVED THIS CHECK IN v1.6.0 AND ADDED check_gtsummary_theme() instead
# if (!x %in% df_theme_elements$name) {
# stop(glue("`x = '{x}'` is not a proper gtsummary theme element."), call. = FALSE)
# }
# returning theme element
# if eval is FALSE, then returning the un-evaluated theme element
if (eval == FALSE) {
return(env_gtsummary_theme[[x]] %||% default)
}
# the theme element is evaluated in the caller env so it may conditionally
# set a default depending on other objects only known at the time it is called
rlang::eval_tidy(env_gtsummary_theme[[x]], env = rlang::caller_env()) %||% default
}