Value of dynamic UI input lost every time main input change - shiny

library(shiny)
ui <- shiny::fluidPage(
shiny::titlePanel("Dynamic UI"),
shiny::sidebarLayout(
shiny::sidebarPanel(width = 2,
shiny::numericInput("num", "Number of input", value=1, min=1, max=7),
htmltools::hr(style = "border-top: 5px dashed skyblue"),
shiny::uiOutput("out")
),
shiny::mainPanel()
)
)
server <- function(input, output, session) {
output$out <- shiny::renderUI({
lapply(1:input$num, function(i) {
htmltools::div(
htmltools::tags$h4(paste0("Input group: ",i)),
shiny::numericInput(paste0("size", i),
label = "size",
value = 3, min = 1, max = 8
),
shiny::numericInput(paste0("inc", i),
label = "incidence",
value = 1, min = 1, max = 8
),
htmltools::hr(style = "border-top: 2px dashed skyblue")
)
})
})
}
shiny::shinyApp(ui, server)
I created an example of app that dynamically create multiple numeric input depending on other input(Number of input here).
Now when Number of input value changes, all the input value that are dynamically created is reset.
I know that because server side creating these input every time Number of input changes.
But, are there any way/trick so dynamic input value will remain unchanged when user change the Number of input?

Use react<-reactiveValues() to have saved values.
Use observeEvent(input$num, {}) to save input values in react values.
library(shiny)
ui <- shiny::fluidPage(
shiny::titlePanel("Dynamic UI"),
shiny::sidebarLayout(
shiny::sidebarPanel(width = 2,
shiny::numericInput("num", "Number of input", value=1, min=1, max=7),
htmltools::hr(style = "border-top: 5px dashed skyblue"),
shiny::uiOutput("out")
),
shiny::mainPanel()
)
)
server <- function(input, output, session) {
react <- reactiveValues(
S=list(),
I=list()
)
output$out <- shiny::renderUI({
lapply(1:input$num, function(i) {
input_size<- isolate(input[[paste0("size", i)]])
input_inc <- isolate(input[[paste0("inc", i)]])
if (is.null(input_size)){
input_size<-3
}
if (is.null(input_inc)){
input_inc<-1
}
htmltools::div(
htmltools::tags$h4(paste0("Input group: ",i)),
shiny::numericInput(paste0("size", i),
label = "size",
value = input_size , min = 1, max = 8
),
shiny::numericInput(paste0("inc", i),
label = "incidence",
value = input_inc, min = 1, max = 8
),
htmltools::hr(style = "border-top: 2px dashed skyblue")
)
})
})
observeEvent(input$num, {
i <- input$num
react$S[i]<- input[[paste0("size", i)]]
react$I[i]<- input[[paste0("inc", i)]]
},ignoreNULL = TRUE, ignoreInit = TRUE
)
}
shiny::shinyApp(ui, server)

Related

R Shiny - uiOutput causes numericInput to deselect when user is still typing in

