Shiny - download markdown file - shiny

library(shiny)
library(ggplot2)
report.Rmd
date: "2023-01-15"
output: html_document
params:
x_column: "Sepal.Width"
y_column: "Sepal.Length"
library(ggplot2)
ggplot(data = iris)+
aes_string(params$x_column,params$y_column)+
geom_point()
shiny app
library(shiny)
library(ggplot)
library(rmarkdown)
ui <- fluidPage(
titlePanel("Iris Data"),
sidebarLayout(
sidebarPanel(
selectizeInput("xcol",
"Choose X Axis",
choices = names(iris)
),
selectizeInput("ycol",
"Choose Y Axis",
choices = names(iris)
),
downloadButton("project", "Download plot")
),
mainPanel(
plotOutput("plot")
)
)
)
server <- function(input, output) {
filename = "report.html
output$plot <- renderPlot({
ggplot(data=iris)+
aes_string(x=input$xcol, y=input$ycol)+
geom_point()
})
output$print <- downloadHandler({
filename="report.html"
content = function(file) {
rmarkdown::render("report.Rmd",
output_file=file, params=list(x_column = input$xcol,
y_column = input$ycol))
}
})
}
I can't get the app to render my rmarkdown file. I can't seem to see the mistake. report.Rmd is in my working directory. I can get it to work outside of shiny, but in shiny it says there is an error and the content is missing without a default value

There are two issues with your code. First, you used the id Project for the downloadButton but named the output print. Second, the API of downloadHandler is a bit different from reactive or renderXXX, i.e. you have to pass filename and content as arguments.
Note: Also it's good practice to copy the report file to a temporary directory before processing it and to evaluate the document in a child of the global environment. See Generating downloadable reports. As an example where missing the last step fails to render an Rmd correctly see this post. Finally, note that aes_string is deprecated since ggplot2 3.4.0. Instead it's recommended to use the .data pro-noun as I do in the code below.
library(shiny)
library(ggplot2)
ui <- fluidPage(
titlePanel("Iris Data"),
sidebarLayout(
sidebarPanel(
selectizeInput("xcol",
"Choose X Axis",
choices = names(iris)
),
selectizeInput("ycol",
"Choose Y Axis",
choices = names(iris)
),
downloadButton("print", "Download plot")
),
mainPanel(
plotOutput("plot")
)
)
)
server <- function(input, output) {
output$plot <- renderPlot({
ggplot(data = iris) +
aes(.data[[params$x_column]], .data[[params$y_column]]) +
geom_point()
})
output$print <- downloadHandler(
filename = "report.html",
content = function(file) {
tempReport <- file.path(tempdir(), "report.Rmd")
file.copy("report.Rmd", tempReport, overwrite = TRUE)
rmarkdown::render(tempReport,
output_file = file,
params = list(
x_column = input$xcol,
y_column = input$ycol
),
envir = new.env(parent = globalenv())
)
}
)
}
shinyApp(ui, server)
#>
#> Listening on http://127.0.0.1:5776
#> Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
#> ℹ Please use tidy evaluation ideoms with `aes()`

Related

R Shiny -- Error in readLines: 'con' is not a connection

I am trying to run the following code, but keep getting this error:
Error in readLines: 'con' is not a connection
The following is my program:
library(shiny)
library(shinyFiles)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
width = 2,
fileInput("file1", "Choose the first HTML File", accept = ".html")
),
mainPanel(
p("File Name"),
verbatimTextOutput("file_name"),
htmlOutput(outputId = 'result')
)
)
)
server <- function(input, output) {
file <- reactive({input$file1})
req(file)
if(is.null( reactive(input$file1$datapath))) return(NULL)
rawHTML <- reactive(paste(readLines(input$file1$datapath), collapse="\n"))
output$file_name <- renderText({
print(rawHTML)
})
output$result <- renderUI({
getPage<-function() {
return(includeHTML( input$file1$datapath))
}
output$inc<-renderUI({getPage()})
})
}
runApp(list(ui = ui, server = server), launch.browser =T)
I want to read the html code from an external file, and save the code as a string. Is there a solution for this?

Remove unwanted white space when rendering leaflet or plot in Shiny

