I'm building a new Shiny app and I although it works, the code is too extensive and it is not as reactive as I wanted. Right now I have at server.R
dayData <- reactive({...})
pday <- function(data){...}
output$distPlotday <- renderPlot(function() {print(pday(dayData)) })
and at ui.R
plotOutput("distPlotday")
for each variable in
checkboxGroupInput("checkGroup", "Dataset Features:",
choices = c("day","hour","source","service","relevancy","tollfree","distance","similarity"))
But I wish I could do something more fancy like this:
shinyServer(function(input, output, session) {
...
output$sliders <- renderUI({
lapply(input$checkGroup, function(i) {
fluidRow(
column(4,
selectInput(paste0('trans',i), i,
choices = c('linear','quadratic','sine')) ,
conditionalPanel(
condition = "input[[paste0('trans',i)]]== 'sine'",
withMathJax(),
h5("Put in your initial kicks for: $$a*\\sin(b*x+c)+d$$"),
textInput3(paste0('trans',i,'a'), h5('A:'),
value = 10),
textInput3(paste0('trans',i,'b'), h5('C:'),
value = 1),
textInput3(paste0('trans',i,'c'), h5('D:'),
value = 0.1),
helpText("Note: B has already been picked up")
),
plotOutput(paste0('distPlot',i))
))
})
})
...
}))
.
shinyUI(navbarPage("",
tabPanel("Data",
sidebarLayout(
sidebarPanel(
checkboxGroupInput("checkGroup", label = h5("Dataset Features:"),
choices = c("day","hour","source","service","relevancy","tollfree","distance","similarity"), inline = F,
selected = c("day","hour","source","service","relevancy","tollfree","distance","similarity"))
),
mainPanel(
numericInput("obs", label = h5("Number of observations to view"), 15, min = 10, max = 20, step = 1),
tableOutput("view")
)
)
),
tabPanel("Variable transformation", uiOutput(outputId = "sliders"))
))
Using lapply and renderUI. But
plotOutput(paste0('distPlot',i))
is not ploting anything, and the
conditionalPanel(condition = "input[[paste0('trans',i)]]== 'sine'",...)
don't show up conditionally, instead it's always there.
Any suggestions? Thanks for the help!
I wasn't sure what you wanted to do with the plotOutput call, since as far as I can tell there wasn't any example code included that linked to it. However, I managed to put together a working example for dynamically showing/hiding the selection boxes and text fields for the sine parameters.
I found it easier to implement by moving the ui generation from the server into the ui. This gets around the problem of conditions being evaluated for input that doesn't exist yet, since on the ui side the functions are just writing html.
An additional benefit is that this way the input fields don't get re-rendered every time the checkbox input changes - this means that their values persist through toggling them on and off, and that enabling or disabling a single variable won't cause the others' values to reset.
The code:
library(shiny)
vars <- c("day","hour","source","service","relevancy",
"tollfree","distance","similarity")
ui <- shinyUI(navbarPage("",
tabPanel("Data",
sidebarLayout(
sidebarPanel(
checkboxGroupInput("checkGroup", label = h5("Dataset Features:"),
choices = c("day","hour","source","service","relevancy",
"tollfree","distance","similarity"), inline = F,
selected = c("day", "hour","source","service","relevancy",
"tollfree","distance","similarity")
)
),
mainPanel(
numericInput("obs", label = h5("Number of observations to view"),
value = 15, min = 10, max = 20, step = 1),
tableOutput("view")
)
)
),
tabPanel("Variable transformation",
fluidRow(
column(4,
lapply(vars, function(i) {
div(
conditionalPanel(
condition =
# javascript expression to check that the box for
# variable i is checked in the input
paste0("input['checkGroup'].indexOf('", i,"') != -1"),
selectInput(paste0('trans',i), i,
choices = c('linear','quadratic','sine'))
),
conditionalPanel(
condition =
paste0("input['trans", i, "'] == 'sine' ",
" && input['checkGroup'].indexOf('", i,"') != -1"),
withMathJax(),
h5("Put in your initial kicks for: $$a*\\sin(b*x+c)+d$$"),
textInput(paste0('trans',i,'a'), h5('A:'), value = 10),
textInput(paste0('trans',i,'b'), h5('C:'), value = 1),
textInput(paste0('trans',i,'c'), h5('D:'), value = 0.1),
helpText("Note: B has already been picked up")
)
)
})
)
)
)
))
server <- shinyServer(function(input, output, session) {})
shinyApp(ui, server)
PS. For dynamically showing/hiding or enabling/disabling objects, the package shinyjs by Dean Attali (link) has some nice tools that allow you to call basic javascript by using only R syntax.
Related
Overview
Hello, I am trying to work with displaying different plots using checkboxes within tabsetPanels. I am working with a dynamic amount number of panels, so that is the reason for creating the UI contents within the server portion.
Ideal Output
For each tabPanel:
iris plot outputs if no checkboxes are selected
mtcars plot outputs if Box One is selected
islands plot outputs if Box Two is selected
sleep plot outputs if both Box One and Box Two are selected
What I have tried
-I have tried to use condtionalPanels to try to & capture the cases. I was under the impression that the value returns 'TRUE' if checked & 'FALSE' if unchecked, however I receive NULL for each of the boxed values, even if I set the default value to be checked.
-I believe my underlying issue is my lack of ability to trigger the dynamic checkboxes for each tabPanel
Disclaimer
This is a reproducible example, the default values of originally created tabPanels is set to 5. I did not accommodate proper code if the value were to change for the sake of simplicity.
Sample Code:
ui <- navbarPage(title="Dynamic tabsetPanels",id="navbar",
tabPanel("Home",
textInput(inputId = "numPanels",
label = "Enter # of Panels to produce",
value = 5)
),tabPanel("Analysis",
tabsetPanel(id = "tabs"))
)
server <- function(input, output) {
plotOne = renderPlot({plot(iris)})
plotTwo = renderPlot({plot(mtcars)})
plotThree = renderPlot({plot(islands)})
plotFour = renderPlot({plot(sleep)})
observe({
req(input$numPanels)
lapply(1:input$numPanels,function(i){
tabName = paste("Tab",i,sep=" ")
first = paste0("first",i)
second = paste0("second",i)
appendTab(inputId = "tabs",
tab = tabPanel(
tabName,
fluidPage(
sidebarLayout(
sidebarPanel(
#side-panel code
h2("Features"),
checkboxInput(inputId=first,label="Box One"),
checkboxInput(inputId=second,label="Box Two")
),mainPanel(
#output when nothing clicked
conditionalPanel(
condition = "!glue(input.{first} && !glue(input.{second})",
plotOutput(iris)
),
#output when box one is clicked
conditionalPanel(
condition = "glue(input.{first})",
plotOutput(mtcars)
),
#output when box two is clicked
conditionalPanel(
condition = "glue(input.{second})",
plotOutput(islands)
),
#output when box one and two are clicked
conditionalPanel(
condition = "glue(input.{first}) && glue(input.{second})",
plotOutput(sleep)
)
)
)
)
)
)
})
})
}
shinyApp(ui=ui, server=server)
Any suggestions would be greatly appreciated!
First issue with your code is the use of glue to create your conditions, i.e. you have to do e.g. condition = glue("input.{first}") instead of condition = "glue(input.{first})" to evaluate the glue string. Second issue is that in the plotOutputs you have to use the names of the outputs, e.g. plotOutput("plotOne") instead of plotOutput(iris). Finally, even after fixing these issues your app will not work as desired as you can't use outputs with the same id in several places or tabs, i.e. you get a duplicated id error. To fix that you also have to create a dynamic list of outputs so that the ids are unique.
library(shiny)
library(glue)
ui <- navbarPage(
title = "Dynamic tabsetPanels", id = "navbar",
tabPanel(
"Home",
textInput(
inputId = "numPanels",
label = "Enter # of Panels to produce",
value = 5
)
), tabPanel(
"Analysis",
tabsetPanel(id = "tabs")
)
)
server <- function(input, output) {
observe({
req(input$numPanels)
lapply(1:input$numPanels, function(i) {
output[[paste0("plotOne", i)]] <- renderPlot(plot(iris))
output[[paste0("plotTwo", i)]] <- renderPlot(plot(mtcars))
output[[paste0("plotThree", i)]] <- renderPlot(plot(islands))
output[[paste0("plotFour", i)]] <- renderPlot(plot(sleep))
})
})
observe({
req(input$numPanels)
lapply(1:input$numPanels, function(i) {
tabName <- paste("Tab", i, sep = " ")
first <- paste0("first", i)
second <- paste0("second", i)
appendTab(
inputId = "tabs",
tab = tabPanel(
tabName,
fluidPage(
sidebarLayout(
sidebarPanel(
# side-panel code
h2("Features"),
checkboxInput(inputId = first, label = "Box One"),
checkboxInput(inputId = second, label = "Box Two")
), mainPanel(
# output when nothing clicked
conditionalPanel(
condition = glue("!input.{first} && !input.{second}"),
plotOutput(paste0("plotOne", i))
),
# output when box one is clicked
conditionalPanel(
condition = glue("input.{first}"),
plotOutput(paste0("plotTwo", i))
),
# output when box two is clicked
conditionalPanel(
condition = glue("input.{second}"),
plotOutput(paste0("plotThree", i))
),
# output when box one and two are clicked
conditionalPanel(
condition = glue("input.{first} && input.{second}"),
plotOutput(paste0("plotFour", i))
)
)
)
)
)
)
})
})
}
shinyApp(ui = ui, server = server)
I am building a Shiny App where users can filter out certain projects. I want the project names to appear in the dropdown only if they appear within a certain date range.
I've been able to populate the selectize menu and have been able to make it so users can select all or remove all projects (from the answer to a question I asked previously). However, now that I'm trying to make these names reactive to the date, the observeEvent code from my previous question crashes. I tried to wrap it in a reactive expression, but then nothing happens.
How do I make my projects filterable by date while still keeping the select all and remove all functionality?
library(shiny)
library(plotly)
library(shinyjs)
library(shinydashboard)
library(shinyWidgets)
library(dplyr)
library(htmltools)
library(lubridate)
ui = fluidPage(
tabsetPanel(
tabPanel("View 1", fluid = TRUE,
sidebarLayout(
sidebarPanel(
h4("Select Your Desired Filters"),
div(id = "inputs",
dateRangeInput(
inputId = "date_filter",
label = "Filter by Month and Year",
start = today(),
end = (today() + 90),
min = "2021-04",
max = NULL,
format = "yyyy-mm",
startview = "month",
weekstart = 0,
language = "en",
separator = " to ",
width = NULL,
autoclose = TRUE
),
br()),
h5("Include/Exclude Specific Projects"),
selectizeInput(inputId = "filter_by_project",
label = "Filter by Project",
choices = sort(unique(test$project)),
multiple = TRUE,
selected = sort(unique(test$project))),
actionButton(inputId = "remove_all",
label = "Unselect All Projects", style = "color: #FFFFFF; background-color: #CA001B; border_color: #CA001B"),
actionButton(inputId = "add_all",
label = "Select All Projects", style = "color: #FFFFFF; background-color: #CA001B; border_color: #CA001B")
),
mainPanel(
)
)
)
)
)
server = function(input, output, session) {
#Here's the dataset
test <- tibble(project = c("Justin", "Corey","Sibley"),
date = ymd(c("2021-04-20", "2021-04-01", "2021-05-05")),
april_2021 = c(10, 100, 101),
may_2021 = c(1, 4, 7))
#I want users to be able to filter the list of projects by date, which should update the selectize options
test <- reactive({
test %>%
dplyr::filter(date >= input$date_filter[1],
date <= input$date_filter[2])
})
observeEvent(input$remove_all, {reactive({
updateSelectizeInput(session,"filter_by_project",choices=sort(unique(test()$project)),
selected=NULL, options = list(placeholder="Please Select at Least One Project")
)
})
})
observeEvent(input$add_all, {reactive({
updateSelectizeInput(session,"filter_by_project",choices=sort(unique(test()$project)), selected=sort(unique(test()$project)) )
})
})
}
shinyApp(ui = ui, server = server)
You have to major problems. First is using the same name for your input data.frame and for your reactive element. You've called them both test which causes confusion as to whether you are trying to use the data.frame or the reactive object. You should use different names. The second problem is you do not need to use reactive() for your observeEvents() calls. You just need to put the code you want to run in a block.
Fixing these problems, your server functon should look more like this
server = function(input, output, session) {
#Here's the dataset
testdata <- tibble(project = c("Justin", "Corey","Sibley"),
date = ymd(c("2021-04-20", "2021-04-01", "2021-05-05")),
april_2021 = c(10, 100, 101),
may_2021 = c(1, 4, 7))
#I want users to be able to filter the list of projects by date, which should update the selectize options
test <- reactive({
testdata %>%
dplyr::filter(date >= input$date_filter[1],
date <= input$date_filter[2])
})
observeEvent(input$remove_all, {
updateSelectizeInput(session,"filter_by_project", choices=sort(unique(test()$project)),
selected=NULL, options = list(placeholder="Please Select at Least One Project")
)
})
observeEvent(input$add_all, {
updateSelectizeInput(session,"filter_by_project", choices=sort(unique(test()$project)), selected=sort(unique(test()$project)) )
})
}
I'm a beginner in shiny app. so first I tried to build an app to calculate distance covered using time taken and speed. I got error as "argument of length zero". Then I entered req(input$num_time,input$select_time,input$slider_speed)this command after that error message is not displaying and also not getting output also. I'm not able to find where I gone wrong. Please help me in getting the output. I have shown the code I used below:
library(shiny)
#library(car)
ui <- fluidPage(
titlePanel("terrain model"),
sidebarLayout(
sidebarPanel(
helpText("To create a suitable model"),
br(),
numericInput("num_time",
label = h6("Enter time"),
value = 1),
selectInput("select_time",
label = h6(""),
choices = list("Hours"= 1,"Minutes" = 2),
selected = "1"),
sliderInput("Speed",
label = "Speed:",
min = 2, max = 4.5, value = 2),
br(),
actionButton("action",label="Refresh & Calculate")
),
mainPanel(
textOutput("text_distance")
)
)
)
server <- function(input, output) {
values <- reactiveValues()
#calculate distance travelled
observe({input$action_Calc
values$int <- isolate({ input$num_time * recode(input$select_time,"1='60';2='1'")*input$slider_speed
})
})
#Display values entered
output$text_distance <- renderText({
req(input$num_time,input$select_time,input$slider_speed)
if(input$action_Calc==0)""
else
paste("Distance:", round(values$int,0))
})
}
shinyApp(ui, server)
I don't find any use of "Refresh & Calculate" button since the calculation is performed as soon as any of the input changes.
You can try this code :
ui <- fluidPage(
titlePanel("terrain model"),
sidebarLayout(
sidebarPanel(
helpText("To create a suitable model"),
br(),
numericInput("num_time",
label = h6("Enter time"),
value = 1),
selectInput("select_time",
label = h6(""),
choices = list("Hours"= 1,"Minutes" = 2),
selected = "1"),
sliderInput("Speed",
label = "Speed:",
min = 2, max = 4.5, value = 2),
br(),
actionButton("action",label="Refresh & Calculate")
),
mainPanel(
textOutput("text_distance")
)
)
)
server <- function(input, output) {
#Display values entered
output$text_distance <- renderText({
val <- input$num_time/dplyr::recode(input$select_time,"1"=1,"2"=60)*input$Speed * 1000
paste("Distance:", round(val,0), 'meters')
})
}
shinyApp(ui, server)
I have achieved to define two interconnected or mutually dependent input in my shiny app. Right now, my problem is to set a specific initial value for these slider and numeric inputs. It seems that they always start with the minimum value, even I don't now exactly why. How can I indicate a unique starting point or an initial value for these input parameters?
I have attached a simplified part of my app in order to provide you a reproducible version of my problem here:
"ui.R"
library(shiny)
shinyUI(fluidPage(
uiOutput("Param_s"),
uiOutput("Param_n")
))
and the "server.R"
library(shiny)
shinyServer(
function(input,output) {
# Mutually dependent slider and numeric inputs
output$Param_s = renderUI({
sliderInput(inputId = "param_slide",
label= "My input parameter",
value= input$param_numeric,
min=1,
max=200)
})
output$Param_n = renderUI({
numericInput(inputId = "param_numeric",
label= "My input parameter",
value= input$param_slide,
min=1,
max=200)
})
})
I tried various things to fix the initial value but eventually nothing worked. Any help would be appreciated!!
wow! I got it guys! You should only update the two input objects at the same time and up to the same value. It means adding these two lines solved my problem to set the initial value to 60 for example:
updateSliderInput(session,"param_slide", value = 60)
updateNumericInput(session,"param_numeric", value = 60 )
Therefore the whole "server.R" would be like this:
#
library(shiny)
shinyServer(
function(input,output,session) {
# Mutually dependent slider and numeric inputs
output$Param_s = renderUI({
sliderInput(inputId = "param_slide",
label= "My input parameter",
value= input$param_numeric,
min=1,
max=200)
})
output$Param_n = renderUI({
numericInput(inputId = "param_numeric",
label= "My input parameter",
value= input$param_slide,
min=1,
max=200)
})
updateSliderInput(session,"param_slide", value = 60)
updateNumericInput(session,"param_numeric", value = 60 )
})
You should only be aware of adding these updates with an
observeEvent()
when you have these input objects on the other tabs. In my case which I am using "sidebarMenu" I used a simple line of code as this:
observeEvent(input$sidebar_id =="tab1",{
updateSliderInput(session,"param_slide", value = 60)
updateNumericInput(session,"param_numeric", value = 60 )
})
Coming from here.
You should try to avoid re-rendering (renderUI) if possible.
It's faster to update existing inputs:
library(shiny)
ui <- fluidPage(
sliderInput(
inputId = "param_slide",
label = "My input parameter",
value = 60,
min = 1,
max = 200
),
numericInput(
inputId = "param_numeric",
label = "My input parameter",
value = 60,
min = 1,
max = 200
)
)
server <- function(input, output, session) {
observeEvent(input$param_numeric, {
updateSliderInput(session, "param_slide", value = input$param_numeric)
}, ignoreInit = TRUE)
observeEvent(input$param_slide, {
updateNumericInput(session, "param_numeric", value = input$param_slide)
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
Furthermore, please check this and this.
New to shiny. I'm creating a data exploration app for some experimental data.
I first select a variable to subset by and then use sliders to select a range for the said variables (shown in image). The subsetted data is overlaid on the original plot in red.
I want the sliderInput to be aligned at the same level as the selectInput. What's the best way to do that? I also want to add a checkbox or radio button for these variables which if selected would add a secondary plot (or secondary axis - I know, not recommended but that's what is desired). I haven't implemented that yet but putting that here since that would require the same aligning.
CODE
The arguments of sliderInput and selectInput have been removed for brevity. zdat() is a reactive data.table.
UI
fluidRow(
column(4,
uiOutput("subsetZoomPlot_x"), # Select x-axis for SortZoomPlot
uiOutput("subsetZoomPlot_y"), # Select y-axis for SortZoomPlot
uiOutput("subsetZoomPlot_var1"), # Subset data by var1
uiOutput("subsetZoomPlot_var2"), # Subset data by var2
uiOutput("subsetZoomPlot_var3") # Subset zoomplot data by var3
),
column(4,
uiOutput("subsetZoomPlot_var1_slider"), # range for subsetZoomPlot_var1 (slider input)
uiOutput("subsetZoomPlot_var2_slider"), # range for subsetZoomPlot_var1 (slider input)
uiOutput("subsetZoomPlot_var3_slider") # range for subsetZoomPlot_var1 (slider input)
)
),
fluidRow(
plotOutput("subsetZoomPlot")
)
SERVER
output$subsetZoomPlot_x <- renderUI({
selectInput(inputId = "subsetZoomPlot_x", ...)
})
output$subsetZoomPlot_y <- renderUI({
selectInput(inputId = "subsetZoomPlot_y", ...)
})
output$subsetZoomPlot_var1 <- renderUI({
selectInput(inputId = "subsetZoomPlot_var1", ...)
})
output$subsetZoomPlot_var2 <- renderUI({
selectInput(inputId = "subsetZoomPlot_var2", ...)
})
output$subsetZoomPlot_var3 <- renderUI({
selectInput(inputId = "subsetZoomPlot_var3", ...)
})
output$subsetZoomPlot_var1_slider <- renderUI({
vmin <- floor(min(zdat()[, get(input$subsetZoomPlot_var1)]))
vmax <- ceiling(max(zdat()[, get(input$subsetZoomPlot_var1)]))
sliderInput(...)
})
output$subsetZoomPlot_var2_slider <- renderUI({
vmin <- floor(min(zdat()[, get(input$subsetZoomPlot_var2)]))
vmax <- ceiling(max(zdat()[, get(input$subsetZoomPlot_var2)]))
sliderInput(...)
})
output$subsetZoomPlot_var3_slider <- renderUI({
vmin <- floor(min(zdat()[, get(input$subsetZoomPlot_var3)]))
vmax <- ceiling(max(zdat()[, get(input$subsetZoomPlot_var3)]))
sliderInput(...)
})
EDIT:
I added my current code for the UI section. It already has fluidrow(column(), column()) for the layout. My problem is that I want the slider for var1 (rh in the screenshot) to be aligned at the same level as the dropdown where rh is selected (selectInput) and the same for the other two variables. This means the second column needs to be vertically displaced. Is the only way to do this using multiple fluidrow statements for each combination of selectInput and sliderInput?
All of the layout issues are things that need to be addressed in the UI section of your shiny app code.
From what I can tell it looks like the code you've included with your question is in the server section since it seems that you have dynamic inputs in your app.
Here is some simple UI code that aligns a select input with a slider input
library(shiny)
ui <- fluidPage(title = 'Example',
fixedRow(
column(width = 2,
selectInput('input1_choice', 'Select Plot variable 1',
choices = letters[11:20])),
column(width = 2,
sliderInput('input1_val', 'Select Variable 1 Value',
0, 10, 5)
)
),
fixedRow(
column(width = 2,
selectInput('input2_choice', 'Select Plot variable 2',
choices = letters[1:10])
),
column(width = 2,
sliderInput('input2_val', 'Select Varaible 2 Value',
0, 10, 5)
)
)
)
shinyApp(ui, server = function(input, output) { })
So each fixedRow call creates a row in the UI, and each column call within a fixedRow call creates a column in that row to fill with a UI object.
As you change the types of UI objects the height of that row can change but it won't overlap or bleed into another row demonstrated here:
library(shiny)
ui <- fluidPage(title = 'Example',
fixedRow(
column(width = 2,
selectInput('input1_choice', 'Select Plot Variable 1',
choices = letters[11:20])),
column(width = 2,
radioButtons('input1_val', 'Select Variable 1 Value',
choices = LETTERS[11:20])
)
),
fixedRow(
column(width = 2,
selectInput('input2_choice', 'Select Plot Variable 2',
choices = letters[1:10])
)
),
fixedRow(
column(width = 2,
selectInput('input3_choice', 'Select Plot Variable 3',
choices = letters[1:10])
),
column(width = 2,
sliderInput('input3_val', 'Select Variable 3 Value',
0, 10, 5)
)
)
)
shinyApp(ui, server = function(input, output) { })
The only difference between these examples and what you'll need is to replace the explicit UI object calls I've made (selectInput for example) with uiOutput calls.
Like some comments have said there's documentation out there. I've found https://shiny.rstudio.com/reference/shiny/latest/ to be especially useful.
Not sure it is the best solution, but you can use columns in your UI :
fluidRow(
column(4,selectInput("select_x", "Select X Axis", ...)),
column(8),
column(4,selectInput("select_y", "Select Y Axis", ...)),
column(8)
column(4, selectInput("subsetZoomPlot_var1", ...)),
column(4, sliderInput(...)),
column(4),
column(4, selectInput("subsetZoomPlot_var2", ...)),
column(4, sliderInput(...)),
column(4),
...
)