I'm trying to create a data collection tool on R Shiny where the user can select as many categories as apply to them and then enter values for each. I've used uiOutput to allow the user to add a new category choice after clicking an action button.
For some reason, the numericInput that is created after clicking the action button will deselect after a split-second when the user is typing in a number, so it only catches one digit and you have to click it repeatedly to type in a full number.
I've tried changing the numericInput to a textInput and the same thing happens, so it's something to do with how I'm generating the uiOutput in the server, does it continually refresh and is there any way to stop it?
Example code given below, click on the new row button then try typing in the numericInput and you'll see. I have been stuck on this for ages and can't find any other questions similar so any help massively appreciated, thanks
library(tidyverse)
library(shiny)
library(shinyjs)
ui <- fluidPage(
fluidRow(wellPanel(h3("Category and quantity input"))),
wellPanel(fluidRow(column(width=4,selectInput("type0",label = h4("type"), choices= list("choice1" = 1,"choice2" = 2, "choice3"=3))),
column(width=4,numericInput("quantity0", label = h4("quantity"), value = 0, min=0)),
column(width=4,actionButton("New_row",label="Add new row"))),
uiOutput("new_row_added")
))
server <- function(input, output) {
ids <<- NULL
observeEvent(input$New_row,{
if (is.null(ids)){
ids <<- 1
}else{
ids <<- c(ids, max(ids)+1)
}
output$new_row_added <- renderUI({
tagList(
lapply(1:length(ids),function(i){
check_input_type <- paste0("type", ids[i])
check_input_quantity <- paste0("quantity", ids[i])
if(is.null(input[[check_input_type]])){
# Create a div that contains 3 new sub divs
div(fluidRow(column(width=4,
div(selectInput(paste0("type",ids[i]),label = "", choices= list("choice1" = 1,"choice2" = 2, "choice3"=3)))),
column(width=4,div(numericInput(paste0("quantity",ids[i]), label = "", value = 00, min=0))))
)
} else {
# Create a div that contains 3 existing sub divs
div(fluidRow(column(width=4,
div(selectInput(paste0("type",ids[i]),label = "", choices= list("choice1" = 1,"choice2" = 2, "choice3"=3), selected = input[[check_input_type]]))),
column(width=4,div(numericInput(paste0("quantity",ids[i]), label = "", min=0, value = input[[check_input_quantity]]))))
)
}
})
)
})
})
}
shinyApp(ui = ui, server = server)
You need to isolate input[[check_input_type]]. By doing isolate(input[[check_input_type]]). If not, every time a new number is inserted inside that input, the ui will re render and cause the deselection.
App:
library(tidyverse)
library(shiny)
library(shinyjs)
ui <- fluidPage(
fluidRow(wellPanel(h3("Category and quantity input"))),
wellPanel(
fluidRow(
column(width = 4, selectInput("type0", label = h4("type"), choices = list("choice1" = 1, "choice2" = 2, "choice3" = 3))),
column(width = 4, numericInput("quantity0", label = h4("quantity"), value = 0, min = 0)),
column(width = 4, actionButton("New_row", label = "Add new row"))
),
uiOutput("new_row_added")
)
)
server <- function(input, output) {
ids <<- NULL
observeEvent(input$New_row, {
if (is.null(ids)) {
ids <<- 1
} else {
ids <<- c(ids, max(ids) + 1)
}
output$new_row_added <- renderUI({
tagList(
lapply(1:length(ids), function(i) {
check_input_type <- paste0("type", ids[i])
check_input_quantity <- paste0("quantity", ids[i])
if (is.null(isolate(input[[check_input_type]]))) {
# Create a div that contains 3 new sub divs
div(fluidRow(
column(
width = 4,
div(selectInput(paste0("type", ids[i]), label = "", choices = list("choice1" = 1, "choice2" = 2, "choice3" = 3)))
),
column(width = 4, div(numericInput(paste0("quantity", ids[i]), label = "", value = 00, min = 0)))
))
} else {
# Create a div that contains 3 existing sub divs
div(fluidRow(
column(
width = 4,
div(selectInput(paste0("type", ids[i]), label = "", choices = list("choice1" = 1, "choice2" = 2, "choice3" = 3), selected = isolate(input[[check_input_type]])))
),
column(width = 4, div(numericInput(paste0("quantity", ids[i]), label = "", min = 0, value = input[[check_input_quantity]])))
))
}
})
)
})
})
}
shinyApp(ui = ui, server = server)

How to create a numericinput widget in Shiny app to add new observations to existing data frame?