I want the user of my Shiny app to be able to choose between two types of plots by clicking on radiobuttons in the Events panel. The code I have written works, but the page leaves a huge white space when going from "Map" to "Plot". Is there any way to get rid of the white space and position the plot at the very top?
# Load R packages
library(shiny)
library(shinythemes)
library(tidyverse)
library(leaflet)
set.seed(123)
year <- 2001:2020
event <- sample(1:100, size = 20, replace = TRUE)
dat <- as.data.frame(cbind(year, event))
# Define UI
ui <- fluidPage(
shinyjs::useShinyjs(),
theme = shinytheme("journal"),
navbarPage(
"Title",
tabPanel("About",
),
tabPanel("Events",
fluidPage(
titlePanel("Title"),
sliderInput("range", label = "Move slider to select time period", min(2001), max(2020),
value = range(2001:2002), step = 1, sep = "", width = "65%"),
sidebarLayout(
sidebarPanel(
radioButtons("plotType", "Plot type", choices = c("Map" = "m", "Chart" = "l"))),
mainPanel(
leafletOutput("map"),
plotOutput("plot"))
)
)
)
)
)
# Define server function
server <- function(input, output, session) {
observeEvent(input$plotType, {
if(input$plotType == "l"){
shinyjs::disable("range")
}else{
shinyjs::enable("range")
}
})
output$plot <- renderPlot({
if (input$plotType == "l") {
ggplot(dat, aes(year, event)) +
geom_line() +
labs(x = "Year", y = "Events") +
theme_bw()
}
})
output$map <- renderLeaflet({
if ( input$plotType == "m") {
leaflet(dat) %>% addTiles() %>%
fitBounds(~min(11), ~min(54), ~max(67), ~max(24))
}
})
} # server
# Create Shiny object
shinyApp(ui = ui, server = server)
There is a big space because the map html object still exists, but is empty. To avoid this, I created and observeEvent that hides or show the map output depending on input value. I did the same thing with the plot, in cas you need to add others elements below it.
Please note that there are others solutions (conditionalPanel for example), I am just giving you the one I think is the simpliest here.
# Load R packages
library(shiny)
library(shinythemes)
library(tidyverse)
library(leaflet)
set.seed(123)
year <- 2001:2020
event <- sample(1:100, size = 20, replace = TRUE)
dat <- as.data.frame(cbind(year, event))
# Define UI
ui <- fluidPage(
shinyjs::useShinyjs(),
theme = shinytheme("journal"),
navbarPage(
"Title",
tabPanel("About",
),
tabPanel("Events",
fluidPage(
titlePanel("Title"),
sliderInput("range", label = "Move slider to select time period", min(2001), max(2020),
value = range(2001:2002), step = 1, sep = "", width = "65%"),
sidebarLayout(
sidebarPanel(
radioButtons("plotType", "Plot type", choices = c("Map" = "m", "Chart" = "l"))),
mainPanel(
leafletOutput("map"),
plotOutput("plot"))
)
)
)
)
)
# Define server function
server <- function(input, output, session) {
# hide or show map and plot
observeEvent(input$plotType, {
if(input$plotType == "l"){
shinyjs::disable("range")
shinyjs::hide("map")
shinyjs::show("plot")
}
if(input$plotType == "m"){
shinyjs::enable("range")
shinyjs::show("map")
shinyjs::hide("plot")
}
})
output$plot <- renderPlot({
req(input$plotType == "l") # good practice to use req instead of if
ggplot(dat, aes(year, event)) +
geom_line() +
labs(x = "Year", y = "Events") +
theme_bw()
})
output$map <- renderLeaflet({
req(input$plotType == "m")
leaflet(dat) %>% addTiles() %>%
fitBounds(~min(11), ~min(54), ~max(67), ~max(24))
})
} # server
# Create Shiny object
shinyApp(ui = ui, server = server)

disable/enable selectInput and fileInput upon the selection of Advanced checkboxInput

I have a Shiny code as like this
library(datasets)
ui <-fluidPage(
titlePanel("Telephones by region"),
sidebarLayout(
sidebarPanel(
selectInput("region", "Region:",
choices=colnames(WorldPhones)), checkboxInput(inputId = "Adv",
label = strong("Advanced"),
value = FALSE),fileInput("file1", "Choose CSV File",
multiple = FALSE,accept = c("text/csv", "text/comma-separated-values,text/plain", ".csv")),
hr(),
helpText("Data from AT&T (1961) The World's Telephones.")),
mainPanel(
plotOutput("phonePlot") )))
server <- function(input, output) {
output$phonePlot <- renderPlot({
barplot(WorldPhones[,input$region]*1000,
main=input$region,
ylab="Number of Telephones",
xlab="Year")})}
shinyApp(ui, server)
I need to implement following modifications
How to disable/enable selectInput and fileInput upon the selection of Advanced checkboxInput. If user choose advanced, the selectInput must be disable (vice versa)
How to use if function for fileInput from user input (Asia,Africa….ect one per line )
To enable/disable the inputs you can use package shinyjs.
Something like this should work:
library(datasets)
library(shiny)
ui <-fluidPage(
shinyjs::useShinyjs(),
titlePanel("Telephones by region"),
sidebarLayout(
sidebarPanel(
selectInput("region", "Region:",
choices=colnames(WorldPhones)),
checkboxInput(inputId = "Adv",
label = strong("Advanced"),
value = FALSE),
fileInput("file1", "Choose CSV File",
multiple = FALSE,accept = c("text/csv", "text/comma-separated-values,text/plain", ".csv")),
hr(),
helpText("Data from AT&T (1961) The World's Telephones.")),
mainPanel(
plotOutput("phonePlot") )))
server <- function(input, output) {
observe({
if((input$Adv == TRUE)) {
shinyjs::disable("region")
shinyjs::disable("file1")
} else {
shinyjs::enable("region")
shinyjs::enable("file1")
}
})
output$phonePlot <- renderPlot({
barplot(WorldPhones[,input$region]*1000,
main=input$region,
ylab="Number of Telephones",
xlab="Year")})}
shinyApp(ui, server)

