Hello I'm building a shinydashboard using several excel files.
I inserted links to these files in the footer of the box and I want to refresh the shinydashboard when changing something in my excel file.
I don't want to run the whole R code each time.
How can I re-render the Output once the file content changes?
Here an example:
sidebar <- dashboardSidebar(
sidebarMenu( menuItem("Hello", tabName = "Hello", icon = icon("dashboard"))
))
body <- dashboardBody(
tabItems(
tabItem(tabName = "Hello",
box(title = "my file",
footer = a("df.xlsx", href="df.xlsx" ) ,
DT::dataTableOutput("df1"),style = "font-size: 100%; overflow: auto;",
width = 12, hight = NULL, solidHeader = TRUE, collapsible = TRUE, collapsed = TRUE, status = "primary")
)))
ui <- dashboardPage(
dashboardHeader(title = "My Dashboard"),
sidebar,
body)
server <- function(input, output) {
output$df1 <- renderDataTable({
df <- read_excel("df.xlsx")
DT::datatable(df, escape = FALSE, rownames=FALSE,class = "cell-border",
options =list(bSort = FALSE, paging = FALSE, info = FALSE)
)
})
}
shinyApp(ui, server)
To monitor the change in a file you could use the cheksum of the file like this:
library(shiny)
library(digest)
# Create data to read
write.csv(file="~/iris.csv",iris)
shinyApp(ui=shinyUI(
fluidPage(
sidebarLayout(
sidebarPanel(
textInput("path","Enter path: "),
actionButton("readFile","Read File"),
tags$hr()
),
mainPanel(
tableOutput('contents')
)))
),
server = shinyServer(function(input,output,session){
file <- reactiveValues(path=NULL,md5=NULL,rendered=FALSE)
# Read file once button is pressed
observeEvent(input$readFile,{
if ( !file.exists(input$path) ){
print("No such file")
return(NULL)
}
tryCatch({
read.csv(input$path)
file$path <- input$path
file$md5 <- digest(file$path,algo="md5",file=TRUE)
file$rendered <- FALSE
},
error = function(e) print(paste0('Error: ',e)) )
})
observe({
invalidateLater(1000,session)
print('check')
if (is.null(file$path)) return(NULL)
f <- read.csv(file$path)
# Calculate ckeksum
md5 <- digest(file$path,algo="md5",file=TRUE)
# If no change in cheksum, do nothing
if (file$md5 == md5 && file$rendered == TRUE) return(NULL)
output$contents <- renderTable({
print('render')
file$rendered <- TRUE
f
})
})
}))
If I understand the question correctly, I'd say you need the reactiveFileReader function.
Description from the function's reference page:
Given a file path and read function, returns a reactive data source
for the contents of the file.
The file reader will poll the file for changes, and once a change is detected the UI gets updated reactively.
Using the gallery example as a guide, I updated the server function in your example to the following:
server <- function(input, output) {
fileReaderData <- reactiveFileReader(500,filePath="df.xlsx", readFunc=read_excel)
output$df1 <- renderDataTable({
DT::datatable(fileReaderData(), escape = FALSE, rownames=FALSE,class = "cell-border",
options =list(bSort = FALSE, paging = FALSE, info = FALSE)
)
})
}
With that, any changes I saved to 'df.xlsx' were propagated almost instantly to the UI.
Related
I have a shiny dashboard where the tables are created with the reactable package. I have simple and nested tables and as far as I can see, there is only a download option for csv:
library(htmltools)
library(fontawesome)
data <- MASS::Cars93[1:15, c("Manufacturer", "Model", "Type", "Price")]
htmltools::browsable(
tagList(
tags$button(
tagList(fontawesome::fa("download"), "Download as CSV"),
onclick = "Reactable.downloadDataCSV('cars-download-table', 'cars.csv')"
),
reactable(
data,
searchable = TRUE,
defaultPageSize = 5,
elementId = "cars-download-table"
)
)
)
I want to create one Excel download file with the following attributes:
the tables to download are selected via a checkboxGroupInput
one Excel sheet per selected item
the name of the sheet corresponds to selected item
if there is more than one table in the selected item, all those tables should be in one sheet (divided by some empty rows)
some captions (read from another file) should be inserted above the tables
The problem is, that I want to use the data shown in the reactable (e.g. the selected columns), therefore I can not use the raw data. Is there some kind of package I can use?
So far, I only have a slow solution where I put the reactable into an additional variable before I render the table and then I read the data from this variable and use the package openxlsx to write the Excel.
Here is a clue. You can get the current state of the table with Reactable.getState, and the current display is in the field sortedData. This is demonstrated by the app below.
library(shiny)
library(reactable)
library(jsonlite)
registerInputHandler(
"xx",
function(data, ...){
fromJSON(toJSON(data))
},
force = TRUE
)
ui <- fluidPage(
fluidRow(
column(
7,
tags$button(
"Get data",
onclick = '
var state = Reactable.getState("cars");
Shiny.setInputValue("dat:xx", state.sortedData);
'
),
reactableOutput("cars")
),
column(
5,
verbatimTextOutput("data")
)
)
)
server <- function(input, output){
output$cars <- renderReactable({
reactable(MASS::Cars93[, 1:5], filterable = TRUE)
})
output$data <- renderPrint({
input$dat
})
}
shinyApp(ui, server)
EDIT
Here is an example of downloading the current display:
library(shiny)
library(shinyjs)
library(reactable)
library(jsonlite)
registerInputHandler(
"xx",
function(data, ...){
fromJSON(toJSON(data))
},
force = TRUE
)
ui <- fluidPage(
useShinyjs(),
br(),
conditionalPanel(
"false", # always hide the download button, because we will trigger it
downloadButton("downloadData") # programmatically with shinyjs
),
actionButton(
"dwl", "Download", class = "btn-primary",
onclick = paste0(
'var state = Reactable.getState("cars");',
'Shiny.setInputValue("dat:xx", state.sortedData);'
)
),
br(),
reactableOutput("cars")
)
server <- function(input, output, session){
output$cars <- renderReactable({
reactable(MASS::Cars93[, 1:5], filterable = TRUE)
})
observeEvent(input$dat, {
runjs("$('#downloadData')[0].click();")
})
output$downloadData <- downloadHandler(
filename = function() {
paste0("data-", Sys.Date(), ".xlsx")
},
content = function(file) {
openxlsx::write.xlsx(input$dat, file)
}
)
}
shinyApp(ui, server)
I have a Shiny App that takes a text file from FileInput , processed it and sets a few variables / data frames which are then displayed in InfoBoxOutputs .
I know I can currently process the file in the output of the server function for elements eg a Datable like in my code below, but this doesnt seem to update the InfoBoxOutputs even though the variables used are set during the Seed() function (they are definetly set ) that runs for the Datatable output.
So is there a way to run my Seed() function to process all the data at once so that all my elements an use the processed data or do I have to rerun the function foreach element ?
#Initialize variables which will later be set in Seed()
mostwanted_variable <- NULL
key_vaiable <- NULL
ui <- dashboardPage(skin = "blue",
dashboardHeader( title = "Text Check"),
dashboardSidebar(
fileInput("file1", "Upload File",
multiple = FALSE,
accept = c( ".txt"))
),
dashboardBody(
tabItems(
tabItem(tabName = "statistics",
fluidRow(
infoBoxOutput("info1"),
infoBoxOutput("info2")
),
# infoBoxes with fill=TRUE
fluidRow(
DT::dataTableOutput("mytable")
)
)
)
)
)
server <- function(input, output) {
# Main List Output
output$mytable = DT::renderDataTable({
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
validate(need(ext == ".txt", "Please upload a text file"))
## Processes Text File and sets many variables & frames
Seed(file$datapath)
## Select maindatasource which is set in the Seed function
maindatasource
})
# Info Box 1
# uses variable set during Seed function
output$info1 <- renderInfoBox({
infoBox(
"Key", key_variable, icon = icon("user-secret"),
color = "aqua" , fill = TRUE
)
})
# Info Box 2
# uses variable set during Seed function
output$info2 <- renderInfoBox({
infoBox(
"Most Wanted", mostwanted_variable, icon = icon("earlybirds"),
color = "aqua"
)
})
}
You could set reactiveValues in the Seed() function, that would be used to update info bpxes.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
skin = "blue",
dashboardHeader(title = "Text Check"),
dashboardSidebar(fileInput(
"file1",
"Upload File",
multiple = FALSE,
accept = c(".txt")
)),
dashboardBody(
fluidRow(infoBoxOutput("info1"),
nfoBoxOutput("info2")),
fluidRow(DT::dataTableOutput("mytable"))
)
)
server <- function(input, output) {
rv <- reactiveValues(
mostwanted_variable = NULL,
key_variable = NULL)
Seed <- function(filename) {
rv$mostwanted_variable <- "a"
rv$key_variable <- 123
return(data.frame(
main = c(1, 2),
data = c("a", "b"),
source = c(3, 4)
))
}
# Main List Output
output$mytable = DT::renderDataTable({
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
validate(need(ext == "txt", "Please upload a text file"))
## Processes Text File and sets many variables & frames
maindatasource <- Seed(file$datapath)
})
# Info Box 1
# uses variable set during Seed function
output$info1 <- renderInfoBox({
infoBox(
"Key",
rv$key_variable,
icon = icon("user-secret"),
color = "aqua" ,
fill = TRUE
)
})
# Info Box 2
# uses variable set during Seed function
output$info2 <- renderInfoBox({
infoBox(
"Most Wanted",
rv$mostwanted_variable,
icon = icon("earlybirds"),
color = "aqua"
)
})
}
shinyApp(ui, server)
I want to upload two csv files and print both tables out.
Here is the code I wrote:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput(inputId = "files", label = "Choose CSV File", multiple = TRUE,accept = c(".csv")
)
),
mainPanel(
fluidRow(tableOutput("Policy1")),
fluidRow(tableOutput("Policy2")),
)
)
)
server <- function(input, output) {
data <- reactiveValues(file1 = NULL,
file2 = NULL)
output$Policy1 <- renderTable({
if(!is.null(input$files$datapath[1]))
data$file1 <- read.csv(input$files$datapath[1], header = TRUE)
data$file1
})
output$Policy2 <- renderTable({
if(is.null(input$files$datapath[2])) {return(1)}
else{return(NULL)}
})
}
shinyApp(ui, server)
and for the output$Policy2 part, I want to test when the is.null(input$files$datapath[2]) is true. I thought it should be true when I only upload one file or don't upload anything but
if I only upload one csv file, it didn't print out the table 1, which means is.null(input$files$datapath[2]) is false in this case.I don't know why this is the case.
And as a result, if I change the code to ask shiny print two tables for me and only upload one file, there will be an error, here is the code:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput(inputId = "files", label = "Choose CSV File", multiple = TRUE,accept = c(".csv")
)
),
mainPanel(
fluidRow(tableOutput("Policy1")),
fluidRow(tableOutput("Policy2")),
)
)
)
server <- function(input, output) {
data <- reactiveValues(file1 = NULL,
file2 = NULL)
output$Policy1 <- renderTable({
if(!is.null(input$files$datapath[1]))
data$file1 <- read.csv(input$files$datapath[1], header = TRUE)
data$file1
})
output$Policy2 <- renderTable({
if(!is.null(input$files$datapath[2]))
data$file2 <- read.csv(input$files$datapath[2], header = TRUE)
data$file2
})
}
shinyApp(ui, server)
where I only change a little part and here is the error :
which I assume is because I should return NULL when only one file inputed in, how can I fix this problem, thanks for any help
The value won't be NULL if it's missing. It's better to check that there are enough values checking the length of the vector or something. For example
output$Policy2 <- renderTable({
if(!is.null(input$files) && length(input$files$datapath)>=2)
data$file2 <- read.csv(input$files$datapath[2], header = TRUE)
data$file2
})
I'm pretty new to shiny (being playing around for about a week). And I'm trying to create an app that takes and input tab-separated text file and perform several exploratory functions. In this case I'm presenting a very simplified version of that app just to highlight what I want to do in a specific case:
Problem:
If you try the app with the sample data (or any data in the same format) you can notice that the app effectively performs the default summary table (if selectInput="summarize", then output$sumfile), but when you try to select "explore", the previous table gets removed from the mainPanel, and outputs the full file (selectInput="explore",then output$gridfile) in the place where it would be as if selectInput="summarize" was still selected.
If you re-select "summarize", excelOutput("sumfile") gets duplicated on the mainPanel.
My goal is simple:
excelOutput("sumfile") when selectInput="summarize" ONLY and
excelOutput("gridfile") when selectInput="explore" ONLY
without placement issues or duplications on the mainPanel
So far I've tried:
inFile=input$df
if(is.null(inFile))
return(NULL)
if(input$show=="summarize")
return(NULL)
or
inFile=input$df
if(is.null(inFile))
return(NULL)
if(input$show=="explore")
return(NULL)
To control what shows up on the mainPanel, but with placement and duplication issues.
sample data:
#Build test data
testdat<-data.frame(W=c(rep("A",3),
rep("B",3),
rep("C",3)),
X=c(letters[1:9]),
Y=c(11:19),
Z=c(letters[1:7],"",NA),
stringsAsFactors = FALSE)
#Export test data
write.table(testdat,
"your/path/file.txt",
row.names = FALSE,
sep = "\t",
quote = FALSE,
na="")
shiny app (app.R):
library(shiny)
library(excelR)
#function to summarize tables
Pivot<-function(df){
cclass<-as.character(sapply(df,
class))
df.1<-apply(df,
2,
function(x) unlist(list(nrows = as.numeric(NROW(x)),
nrows.unique = length(unique(x))-(sum(is.na(x))+length(which(x==""))),
nrows.empty = (sum(is.na(x))+length(which(x==""))))))
df.2<-data.frame(df.1,
stringsAsFactors = FALSE)
df.3<-data.frame(t(df.2),
stringsAsFactors = FALSE)
df.3$col.class<-cclass
df.3$col.name<-row.names(df.3)
row.names(df.3)<-NULL
df.3<-df.3[c(5,4,1,2,3)]
return(df.3)
}
ui <- fluidPage(
ui <- fluidPage(titlePanel(title=h1("Summary generator",
align="center")),
sidebarLayout(
sidebarPanel(
h3("Loading panel",
align="center"),
fileInput("df",
"Choose file (format: file.txt)",
accept = c("plain/text",
".txt")),
selectInput("show",
"Choose what to do with file",
choices=c("summarize","explore")),
p("**'summarize' will output a summary of the selected table"),
p("**'explore' will output the full selected editable table"),
tags$hr()
),
mainPanel(
excelOutput("gridfile"),
excelOutput("sumfile")
))))
server <- function(input, output) {
dat<-reactive({
fp<-input$df$datapath
read.delim(fp,
quote="",
na.strings="\"\"",
stringsAsFactors=FALSE)
})
#get summary
output$sumfile<-renderExcel({
inFile=input$df
if(is.null(inFile)) #if fileInput is empty return nothing
return(NULL)
if(input$show=="explore") #if selectInput = "explore" return nothing
return(NULL)
dat.1<-data.frame(dat())
dat.2<-Pivot(dat.1)
excelTable(dat.2,
defaultColWidth = 100,
search = TRUE)
})
#get full file
output$gridfile<-renderExcel({
inFile=input$df
if(is.null(inFile)) #if fileInput is empty return nothing
return(NULL)
if(input$show=="summarize") #if selectInput = "summarize" return nothing
return(NULL)
dat.1<-data.frame(dat())
excelTable(dat.1,
defaultColWidth = 100,
search = TRUE)
})
}
shinyApp(ui = ui, server = server)
One way to do what you want is to use observeEvent for your inputs input$show and input$df and renderExcel based on your selection of `input$show. Here is an updated version for your code:
library(shiny)
library(excelR)
#function to summarize tables
Pivot<-function(df){
cclass<-as.character(sapply(df,
class))
df.1<-apply(df,
2,
function(x) unlist(list(nrows = as.numeric(NROW(x)),
nrows.unique = length(unique(x))-(sum(is.na(x))+length(which(x==""))),
nrows.empty = (sum(is.na(x))+length(which(x==""))))))
df.2<-data.frame(df.1,
stringsAsFactors = FALSE)
df.3<-data.frame(t(df.2),
stringsAsFactors = FALSE)
df.3$col.class<-cclass
df.3$col.name<-row.names(df.3)
row.names(df.3)<-NULL
df.3<-df.3[c(5,4,1,2,3)]
return(df.3)
}
ui <- fluidPage(
ui <- fluidPage(titlePanel(title=h1("Summary generator",
align="center")),
sidebarLayout(
sidebarPanel(
h3("Loading panel",
align="center"),
fileInput("df",
"Choose file (format: file.txt)",
accept = c("plain/text",
".txt")),
selectInput("show",
"Choose what to do with file",
choices=c("summarize","explore")),
p("**'summarize' will output a summary of the selected table"),
p("**'explore' will output the full selected editable table"),
tags$hr()
),
mainPanel(
excelOutput("gridfile"),
excelOutput("sumfile")
))))
server <- function(input, output) {
dat<-reactive({
fp<-input$df$datapath
read.delim(fp,
quote="",
na.strings="\"\"",
stringsAsFactors=FALSE)
})
observeEvent({
input$show
input$df
}, {
inFile=input$df
if(is.null(inFile)) #if fileInput is empty return nothing
return(NULL)
if(input$show=="explore") {
output$gridfile<-renderExcel({
dat.1<-data.frame(dat())
excelTable(dat.1,
defaultColWidth = 100,
search = TRUE)
})
}
if(input$show=="summarize") {
output$sumfile<-renderExcel({
dat.1<-data.frame(dat())
dat.2<-Pivot(dat.1)
excelTable(dat.2,
defaultColWidth = 100,
search = TRUE)
})
}
})
}
shinyApp(ui = ui, server = server)
Hope it helps!
I'm building a simple shiny app which will take inputs from the user and fetch data from a table in the DB and take the number of records to be downloaded as an input and provide a download file option.
Everything below works just fine. My only concern is the textInput bar( variable : uiOutput("text") in the ui and output$text in the server) appears only after the datatableOutput is displayed. I do not understand why this happens.
Ideally, I want the textInput bar ('uiOutput("text")') object to be displayed once the leaf(i.e. input$leaf1 is not null) is selected and then I want the datatableOutput to be displayed and then the Download Button should come up.
Is there a way I can achieve this? Thanks
library(shiny)
library(shinydashboard)
#library(stringr)
library(DT)
#library(shinyBS)
ui <- dashboardPage(
dashboardHeader(title = strong("DASHBOARD"),titleWidth = 240),
dashboardSidebar(
sidebarMenu(
selectizeInput("x", "Choose a number:", choices = sort(unique(lftable$x)), multiple = TRUE),
uiOutput("leaf_categ")
)
),
dashboardBody(
fluidRow(
uiOutput("text"),
dataTableOutput("lm_df"),
downloadButton('downloadData', 'Download')
)))
server <- function(input, output){
output$leaf_categ <- renderUI(
selectizeInput("leaf1", "Choose leaf categories:",
choices = reactive(unique(lftable[lftable$num %in% input$x, c("X_NAME")]))(),
multiple = TRUE)
)
#### creates a text input box
#### number of records to be downloaded is provided as input
output$text <- renderUI({
if(is.null(reactive(input$leaf1)())){
return()
}else{
textInput("var1", label = "Enter the number of records to be downloaded", value = "")
}
})
#### fetches data from DB
lm <- reactive({
if(is.null(input$leaf1)){
return()
}else{
leaf_id <- unique(lftable[lftable$X_NAME %in% input$leaf1, c("leaf_id")])
query_str <- paste('select * from table1 where current_date between start_dt and end_dt and score_num >= 0.1 and x in (' , input$x, ')', ' and X_ID in (', leaf_id, ')', ';', sep = "")
}
lm_data <- getDataFrmDW(query_str)
})
###creates a download tab
output$downloadData <- downloadHandler(
filename = function() { paste("lm_user_data", '.csv', sep='') },
content = function(file) {
lm_df <- lm()
lm_df <- lm_df[1:(as.integer(input$text)),]
print(dim(lm_df))
write.csv(lm_df, file, row.names = F)
})
output$lm_df <- DT::renderDataTable(lm())
}
shinyApp(ui, server)