sorry if this is repetitive and so simple it is annoying, but I am new to Shiny.
I need help with a shiny app I am trying to create for my golf game. I have loaded a CSV file with previous distance and accuracy observations to Rstudio and completed a script file with what will generally be done: data preprocessing and then visualizations.
I am now struggling with converting that to the app.R file, specifically, how to create a widget where I can add new numeric observations to the current data frame. The end goal is to use the app to log data as I play (practice or an actual round), which updates in real time for quick insight into the average distance and accuracy for each club.
Here is the very basic shiny code I have got to work for the numeric input:
`library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
titlePanel("Numeric Add Test"),
column(3,
numericInput("num",
h3("Numeric input"),
value = 1,
min = 50,
max = 400,
step = 25))
)
# Define server logic required to draw a histogram
server <- function(input, output) {
}
# Run the application
shinyApp(ui = ui, server = server)`
I have found ways to include an 'add' button to a list, but what I am hoping to do is have the new numeric input be added to the specified variable (club, distance, accuracy) in the referenced dataset. This action would change the existing data, but add to it and grow the dataset over time.
Not sure if this helps for context at all, but below is the script file for preprocessing and visuals I described above:
`######### Golf Data Practice for App #############
## Read in Data set and address the column names starting with a number
Golfdata <- data.frame(read_csv("Shiny Apps/Golf Dataset .csv"))
Golfdata <- as.data.frame(Golfdata)
#Drop the last two columns for only clubs. Then create shot bias DF as well.
Clubs <- Golfdata %>% select(-c(11,12))
ShotBias <- Golfdata %>% select(c(11,12))
#Visualize the Average club distance
##Convert the club df by summarizing each variable by its average,
## then use the gather() to convert to long instead of wide to finally
## prepare the df for visualizing.
ClubAverage <- Clubs %>% summarise_all(mean) %>% gather(ClubAverage) %>%
mutate_if(is.numeric, round, digits = 0)
library(ggplot2)
value <- ClubAverage$value
ggplot(ClubAverage) +
aes(x = fct_reorder(ClubAverage, value, .desc = TRUE), y = value, label = value,
color = ClubAverage) +
geom_col( show.legend = FALSE, fill = "white") +
geom_text(nudge_y = 10, color = "black", size=4, fontface = "bold") +
labs(x = "Club",
y = "Yards", title = "Average Club Distance") +
theme(panel.background = element_rect(fill="forestgreen"),
panel.grid.major.x = element_blank(),
panel.grid.major = element_line(color = "yellow"),
panel.grid.minor = element_line(color = "yellow1")) +
theme(plot.title = element_text(size = 24L,
face = "bold", hjust = 0.5), axis.title.y = element_text(size = 18L, face = "bold"), axis.title.x =
element_text(size = 18L,
face = "bold"))
## Visualize the Average Accuracy ##
## This time, summarize the columns by their mean,
## but keep as wide -- no gather() function needed.
AverageShotBias <- ShotBias %>% summarise_all(mean)
ggplot(AverageShotBias) +
aes(x = Accuracy.Bias, y = Distance.Bias) +
geom_point(shape = "circle filled",
size = 18L, fill = "yellow") +
labs(x = "Accuracy", y = "Distance", title = "Average Shot Bias") +
theme(panel.background = element_rect(fill="forestgreen")) +
theme(plot.title = element_text(size = 24L, face = "bold", hjust = 0.5), axis.title.y =
element_text(size = 14L,
face = "bold"), axis.title.x = element_text(size = 14L, face = "bold")) +
xlim(-1, 1) +
ylim(-1, 1) +
geom_hline(yintercept = 0, size=1) +
geom_vline(xintercept = 0, size=1)`
Something I have found regarding the add button is the code here:
` ,actionButton('add','add')
,verbatimTextOutput('list')
)`
This does create an add button, which after updating the server code does create a list of added inputs, however I would like to be able to adjust the code for adding the observation to the variables in the data set.
I assume I would create an add button for each variable, just need to understand better how to do that.
The structure of your data used in the plot is not clear, but this is how to get the inputs or update dataset using eventReactive or observeEvent in the server. you can read this to learn the difference
server <- function(input, output) {
distance <- eventReactive(input$addButton, {
input$distInput
}, ignoreInit = T, ignoreNULL = F)
accbias <- eventReactive(input$accBiasButton, {
input$accslider
})
distbias <- eventReactive(input$DistBiasButton, {
input$distslider
}, ignoreNULL=F)
output$plot1 <- renderPlot({
input$distInput
mydist <- distance()
# plot
})
output$plot2 <- renderPlot({
input$distInput # use the inputs here
mydist <- distance() # or the reactives
})
}
the two output plots in your code have the same outputId
Follow UP to my Question: I have written the code for the ui, now I am still trying to figure out how to code the server properly so the distance and accuracy numeric inputs can be added to a data frame. That data frame will then be used to create the two visuals.
library(shiny)
library(gridlayout)
library(DT)
library(tidyverse)
ui <- grid_page(
layout = c(
"title title title",
"h1 h2 h3 ",
"h4 h4 h5 "
),
row_sizes = c(
"100px",
"0.86fr",
"1.14fr"
),
col_sizes = c(
"250px",
"0.71fr",
"1.29fr"
),
gap_size = "1rem",
grid_card_text(
area = "title",
content = "My Golf Data",
alignment = "center",
is_title = FALSE
),
grid_card(
area = "h2",
title = "Distance Input",
numericInput(
inputId = "distInput",
label = "Distance",
value = 50L,
min = 50L,
max = 400L,
step = 15L
),
actionButton(
inputId = "addButton",
label = "Add",
width = "100%"
)
),
grid_card(
area = "h1",
title = "Club Select",
radioButtons(
inputId = "clubRadiobuttons",
label = "",
choices = list(
Driver = "D",
`5Wood` = "5W",
`4H` = "4H",
`5I` = "5I",
`6I` = "6I",
`7I` = "7I",
`8I` = "8I",
`9I` = "9I",
PW = "PW",
SW = "SW"
),
width = "100%"
)
),
grid_card(
area = "h3",
title = "Accuracy",
sliderInput(
inputId = "accslider",
label = "Accuracy Bias",
min = -1L,
max = 1L,
value = 0L,
width = "98%",
step = 1L
),
actionButton(
inputId = "accBiasButton",
label = "Add Acc Bias",
width = "100%"
),
sliderInput(
inputId = "distslider",
label = "Distance Bias",
min = -1L,
max = 1L,
value = 0L,
width = "100%",
step = 1L
),
actionButton(
inputId = "DistBiasButton",
label = "Add Dist Bias",
width = "100%"
)
),
grid_card(
area = "h5",
title = "Average Club Distance",
plotOutput(
outputId = "plot",
width = "100%",
height = "400px"
)
),
grid_card(
area = "h4",
title = "Accuracy Average",
plotOutput(
outputId = "plot",
width = "100%",
height = "400px"
)
)
)
server <- function(input, output) {
}
shinyApp(ui, server)