Save DT table with additional information (Shiny)

I was wondering if it is possible to save DT table content together with some additional information which is not part of the data frame/table like app version number, date of execution, sliderInput value etc.
Thank you!
Reprex below:
library(shiny)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "range", "Set range", 1, 10, 5, 1)
),
mainPanel(
DT::dataTableOutput("table")
)
)
)
server <- function(input, output) {
dfr <- data.frame(var1 <- c(1,2,3),
var2 <- c(11, 22, 33))
output$table <- DT::renderDataTable(
datatable(dfr, extensions = 'Buttons',
class="cell-border stripe",
rownames = FALSE, colnames = c("var1", "var2"),
options = list(dom = "Blfrtip",
buttond = list("copy", list(extend = "collection",
buttons = c("csv", "excel", "pdf"),
text = "Download")), pageLength=10, autoWidth = TRUE,
searchHighlight = TRUE, filter = "top"))
)
}
shinyApp(ui = ui, server = server)
You could save the contents of the data frame and the other information in a list and then save the list.
Or, any R object can have attributes which are completely arbitrary and under your control. You could set attributes of the data frame to record the information you want.
Personally, I'd use the list approach, purely because I don't like attributes.
Here's a suggestion in response to OP's request below.
library(shiny)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "range", "Set range", 1, 10, 5, 1),
actionButton("saveRds", "Save to Rds"),
actionButton("loadRds", "Load from Rds")
),
mainPanel(
DT::dataTableOutput("table"),
wellPanel(h4("Current data"), verbatimTextOutput("text")),
wellPanel(h4("File data"), verbatimTextOutput("loadedData"))
)
)
)
server <- function(input, output) {
dfr <- data.frame(var1 <- c(1,2,3),
var2 <- c(11, 22, 33))
output$table <- DT::renderDataTable(
datatable(dfr, extensions = 'Buttons',
class="cell-border stripe",
rownames = FALSE, colnames = c("var1", "var2"),
options = list(dom = "Blfrtip",
buttond = list("copy", list(extend = "collection",
buttons = c("csv", "excel", "pdf"),
text = "Download")), pageLength=10, autoWidth = TRUE,
searchHighlight = TRUE, filter = "top"))
)
listInfo <- reactive({
list("data"=dfr, "version"="WebApp Version 1.0", "runDate"=date(), "sliderValue"=input$range)
})
output$text <- renderPrint({
listInfo()
})
observeEvent(input$saveRds, {
saveRDS(listInfo(), "data.Rds")
})
fileData <- reactive({
req(input$loadRds)
readRDS("data.Rds")
})
output$loadedData <- renderPrint({
fileData()
})
}
shinyApp(ui = ui, server = server)
The way you implement "save to file" will depend on the file format: Excel files will clearly have different requirements to PDF files, for example. As a minimum effort demonstation, I've created "Save to Rds" and "Load from RDS" buttons in the sidebar and added a verbatimTextOutput to display the contents of the file when it's loaded. [I'm not sufficiently familiar with DT to know how to add the buttons in the table toolbar.]
OP's effort was pretty close: it's just that writing a list to CSV file takes a little more effort than just calling write.csv...

Shiny: subsetting a table from a textInput with multiple values

I have a simple Shiny app. The user enters a code eg: a1, b1, c1 etc in the textInput.
When only one code is listed it works great, but if the user writes two or more codes separated by a comma it doesn't.
How can the user input as many codes as they like?
library(shiny)
ui <- fluidPage(
titlePanel(""),
sidebarLayout(
sidebarPanel(
textInput(inputId = "textBox",
label = "Code Search",
placeholder = "Enter codes here seperated by a comma"),
actionButton("textSearchButton", "Generate the Table")
),
fluidRow(
tableOutput("dtOut")
)
)
)
server <- function(input, output) {
df <- data.frame(Code = paste0(letters, 1),
Description = "Something here",
Value = "Some value")
outputFunc <- function(code, df){
# # Dummy data
# code <- c('a1', 'b1', 'c1')
outTbl <- df[df$Code %in% code,]
return(list(outTbl))
}
textSearch <- eventReactive(input$textSearchButton, {
outputFunc(input$textBox, df)
})
output$dtOut <- renderTable({
textSearch()[[1]]
})
}
shinyApp(ui, server)
I simplified your code a bit:
library(shiny)
ui <- fluidPage(
titlePanel(""),
sidebarLayout(
sidebarPanel(
textInput(inputId = "textBox",
label = "Code Search",
placeholder = "Enter codes here seperated by a comma"),
actionButton("textSearchButton", "Generate the Table")
),
fluidRow(
tableOutput("dtOut")
)
)
)
server <- function(input, output) {
df <- eventReactive(input$textSearchButton, {
# outputFunc(input$textBox, df)
req(input$textBox)
codes <- unlist(strsplit(input$textBox, ", "))
return(data.frame(Code = codes,
Description = "Something here",
Value = "Some value"))
})
output$dtOut <- renderTable({
df()
})
}
shinyApp(ui, server)
Does it respond to your need ?