################################################################################
## function to save app state on refresh or crash
################################################################################
## drop NULLs in list
toList <- function(x) reactiveValuesToList(x) %>% .[!sapply(., is.null)]
## from https://gist.github.com/hadley/5434786
env2list <- function(x) mget(ls(x), x)
is_active <- function(env = r_data) {
sapply(ls(envir = env), function(x) bindingIsActive(as.symbol(x), env = env))
}
## remove non-active bindings
rem_non_active <- function(env = r_data) {
iact <- is_active(env = r_data)
rm(list = names(iact)[!iact], envir = env)
}
active2list <- function(env = r_data) {
iact <- is_active(env = r_data) %>% (function(x) names(x)[x])
if (length(iact) > 0) {
mget(iact, env)
} else {
list()
}
}
## deal with https://github.com/rstudio/shiny/issues/2065
MRB <- function(x, env = parent.frame(), init = FALSE) {
if (exists(x, envir = env)) {
## if the object exists and has a binding, don't do anything
if (!bindingIsActive(as.symbol(x), env = env)) {
shiny::makeReactiveBinding(x, env = env)
}
} else if (init) {
## initialize a binding (and value) if object doesn't exist yet
shiny::makeReactiveBinding(x, env = env)
}
}
saveSession <- function(session = session, timestamp = FALSE, path = "~/.radiant.sessions") {
if (!exists("r_sessions")) {
return()
}
if (!dir.exists(path)) dir.create(path)
isolate({
LiveInputs <- toList(input)
r_state[names(LiveInputs)] <- LiveInputs
## removing the non-active bindings
rem_non_active()
r_data <- env2list(r_data)
r_info <- toList(r_info)
r_sessions[[r_ssuid]] <- list(
r_data = r_data,
r_info = r_info,
r_state = r_state,
timestamp = Sys.time()
)
## saving session information to state file
if (timestamp) {
fn <- paste0(normalizePath(path), "/r_", r_ssuid, "-", gsub("( |:)", "-", Sys.time()), ".state.rda")
} else {
fn <- paste0(normalizePath(path), "/r_", r_ssuid, ".state.rda")
}
save(r_data, r_info, r_state, file = fn)
})
}
observeEvent(input$refresh_radiant, {
if (isTRUE(getOption("radiant.local"))) {
fn <- normalizePath("~/.radiant.sessions")
file.remove(list.files(fn, full.names = TRUE))
} else {
fn <- paste0(normalizePath("~/.radiant.sessions"), "/r_", r_ssuid, ".state.rda")
if (file.exists(fn)) unlink(fn, force = TRUE)
}
try(r_ssuid <- NULL, silent = TRUE)
})
saveStateOnRefresh <- function(session = session) {
session$onSessionEnded(function() {
isolate({
url_query <- parseQueryString(session$clientData$url_search)
if (not_pressed(input$refresh_radiant) &&
not_pressed(input$stop_radiant) &&
not_pressed(input$state_load) &&
not_pressed(input$state_upload) &&
!"fixed" %in% names(url_query)) {
saveSession(session)
} else {
if (not_pressed(input$state_load) && not_pressed(input$state_upload)) {
if (exists("r_sessions")) {
sshhr(try(r_sessions[[r_ssuid]] <- NULL, silent = TRUE))
sshhr(try(rm(r_ssuid), silent = TRUE))
}
}
}
})
})
}
################################################################
## functions used across tools in radiant
################################################################
## get active dataset and apply data-filter if available
.get_data <- reactive({
req(input$dataset)
filter_cmd <- input$data_filter %>%
gsub("\\n", "", .) %>%
gsub("\"", "\'", .) %>%
fix_smart()
arrange_cmd <- input$data_arrange
if (!is.empty(arrange_cmd)) {
arrange_cmd <- arrange_cmd %>%
strsplit(., split = "(&|,|\\s+)") %>%
unlist() %>%
.[!. == ""] %>%
paste0(collapse = ", ") %>%
(function(x) glue("arrange(x, {x})"))
}
slice_cmd <- input$data_rows
if ((is.empty(filter_cmd) && is.empty(arrange_cmd) && is.empty(slice_cmd)) || input$show_filter == FALSE) {
isolate(r_info[["filter_error"]] <- "")
} else if (grepl("([^=!<>])=([^=])", filter_cmd)) {
isolate(r_info[["filter_error"]] <- "Invalid filter: Never use = in a filter! Use == instead (e.g., city == 'San Diego'). Update or remove the expression")
} else {
## %>% needed here so . will be available
seldat <- try(
r_data[[input$dataset]] %>%
(function(x) if (!is.empty(filter_cmd)) x %>% filter(!!rlang::parse_expr(filter_cmd)) else x) %>%
(function(x) if (!is.empty(arrange_cmd)) eval(parse(text = arrange_cmd)) else x) %>%
(function(x) if (!is.empty(slice_cmd)) x %>% slice(!!rlang::parse_expr(slice_cmd)) else x),
silent = TRUE
)
if (inherits(seldat, "try-error")) {
isolate(r_info[["filter_error"]] <- paste0("Invalid input: \"", attr(seldat, "condition")$message, "\". Update or remove the expression(x)"))
} else {
isolate(r_info[["filter_error"]] <- "")
if ("grouped_df" %in% class(seldat)) {
return(droplevels(ungroup(seldat)))
} else {
return(droplevels(seldat))
}
}
}
if ("grouped_df" %in% class(r_data[[input$dataset]])) {
ungroup(r_data[[input$dataset]])
} else {
r_data[[input$dataset]]
}
})
## using a regular function to avoid a full data copy
.get_data_transform <- function(dataset = input$dataset) {
if (is.null(dataset)) {
return()
}
if ("grouped_df" %in% class(r_data[[dataset]])) {
ungroup(r_data[[dataset]])
} else {
r_data[[dataset]]
}
}
.get_class <- reactive({
get_class(.get_data())
})
groupable_vars <- reactive({
.get_data() %>%
summarise_all(
list(
~ is.factor(.) || is.logical(.) || lubridate::is.Date(.) ||
is.integer(.) || is.character(.) ||
((length(unique(.)) / n()) < 0.30)
)
) %>%
(function(x) which(x == TRUE)) %>%
varnames()[.]
})
groupable_vars_nonum <- reactive({
.get_data() %>%
summarise_all(
list(
~ is.factor(.) || is.logical(.) ||
lubridate::is.Date(.) || is.integer(.) ||
is.character(.)
)
) %>%
(function(x) which(x == TRUE)) %>%
varnames()[.]
})
## used in compare proportions, logistic, etc.
two_level_vars <- reactive({
two_levs <- function(x) {
if (is.factor(x)) {
length(levels(x))
} else {
length(unique(na.omit(x)))
}
}
.get_data() %>%
summarise_all(two_levs) %>%
(function(x) x == 2) %>%
which(.) %>%
varnames()[.]
})
## used in visualize - don't plot Y-variables that don't vary
varying_vars <- reactive({
.get_data() %>%
summarise_all(does_vary) %>%
as.logical() %>%
which() %>%
varnames()[.]
})
## getting variable names in active dataset and their class
varnames <- reactive({
var_class <- .get_class()
req(var_class)
names(var_class) %>%
set_names(., paste0(., " {", var_class, "}"))
})
## cleaning up the arguments for data_filter and defaults passed to report
clean_args <- function(rep_args, rep_default = list()) {
if (!is.null(rep_args$data_filter)) {
if (rep_args$data_filter == "") {
rep_args$data_filter <- NULL
} else {
rep_args$data_filter %<>% gsub("\\n", "", .) %>% gsub("\"", "\'", .)
}
}
if (is.empty(rep_args$rows)) {
rep_args$rows <- NULL
}
if (is.empty(rep_args$arr)) {
rep_args$arr <- NULL
}
if (length(rep_default) == 0) rep_default[names(rep_args)] <- ""
## removing default arguments before sending to report feature
for (i in names(rep_args)) {
if (!is.language(rep_args[[i]]) && !is.call(rep_args[[i]]) && all(is.na(rep_args[[i]]))) {
rep_args[[i]] <- NULL
next
}
if (!is.symbol(rep_default[[i]]) && !is.call(rep_default[[i]]) && all(is_not(rep_default[[i]]))) next
if (length(rep_args[[i]]) == length(rep_default[[i]]) && !is.name(rep_default[[i]]) && all(rep_args[[i]] == rep_default[[i]])) {
rep_args[[i]] <- NULL
}
}
rep_args
}
## check if a variable is null or not in the selected data.frame
not_available <- function(x) any(is.null(x)) || (sum(x %in% varnames()) < length(x))
## check if a variable is null or not in the selected data.frame
available <- function(x) !not_available(x)
## check if a button was pressed
pressed <- function(x) !is.null(x) && (is.list(x) || x > 0)
## check if a button was NOT pressed
not_pressed <- function(x) !pressed(x)
## check for duplicate entries
has_duplicates <- function(x) length(unique(x)) < length(x)
## is x some type of date variable
is_date <- function(x) inherits(x, c("Date", "POSIXlt", "POSIXct"))
## drop elements from .._args variables obtained using formals
r_drop <- function(x, drop = c("dataset", "data_filter", "arr", "rows", "envir")) x[!x %in% drop]
## show a few rows of a dataframe
show_data_snippet <- function(dataset = input$dataset, nshow = 7, title = "", filt = "", arr = "", rows = "") {
if (is.character(dataset) && length(dataset) == 1) dataset <- get_data(dataset, filt = filt, arr = arr, rows = rows, na.rm = FALSE, envir = r_data)
nr <- nrow(dataset)
## avoid slice with variables outside of the df in case a column with the same
## name exists
dataset[1:min(nshow, nr), , drop = FALSE] %>%
mutate_if(is_date, as.character) %>%
mutate_if(is.character, list(~ strtrim(., 40))) %>%
xtable::xtable(.) %>%
print(
type = "html", print.results = FALSE, include.rownames = FALSE,
sanitize.text.function = identity,
html.table.attributes = "class='table table-condensed table-hover snippet'"
) %>%
paste0(title, .) %>%
(function(x) if (nr <= nshow) x else paste0(x, "\n<label>", nshow, " of ", format_nr(nr, dec = 0), " rows shown. See View-tab for details.</label>")) %>%
enc2utf8()
}
suggest_data <- function(text = "", df_name = "diamonds") {
paste0(text, "For an example dataset go to Data > Manage, select 'examples' from the\n'Load data of type' dropdown, and press the 'Load examples' button. Then\nselect the \'", df_name, "\' dataset.")
}
## function written by @wch https://github.com/rstudio/shiny/issues/781#issuecomment-87135411
capture_plot <- function(expr, env = parent.frame()) {
structure(
list(expr = substitute(expr), env = env),
class = "capture_plot"
)
}
## function written by @wch https://github.com/rstudio/shiny/issues/781#issuecomment-87135411
print.capture_plot <- function(x, ...) {
eval(x$expr, x$env)
}
################################################################
## functions used to create Shiny in and outputs
################################################################
## textarea where the return key submits the content
returnTextAreaInput <- function(inputId, label = NULL, rows = 2,
placeholder = NULL, resize = "vertical",
value = "") {
## avoid all sorts of 'helpful' behavior from your browser
## see https://stackoverflow.com/a/35514029/1974918
tagList(
tags$div(
# using containing element based on
# https://github.com/niklasvh/html2canvas/issues/2008#issuecomment-1445503369
tags$label(label, `for` = inputId), br(),
tags$textarea(
value,
id = inputId,
type = "text",
rows = rows,
placeholder = placeholder,
resize = resize,
autocomplete = "off",
autocorrect = "off",
autocapitalize = "off",
spellcheck = "false",
class = "returnTextArea form-control"
)
)
)
}
## from https://github.com/rstudio/shiny/blob/master/R/utils.R
`%AND%` <- function(x, y) {
if (!all(is.null(x)) && !all(is.na(x))) {
if (!all(is.null(y)) && !all(is.na(y))) {
return(y)
}
}
return(NULL)
}
## using a custom version of textInput to avoid browser "smartness"
textInput <- function(inputId, label, value = "", width = NULL,
placeholder = NULL, autocomplete = "off",
autocorrect = "off", autocapitalize = "off",
spellcheck = "false", ...) {
value <- restoreInput(id = inputId, default = value)
div(
class = "form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
label %AND% tags$label(label, `for` = inputId),
tags$input(
id = inputId,
type = "text",
class = "form-control",
value = value,
placeholder = placeholder,
autocomplete = autocomplete,
autocorrect = autocorrect,
autocapitalize = autocapitalize,
spellcheck = spellcheck,
...
)
)
}
## using a custom version of textAreaInput to avoid browser "smartness"
textAreaInput <- function(inputId, label, value = "", width = NULL,
height = NULL, cols = NULL, rows = NULL,
placeholder = NULL, resize = NULL,
autocomplete = "off", autocorrect = "off",
autocapitalize = "off", spellcheck = "true",
...) {
value <- restoreInput(id = inputId, default = value)
if (!is.null(resize)) {
resize <- match.arg(
resize,
c("both", "none", "vertical", "horizontal")
)
}
style <- paste(if (!is.null(width)) {
paste0("width: ", validateCssUnit(width), ";")
}, if (!is.null(height)) {
paste0("height: ", validateCssUnit(height), ";")
}, if (!is.null(resize)) {
paste0("resize: ", resize, ";")
})
if (length(style) == 0) {
style <- NULL
}
div(
class = "form-group shiny-input-container",
label %AND% tags$label(label, `for` = inputId),
tags$textarea(
id = inputId,
class = "form-control",
placeholder = placeholder,
style = style,
rows = rows,
cols = cols,
autocomplete = autocomplete,
autocorrect = autocorrect,
autocapitalize = autocapitalize,
spellcheck = spellcheck,
...,
value
)
)
}
## avoid all sorts of 'helpful' behavior from your browser
## based on https://stackoverflow.com/a/35514029/1974918
returnTextInput <- function(inputId, label = NULL,
placeholder = NULL, value = "") {
tagList(
tags$label(label, `for` = inputId),
tags$input(
id = inputId,
type = "text",
value = value,
placeholder = placeholder,
autocomplete = "off",
autocorrect = "off",
autocapitalize = "off",
spellcheck = "false",
class = "returnTextInput form-control"
)
)
}
if (getOption("radiant.shinyFiles", FALSE)) {
download_link <- function(id) {
uiOutput(paste0("ui_", id))
}
download_button <- function(id, ...) {
uiOutput(paste0("ui_", id))
}
download_handler <- function(id, label = "", fun = id, fn, type = "csv", caption = "Save to csv",
class = "", ic = "download", btn = "link", onclick = "function() none;", ...) {
## create observer
shinyFiles::shinyFileSave(input, id, roots = sf_volumes, session = session)
## create renderUI
if (btn == "link") {
output[[paste0("ui_", id)]] <- renderUI({
if (is.function(fn)) fn <- fn()
if (is.function(type)) type <- type()
shinyFiles::shinySaveLink(
id, label, caption,
filename = fn, filetype = type,
class = "alignright", icon = icon(ic, verify_fa = FALSE), onclick = onclick
)
})
} else {
output[[paste0("ui_", id)]] <- renderUI({
if (is.function(fn)) fn <- fn()
if (is.function(type)) type <- type()
shinyFiles::shinySaveButton(
id, label, caption,
filename = fn, filetype = type,
class = class, icon = icon("download", verify_fa = FALSE), onclick = onclick
)
})
}
observeEvent(input[[id]], {
if (is.integer(input[[id]])) {
return()
}
path <- shinyFiles::parseSavePath(sf_volumes, input[[id]])
if (!inherits(path, "try-error") && !is.empty(path$datapath)) {
fun(path$datapath, ...)
}
})
}
} else {
download_link <- function(id, ...) {
downloadLink(id, "", class = "fa fa-download alignright", ...)
}
download_button <- function(id, label = "Save", ic = "download", class = "", ...) {
downloadButton(id, label, class = class, ...)
}
download_handler <- function(id, label = "", fun = id, fn, type = "csv", caption = "Save to csv",
class = "", ic = "download", btn = "link", ...) {
output[[id]] <- downloadHandler(
filename = function() {
if (is.function(fn)) fn <- fn()
if (is.function(type)) type <- type()
paste0(fn, ".", type)
},
content = function(path) {
fun(path, ...)
}
)
}
}
plot_width <- function() {
if (is.null(input$viz_plot_width)) r_info[["plot_width"]] else input$viz_plot_width
}
plot_height <- function() {
if (is.null(input$viz_plot_height)) r_info[["plot_height"]] else input$viz_plot_height
}
download_handler_plot <- function(path, plot, width = plot_width, height = plot_height) {
plot <- try(plot(), silent = TRUE)
if (inherits(plot, "try-error") || is.character(plot) || is.null(plot)) {
plot <- ggplot() +
labs(title = "Plot not available")
inp <- c(500, 100, 96)
} else {
inp <- 5 * c(width(), height(), 96)
}
png(file = path, width = inp[1], height = inp[2], res = inp[3])
print(plot)
dev.off()
}
## fun_name is a string of the main function name
## rfun_name is a string of the reactive wrapper that calls the main function
## out_name is the name of the output, set to fun_name by default
register_print_output <- function(fun_name, rfun_name, out_name = fun_name) {
## Generate output for the summary tab
output[[out_name]] <- renderPrint({
## when no analysis was conducted (e.g., no variables selected)
fun <- get(rfun_name)()
if (is.character(fun)) {
cat(fun, "\n")
} else {
rm(fun)
}
})
return(invisible())
}
## fun_name is a string of the main function name
## rfun_name is a string of the reactive wrapper that calls the main function
## out_name is the name of the output, set to fun_name by default
register_plot_output <- function(fun_name, rfun_name, out_name = fun_name,
width_fun = "plot_width", height_fun = "plot_height") {
## Generate output for the plots tab
output[[out_name]] <- renderPlot(
{
## when no analysis was conducted (e.g., no variables selected)
p <- get(rfun_name)()
if (is_not(p) || is.empty(p)) p <- "Nothing to plot ...\nSelect plots to show or re-run the calculations"
if (is.character(p)) {
plot(
x = 1, type = "n", main = paste0("\n\n\n\n\n\n\n\n", p),
axes = FALSE, xlab = "", ylab = "", cex.main = .9
)
} else {
print(p)
}
},
width = get(width_fun),
height = get(height_fun),
res = 96
)
return(invisible())
}
stat_tab_panel <- function(menu, tool, tool_ui, output_panels,
data = input$dataset) {
sidebarLayout(
sidebarPanel(
wellPanel(
HTML(paste("<label><strong>Menu:", menu, "</strong></label><br>")),
HTML(paste("<label><strong>Tool:", tool, "</strong></label><br>")),
if (!is.null(data)) {
HTML(paste("<label><strong>Data:", data, "</strong></label>"))
}
),
uiOutput(tool_ui)
),
mainPanel(
output_panels
)
)
}
################################################################
## functions used for app help
################################################################
help_modal <- function(modal_title, link, help_file,
author = "Vincent Nijs",
year = lubridate::year(lubridate::now()),
lic = "by-nc-sa") {
sprintf(
"<div class='modal fade' id='%s' tabindex='-1' role='dialog' aria-labelledby='%s_label' aria-hidden='true'>
<div class='help-modal-dialog modal-dialog modal-lg'>
<div class='modal-content'>
<div class='modal-header'>
<h4 class='modal-title' id='%s_label'>%s</h4>
<button type='button' class='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>
</div>
<div class='help-modal-body modal-body'>%s<br>
© %s (%s) <a rel='license' href='http://creativecommons.org/licenses/%s/4.0/' target='_blank'><img alt='Creative Commons License' style='border-width:0' src ='imgs/%s.png' /></a>
</div>
</div>
</div>
</div>
<i title='Help' class='fa fa-question' data-toggle='modal' data-target='#%s'></i>",
link, link, link, modal_title, help_file, author, year, lic, lic, link
) %>%
enc2utf8() %>%
HTML()
}
help_and_report <- function(modal_title, fun_name, help_file,
author = "Vincent Nijs",
year = lubridate::year(lubridate::now()),
lic = "by-nc-sa") {
sprintf(
"<div class='modal fade' id='%s_help' tabindex='-1' role='dialog' aria-labelledby='%s_help_label' aria-hidden='true'>
<div class='help-modal-dialog modal-dialog modal-lg'>
<div class='modal-content'>
<div class='modal-header'>
<h4 class='modal-title' id='%s_help_label'>%s</h4>
<button type='button' class='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>
</div>
<div class='help-modal-body modal-body'>%s<br>
© %s (%s) <a rel='license' href='http://creativecommons.org/licenses/%s/4.0/' target='_blank'><img alt='Creative Commons License' style='border-width:0' src ='imgs/%s.png' /></a>
</div>
</div>
</div>
</div>
<i title='Help' class='fa fa-question alignleft' data-toggle='modal' data-target='#%s_help'></i>
<i title='Report results & Screenshot' class='fa fa-camera action-button shiny-bound-input aligncenter' href='#%s_screenshot' id='%s_screenshot' onclick='generate_screenshot();'></i>
<i title='Report results' class='fa fa-edit action-button shiny-bound-input alignright' href='#%s_report' id='%s_report'></i>
<div style='clear: both;'></div>",
fun_name, fun_name, fun_name, modal_title, help_file, author, year, lic, lic, fun_name, fun_name, fun_name, fun_name, fun_name
) %>%
enc2utf8() %>%
HTML() %>%
withMathJax()
}
## function to render .md files to html
inclMD <- function(path) {
paste(readLines(path, warn = FALSE), collapse = "\n") %>%
markdown::mark_html(text = ., template = FALSE, meta = list(css = ""), output = FALSE)
}
## function to render .Rmd files to html
inclRmd <- function(path) {
paste(readLines(path, warn = FALSE), collapse = "\n") %>%
knitr::knit2html(
text = ., template = FALSE, quiet = TRUE,
envir = r_data, meta = list(css = ""), output = FALSE
) %>%
HTML() %>%
withMathJax()
}
## capture the state of a dt table
dt_state <- function(fun, vars = "", tabfilt = "", tabsort = "", nr = 0) {
## global search
search <- input[[paste0(fun, "_state")]]$search$search
if (is.null(search)) search <- ""
## table ordering
order <- input[[paste0(fun, "_state")]]$order
if (length(order) == 0) {
order <- "NULL"
} else {
order <- list(order)
}
## column filters, gsub needed for factors
sc <- input[[paste0(fun, "_search_columns")]] %>% gsub("\\\"", "'", .)
sci <- which(sc != "")
nr_sc <- length(sci)
if (nr_sc > 0) {
sc <- list(lapply(sci, function(i) list(i, sc[i])))
} else if (nr_sc == 0) {
sc <- "NULL"
}
dat <- get(paste0(".", fun))()$tab %>%
(function(x) {
nr <<- nrow(x)
x[1, , drop = FALSE]
})
if (order != "NULL" || sc != "NULL") {
## get variable class and name
gc <- get_class(dat) %>%
(function(x) if (is.empty(vars[1])) x else x[vars])
cn <- names(gc)
if (length(cn) > 0) {
if (order != "NULL") {
tabsort <- c()
for (i in order[[1]]) {
cname <- cn[i[[1]] + 1] %>% gsub("^\\s+|\\s+$", "", .)
if (grepl("[^0-9a-zA-Z_\\.]", cname) || grepl("^[0-9]", cname)) {
cname <- paste0("`", cname, "`")
}
if (i[[2]] == "desc") cname <- paste0("desc(", cname, ")")
tabsort <- c(tabsort, cname)
}
tabsort <- paste0(tabsort, collapse = ", ")
}
if (sc != "NULL") {
tabfilt <- c()
for (i in sc[[1]]) {
cname <- cn[i[[1]]]
type <- gc[cname]
if (type == "factor") {
cname <- paste0(cname, " %in% ", sub("\\[", "c(", i[[2]]) %>% sub("\\]", ")", .))
} else if (type %in% c("numeric", "integer", "ts")) {
bnd <- strsplit(i[[2]], "...", fixed = TRUE)[[1]]
cname <- paste0(cname, " >= ", bnd[1], " & ", cname, " <= ", bnd[2]) %>% gsub(" ", " ", .)
} else if (type %in% c("date", "period")) {
bnd <- strsplit(i[[2]], "...", fixed = TRUE)[[1]] %>% gsub(" ", "", .)
cname <- paste0(cname, " >= '", bnd[1], "' & ", cname, " <= '", bnd[2], "'") %>% gsub(" ", " ", .)
} else if (type == "character") {
cname <- paste0("grepl('", i[[2]], "', ", cname, ", ignore.case = TRUE)")
} else if (type == "logical") {
cname <- paste0(cname, " == ", toupper(sub("\\['(true|false)'\\]", "\\1", i[[2]])))
} else {
message("Variable ", cname, " has type ", type, ". This type is not currently supported to generate code for Report > Rmd or Report > R")
next
}
tabfilt <- c(tabfilt, cname)
}
tabfilt <- paste0(tabfilt, collapse = " & ")
}
}
}
list(search = search, order = order, sc = sc, tabsort = tabsort, tabfilt = tabfilt, nr = nr)
}
## use the value in the input list if available and update r_state
state_init <- function(var, init = "", na.rm = TRUE) {
isolate({
ivar <- input[[var]]
if (var %in% names(input) || length(ivar) > 0) {
ivar <- input[[var]]
if ((na.rm && is.empty(ivar)) || length(ivar) == 0) {
r_state[[var]] <<- NULL
}
} else {
ivar <- .state_init(var, init, na.rm)
}
ivar
})
}
## need a separate function for checkboxGroupInputs
state_group <- function(var, init = "") {
isolate({
ivar <- input[[var]]
if (var %in% names(input) || length(ivar) > 0) {
ivar <- input[[var]]
if (is.empty(ivar)) r_state[[var]] <<- NULL
} else {
ivar <- .state_init(var, init)
r_state[[var]] <<- NULL ## line that differs for CBG inputs
}
ivar
})
}
.state_init <- function(var, init = "", na.rm = TRUE) {
rs <- r_state[[var]]
if ((na.rm && is.empty(rs)) || length(rs) == 0) init else rs
}
state_single <- function(var, vals, init = character(0)) {
isolate({
ivar <- input[[var]]
if (var %in% names(input) && is.null(ivar)) {
r_state[[var]] <<- NULL
ivar
} else if (available(ivar) && all(ivar %in% vals)) {
if (length(ivar) > 0) r_state[[var]] <<- ivar
ivar
} else if (available(ivar) && any(ivar %in% vals)) {
ivar[ivar %in% vals]
} else {
if (length(ivar) > 0 && all(ivar %in% c("None", "none", ".", ""))) {
r_state[[var]] <<- ivar
}
.state_single(var, vals, init = init)
}
})
}
.state_single <- function(var, vals, init = character(0)) {
rs <- r_state[[var]]
if (is.empty(rs)) init else vals[vals == rs]
}
state_multiple <- function(var, vals, init = character(0)) {
isolate({
ivar <- input[[var]]
if (var %in% names(input) && is.null(ivar)) {
r_state[[var]] <<- NULL
ivar
} else if (available(ivar) && all(ivar %in% vals)) {
if (length(ivar) > 0) r_state[[var]] <<- ivar
ivar
} else if (available(ivar) && any(ivar %in% vals)) {
ivar[ivar %in% vals]
} else {
if (length(ivar) > 0 && all(ivar %in% c("None", "none", ".", ""))) {
r_state[[var]] <<- ivar
}
.state_multiple(var, vals, init = init)
}
})
}
.state_multiple <- function(var, vals, init = character(0)) {
rs <- r_state[[var]]
r_state[[var]] <<- NULL
## "a" %in% character(0) --> FALSE, letters[FALSE] --> character(0)
if (is.empty(rs)) vals[vals %in% init] else vals[vals %in% rs]
}
## cat to file
## use with tail -f ~/r_cat.txt in a terminal
# cf <- function(...) {
# cat(paste0("\n--- called from: ", environmentName(parent.frame()), " (", lubridate::now(), ")\n"), file = "~/r_cat.txt", append = TRUE)
# out <- paste0(capture.output(...), collapse = "\n")
# cat("--\n", out, "\n--", sep = "\n", file = "~/r_cat.txt", append = TRUE)
# }
## autosave option
## provide a list with (1) the save interval in minutes, (2) the total duration in minutes, and (3) the path to use
# options(radiant.autosave = list(1, 5, "~/.radiant.sessions"))
# options(radiant.autosave = list(.1, 1, "~/Desktop/radiant.sessions"))
# options(radiant.autosave = list(10, 180, "~/Desktop/radiant.sessions"))
if (length(getOption("radiant.autosave", default = NULL)) > 0) {
start_time <- Sys.time()
interval <- getOption("radiant.autosave")[[1]] * 60000
max_duration <- getOption("radiant.autosave")[[2]]
autosave_path <- getOption("radiant.autosave")[[3]]
autosave_path <- ifelse(length(autosave_path) == 0, "~/.radiant.sessions", autosave_path)
autosave_poll <- reactivePoll(
interval,
session,
checkFunc = function() {
curr_time <- Sys.time()
diff_time <- difftime(curr_time, start_time, units = "mins")
if (diff_time < max_duration) {
saveSession(session, timestamp = TRUE, autosave_path)
options(radiant.autosave = list(interval, max_duration - diff_time, autosave_path))
message("Radiant state was auto-saved at ", curr_time)
} else {
if (length(getOption("radiant.autosave", default = NULL)) > 0) {
showModal(
modalDialog(
title = "Radiant state autosave",
span(glue("The autosave feature has been turned off. Time to save and submit your work by clicking
on the 'Save' icon in the navigation bar and then on 'Save radiant state file'. To clean the
state files that were auto-saved, run the following command from the R(studio) console:
unlink('{autosave_path}/*.state.rda', force = TRUE)")),
footer = modalButton("OK"),
size = "m",
easyClose = TRUE
)
)
options(radiant.autosave = NULL)
}
}
},
valueFunc = function() {
return()
}
)
}
## update "run" button when relevant inputs are changed
run_refresh <- function(args, pre, init = "evar", tabs = "",
label = "Estimate model", relabel = label,
inputs = NULL, data = TRUE) {
observe({
## dep on most inputs
if (data) {
input$data_filter
input$data_arrange
input$data_rows
input$show_filter
}
sapply(r_drop(names(args)), function(x) input[[paste0(pre, "_", x)]])
## adding dependence in more inputs
if (length(inputs) > 0) {
sapply(inputs, function(x) input[[paste0(pre, "_", x)]])
}
run <- isolate(input[[paste0(pre, "_run")]]) %>% pressed()
check_null <- function(init) {
all(sapply(init, function(x) is.null(input[[paste0(pre, "_", x)]])))
}
if (isTRUE(check_null(init))) {
if (!is.empty(tabs)) {
updateTabsetPanel(session, paste0(tabs, " "), selected = "Summary")
}
updateActionButton(session, paste0(pre, "_run"), label, icon = icon("play", verify_fa = FALSE))
} else if (run) {
updateActionButton(session, paste0(pre, "_run"), relabel, icon = icon("sync", class = "fa-spin", verify_fa = FALSE))
} else {
updateActionButton(session, paste0(pre, "_run"), label, icon = icon("play", verify_fa = FALSE))
}
})
observeEvent(input[[paste0(pre, "_run")]], {
updateActionButton(session, paste0(pre, "_run"), label, icon = icon("play", verify_fa = FALSE))
})
}
radiant_screenshot_modal <- function(report_on = "") {
add_button <- function() {
if (is.empty(report_on)) {
""
} else {
actionButton(report_on, "Report", icon = icon("edit", verify_fa = FALSE), class = "btn-success")
}
}
showModal(
modalDialog(
title = "Radiant screenshot",
span(shiny::tags$div(id = "screenshot_preview")),
span(HTML("</br>To include a screenshot in a report first save it to disk by clicking on the <em>Save</em> button. Then click the <em>Report</em> button to insert a reference to the screenshot into <em>Report > Rmd</em>.")),
footer = tagList(
tags$table(
tags$td(download_button("screenshot_save", "Save", ic = "download")),
tags$td(add_button()),
tags$td(modalButton("Cancel")),
align = "right"
)
),
size = "l",
easyClose = TRUE
)
)
}
observeEvent(input$screenshot_link, {
radiant_screenshot_modal()
})
render_screenshot <- function() {
plt <- sub("data:.+base64,", "", input$img_src)
png::readPNG(base64enc::base64decode(what = plt))
}
download_handler_screenshot <- function(path, plot, ...) {
plot <- try(plot(), silent = TRUE)
if (inherits(plot, "try-error") || is.character(plot) || is.null(plot)) {
plot <- ggplot() +
labs(title = "Plot not available")
png(file = path, width = 500, height = 100, res = 96)
print(plot)
dev.off()
} else {
ppath <- parse_path(path, pdir = getOption("radiant.launch_dir", find_home()), mess = FALSE)
# r_info[["latest_screenshot"]] <- glue("![]({ppath$rpath})")
# r_info[["latest_screenshot"]] <- glue("<details>\n<summary>Click to show screenshot</summary>\n<img src='{ppath$rpath}' alt='Radiant screenshot'>\n</details>")
r_info[["latest_screenshot"]] <- glue("\n<details>\n<summary>Click to show screenshot with Radiant settings to generate output shown below</summary>\n\n![]({ppath$rpath})\n</details></br>\n")
png::writePNG(plot, path, dpi = 144)
}
}
observe({
if (length(input$nav_radiant) > 0) {
tabset <- names(getOption("radiant.url.list")[[input$nav_radiant]])
rtn <- if (length(tabset) > 0) {
paste0(input$nav_radiant, " ", input[[tabset]])
} else {
input$nav_radiant
}
r_info[["radiant_tab_name"]] <- gsub("[ ]+", "-", rtn) %>%
gsub("(\\(|\\))", "", .) %>%
gsub("[-]{2,}", "-", .) %>%
tolower()
}
})
download_handler(
id = "screenshot_save",
fun = download_handler_screenshot,
fn = function() paste0(r_info[["radiant_tab_name"]], "-screenshot"),
type = "png",
caption = "Save radiant screenshot",
plot = render_screenshot,
btn = "button",
label = "Save",
class = "btn-primary",
onclick = "get_img_src();"
)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.