Reputation: 8454
I have the shiny
app below in which when the user uploads an excel file then a selectInput()
with its column names is displayed and also a url that leads to a deployed shiny
app.
This deployed shiny
app is now deployed with data<-iris
and y<-Petal.Length
but what I want to do is to pass to it the uploaded file as data
and the selected column name as y
. Then it will work (no problem with this)
How can I achieve it? I know that maybe one option would with an API and another with the pins
package but Im not sure how to do it. Of course Im open to alternative solution.
initial app
# Install and load necessary packages
library(shiny)
library(pins)
# Define the UI
ui <- fluidPage(
titlePanel("Shiny App with Link"),
column(3, fileInput("file1", "Upload File", multiple = FALSE, accept = c(".csv", ".xlsx", ".xls"))),
uiOutput("select"),
uiOutput("tab"),
)
# Define the server
server <- function(input, output,session) {
url <- a("Shinyapp", href="https://deniz4shinyml.shinyapps.io/iris/")
output$tab <- renderUI({
req(input$file1)
tagList("URL link:", url)
})
file_info <- reactive({
req(input$file1)
"xlsx" = readxl::read_excel(input$file1$datapath)
})
#####pins######
board_rsc <- pins::board_connect()
board_rsc %>% pin_write(file_info())
######pins#####
output$select<-renderUI({
req(input$file1)
selectInput("sel","select one column",choices = unique(colnames(file_info())),
selected = unique(colnames(file_info()))[1],
multiple = F)
})
}
# Run the app
shinyApp(ui, server)
deployed app
# Load required libraries
library(shiny)
library(ggplot2)
library(pins)
# Load Iris dataset
data<-iris
y<-"Petal.Length"
######pin section
#data<-pin_read(board_rsc)
#y=?
########
# Define the UI for the Shiny app
ui <- fluidPage(
titlePanel("Iris Sepal Scatterplot"),
sidebarLayout(
sidebarPanel(
),
mainPanel(
plotOutput("scatterplot")
)
)
)
# Define the server logic for the Shiny app
server <- function(input, output) {
output$scatterplot <- renderPlot({
ggplot(data, aes(x = Sepal.Length, y = data[[y]])) +
geom_point()
})
}
# Run the Shiny app
shinyApp(ui, server)
Upvotes: 1
Views: 131
Reputation: 21349
While the above answers look fine, I will give another answer using pins
package.
First pin a dataset to be used by deployed app.
library(pins)
os <- Sys.info()[['sysname']]
if (os == "Windows"){ ####### local laptop folder for initial set-up
my_psr_board <- board_folder("C:/Users/yourpath")
} else if (os == "Linux"){ ############## for publishing on Posit WorkBench
## connect to Posit Connect
my_psr_board <- pins::board_connect()
}
df_pinned <- mtcars
pins::pin_write(my_psr_board, x = df_pinned, name = "df_pinned", type="rds")
Then you deploy your app which uses this pinned dataset named df_pinned
.
library(shiny)
library(ggplot2)
library(pins)
library(dplyr)
# Define the UI for the Shiny app
ui <- fluidPage(
titlePanel("Iris Sepal Scatterplot"),
sidebarLayout(
sidebarPanel( uiOutput("xy")
),
mainPanel(
plotOutput("scatterplot")
)
)
)
os <- Sys.info()[['sysname']]
if (os == "Windows"){ ####### local laptop folder for initial set-up
user = ""
my_psr_board <- board_folder("C:/Users/yourpath")
} else if (os == "Linux"){ ############## for publishing on Posit WorkBench
user = "<userID>/" ### replace userID with your actual user ID
## connect to Posit Connect
my_psr_board <- pins::board_connect()
}
# Define the server logic for the Shiny app
server <- function(input, output) {
data_pinned <- reactive({
dataset <- "df_pinned"
pin_read(my_psr_board,paste0(user,dataset))
})
output$xy <- renderUI({
req(data_pinned())
tagList(
selectInput("xvar", "Choose variable for x-axis", choices = names(data_pinned())),
selectInput("yvar", "Choose variable for y-axis", choices = names(data_pinned()), selected = names(data_pinned())[2])
)
})
data <- reactive({
req(data_pinned(),input$xvar,input$yvar)
df <- data_pinned() %>% dplyr::select(input$xvar,input$yvar)
df
})
output$scatterplot <- renderPlot({
req(data(),input$xvar,input$yvar)
ggplot(data(), aes(x = .data[[input$xvar]], y = .data[[input$yvar]])) +
geom_point()
})
}
# Run the Shiny app
shinyApp(ui, server)
Next set-up your initial app that allows user to select the csv file to be pinned.
# Install and load necessary packages
library(shiny)
library(pins)
library(readxl)
# Define the UI
ui <- fluidPage(
titlePanel("Shiny App with Link"),
column(3, fileInput("file1", "Upload File", multiple = FALSE, accept = c(".csv"))),
# uiOutput("select"),
uiOutput("tab"),
)
os <- Sys.info()[['sysname']]
if (os == "Windows"){ ####### local laptop folder for initial set-up
my_psr_board <- board_folder("C:/Users/yourpath")
} else if (os == "Linux"){ ############## for publishing on Posit WorkBench
## connect to Posit Connect
my_psr_board <- pins::board_connect()
}
# Define the server
server <- function(input, output,session) {
url <- a("Shinyapp", href="https://deniz4shinyml.shinyapps.io/iris/")
output$tab <- renderUI({
req(input$file1)
tagList("URL link:", url)
})
file_info <- reactive({
req(input$file1)
newdf = read.csv(input$file1$datapath)
newdf
})
observeEvent(file_info(), {
df_pinned <- file_info()
pins::pin_write(my_psr_board, x = df_pinned, name = "df_pinned", type="rds")
})
}
# Run the app
shinyApp(ui, server)
Please note that in this set-up I am selecting x and y variables in the deployed app. You can change it to the initial app. Also, you can easily modify to select other files other than csv. Also, you need to include the correct URL of the deployed app.
Upvotes: 1
Reputation: 84659
To elaborate my comment, using parameters, your initial app would look like:
ui <- fluidPage(
titlePanel("Shiny App with Link"),
fileInput("file1", "Upload File", multiple = FALSE, accept = c(".csv", ".xlsx", ".xls")),
uiOutput("select"),
uiOutput("tab")
)
server <- function(input, output,session) {
output$tab <- renderUI({
jsonData <- jsonlite::toJSON(Data())
parameters <-
paste0("data=", URLencode(jsonData), "&y=", URLencode(input$sel))
url <- paste0("https://deniz4shinyml.shinyapps.io/iris/?", parameters)
tags$a("Shinyapp", href = url)
}) |> bindEvent(input$sel)
Data <- eventReactive(input$file1, {
path <- input$file1$datapath
ext <- tools::file_ext(path)
switch(
ext,
xlsx = readxl::read_xlsx(path),
xls = readxl::read_xls(path),
csv = read.csv(path)
)
})
output$select <- renderUI({
selectInput(
"sel", "select one column", choices = colnames(Data()),
multiple = FALSE
)
}) |> bindEvent(Data())
}
And the deployed app would look like:
ui <- fluidPage(
titlePanel("Iris Sepal Scatterplot"),
sidebarLayout(
sidebarPanel(),
mainPanel(
plotOutput("scatterplot")
)
)
)
server <- function(input, output, session) {
Data <- reactiveVal()
y <- reactiveVal()
observe({
query <- parseQueryString(session$clientData$url_search)
Data(jsonlite::fromJSON(query$data))
y(query$y)
})
output$scatterplot <- renderPlot({
ggplot(Data(), aes(x = Sepal.Length, y = .data[[y()]])) +
geom_point()
}) |> bindEvent(Data(), y())
}
But as I said, if the dataset is large, this would generate a long url which is not acceptable in some browsers. One way to reduce is to send only the selected column.
Or, instead of using a URL parameter for the data, upload the data to a Gist with the gistr package, put the Gist identifiant in a URL parameter, and in the deployed app, use gistr to get the data from this Gist.
If you don't have a Github account, you can use the JSON blob website to store the data and retrieve it in the deployed app. Below I show how to do so by using the httr2 package to perform the HTTP requests.
Initial app:
library(shiny)
library(httr2)
ui <- fluidPage(
titlePanel("Shiny App with Link"),
fileInput("file1", "Upload File", multiple = FALSE, accept = c(".csv", ".xlsx", ".xls")),
uiOutput("select"),
uiOutput("tab")
)
server <- function(input, output,session) {
output$tab <- renderUI({
dataToSend <- list(data = Data(), y = input$sel)
# send the data to jsonBlob
req <- request("https://jsonblob.com/api/jsonBlob")
post <- req |>
req_body_json(dataToSend) |>
req_perform()
# get the url of the posted data
blobURL <- resp_header(post, "location")
#
parameters <- paste0("url=", URLencode(blobURL))
url <- paste0("https://deniz4shinyml.shinyapps.io/iris/?", parameters)
tags$a("Shinyapp", href = url)
}) |> bindEvent(input$sel)
Data <- eventReactive(input$file1, {
path <- input$file1$datapath
ext <- tools::file_ext(path)
switch(
ext,
xlsx = readxl::read_xlsx(path),
xls = readxl::read_xls(path),
csv = read.csv(path)
)
})
output$select <- renderUI({
selectInput(
"sel", "select one column", choices = colnames(Data()),
multiple = FALSE
)
}) |> bindEvent(Data())
}
shinyApp(ui, server)
Deployed app:
library(shiny)
library(httr2)
ui <- fluidPage(
titlePanel("Iris Sepal Scatterplot"),
sidebarLayout(
sidebarPanel(),
mainPanel(
plotOutput("scatterplot")
)
)
)
server <- function(input, output, session) {
Data <- reactiveVal()
y <- reactiveVal()
observe({
query <- parseQueryString(session$clientData$url_search)
url <- query$url
if(!is.null(url)) {
# get the contents of the blob at this url
List <- request(url) |> req_perform() |> resp_body_json()
Data(List$data)
y(List$y)
} else {
print("hmm.. strange!")
}
})
output$scatterplot <- renderPlot({
ggplot(Data(), aes(x = Sepal.Length, y = .data[[y()]])) +
geom_point()
}) |> bindEvent(Data(), y())
}
Upvotes: 1
Reputation: 20399
You could rewrite your deployed app such that
POST
requests.POST
request you receive your data and your column name and store it in a global variable.Below a POC.
library(shiny)
library(htmltools)
library(jsonlite)
params <- list()
ui <- function(req) {
if (identical(req$REQUEST_METHOD, "GET")) {
## Handle the "normal" app
fluidPage(
fluidRow(
column(width = 8L,
tableOutput("table")
),
column(width = 2L,
tags$p("Passed column name:",
textOutput("column", inline = TRUE)))
)
)
} else if (identical(req$REQUEST_METHOD, "POST")) {
# Handle the POST
body <- rawToChar(req$rook.input$read(-1))
## store the parameters
params <<- c(params, latest = list(fromJSON(body)))
httpResponse(
status = 303L,
headers = list(Location = paste0("http://", req$HTTP_HOST))
)
}
}
attr(ui, "http_methods_supported") <- c("GET", "POST")
server <- function(input, output, session) {
### store params with unique id to allow for a per session copy of data
params[[session$token]] <- params$latest
params$latest <<- NULL
output$table <- renderTable({
shiny::validate(
need(!is.null(params[[session$token]]$data),
"No data provided, app should be kicked off with a POST")
)
params[[session$token]]$data
})
output$column <- renderText({
shiny::validate(
need(!is.null(params[[session$token]]$column),
"No column name provided, app should be kicked off with a POST")
)
params[[session$token]]$column
})
}
shinyApp(ui, server)
library(shiny)
library(httr)
library(jsonlite)
library(magrittr)
jscode <- HTML("
Shiny.addCustomMessageHandler('redirect', function(message) {
window.location = message.location;
});")
ui <- fluidPage(
tags$head(tags$script(jscode)),
fluidRow(
column(width = 4L,
selectInput("dataset", "Select Data Source", c("mtcars", "iris"))
),
column(width = 4L,
selectInput("column", "Select Column", names(mtcars))
),
column(width = 4L, style = "margin-top: 25px;",
actionButton("send", "Send Data")
)
)
)
server <- function(input, output, session) {
my_data <- reactive({
get(req(input$dataset))
})
observe({
updateSelectInput(session, "column", choices = names(my_data()))
})
observe({
deployed_app_url <- "http://127.0.0.1:7770"
rq <- POST(deployed_app_url,
config(followlocation = 0),
body = list(data = my_data(),
column = input$column),
encode = "json")
session$sendCustomMessage("redirect",
list(location = rq$headers$location))
}) %>%
bindEvent(input$send)
}
shinyApp(ui, server)
If you do not want to move from the local app to the deployed app, simply change your local app slightly:
ui <- fluidPage(
tags$head(tags$script(jscode)),
fluidRow(
column(width = 4L,
selectInput("dataset", "Select Data Source", c("mtcars", "iris"))
),
column(width = 4L,
selectInput("column", "Select Column", names(mtcars))
),
column(width = 4L, style = "margin-top: 25px;",
actionButton("send", "Send Data")
)
),
fluidRow(
column(width = 12L,
uiOutput("lnk"))
)
)
server <- function(input, output, session) {
my_data <- reactive({
get(req(input$dataset))
})
observe({
updateSelectInput(session, "column", choices = names(my_data()))
})
output$lnk <- renderUI({
deployed_app_url <- "http://127.0.0.1:7770"
rq <- POST(deployed_app_url,
config(followlocation = 0),
body = list(data = my_data(),
column = input$column),
encode = "json")
tags$a(href = rq$headers$location, "Deployed App", target = "_blank")
# session$sendCustomMessage("redirect",
# list(location = ))
}) %>%
bindEvent(input$send)
}
N.B. The renderUI
is an overkill, as the link to the deployed app won't change anyways. However, this workflow makes sure that we send the data at least once before proceeding, so making the link conditional on an inital POST makes sense to me.
Upvotes: 1