I want to split my app into smaller peaces for better handling.
server.R
library(shiny)
source("onLoad.R", local = TRUE)
shinyServer(function(input, output, session) {
sourceRecursive("/.../")
})
sourceRecursive
#check folder and all subfolders for .R files
#source() them!
sourceRecursive <- function(path) {
dirs <- list.dirs()
files <- dir(pattern = "^.*[Rr]$", include.dirs = FALSE)
for (f in files)
source(f)
for (d in dirs)
sourceRecursive(d)
}
example file I try to source. file.R
output$myChoices <- renderUI({
selectInput(inputId = 'x',
label = 'y',
choices = levels(myDataSet$df$z),
multiple = T
)
})
Bounces back with:
Error in output$myChoices <- renderUI({ :
object 'output' not found
Obviously the problem is that within the file.R the variable output is not defined since this is a variable which is used in the shiny context. How would I tell R (or shiny) to treat all the variables as shiny defined variables (such as output$whatever, input$something, reactive etc). That seems crucial to me in order to break up the programme into smaller peaces.
I'm using both source(local=TRUE) and sys.source to load the file into the proper environment, it seems to work:
library(shiny)
shinyServer(function(input, output, session) {
# From http://shiny.rstudio.com/articles/scoping.html
output$text <- renderText({
source('each_call.R', local=TRUE)
})
# Source in the file.R from the example in the question
sys.source('file.R', envir=environment())
})
I didn't test it, but you might be able to use:
sourceRecursive <- function(path, env) {
files <- list.files(path = path, pattern = "^.*[Rr]$", recursive = TRUE)
for (f in files) sys.source(f, env)
}
shinyServer(function(input, output, session) {
session.env <- environment()
sourceRecursive(path = ".", env = session.env)
})
What if you use local=TRUE in your call to source provided that sourceRecursive is in the right scope (maybe put it in server.R). See this documentation here
Related
I want to use Shiny within RMarkdown for users to upload data (xlsx file).
Then I want to pass all the worksheets as R data frames (w/o reactivity) to run rest of the RMarkdown file.
I mainly want to convert them into data frames so I can use reticulate to run Python code as well.
I've tried this, and it doesn't seem to quite work:
library(dplyr)
library(miniUI)
library(shiny)
library(XLConnect)
launch_shiny <- function() {
ui <- miniPage(
gadgetTitleBar("Input Data"),
miniContentPanel(
fileInput(inputId = "my.file", label = NULL, multiple = FALSE)
)
)
server <- function(input, output, session) {
wb <- reactive({
new.file <- input$my.file
loadWorkbook(
filename = new.file$datapath,
create = FALSE,
password = NULL
)
})
observeEvent(input$done, {
stopApp(c(wb()))
})
}
runGadget(ui, server)
}
test <- launch_shiny()
df1 <- readWorksheet(object = test, sheet = "sheet1")
df2 <- readWorksheet(object = test, sheet = "sheet2")
It throws this error:
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘readWorksheet’ for signature ‘"list", "character"’
I can return one sheet at a time using stopApp(readWorksheet(object = wb(), sheet = "sheet1")), but I can't seem to return an entire workbook or multiple data frames at the same time.
I don't really want to read in xlsx, save each sheet as csv in working directory, then read those files in again.
Would anyone have a good suggestion on how to get around this?
The documentation of fileInput() states in the details:
datapath
The path to a temp file that contains the data that was
uploaded. This file may be deleted if the user performs another upload
operation.
Meaning that the datapath given in the input variable is a temporary file that is no longer accessible after you close the App, which is what the function readWorksheet will try to do.
So you'll have to read the sheets in the server and return the dataframes somehow.
I did that by defining a second reactive value which is basically a list of dataframes returned by applying lapply on all the sheets in wb, in this case test will be this list of data frames.
There might be other ways (more efficient, or suits your purpose better) to do this, but here it is:
library(dplyr)
library(miniUI)
library(shiny)
library(XLConnect)
launch_shiny <- function() {
ui <- miniPage(
gadgetTitleBar("Input Data"),
miniContentPanel(
fileInput(inputId = "my.file", label = NULL,
multiple = FALSE)
)
)
server <- function(input, output, session) {
wb <- reactive({
new.file <- input$my.file
loadWorkbook(
filename = new.file$datapath,
create = FALSE,
password = NULL
)
})
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
df_lst <- reactive({
# read all sheets into a list
lapply(getSheets(wb()),
function(sheet){
readWorksheet(object = wb(),
sheet = sheet)
})
})
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
observeEvent(input$done, {
# get the list of dfs from the app
stopApp(c(df_lst()))
})
}
runGadget(ui, server)
}
test <- launch_shiny()
I have a shiny app to generate a .txt file to download.
In addition, I would like to keep a copy of the file that users generate in my shiny server.
the server function looks like :
server <- function(input, output, session){
data_gen <- reactive({
d1= data.frame(...)
d2= data.frame(...)
result <- list(d1=d1, d2=d2)
return(result)
})
create_file <- reactive({
sink("/srv/shiny-server/S3/file.txt",append = TRUE)
print(data_gen()$d1)
print(data_gen()$d2)
sink()
})
output$downloadData <- downloadHandler(
filename = function() {"input.txt"},
content = function(file) {
sink(file,append = TRUE)
print(data_gen()$d1)
print(data_gen()$d2)
sink()
}
)
}
I'm able to download the data but the app does not react to the create_file function and it does not write a copy into shiny server.
Any Idea how could I fix this ?
Your create_file function is a reactive. Reactive functions only evaluate when 1) their output is required, and 2) their inputs have changed. Neither appears to apply here.
What you could do is move the contents of create_file inside your downloadhandler. content must receive a function that returns a file, but the function can do other things first. So try the following:
server <- function(input, output, session){
data_gen <- reactive({
d1= data.frame(...)
d2= data.frame(...)
result <- list(d1=d1, d2=d2)
return(result)
})
output$downloadData <- downloadHandler(
filename = function() {"input.txt"},
content = function(file) {
# save non-user copy
sink("/srv/shiny-server/S3/file.txt",append = TRUE)
print(data_gen()$d1)
print(data_gen()$d2)
sink()
# copy to be returned for user
sink(file,append = TRUE)
print(data_gen()$d1)
print(data_gen()$d2)
sink()
})
}
I'm new to shiny.
I have a very basic question but I can't find a solution here on stackoverflow.
I am using directory Input function created by wleepang (https://github.com/wleepang/shiny-directory-input).
I wrote a function read_files that rbind all files in the directory selected.
I can display this table with renderTable, this works perfectly. But I do not manage to save this table to work with later (check for missing data, add columns, draw ggplots..) and to download is with write.xlsx
ui <- fluidPage(
directoryInput('directory', label = 'select a directory'),
actionButton("upload", label="Hochladen"),
downloadButton("download", label="Runterladen")
)
server <- function(input, output, session) {
#this part is to set the directory
observeEvent(
ignoreNULL = TRUE,
eventExpr = {
input$directory
},
handlerExpr = {
if (input$directory > 0) {
path = choose.dir(default = readDirectoryInput(session, 'directory'))
updateDirectoryInput(session, 'directory', value = path)
}})
#now comes the actual code
observeEvent(input$upload,{
df <- read_files(readDirectoryInput(session, 'directory'))
})
How can I access this df later?
output$downloadData <- downloadHandler(
filename = function() {
paste('tabelle', '.csv', sep="") },
content = function(file) {
write.xlsx(df, file)
}
)
}
And my second question how can I download it as a xlsx file in the set directory?
my global.r with the read_files function
source('directoryInput.R')
read_files = function(inDir, pat="*.csv", readMe=read.csv2){
files = list.files(inDir, pattern=pat)
files = lapply(files, function(x) file.path(inDir, x))
df = do.call(rbind, lapply(files, readMe))
return(df)
}
I have saved the element using a reactive function
upload_data <- eventReactive(input$upload, {
read_files(readDirectoryInput(session, 'directory')) })
and can access it through upload_data() this works for me
I want to import a .RData file with fileInput but It doesn't work, I have this error message :
Error in my.data$TYPE_DE_TERMINAL : $ operator is invalid for
atomic vectors
dt <- reactive({
inFile <- input$file1
if (is.null(inFile))
return(NULL)
load(inFile$datapath)
})
GetData <- reactive({
my.data <- dt()
When I try my application with a .RData imported manually it works well (I remplaced dt() directly with a dataframe in my directory) ...
The following example solves the problem. It allows you to upload all .RData files.
Thanks to #Spacedman for pointing me to a better approach of loading the data:
Load the file into a new environment and get it from there.
For the matter of the example being "standalone" I inserted the top section that stores two vectors to your disk in order to load and plot them later.
library(shiny)
# Define two datasets and store them to disk
x <- rnorm(100)
save(x, file = "x.RData")
rm(x)
y <- rnorm(100, mean = 2)
save(y, file = "y.RData")
rm(y)
# Define UI
ui <- shinyUI(fluidPage(
titlePanel(".RData File Upload Test"),
mainPanel(
fileInput("file", label = ""),
actionButton(inputId="plot","Plot"),
plotOutput("hist"))
)
)
# Define server logic
server <- shinyServer(function(input, output) {
observeEvent(input$plot,{
if ( is.null(input$file)) return(NULL)
inFile <- isolate({input$file })
file <- inFile$datapath
# load the file into new environment and get it from there
e = new.env()
name <- load(file, envir = e)
data <- e[[name]]
# Plot the data
output$hist <- renderPlot({
hist(data)
})
})
})
# Run the application
shinyApp(ui = ui, server = server)
Beginner Shiny question.
I have two models living in different folders, A and B, both called inputs.R, and want to load one or the other using selectInput to choose the folder (in reality, there is more than one file in each folder, so I don't want to load the file directly).
Currently, I have
ui <- fluidPage(selectInput("model_folder", "Select folder", c("A", "B")))
server <- function(input, output){
reactive({
inpts <- paste0("models/",input$model_folder, "/inputs.R")
source(inpts, local = T)
})
}
This does not work. Any thoughts would be greatly appreciated.
This will depend where you have your 'models' folder stored. So, pretend it is in the same directory as your shiny app. Here is some code that should recreate this situation, along with some models and data in the two separate folders. Just change the variable appDir to wherever you don't have a folder.
## Create the models/folders in a temporary location
## define it in appDir
appDir <- 'c:/path/to/temp/app'
dir.create(appDir)
dir.create(file.path(appDir, "models"))
for (i in 1:2) {
dir.create((folder = file.path(appDir, "models/", LETTERS[i])))
code <- bquote({
dat <- data.frame((x=rnorm(100)), y=rnorm(100, mean=.(i)*x))
mod <- lm(y ~ x, data=dat)
})
writeLines(deparse(code), file.path(folder, 'input.R'))
}
Then, in the new folder appDir, create a file app.R, which will be the example application. There are problems with how you are trying to use reactive, illustrated below. I capture all the variables from the sourced input.R files using mget() in this example.
library(shiny)
app <- shinyApp(
ui = fluidPage(
selectInput("model_folder", "Select folder", c("A", "B")),
uiOutput('info'),
tableOutput('summ')
),
server = function(input, output) {
output$info <- renderUI({
inp <- inpts()
list(
helpText(sprintf("Now looking at variables from %s", inp$name)),
radioButtons('vars', 'Variables', choices=names(inp), inline=TRUE)
)
})
output$summ <- renderTable({
inp <- inpts()
if (input$vars == 'mod') summary(inp$mod)
})
inpts <- reactive({
name <- file.path("models", input$model_folder, "input.R")
source(name, local=TRUE)
mget(ls())
})
}
)
Now, to run it you can just do
library(shiny)
runApp(appDir = normalizePath(appDir))