How can I plot the model output in shiny

This is the output, that I would like to plot with shiny.
<constr <- c(+4,-3,-2,-5)
# Uhlig rejection
model1s <- uhlig.reject(Y=uhligdata, nlags=12, draws=200, subdraws=200, nkeep=100, KMIN=1,
KMAX=5, constrained = constr, constant=FALSE, steps=60)
irf1s <- model1s$IRFS
irfplot(irf1s)
# Uhlig penalty
model1d <- uhlig.penalty(Y=uhligdata, nlags=12, draws=200, subdraws=1000,nkeep=100, KMIN=1, KMAX=5, constrained=constr,
constant=FALSE, steps=60, penalty=100, crit=0.001)
irf1d <- model1d$IRFS
irfplot(irf1d)>
and below is my attemp. I am trying to have the test, lags and periods dynamic and based on them to have the IRFs plotted.
ui <- dashboardPage(
dashboardHeader(title = "НАЧАЛО"),
dashboardSidebar(
sidebarMenu(
menuItem("BVAR",
tabName = "test_tab",
icon = icon("dashboard"))
)
),
dashboardBody(
tabItems(
tabItem(tabName = "test_tab",
box(column(10,
radioButtons("test1",
label = "Изберете тест",
choices = c("Uhlig rejection", "Uhlig penalty")),
numericInput("nlags", "NLAGS", min = 1, max = 20, value = 1, step = 1),
numericInput("kmin", "KMIN", min = 1, max = 10, value = 1, step = 1),
numericInput("kmax", "KMAX", min = 2, max = 10, value = 2, step = 1),
submitButton("Submit"))),
box(column(12,
plotOutput("plot2",8))),)
)
))
server <- function(input, output){
modelselect <- reactive({
if(input$test1 == "Uhlig Rejection"){
fit <- uhlig.reject(uhligdata, nlags = input$nlags,constrained = constr, KMIN = input$kmin, KMAX = input$kmax)
return(fit)
}else
if(input$test1 == "Uhlig Penalty"){
fit <- uhlig.penalty(uhligdata,nlags = input$nlags, KMIN = input$kmin, KMAX = input$kmax)
return(fit)
}
})
myplot1 <- reactive({
if(input$test1 == "Uhlig Rejection"){
irfs <- modelselect()$IRFS
irfs} else
if(input$test1 == "Uglig Penalty"){
irfs <-modelselect()$IRFS
irfs}
})
output$plot2 <- renderPlot({
irfplot(myplot1())
})
}
shinyApp(ui = ui, server = server)
The dashboard loads fine but I cannot access the IRF plot. I wonder if the problem is with the reactive function or I do not access the model output correctly(I am quite a newbie to shiny)?

How can a scrollbar be included in a legend?

It would be nice to see more than 18 lines of a legend.
Wrapping the output in a wellPanel( style = 'overflow-y:scroll;' ) appears like it would be the correct function, but did not work.
library(shiny)
shinyApp(
ui <- fluidPage(
splitLayout(cellWidths = c('20%', '30%', '50%'),
sliderInput('mySldr' , value = 4 , min = 1 ,
label = 'how many groups ?', max = 44 ), # max 52 > 44
plotOutput( 'myLgnd' ),
plotOutput( 'myPlt' )
)
),
server <- function(input, output, session) {
n <- reactive({ input$mySldr })
theD <- reactive({ matrix(runif( 5*n() ), nrow = 5,
dimnames = (list( 1:5, rep(letters,2)[ 1:n() ] ))) })
output$myLgnd <- renderPlot({
legend( x = 'center', legend = colnames( theD() ),
fill = rainbow( n() ) ) })
output$myPlt <- renderPlot({
matplot( x = c(2001:2005), type = 'o', xlab = '', ylab = '',
y = theD() , col = rainbow( n() ) ) })
}
)
Any help towards a scrollable legend would be appreciated. Thank you.
Not very robust but this works for your example:
div(
style = "overflow-y: auto; overflow-x: hidden",
plotOutput( 'myLgnd' )
),
and
output$myLgnd <- renderPlot({
par(mar = c(0,0,0,0))
legend( x = 'center', legend = colnames( theD() ),
fill = rainbow( n() ) ) }, height = function(){170+10*ncol(theD())})
I've found the values 170 and 10 after many trials-errors. Maybe there are better values.
It would be better to do the graphic with plotly.

