Related
I'm trying to make a simple Shiny app with the following features:
2 or 3 inputs:
input1: a selectInput
input2: a selectInput, conditional of input1
input3: a numeric value
The output is a ggplot where the labels inside of it change with the inputs.
I also want a default plot and a Reset button.
I made 3 files: ui.R, server.R and function.R. The last one is the one that makes the plot.
When input1 takes the values of "A" or "B", I get the desired output. The "Reset" button also seems to work fine. However, when I select "C" in input 1, it brings me back to the default plot: a plot whith the label "Nothing", instead of something like "C1 10".
I checked the code many times, but I can't get where is the problem.
Here is the code of my files:
# file ui.R
library(shiny)
shinyUI(fluidPage(
plotOutput("printsomething"),
selectInput(
inputId="input1",
label="input1",
choices=c("A","B","C"),
selected = NULL,
multiple = FALSE,
selectize = TRUE,
width = NULL,
size = NULL),
conditionalPanel(
condition = "input.input1 == 'A'",
selectInput("input2", "input2",
list("A1", "A2"))),
conditionalPanel(
condition = "input.input1 == 'C'",
selectInput("input2", "input2",
list("C1", "C2"))),
numericInput("num","Number",value=0,min=0),
actionButton("Run","Run"),
actionButton("Reset","Reset")
)
)
# file server.R
source("function.R")
library(shiny)
shinyServer(function(input, output) {
graph <- reactiveValues(data = NULL)
observeEvent(input$Run, {
graph$data <- printsomething(data,input$input1,input$input2,input$num)
})
observeEvent(input$Reset, {
graph$data <- printsomething("Nothing","NA","NA","NA")
})
output$printsomething <- renderPlot({
if (is.null(graph$data)) return(printsomething(data,"NA","NA","NA"))
graph$data
})
})
# file function.R
library(ggplot2)
data <- "Nothing"
printsomething <- function(data,input1=NA,input2=NA,num=0) {
if(is.na(input1)) {
data <- "Nothing"
} else if(input1=="A") {
if(input2=="A1") {
data <- paste("A1",num)
} else if(input2=="A2") {
data <- paste("A2",num)
}
} else if(input1=="B") {
data <- paste("B",num)
} else if(input1=="C") {
if(input2=="C1") {
data <- paste("C1",num)
} else if(input2=="C2") {
data <- paste("C2",num)
}
}
ggplot() +
geom_label(aes(x=1,y=1,label=data))
}
I'll really appreciate it if someone can help me. I'm new to Shiny.
Thanks.
As stated in the comment, unique inputId will make it work. There is a minor adjustment in the server side, and no change in the function printsomething. Try this
library(shiny)
ui <- shinyUI(fluidPage(
plotOutput("printsomething"),
selectInput(
inputId="input1",
label="input1",
choices=c("A","B","C"),
selected = NULL,
multiple = FALSE,
selectize = TRUE,
width = NULL,
size = NULL),
conditionalPanel(
condition = "input.input1 == 'A'",
selectInput("input2", "input2", list("A1", "A2"))),
conditionalPanel(
condition = "input.input1 == 'C'",
selectInput("input3", "input2", list("C1", "C2"))),
numericInput("num","Number",value=0,min=0),
actionButton("Run","Run"),
actionButton("Reset","Reset")
))
server <- shinyServer(function(input, output) {
graph <- reactiveValues(data = NULL)
observeEvent(input$Run, {
if (input$input1=="A"){ input2 = input$input2
}else if (input$input1=="C") input2 = input$input3
graph$data <- printsomething(data,input$input1,input2,input$num)
})
observeEvent(input$Reset, {
graph$data <- printsomething("Nothing","NA","NA","NA")
})
output$printsomething <- renderPlot({
if (is.null(graph$data)) return(printsomething(data,"NA","NA","NA"))
graph$data
})
})
shinyApp(ui, server)
I have two problems:
I have two dependent filters in the database, and I want to search either by player or by their ID. I also want the first filter (SelectInput) to be responsive.
If for example I enter the number 2 in the ID, I want my selectInput to display Lionel Messi automatically.
Here is the code and thank you for your answers
library(DT)
library(shinydashboard)
library(shiny)
library(shinyWidgets)
library(dplyr)
Database<- data.frame(Player=c("Cristiano Ronaldo","Lionel Messi","Neymar Jr","Cristiano Ronaldo"),ID=c(1,2,3,1))
ui<-dashboardPage(title="Application",skin="red",
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
selectInput("player",HTML('Please select your player'),choices=names(table(Database$Player))),
searchInput(inputId = "IDSEARCH", label = HTML('Or Please write the ID player'),
#placeholder = "13850",
btnSearch = icon("search"),
btnReset = icon("remove"),
width = "500px"),
DT::dataTableOutput("mtable2")
))
server <- function(input, output){
mtable2 <- reactive({filter(Database,(Player==input$player|ID==input$IDSEARCH))})
output$mtable2<-DT::renderDataTable({DT::datatable(mtable2())})
}
shinyApp(ui,server)
this is my solution to your problem. After the code I explain several things there.
library(DT)
library(shinydashboard)
library(shiny)
library(shinyWidgets)
Database <- data.frame(
Player = c("Cristiano Ronaldo", "Lionel Messi", "Neymar Jr", "Cristiano Ronaldo"),
ID = c(1, 2, 3, 1),
stringsAsFactors = FALSE
)
ui <- dashboardPage(title = "Application", skin = "red",
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
selectInput(
inputId = "player",
label = "Please select your player",
choices = unique(Database$Player)
),
searchInput(
inputId = "id",
label = "Or Please write the ID player",
btnSearch = icon("search"),
btnReset = icon("remove"),
width = "500px"
),
DT::dataTableOutput("mtable2")
)
)
server <- function(input, output, session) {
mtable2 <- reactive({
if (!isTruthy(input$id)) {
idx <- Database$Player == input$player
} else {
idx <- Database$ID == input$id
}
Database[idx, ]
})
output$mtable2 <- DT::renderDataTable({
DT::datatable(mtable2())
})
observeEvent(input$id, {
req(input$id)
selected_plyr <- unique(Database[Database$ID == input$id, ]$Player)
if (length(selected_plyr) == 0) {
showNotification("There is no player for the given ID", type = "error")
req(FALSE)
}
if (length(selected_plyr) > 1) {
showNotification("There is more than one player for a given ID", type = "error")
req(FALSE)
}
updateSelectInput(
session = session,
inputId = "player",
selected = selected_plyr
)
})
}
shinyApp(ui,server)
There is no need to wrap the input labels within HTML().
I've slightly modified how you pick the choices for the selectInput(). Note the stringsAsFactors = FALSE when creating the data frame (in R >= 4.0.0 this is not needed).
I wouldn't use a searchInput for the ID, but since it was your choice I'm keeping it here.
The isTruthy() function checks whether the value in input$id is "truthy" as the name says. Basically it checks it is not NULL, empty string, NA, etc. So, when no ID is given, we use the name in the selectInput() to filter.
The filtering could be done with {dplyr} but it is also very easy with base R (just subset notation Database[idx, ].
I added an observer to input$id that updates the selectInput(). Note you need to pass the session, which becomes an argument to your server function...
Well, just feel free to ask if you have any questions!
EDIT:
To use {dplyr} I would change the following
if (!isTruthy(input$id)) {
idx <- Database$Player == input$player
} else {
idx <- Database$ID == input$id
}
Database[idx, ]
would be rewritten as
if (!isTruthy(input$id)) {
Database %>% filter(Player == input$player)
} else {
Database %>% filter(ID == input$id)
}
and replace
selected_plyr <- unique(Database[Database$ID == input$id, ]$Player)
with
selected_plyr <- Database %>% filter(ID == input$id) %>% pull(Player) %>% unique()
I have this simple Shiny app in which I want the user to choose among four dataframes -- namely df1, df2, df3 and df4 -- the one than will be displayed using DT:dataTableOutput. The problem is I get this error:
Error in dots_list(...) : object 'df_opt' not found
And I do not know why. I thought the error messages was firing because of something related to the input$df_opt argument I previously had in eventReactive, but then I removed it by adding a button input. Nevertheless, I keep getting the same error message
Does anyone understand why I'm getting this message?
This is a reproducible example of my app:
library(shiny)
library(DT)
choices_df <- c("opt1", "opt2", "opt3", "opt4")
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = df_opt,
label = "Choose a database",
choices = df_choices,
selected = choices_df[1] ),
actionButton(
inputId = df_button,
label = "")
),
mainPanel(
DT::dataTableOutput("base_p1")
)
))
server <- function(input, output) {
df_selected <- eventReactive(
input$df_button, {
if( input$df_opt == choices_df[1] ){
df1
}
if( input$df_opt == choices_df[2] ){
df2
}
if( input$df_opt == choices_df[3] ){
df3
}
if( input$df_opt == choices_df[4] ){
df4
}
})
output$base_p1 <- DT::renderDataTable( df_selected(), filter = "top")
}
shinyApp(ui = ui, server = server)
Here is a working version of what you want to do, but with reactiveValues. So basically I find it better to create like an empty holder for reactive variable, then just assign the wanted df to it when user changes input. Code:
library(shiny)
library(DT)
df_choices <- c("opt1", "opt2", "opt3", "opt4")
df1 <- matrix(rnorm(100,10,10),10,10)
df2 <- matrix(rnorm(100,100,10),10,10)
df3 <- matrix(rnorm(100,1099,10),10,10)
df4 <- matrix(rnorm(100,100000,10),10,10)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = "df_opt",
label = "Choose a database",
choices = df_choices,
selected = choices_df[1] ),
actionButton(
inputId = "df_button",
label = "click to show")
),
mainPanel(
DT::dataTableOutput("base_p1")
)
))
server <- function(input, output) {
df <- reactiveValues(df=df1)
observeEvent(input$df_button,{
if( input$df_opt == choices_df[1] ){
df$df <- df1
}
if( input$df_opt == choices_df[2] ){
df$df <-df2
}
if( input$df_opt == choices_df[3] ){
df$df <- df3
}
if( input$df_opt == choices_df[4] ){
df$df <- df4
}
})
output$base_p1 <- DT::renderDataTable( df$df, filter = "top")
}
shinyApp(ui = ui, server = server)
EDIT: And yes, inputID should be strings, so "df_opt" and "df_button".
I have the following sample app and I need to be able to switch inputs for multiple_choice_1_source OR multiple_choice_2_type without breaking the app and hiding submit_request_button_ui and ColnamesInput when the inputs from change. Basically, the user should be able to revise the inputs after clicking the "submit" button and the app should reset to its previous state.
What I have tried:
shinyjs() - this just hides and does not clear the inputs. This means that once I press the submit_request_button then any change made to multiple_choice_2_type is still processed and reacted upon. In the actual app, I have the submit tied to very large tables. I want to prevent the fetch for
selected_data() from re-running and clear and hide the elements that were created in the first two choices.
reactive - I tried to make the observers listen to some reactive triggers that take dependencies from more than one input. I used user_input_rv to store the values etc but this fails as the observers are triggered more than once, so when I click the submit button, the if statements within the reactive({}) are triggered twice, essentially downloading each dataset more than once. Also it fails.
isolate - I have not been able to make this work. I tried multiple combinations of isolate with no success.
library(shiny)
library(tidyverse)
ui <- fluidPage(
selectizeInput(inputId ='multiple_choice_1_source',
choices = c("db1","db2","db3","db4"), # like this because we want the selected to be blank on initialisation
label = "1. Select source",
multiple = FALSE,
size = 10,
width = '100%'
)
,uiOutput(outputId="multiple_choice_2_type_ui")
,uiOutput(outputId="submit_request_button_ui")
,uiOutput(outputId="ColnamesInput")
)
server <- function(input, output)
{
user_input_rv = reactiveValues(
source_picked = NULL,
last_used_source = NULL,
type_picked = NULL,
series_picked = NULL,
last_used_series = NULL,
selected_data = NULL,
final_selection = NULL
)
observeEvent(input$multiple_choice_1_source, {
user_input_rv$source_picked <- input$multiple_choice_1_source
#change data loaded under type picked.
user_input_rv$type_picked <-
if ( input$multiple_choice_1_source == "db1"){ paste0(colnames(mtcars))
} else if ( input$multiple_choice_1_source == "db2"){ paste0(colnames(diamonds))
} else if ( input$multiple_choice_1_source == "db3"){ NULL
} else if ( input$multiple_choice_1_source == "db4"){ NULL
}
output$multiple_choice_2_type_ui <- renderUI({
selectizeInput( inputId = 'multiple_choice_2_type',
choices = paste(user_input_rv$type_picked),
label= "2. Select type",
multiple = TRUE,
size = 10,
width = '100%',
options = list( placeholder = 'Type',
maxItems =1
)
)
})
}) #first observeEvent for source type and data load.
observeEvent(input$multiple_choice_2_type,{
output$submit_request_button_ui <- renderUI({
actionButton(
inputId = "submit_request_button",
label = " Get data "
)
})
})#second observeEvent for submit_request_button_ui
observeEvent(input$submit_request_button, {
selected_data <- reactive({
if( input$multiple_choice_1_source =="db1"){
mtcars
} else if ( input$multiple_choice_1_source == "db1") {
diamonds
} else if ( input$multiple_choice_1_source == "db3") { NULL
} else if ( input$multiple_choice_1_source == "db4"){ NULL
}
})
user_input_rv$series_picked <- input$multiple_choice_2_type
user_input_rv$selected_data <- selected_data()
min_cols <- as.integer(1) # default 1
max_cols <- as.integer(length(colnames(selected_data())))
#print(max_cols)
#this renderUI creates the right-hand side column of the app COLUMNS
output$ColnamesInput <- renderUI({
lapply(min_cols:max_cols, function(z) {
column(width = 3,
offset = 0,
selectInput( inputId = paste0("cols","_",z),
label = paste(input$multiple_choice_2_type,": ",colnames(selected_data())[z]),
choices = unique(selected_data()[[z]]),
multiple = TRUE
) #selectizeInput
)
})#lapply inner
}) #renderUI for columns
}) #third observeEvent for data selection and customisation
}
shinyApp(ui = ui, server = server)
Here is a the code in which I have removed the reactive expression from and used a local variable selected_data instead.
observeEvent(input$submit_request_button, {
# selected_data <- reactive({
# browser()
selected_data <- NULL
if( input$multiple_choice_1_source =="db1"){
selected_data <- mtcars
} else if ( input$multiple_choice_1_source == "db1") {
selected_data <- diamonds
} else if ( input$multiple_choice_1_source == "db3") { selected_data <- NULL
} else if ( input$multiple_choice_1_source == "db4"){selected_data <- NULL
}
# })
user_input_rv$series_picked <- isolate(input$multiple_choice_2_type)
user_input_rv$selected_data <- selected_data
min_cols <- as.integer(1) # default 1
max_cols <- as.integer(length(colnames(selected_data)))
#print(max_cols)
#this renderUI creates the right-hand side column of the app COLUMNS
output$ColnamesInput <- renderUI({
lapply(min_cols:max_cols, function(z) {
column(width = 3,
offset = 0,
selectInput( inputId = paste0("cols","_",z),
label = paste(isolate(input$multiple_choice_2_type),": ",colnames(selected_data)[z]),
choices = unique(selected_data[[z]]),
multiple = TRUE
) #selectizeInput
)
})#lapply inner
}) #renderUI for columns
}) #third observeEvent for data selection and customisation
Now when you change the select input options the ColnamesInput do not get triggered. It gets triggered only after you click the submit button.
[EDIT]:
Might not be the best method, but I think I am able to achieve what you wanted. Also, I have taken the liberty on using the reactiveValue that was already defined in your server. Have a look at the modified server code below:
server <- function(input, output)
{
user_input_rv = reactiveValues(
source_picked = NULL,
last_used_source = NULL,
type_picked = NULL,
series_picked = NULL,
last_used_series = NULL,
selected_data = NULL,
final_selection = NULL
)
observeEvent(input$multiple_choice_1_source, {
user_input_rv$source_picked <- input$multiple_choice_1_source
###Start: To check if the source changed#########
if(!is.null(user_input_rv$last_used_source))
{
if(user_input_rv$last_used_source != user_input_rv$source_picked)
{
shinyjs::hide("ColnamesInput")
user_input_rv$last_used_source = user_input_rv$source_picked
}
}else
{
user_input_rv$last_used_source = user_input_rv$source_picked
}
###End: To check if the source changed#########
#change data loaded under type picked.
user_input_rv$type_picked <-
if ( input$multiple_choice_1_source == "db1"){ paste0(colnames(mtcars))
} else if ( input$multiple_choice_1_source == "db2"){ paste0(colnames(diamonds))
} else if ( input$multiple_choice_1_source == "db3"){ NULL
} else if ( input$multiple_choice_1_source == "db4"){ NULL
}
output$multiple_choice_2_type_ui <- renderUI({
selectizeInput( inputId = 'multiple_choice_2_type',
choices = paste(user_input_rv$type_picked),
label= "2. Select type",
multiple = TRUE,
size = 10,
width = '100%',
options = list( placeholder = 'Type',
maxItems =1
)
)
})
}) #first observeEvent for source type and data load.
observeEvent(input$multiple_choice_2_type,{
###Start: To check if the series changed#########
user_input_rv$series_picked <- input$multiple_choice_2_type
if(!is.null(user_input_rv$last_used_series))
{
if(user_input_rv$last_used_series != user_input_rv$series_picked)
{
shinyjs::hide("ColnamesInput")
user_input_rv$last_used_series = user_input_rv$series_picked
}
}else
{
user_input_rv$last_used_series = user_input_rv$series_picked
}
###End: To check if the series changed#########
output$submit_request_button_ui <- renderUI({
actionButton(
inputId = "submit_request_button",
label = " Get data "
)
})
})#second observeEvent for submit_request_button_ui
observeEvent(input$submit_request_button, {
# selected_data <- reactive({
# browser()
shinyjs::show("ColnamesInput")
selected_data <- NULL
if( input$multiple_choice_1_source =="db1"){
selected_data <- mtcars
} else if ( input$multiple_choice_1_source == "db1") {
selected_data <- diamonds
} else if ( input$multiple_choice_1_source == "db3") { selected_data <- NULL
} else if ( input$multiple_choice_1_source == "db4"){selected_data <- NULL
}
# })
user_input_rv$series_picked <- isolate(input$multiple_choice_2_type)
user_input_rv$selected_data <- selected_data
min_cols <- as.integer(1) # default 1
max_cols <- as.integer(length(colnames(selected_data)))
#print(max_cols)
#this renderUI creates the right-hand side column of the app COLUMNS
output$ColnamesInput <- renderUI({
lapply(min_cols:max_cols, function(z) {
column(width = 3,
offset = 0,
selectInput( inputId = paste0("cols","_",z),
label = paste(isolate(input$multiple_choice_2_type),": ",colnames(selected_data)[z]),
choices = unique(selected_data[[z]]),
multiple = TRUE
) #selectizeInput
)
})#lapply inner
}) #renderUI for columns
}) #third observeEvent for data selection and customisation
}
Hope it helps!
SelectInput function in ui is supposed to give me an option to choose "YES" or "NO". When "NO" is selected, it will choose the " if(("NO" %in% input$qualify_pit))" block in renderDataTable function in server and execute that perfectly. However, when I choose "YES" option, its block does not run, not displaying any table. I tried everything to get it to run its block (if(("YES" %in% input$qualify_pit))) but to no avail.
library(shiny)
library(shinythemes)
library(DT)
pit <- read.csv("PIT_STAT.csv")
pit_stat <- c("MLB_name","MLBId","LastName","FirstName","LW","W","L","Sv","BS","HLD","G","GS","CG","GF","QS","SHO","IP","BFP","H","X1B","X2B","X3B",
"HR","R","ER","SH","SF","HBP","BB","IBB","BB_noIBB","K","WP","BLK","GB","FB","LD","POPU","SB",
"CS","PKO","SVO","OUTS","K9","BB9","AVG","BABIP","HR9","GB_percent","HRperFB","ERA","KperBB",
"K_percent","BB_percent","K_minus_BB","WHIP","LD_percent","FB_percent","GBperFB")
pit_def <- c("MLB_name","MLBId","LastName","FirstName","LW","W","L","ERA","IP","H","HR","R","ER","BB","K","K9","BB9","HR9","WHIP",
"GB_percent","FB_percent","LD_percent","K_percent","BB_percent","KperBB","K_minus_BB")
ui <- shinyUI(fluidPage(
shinythemes::themeSelector(),
theme = shinytheme("paper"),
titlePanel("WSFB Stats Lab"),
fluidRow(
uiOutput("uis")
),
fluidRow(
tabsetPanel(id = "tabs",
tabPanel("Pitch Table",dataTableOutput("pitch_table"))
)
)
)
)
server <- shinyServer(function(input, output, session){
output$uis <- renderUI({
if(input$tabs == "Pitch Table")
{
pit <- read.csv("PIT_STAT.csv")
pit_stat <- c("MLB_name","MLBId","LastName","FirstName","LW","W","L","Sv","BS","HLD","G","GS","CG","GF","QS","SHO","IP","BFP","H","X1B","X2B","X3B",
"HR","R","ER","SH","SF","HBP","BB","IBB","BB_noIBB","K","WP","BLK","GB","FB","LD","POPU","SB",
"CS","PKO","SVO","OUTS","K9","BB9","AVG","BABIP","HR9","GB_percent","HRperFB","ERA","KperBB",
"K_percent","BB_percent","K_minus_BB","WHIP","LD_percent","FB_percent","GBperFB")
pit_def <- c("MLB_name","MLBId","LastName","FirstName","LW","W","L","ERA","IP","H","HR","R","ER","BB","K","K9","BB9","HR9","WHIP",
"GB_percent","FB_percent","LD_percent","K_percent","BB_percent","KperBB","K_minus_BB")
wellPanel(
checkboxGroupInput('show_vars', 'Variables to display', pit_stat, inline = TRUE, selected = pit_def),
selectInput("qualify_pit","MIN IP:",choices = c("YES","NO"))
)
}
})
output$pitch_table <- renderDataTable({
if(("YES" %in% input$qualify_pit))
{
pit <- read.csv("PIT_STAT.csv")
pit2 <- pit[pit$IP >= 162,]
DT::datatable(pit2[,input$show_vars, drop = FALSE])
}
if(("NO" %in% input$qualify_pit))
{
pit <- read.csv("PIT_STAT.csv")
DT::datatable(pit[,input$show_vars, drop = FALSE])
}
})
})
shinyApp(ui = ui, server = server)
It works with NO because the DT:datatable is the last expression of the function, therefore it is the implicit return value.
But for the YES, which is not the last evaluation (the if("NO" %in%... is), you have to explicitly use return:
return(DT::datatable(pit2[,input$show_vars, drop = FALSE]))
Otherwise you can simply use else
output$pitch_table <- renderDataTable({
if(("YES" %in% input$qualify_pit))
{
DT::datatable(pit[pit$IP >= 162,input$show_vars, drop = FALSE])
}
else
{
DT::datatable(pit[,input$show_vars, drop = FALSE])
}
})
For more details about using return you can read this thread