plotlyOutput shows previous plot before refresh in Shiny Apps

I have created a small Shiny Application using Plotly heatmap and intend to use withSpinner to plot Heat Map depending on user input. Currently i have two issues.
a.) WithSpinner appears only for the first time when heat map is generated. It doesn't appear if the User input is changed and replotting is done.
b.) On change of User input, the previous heatmap is shown instead of spinner and it refreshes after sometime. I intend to use spinner instead of showing old plot during redrawing of heatmap.
library(shiny)
library(shinydashboard)
library(shinycssloaders)
library(shinyjs)
library(plotly)
ui <- shinydashboard::dashboardPage(
# Dashboard header
shinydashboard::dashboardHeader(),
# Dashboard sidebar
shinydashboard::dashboardSidebar(disable = TRUE),
# Dashboard body
shinydashboard::dashboardBody(
id = "myBody",
# Tab items
shinydashboard::tabItem(tabName = "visual",
fluidRow(
shinydashboard::tabBox(id="tabBix1",
shiny::tabPanel(
"Parameters & Settings",
value = "paramsetting",
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500
)),
shiny::tabPanel(
"PlotOutput",
value = "Ops",
withSpinner(plotly::plotlyOutput("plotNewExp"))
)
)
))
)
)
server <- function(input, output, session) {
p <- NULL
observeEvent(input$obs,{
p <- NULL
m <- matrix(rnorm(input$obs), nrow = 3, ncol = 3)
output$plotNewExp <- plotly::renderPlotly({
p <- plot_ly(
x = c("a", "b", "c"), y = c("d", "e", "f"),
z = m, type = "heatmap"
)
})
})
}
shinyApp(ui=ui,server=server)
If you try this you will see that the spinner is working but it is fast. So you probably don't have the time to see it when you switch from one tab to the other.
ui <- shinydashboard::dashboardPage(
# Dashboard header
shinydashboard::dashboardHeader(),
# Dashboard sidebar
shinydashboard::dashboardSidebar(disable = TRUE),
# Dashboard body
shinydashboard::dashboardBody(
id = "myBody",
# Tab items
shinydashboard::tabItem(tabName = "visual",
fluidRow(
shinydashboard::tabBox(
tabPanel(
"Parameters & Settings",
value = "paramsetting",
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500
),
withSpinner(plotly::plotlyOutput("plotNewExp")))
# tabPanel(
# "PlotOutput",
# withSpinner(plotly::plotlyOutput("plotNewExp"))
# )
)
))
)
)
server <- function(input, output, session) {
# p <- NULL
# observeEvent(input$obs,{
# p <- NULL
m <- reactive({matrix(rnorm(input$obs), nrow = 3, ncol = 3)})
output$plotNewExp <- renderPlotly({
p <- plot_ly(
x = c("a", "b", "c"), y = c("d", "e", "f"),
z = m(), type = "heatmap"
)
# })
})
}
shinyApp(ui=ui,server=server)
or if you add a delay, you will see it is working.
library(shinyjs)
ui <- shinydashboard::dashboardPage(
# Dashboard header
shinydashboard::dashboardHeader(),
# Dashboard sidebar
shinydashboard::dashboardSidebar(disable = TRUE),
# Dashboard body
shinydashboard::dashboardBody(
useShinyjs(),
id = "myBody",
# Tab items
shinydashboard::tabItem(tabName = "visual",
fluidRow(
shinydashboard::tabBox(id="tabBix1",
shiny::tabPanel(
"Parameters & Settings",
value = "paramsetting",
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500
)),
shiny::tabPanel(
"PlotOutput",
value = "Ops",
withSpinner(plotly::plotlyOutput("plotNewExp"))
)
)
))
)
)
server <- function(input, output, session) {
# p <- NULL
Graph <- function() {
p <- NULL
m <- matrix(rnorm(input$obs), nrow = 3, ncol = 3)
output$plotNewExp <- plotly::renderPlotly({
p <- plot_ly(
x = c("a", "b", "c"), y = c("d", "e", "f"),
z = m, type = "heatmap"
)
})
}
observeEvent(input$obs,{
delay(4000, Graph())
})
}
shinyApp(ui=ui,server=server)