Hello heroes of Stack overflow,
SHORT SUMMARY:
App works great, until you change the entered number in the input field. UI re-renders great, but server side fails on stuff still in the memory it seems. Detailed explanation below:
I have a nicely working dynamic app, but I'm still dealing with a few bugs and one core problem.
The problem must be somewhere in the reactivity but I'm having a lot of difficulty to figure out what it is that Im doing wrong. I've tried dozens of things already, and none of them work, or end up breaking the app in other areas.
Here is the MAIN PROBLEM:
The app records the user click actions as 1's or 0's in a reactiveValues() list called dynamicvalues_highlight_button_sf1 and the elements are dynamically made within an lapply function that makes the dynamic observers the same way the dynamic buttons are made.
When you enter a number, buttons appear and everything works perfect
UNTIL you change the number in the text field.
-The buttons are updated and new amount is rendered, etc,
BUT: the old dynamicvalues_highlight_button_sf1 and dynamiclist is still being printed.
I am clueless why the old results are still there as well as new ones.
So instead of just the new results:
[1] "dl = 0, 0, 0, 0, 1" ## status of the current nr of elements (here its 5)
[1] "ob = 5" ### nr of the last clicked button
[1] "-----------next click event prints the below this line-----------"
the printout I get is old and new results:
[1] "dl = 0, 0, 0, 0, 1, 0" ## old results
[1] "ob = 5"
[1] "-----------next click event prints the below this line-----"
[1] "dl = 0, 0, 0, 0, 0, 0" ## new results
[1] "ob = 5"
[1] "-----------next click event prints the below this line-----"
I've tried things like rm(dynamicvalues_highlight_button_sf1) and rm(dynamiclist) but those can only work if the values are there, and cause a crash when the app starts since they don't.
Wrapping them inside an if(exists("dynamicvalues_highlight_button_sf1")) { }
doens't work because exists seems not to work on reactivevalues lists. (I've also tried evaluate(need(...the variable..., "text")) and if(!is.null(...the variable...)){...} but all failed. Also tried to put these in different places in the server but no succes. I'm lost and my knowledge of R shiny still is too limited for this complexity it seems.
SECOND Part of the problem
if I first enter i.e. 5, click something, and then recreate buttons for a number larger than 5 i.e. 6: BUTTON nr 6 works (gets blue etc), but buttons 1:5 DO NOT work.
I suspect the two problems are related to each other.
The UI and server are posted below. Have some fun trying it before you dive into the problem if you like.
NOTES:
- posted the "minimal example" but its a rather complex app in order to have the whole functionality here.
- the real app will spit the input NR out from a big modeling step rather than the input field in this demo
- I annotated as much as possible for clarity
- I left a little bit of code of my last attempt to solve the problem in the server.r at lines 18-25.
Thanks for any help you can offer!
UI.r
library(shiny)
library(shinydashboard)
library(shinyBS)
ui <- dashboardPage(
dashboardHeader(title = "My Test App"),
dashboardSidebar(
sidebarMenu(id = "tabs", menuItem("testpage", tabName = "testpage", icon = icon("book"))
)
),
dashboardBody(
tags$head(tags$style(HTML('.skin-blue .content-wrapper, .right-side {background-color: #ffffff; }, '))),
tabItems(
### test page ###_________
tabItem(tabName = "testpage",
h5("Enter desired nr of elements here"),
textInput(inputId ="NrOfClusters", label = NULL , placeholder = "NULL"),
fluidRow(
column(2,
uiOutput("buttons_highlight_sf1")),
column(1,
uiOutput("button_hightlight_all_sf1"),
uiOutput("multi_highlight"),
br(),
actionButton(inputId = "statuscheck", label = "status", style = "background-color: white")
))))))
SERVER.R
shinyServer = function(input, output, session) {
################# start functionality HOME TAB #############################
### create 2 reactive environment lists
values <- reactiveValues()
dynamicvalues_highlight_button_sf1 <- reactiveValues()
### set initial state of two buttons
values$HL_multi_switch_sf1 <- FALSE
values$HL_all_switch_sf1 <- FALSE
### if the user types in a value, then convert it to a reactive value of this nr
observeEvent (input$NrOfClusters, {
isolate(values$nrofelements <- paste0(input$NrOfClusters))
##TRY THERE TO REMOVE THE dynamiclist and all the reactive elements in dynamic_highlight_button_sf1
if (exists("dynamiclist")) {
rm(dynamiclist)
rm(dynamicvalues_highlight_button_sf1)
dynamicvalues_highlight_button_sf1 <- reactiveValues() }
isolate( dynamiclist <- as.character(unlist(reactiveValuesToList(dynamicvalues_highlight_button_sf1), use.names = FALSE)))
isolate( print(paste0("dl length = ", length(dynamiclist))))
})
#### RENDER DYNAMIC UI and DYNAMIC OBSERVERS
observeEvent(values$nrofelements, {
print(values$nrofelements == 1 | values$nrofelements >1)
### create a nr of buttons equal to the entered value
if (values$nrofelements == 1 | values$nrofelements >1) {
output$buttons_highlight_sf1 <- renderUI({
lapply(1:values$nrofelements, function(ab) {
if (!is.null(dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ab)]])) {
if(dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ab)]] == 0 ) {
div(br(), actionButton(inputId = paste0("highlight_button_sf1", ab), label = icon("lightbulb-o"),style = "color: grey;
background-color: white;
height: 35px;
width: 35px;
text-align:center;
text-highlight_buttonent: 0,5px;
border-radius: 6px;
display:block;
margin: auto;
border-width: 2px")) }
else { div(br(), actionButton(inputId = paste0("highlight_button_sf1", ab), label = icon("lightbulb-o"),style = "color: black;
background-color: white;
border-color: blue;
height: 35px;
width: 35px;
text-align:center;
text-highlight_buttonent: 0,5px;
border-radius: 6px;
display:block;
margin: auto;
border-width: 2px")) } }
else { div(br(), actionButton(inputId = paste0("highlight_button_sf1", ab), label = icon("lightbulb-o"),style = "color: grey;
background-color: white;
height: 35px;
width: 35px;
text-align:center;
text-highlight_buttonent: 0,5px;
border-radius: 6px;
display:block;
margin: auto;
border-width: 2px")) }
})
})
### create a button to highlight all
output$button_hightlight_all_sf1 <- renderUI({
if(values$HL_all_switch_sf1 == TRUE) {
div( br(), actionButton(inputId = "hightlight_all_button_sf1", label = "All", style = "color: blue; background-color:white"), br())}
else { div( br(), actionButton(inputId = "hightlight_all_button_sf1", label ="All", style = "color: grey; background-color:white"), br())}
})
### create a button to enable highlight multiple or sinle boxes
output$multi_highlight <- renderUI({
if(values$HL_multi_switch_sf1 == TRUE) {
div( br(), actionButton(inputId = "multi_highlight", label ="multi", style = "color: blue; background-color:white"), br())}
else { div( br(), actionButton(inputId = "multi_highlight", label ="single", style = "color: green; background-color:white"), br())}
})
### loop apply function over all dynamically created buttons
isolate(lapply(1:values$nrofelements, function(ob) {
observeEvent(input[[paste0("highlight_button_sf1", ob)]], {
### complex observer structure to check what to do depending on the ALL and MULTI status
### FALSE all FALSE multi
if (values$HL_all_switch_sf1 == FALSE) {
if (values$HL_multi_switch_sf1 == FALSE) {
for (each in 1:values$nrofelements) {
if ( ob != each) { dynamicvalues_highlight_button_sf1[[paste0("highlight_button", each)]] <- 0}
else if (ob == each) {
if (is.null(dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]])) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 1}
else if (dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] == 1) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 0}
else if (dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] == 0) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 1}
}}}
### FALSE all TRUE multi
if (values$HL_multi_switch_sf1 == TRUE){
if (is.null(dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]])) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 1}
else if (dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] == 1) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 0}
else if (dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] == 0) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 1}
}}
### TRUE all TRUE multi
if(values$HL_all_switch_sf1 == TRUE) {
if (values$HL_multi_switch_sf1 == TRUE) {
dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 0
isolate(values$HL_all_switch_sf1 <- FALSE)}
### TRUE all FALSE multi
else if (values$HL_multi_switch_sf1 == FALSE) { for (each in 1:values$nrofelements)
{if (ob != each) { dynamicvalues_highlight_button_sf1[[paste0("highlight_button", each)]] <- 0}
}
isolate(values$HL_all_switch_sf1 <- FALSE)
}}
dynamiclist <- as.character(unlist(reactiveValuesToList(dynamicvalues_highlight_button_sf1), use.names = FALSE))
print(paste0("dl = ", toString(dynamiclist)))
print(paste("ob =", ob ))
lastclicked_button_nr <- ob
colorpalette <- vector(mode="character", length=values$nrofelements)
colorpalette <- replace(colorpalette, colorpalette == "", "GREY")
colorpalette[values$button_nr_clicked]="RED"
print( "-----------next click event prints the below this line--------------------------------------------------------------")
})
}))
}
})
#### OBSERVE DYNAMIC UI
observeEvent(input$multi_highlight, {
if (values$HL_multi_switch_sf1 == TRUE) { values$HL_multi_switch_sf1 <- FALSE }
else if (values$HL_multi_switch_sf1 == FALSE) { values$HL_multi_switch_sf1 <- TRUE }
})
observeEvent(input$hightlight_all_button_sf1,{
if (values$HL_all_switch_sf1 == TRUE) { values$HL_all_switch_sf1 <- FALSE }
else if (values$HL_all_switch_sf1 == FALSE) {values$HL_all_switch_sf1 <- TRUE}
if (values$HL_all_switch_sf1 == TRUE) { for (any in 1:values$nrofelements) { dynamicvalues_highlight_button_sf1[[paste0("highlight_button", any)]] <- 1}}
else if (values$HL_all_switch_sf1 == FALSE) { for (any in 1:values$nrofelements) { dynamicvalues_highlight_button_sf1[[paste0("highlight_button", any)]] <- 0}}
colorpalette <- NULL
colorpalette <- vector(mode="character", length=values$nrofelements)
colorpalette <- replace(colorpalette, colorpalette == "", "RED")
})
### button to print the status of Multi and All on console to check what they are
observeEvent(input$statuscheck, {
print(paste("ALL switch: ", values$HL_all_switch_sf1))
print(paste("MULTI switch: ", values$HL_multi_switch_sf1))
})
}
additional bug 1:
if you change the numberinput to nothing we get an error
additional bug 2:
if I start with entering "0" it goes well and we get no buttons, if I enter any number higher than 0 we get that many buttons, but if I then change it to 0 buttons I get 2 buttons!:
eventough the dynamic renderUI in line 36 of the server is wrapped inside a condtion:
if (values$nrofelements == 1 | values$nrofelements >1) { ......
Okay, your problem is a tricky one that people have fallen for before, if you look at the documentation of reactiveValues (here reactiveValues docs) it says that
"Note that values taken from the reactiveValues object are reactive,
but the reactiveValues object itself is not."
So you should not be using dynamicvalues_highlight_button_sf1 the way your are, you should be using named elements of it. I got it to work by doing the following:
replacing dynamicvalues_highlight_button_sf1 with dhbs globally (not necssary but the lines were getting way too long for me to see what was going on).
replacing dhbs with dhbs$el globally.
getting rid of all the reactiveValuesToList calls.
getting rid of all the attempts to rm(...) things out of the reactive environment.
adding a dhbs$el <- NULL statement as the first line of the observeEvent(values$nrofelements, { node code.
added an extra output field to inspect dhbs with a renderTextVerbatum statement. This is a useful debugging technique when you get used to it.
eliminated a lot of redundant code.
eliminated all the isolate statements which were not doing anything.
added a clickcount to handle the reactivity better.
Seems to work now, although there might be a few other problems to fix up still as a result of those changes. I also think that many of those isolates are probably unnecessary and just a result of your debugging activities.
The code:
library(shiny)
library(shinydashboard)
library(shinyBS)
ui <- dashboardPage(
dashboardHeader(title = "My Test App"),
dashboardSidebar(
sidebarMenu(id = "tabs", menuItem("testpage", tabName = "testpage", icon = icon("book"))
)
),
dashboardBody(
tags$head(tags$style(HTML('.skin-blue .content-wrapper, .right-side {background-color: #ffffff; }, '))),
tabItems(
### test page ###_________
tabItem(tabName = "testpage",
h5("Enter desired nr of elements here"),
textInput(inputId ="NrOfClusters", label = NULL , placeholder = "NULL"),
verbatimTextOutput("values"),
verbatimTextOutput("clickcount"),
fluidRow(
column(2,
uiOutput("buttons_highlight_sf1")),
column(1,
uiOutput("button_hightlight_all_sf1"),
uiOutput("multi_highlight"),
br(),
actionButton(inputId = "statuscheck", label = "status", style = "background-color: white")
))))))
off_style <-
"color: grey;
background-color: white;
height: 35px;
width: 35px;
text-align:center;
text-highlight_buttonent: 0,5px;
border-radius: 6px;
display:block;
margin: auto;
border-width: 2px"
on_style <-
"color: grey;
background-color: white;
border-color: blue;
height: 35px;
width: 35px;
text-align:center;
text-highlight_buttonent: 0,5px;
border-radius: 6px;
display:block;
margin: auto;
border-width: 2px"
shinyServer = function(input, output, session) {
################# start functionality HOME TAB #############################
### create 2 reactive environment lists
values <- reactiveValues(clickcount=0)
dhbs <- reactiveValues(el=NULL)
### set initial state of two buttons
values$HL_multi_switch_sf1 <- FALSE
values$HL_all_switch_sf1 <- FALSE
### if the user types in a value, then convert it to a reactive value of this nr
observeEvent (input$NrOfClusters, {
values$nrofelements <- input$NrOfClusters
dynamiclist <- as.character(unlist(dhbs$el), use.names = FALSE)
print(paste0("dl length = ", length(dynamiclist)))
})
hibutname <- function(idx){
sprintf("highlight_button_sf1-%s-%d",values$nrofelements,idx)
}
atbutname <- function(idx){
sprintf("activate_button_sf1-%s-%d",values$nrofelements,idx)
}
fliphib <- function(idx){
hib <- hibutname(idx)
dhbs$el[hib] <- abs(1-dhbs$el[hib])
}
sethib <- function(idx,v){
hib <- hibutname(idx)
dhbs$el[hib] <- v
}
#### RENDER DYNAMIC UI and DYNAMIC OBSERVERS
observeEvent(values$nrofelements, {
req(input$NrOfClusters)
nel <- values$nrofelements
dhbs$el <- rep(0,nel)
names(dhbs$el) <- sapply(1:nel,hibutname)
print(names(dhbs$el))
output$buttons_highlight_sf1 <- renderUI({
values$clickcount
print("clickcount")
print(values$clickcount)
lapply(1:values$nrofelements, function(ab) {
if(dhbs$el[[hibutname(ab)]] == 0 ) {
print("gray")
div(br(), actionButton(inputId = hibutname(ab), label = icon("lightbulb-o"),style = off_style))
} else {
print("black")
div(br(), actionButton(inputId = hibutname(ab), label = icon("lightbulb-o"),style = on_style))
}
})
})
### create a button to highlight all
output$button_hightlight_all_sf1 <- renderUI({
if(values$HL_all_switch_sf1 == TRUE) {
div( br(), actionButton(inputId = "hightlight_all_button_sf1", label = "All", style = "color: blue; background-color:white"), br())
} else {
div( br(), actionButton(inputId = "hightlight_all_button_sf1", label ="All", style = "color: grey; background-color:white"), br())
}
})
### create a button to enable highlight multiple or single boxes
output$multi_highlight <- renderUI({
if(values$HL_multi_switch_sf1 == TRUE) {
div( br(), actionButton(inputId = "multi_highlight", label ="multi", style = "color: blue; background-color:white"), br())
} else {
div( br(), actionButton(inputId = "multi_highlight", label ="single", style = "color: green; background-color:white"), br())
}
})
lapply(1:values$nrofelements, function(ob) {
butname <- hibutname(ob)
observeEvent(input[[butname]], {
hibut <- hibutname(ob)
print(hibut)
values$clickcount <- values$clickcount+1
print("clicked")
print(values$clickcount)
### complex observer structure to check what to do depending on the ALL and MULTI status
### FALSE all FALSE multi
if (values$HL_all_switch_sf1 == FALSE) {
if (values$HL_multi_switch_sf1 == FALSE) {
for (each in 1:values$nrofelements) {
if ( ob != each) {
sethib(each,0)
} else {
fliphib(each)
}
}
}
### FALSE all TRUE multi
if (values$HL_multi_switch_sf1 == TRUE){
fliphib(ob)
}
}
### TRUE all TRUE multi
if(values$HL_all_switch_sf1 == TRUE) {
if (values$HL_multi_switch_sf1 == TRUE) {
sethib(ob,0)
values$HL_all_switch_sf1 <- FALSE
}
### TRUE all FALSE multi
else if (values$HL_multi_switch_sf1 == FALSE) {
for (each in 1:values$nrofelements) {
if (ob != each) { sethib(each,0) }
}
values$HL_all_switch_sf1 <- FALSE
}
}
dynamiclist <- as.character(unlist(dhbs$el), use.names = FALSE)
print(paste0("dl = ", toString(dynamiclist)))
print(paste("ob =", ob ))
lastclicked_button_nr <- ob
colorpalette <- vector(mode="character", length=values$nrofelements)
colorpalette <- replace(colorpalette, colorpalette == "", "GREY")
colorpalette[values$button_nr_clicked]="RED"
print( "-----------next click event prints the below this line--------------------------------------------------------------")
})
})
})
#### OBSERVE DYNAMIC UI
observeEvent(input$multi_highlight, { values$HL_multi_switch_sf1 <- !values$HL_multi_switch_sf1 })
observeEvent(input$hightlight_all_button_sf1,{
values$HL_all_switch_sf1 <- !values$HL_all_switch_sf1;
for (any in 1:values$nrofelements) { dhbs$el[[hibutname(any)]] <- as.integer(values$HL_all_switch_sf1) }
colorpalette <- NULL
colorpalette <- vector(mode="character", length=values$nrofelements)
colorpalette <- replace(colorpalette, colorpalette == "", "RED")
})
### button to print the status of Multi and All on console to check what they are
observeEvent(input$statuscheck, {
print(paste("ALL switch: ", values$HL_all_switch_sf1))
print(paste("MULTI switch: ", values$HL_multi_switch_sf1))
})
output$values <- renderPrint(as.character(unlist(dhbs$el), use.names = FALSE))
output$clickcount <- renderPrint(values$clickcount)
}
options(shiny.reactlog = TRUE)
shinyApp(ui,shinyServer)
Screenshot:
Related
I have 2 checkboxes and 1 action button. When clicking on either of the checkboxes, a graph should output BUT only after clicking on the action button. The code I have bellow does this well already. My issue here is that once the action button has been clicked and the graph generated, unclicking the checkbox removes the graph. Similarly, clicking again generates a new graph without clicking on the action button. I would like for the graph to stay on the screen for as long as I dont click on the action button again. I imagine this has to do with "isolating" the checkboxes but Im not too sure how to do so.
As a side note, imagine there was a third function generating a plot in my server when clicking on the action button (regardless of the checkboxes). Is there a way to code my "showmodal, removemodal" such that the pop up stays while all functions are running (instead of only during the first function)?
Here is my code
library(shiny)
#Function 1
X <- function(a,b,c){
plot(c(a,b),c(b,c))
}
#Function 2
Y <- function(d,e,f){
plot(c(d,e),c(e,f))
}
ui <- fluidPage(
titlePanel("title"),
sidebarLayout(
sidebarPanel(
checkboxInput("EF", "Efficient Frontier"),
checkboxInput("MonteCarlo", "Monte Carlo Simulation"),
actionButton("Go", "Go", style="color: #fff; background-color: #337ab7; border-color: #2e6da4; margin: auto")
),
mainPanel(
fluidRow(
align = "center",
conditionalPanel(condition = "input.EF == true", plotOutput("GraphEF")),
conditionalPanel(condition = "input.MonteCarlo == true", plotOutput("GraphMC"))
)
)
)
)
server <- function(input, output) {
OPw <- reactiveValues()
output$Graphw <- renderPlot({
OPw$PC}, height = 400, width = 400)
observeEvent(input$Go, {
showModal(modalDialog("Loading... Please Wait", footer=NULL))
output$GraphEF <- renderPlot({ #Efficient Frontier
if(input$EF){
X(5,10,15)
}
}, height = 550, width = 700)
output$GraphMC <- renderPlot({ #Monte Carlo Simulation
if(input$MonteCarlo){
Y(5,10,15)
}
},height = 550, width = 700)
removeModal() #Removes Loading Pop-up Message
})
}
shinyApp(ui = ui, server = server)
Thanks a lot for your help!
Perhaps you should use eventReactive(). Try this
library(shiny)
# Function 1
X <- function(a, b, c) {
plot(c(a, b), c(b, c))
}
# Function 2
Y <- function(d, e, f) {
plot(c(d, e), c(e, f))
}
ui <- fluidPage(
titlePanel("title"),
sidebarLayout(
sidebarPanel(
checkboxInput("EF", "Efficient Frontier"),
checkboxInput("MonteCarlo", "Monte Carlo Simulation"),
actionButton("Go", "Go", style = "color: #fff; background-color: #337ab7; border-color: #2e6da4; margin: auto")
),
mainPanel(
fluidRow(
align = "center",
uiOutput("plot1"),
plotOutput("GraphMC")
)
)
)
)
server <- function(input, output) {
GEF <- eventReactive(input$Go, {
if (input$EF) {
X(5, 10, 15)
} else {
NULL
}
})
showme <- eventReactive(input$Go, {
if (input$EF) TRUE else FALSE
})
GMC <- eventReactive(input$Go, {
if (isolate(input$MonteCarlo)) {
Y(5, 10, 15)
} else {
NULL
}
})
output$GraphMC <- renderPlot({
GMC()
})
output$GraphEF <- renderPlot({ # Efficient Frontier
GEF()
})
output$plot1 <- renderUI({
if (showme()) {plotOutput("GraphEF")} else NULL
})
observeEvent(input$Go, {
showModal(modalDialog("Loading... Please Wait", footer = NULL))
Sys.sleep(2)
removeModal() # Removes Loading Pop-up Message
})
}
shinyApp(ui = ui, server = server)
Leaving a conditionalPanel-approach, which is referring to a discussion over here:
library(shiny)
# Function 1
X <- function(a, b, c) {
plot(c(a, b), c(b, c))
}
# Function 2
Y <- function(d, e, f) {
plot(c(d, e), c(e, f))
}
ui <- fluidPage(
titlePanel("title"),
sidebarLayout(
sidebarPanel(
checkboxInput("EF", "Efficient Frontier"),
checkboxInput("MonteCarlo", "Monte Carlo Simulation"),
actionButton("Go", "Go", style = "color: #fff; background-color: #337ab7; border-color: #2e6da4; margin: auto")
),
mainPanel(
fluidRow(
align = "center",
conditionalPanel("output.showme == true", plotOutput("GraphEF")),
plotOutput("GraphMC")
)
)
)
)
server <- function(input, output) {
GEF <- eventReactive(input$Go, {
if (input$EF) {
X(5, 10, 15)
} else {
NULL
}
})
output$showme <- eventReactive(input$Go, {
if (input$EF) TRUE else FALSE
})
outputOptions(output, "showme", suspendWhenHidden = FALSE)
GMC <- eventReactive(input$Go, {
if (isolate(input$MonteCarlo)) {
Y(5, 10, 15)
} else {
NULL
}
})
output$GraphMC <- renderPlot({
GMC()
})
output$GraphEF <- renderPlot({ # Efficient Frontier
GEF()
})
observeEvent(input$Go, {
showModal(modalDialog("Loading... Please Wait", footer = NULL))
Sys.sleep(2)
removeModal() # Removes Loading Pop-up Message
})
}
shinyApp(ui = ui, server = server)
Furthermore, please see this related answer.
The modal is working well, because both functions take so little time to run it creates de sensation than is there less than it should be. We can show this by adding a sys.sleep to simulate a long calculation.
Regarding the checkboxes, using conditionalPanel will hide or show the plots independently of the presence of isolate inside the server. A workaround is just to return NULL when the checkbox is not clicked.
library(shiny)
# Function 1
X <- function(a, b, c) {
plot(c(a, b), c(b, c))
}
# Function 2
Y <- function(d, e, f) {
plot(c(d, e), c(e, f))
}
ui <- fluidPage(
titlePanel("title"),
sidebarLayout(
sidebarPanel(
checkboxInput("EF", "Efficient Frontier"),
checkboxInput("MonteCarlo", "Monte Carlo Simulation"),
actionButton("Go", "Go", style = "color: #fff; background-color: #337ab7; border-color: #2e6da4; margin: auto")
),
mainPanel(
fluidRow(
align = "center",
plotOutput("GraphEF"),
plotOutput("GraphMC")
)
)
)
)
server <- function(input, output) {
OPw <- reactiveValues()
output$Graphw <- renderPlot(
{
OPw$PC
},
height = 400,
width = 400
)
observeEvent(input$Go, {
showModal(modalDialog("Loading... Please Wait", footer = NULL))
output$GraphEF <- renderPlot(
{ # Efficient Frontier
if (isolate(input$EF)) {
X(5, 10, 15)
} else {
NULL
}
},
height = 550,
width = 700
)
Sys.sleep(2)
output$GraphMC <- renderPlot(
{ # Monte Carlo Simulation
if (isolate(input$MonteCarlo)) {
Y(5, 10, 15)
} else {
NULL
}
},
height = 550,
width = 700
)
removeModal() # Removes Loading Pop-up Message
})
}
shinyApp(ui = ui, server = server)
I have some data in a dataframe. I can display the dataframe as a table with DataTables.
However, I would like to display the data as a grid with N columns, so that every N rows from the data frame are shown in the same row.
Show data as a grid:
As shown in the image above, I have gotten a grid to show up by using HTML to render the data frame directly.
But the next step is where I am stuck, which is I want to be able to show a modal dialog when a cell in the grid is clicked.
I have that working in the data table, but I haven't been able to figure out how to make a div clickable, such that when handling the event I know which cell was clicked?
library("shiny")
library("tidyr")
library("tidyverse")
library("dplyr")
library("shinydashboard")
# generate html grid from data frame
getHTML <- function (frames) {
innerhtml = '<div class="grid-container">'
for (row in 1:(nrow(frames))) {
id <- frames[row, "id"]
name <- frames[row, "names"]
row_html = '<div class="grid-item">'
row_html = paste(row_html, '<span>Name: ' , name, "id ", row , '</span>')
row_html = paste(row_html, '</div>')
innerhtml = paste(innerhtml, row_html)
}
paste(innerhtml, "</div>")
return (innerhtml)
}
#show modal dialog for player id and name
plotModal <- function(id, name) {
modalDialog(
p(paste("Player # ", id, ", " , name,", was clicked")),
title = paste("Player " , id),
easyClose = TRUE
)
}
ui <- dashboardPage(
# Application title
dashboardHeader(title = "Dashboard"),
dashboardSidebar(
h3("Filters")
),
dashboardBody(
tags$head(tags$style(HTML('
.grid-container {
display: grid;
grid-template-columns: auto auto auto auto;
}
.grid-item {
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 20px;
}'))),
fluidRow(
box(title="Render as table", column(width=12, DT::dataTableOutput("player_table"))),
box(title="Render as Grid", column(width=12, uiOutput("player_grid")))
)
)
)
server <- function(input, output, session) {
# data to be rendered
frames = data.frame(names= c("james","kyle", "sally","hannah","jeff","kurt"), ids=c(1:6))
output$player_table <- DT::renderDataTable({
DT::datatable(frames, rownames=FALSE, selection = 'single')
})
#when a row in the table is clicked, show popup
observeEvent(input$player_table_cell_clicked, {
info = input$player_table_cell_clicked
# do nothing if not clicked yet, or the clicked cell is not in the 1st column
if (is.null(info$value)) {
return()
}
row = frames[info$row, ]
showModal(plotModal(row$id, row$names))
})
output$player_grid <- renderUI ({
HTML(getHTML(frames))
})
}
# Create Shiny app ----
shinyApp(ui, server,options=list(host="0.0.0.0", port=8015))
Here is a way:
library(shiny)
library(shinydashboard)
js <- "
$(document).ready(function(){
$('body').on('click', '.grid-item span', function(){
var name = $(this).data('name'),
id = $(this).data('id');
Shiny.setInputValue('cell', {name: name, id: id});
});
});
"
# generate html grid from data frame
getHTML <- function (frames) {
innerhtml = '<div class="grid-container">'
for (row in 1:(nrow(frames))) {
id <- frames[row, "ids"]
name <- frames[row, "names"]
row_html = '<div class="grid-item">'
cell <- sprintf("<span data-name='%s' data-id='%s'>Name: %s - id: %s</span>",
name, id, name, id)
row_html = paste(row_html, cell)
row_html = paste(row_html, '</div>')
innerhtml = paste(innerhtml, row_html)
}
paste(innerhtml, "</div>")
return (innerhtml)
}
#show modal dialog for player id and name
plotModal <- function(id, name) {
modalDialog(
p(paste("Player # ", id, ", " , name,", was clicked")),
title = paste("Player " , id),
easyClose = TRUE
)
}
ui <- dashboardPage(
# Application title
dashboardHeader(title = "Dashboard"),
dashboardSidebar(
h3("Filters")
),
dashboardBody(
tags$head(tags$style(HTML('
.grid-container {
display: grid;
grid-template-columns: auto auto auto auto;
}
.grid-item {
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 20px;
}')),
tags$script(HTML(js))),
fluidRow(
box(title="Render as Grid", column(width=12, uiOutput("player_grid")))
)
)
)
server <- function(input, output, session) {
# data to be rendered
frames = data.frame(
names= c("james","kyle", "sally","hannah","jeff","kurt"),
ids=c(1:6)
)
#when a row in the table is clicked, show popup
observeEvent(input$cell, {
showModal(plotModal(input$cell$id, input$cell$name))
})
output$player_grid <- renderUI ({
HTML(getHTML(frames))
})
}
# Create Shiny app ----
shinyApp(ui, server)
I have a shiny app with list of wellPanels. They are used in jqui_sortable from shinyjqui. Panels are generated in server part (to uiOutput in ui). Order of panels can be changed by mouse and is written to file (by ids). Then I would like to open this file and change default order with loaded data.
Issue: I can't get out of rendered words "div" between panels (run code below).
Code was written with some lines from solution (thanks to #TimTeaFan):
Distorted spacing between div elements after sorting with jqui_sortable
library(shiny)
library(shinyjqui)
ui <- fluidPage(
sidebarLayout(fluid = TRUE,
sidebarPanel(helpText("HelpText")),
mainPanel(
fluidRow(column(12,
actionButton(inputId = "btn1",label = "Button1"),
tags$style(HTML(".ui-sortable {
width: 1200px !important;
} ")),
uiOutput('multiobject'),
actionButton(inputId = "btn2",label = "Button2")
))
)
)
)
server <- function(input, output, session) {
sortableorderednameList<-reactiveVal(
c("A","B","C")
)
wpFunc <- function(v,name,helptext){
return(tags$div(wellPanel(id=paste0(v,"P"),
div(style="display: inline-block; width: 10px;",
checkboxInput(paste0(v,"Chk"), label = NULL, value = TRUE)),
div(style="display: inline-block; width: 150px;",
textInput(paste0(v,"TI"), label = NULL, value = name)),
div(style="display: inline-block;",helpText(helptext)),
style = "padding: 1px;")))
}
observe({
if(is.null(input$sortablecollistJQ_order$id)) {return()}
mylist <- input$sortablecollistJQ_order$id
mylist <- unlist(lapply(mylist, function(v) substr(v,1,nchar(v)-1)))
print(mylist)
print(" ")
isolate(sortableorderednameList(mylist))
})
output$multiobject <- renderUI({
uiList <- list()
for (v in sortableorderednameList()) {
switch(v,
"A" = {uiList <- append(uiList,wpFunc(v,"A","There is A"))},
"B" = {uiList <- append(uiList,wpFunc(v,"B","There is B"))},
"C" = {uiList <- append(uiList,wpFunc(v,"C","There is C"))}
)
}
jqui_sortable(div(id = 'sortablecollistJQ',uiList))
})
}
shinyApp(ui, server)
I have got an answer after experiments. If somebody is interested.
for (i in 1:length(uiList)) {
uiList[i] <- uiList[i]$children
}
It changes structure of list, put it before jqui_sortable call.
The problem I face is that observers linked to dynamically rendered elements seem to fire on render, while this is not how I want it to be.
The reason this is a problem, is that the color buttons I'm making are linked to a plot that takes several seconds to render (plotly widget)
I added ignoreInit = T the observers that are created, but they still fire on rendering, unlike normal observers linked to a button build directly in the UI
How do I stop the observers linked to the dynamically rendered colourInput from firing when the element is rendered?
In the dummy app below the following series of events is recreated in simplified form:
A model spits out a number (simulated by test button in demo app)
Based on this number, a number of colourInput buttons are made
A same number of observeEvents are made for each.
Not in the dummy app: When the user chooses to change a color, the corresponding group in plots is recolored accordingly
The test app contains a working static colourInput, and a dynamic part that demonstrates the problem scenario.
Test app:
library(shiny)
library("colourpicker")
THECOLORS <- c('#383838', '#5b195b','#1A237E', '#000080', '#224D17', '#cccc00', '#b37400', '#990000',
'#505050', '#a02ca0', '#000099', '#2645e0', '#099441', '#e5e500', '#cc8400', '#cc0000',
'#737373', '#e53fe5', '#0000FF', '#4479e1', '#60A830', '#ffff00','#e69500', '#ff0000',
'#b2b2b2', '#eb6ceb', '#6666ff', '#d0a3ff', '#9FDA40', '#ffff7f', '#ffa500', '#ff4c4c')
ui <- fluidPage(
h1("WELCOME TO THE TEST APP", style = 'text-align: center; font-weight:bold' ),
br(),
h3("STATIC PART: doesn't fire on startup, great!", style = 'font-weight:bold'),
div(colourpicker::colourInput(inputId = 'StaticColor', label = NULL, palette = "limited", allowedCols = THECOLORS, value = THECOLORS[14], showColour = "background", returnName = TRUE),
style = " height: 30px; width: 30px; border-radius: 6px; border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"),
br(),
h3("Dynamic part: fires on render, NOT great!", style = 'font-weight:bold'),
actionButton(inputId = 'Tester', label = 'Click me'),
br(),
uiOutput('colorbutton')
)
server <- function(input, output, session) {
values <- reactiveValues()
values$mycolors <- THECOLORS
observeEvent(input$Tester, { values$NrofButtons <- sample(1:10, 1) })
observeEvent(values$NrofButtons, {
COLElement <- function(idx){sprintf("COL_button-%s-%d",values$NrofButtons,idx)}
output$colorbutton <- renderUI({
lapply(1:values$NrofButtons, function(x) {
div(colourpicker::colourInput(inputId = COLElement(x), label = NULL, palette = "limited", allowedCols = values$mycolors, value = values$mycolors[x], showColour = "background", returnName = TRUE),
style = " height: 30px; width: 30px; border-radius: 6px; border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px") })
})
lapply(1:values$NrofButtons, function(x) { observeEvent(input[[COLElement(x)]], { print(input[[COLElement(x)]] )}, ignoreInit = T) }) # make observer for each button
})
observeEvent(input[['StaticColor']], { print(input[['StaticColor']] )}, ignoreInit = T)
}
shinyApp(ui,server)
Renders should always be by themselves and be data driven, not event driven -- so I've made the render require the number of colors to be defined before rendering. Of course the number of colors aren't defined until the observeEvent is fired by clicking the button.
Overall there is still the issue that every time the button is clicked more observers are created for the same ID, working on a way to destroy these automatically on a subsequent click of the tester button.
The key addition was a ignoreInit = TRUE in your observeEvent(input$Tester, {...}) observer.
library(shiny)
library("colourpicker")
THECOLORS <- c('#383838', '#5b195b','#1A237E', '#000080', '#224D17', '#cccc00', '#b37400', '#990000',
'#505050', '#a02ca0', '#000099', '#2645e0', '#099441', '#e5e500', '#cc8400', '#cc0000',
'#737373', '#e53fe5', '#0000FF', '#4479e1', '#60A830', '#ffff00','#e69500', '#ff0000',
'#b2b2b2', '#eb6ceb', '#6666ff', '#d0a3ff', '#9FDA40', '#ffff7f', '#ffa500', '#ff4c4c')
ui <- fluidPage(
h1("WELCOME TO THE TEST APP", style = 'text-align: center; font-weight:bold' ),
br(),
h3("STATIC PART: doesn't fire on startup, great!", style = 'font-weight:bold'),
div(colourpicker::colourInput(inputId = 'StaticColor', label = NULL, palette = "limited", allowedCols = THECOLORS, value = THECOLORS[14], showColour = "background", returnName = TRUE),
style = " height: 30px; width: 30px; border-radius: 6px; border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"),
br(),
h3("Dynamic part: fires on render, NOT great!", style = 'font-weight:bold'),
actionButton(inputId = 'Tester', label = 'Click me'),
br(),
uiOutput('colorbutton')
)
COLElement <- function(idx) sprintf("COL_button-%d", idx)
server <- function(input, output, session) {
values <- reactiveValues(previous_max = 1)
observeEvent(input$Tester, {
values$NrofButtons <- sample(1:10, 1)
# reset counters for all observers
for (i in seq(values$NrofButtons)) {
values[[sprintf("observer%d_renders", i)]] <- 0L
}
# only initialize incremental observers
lapply(values$previous_max:values$NrofButtons, function(x) {
observeEvent(input[[COLElement(x)]], {
# only execute the second time, since the `ignoreInit` isn't obeyed
if (values[[sprintf("observer%d_renders", x)]] > 0) {
print(input[[COLElement(x)]] )
} else {
values[[sprintf("observer%d_renders", x)]] <- 1L
}
}, ignoreInit = TRUE)
}) # make observer for each button
# record the max
values$previous_max <- max(values$previous_max, max(values$NrofButtons))
}, ignoreInit = TRUE)
output$colorbutton <- renderUI({
req(length(values$NrofButtons) > 0)
lapply(1:values$NrofButtons, function(x) {
div(colourpicker::colourInput(
inputId = COLElement(x)
, label = NULL
, palette = "limited"
, allowedCols = THECOLORS
, value = THECOLORS[x]
, showColour = "background"
, returnName = TRUE
)
, style = " height: 30px; width: 30px; border-radius: 6px; border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"
)
})
})
observeEvent(input$StaticColor, {
print(input$StaticColor )
}, ignoreInit = TRUE)
}
shinyApp(ui,server)
I'm building an app that allows user to pass the value from selectizeInput or checkboxInput to form a dataframe. I've searched a while and found these sources that similar to what I expect:
handsontable
It is from here: https://github.com/jrowen/rhandsontable. Mine is quite similar to this exampe:
shiny::runGitHub("jrowen/rhandsontable",
subdir = "inst/examples/rhandsontable_portfolio")
But I want to use shiny widgets to pass values to the dataframe. It should be able to add/remove rows as following example:
shinyIncubator
code here:
library("shiny")
library('devtools')
install_github('shiny-incubator', 'rstudio')
library("shinyIncubator")
# initialize data with colnames
df <- data.frame(matrix(c("0","0"), 1, 2))
colnames(df) <- c("Input1", "Input2")
server = shinyServer(
function(input, output) {
# table of outputs
output$table.output <- renderTable(
{ res <- matrix(apply(input$data,1,prod))
res <- do.call(cbind, list(input$data, res))
colnames(res) <- c("Input 1","Input 2","Product")
res
}
, include.rownames = FALSE
, include.colnames = TRUE
, align = "cccc"
, digits = 2
, sanitize.text.function = function(x) x
)
}
)
ui = shinyUI(
pageWithSidebar(
headerPanel('Simple matrixInput example')
,
sidebarPanel(
# customize display settings
tags$head(
tags$style(type='text/css'
, "table.data { width: 300px; }"
, ".well {width: 80%; background-color: NULL; border: 0px solid rgb(255, 255, 255); box-shadow: 0px 0px 0px rgb(255, 255, 255) inset;}"
, ".tableinput .hide {display: table-header-group; color: black; align-items: center; text-align: center; align-self: center;}"
, ".tableinput-container {width: 100%; text-align: center;}"
, ".tableinput-buttons {margin: 10px;}"
, ".data {background-color: rgb(255,255,255);}"
, ".table th, .table td {text-align: center;}"
)
)
,
wellPanel(
h4("Input Table")
,
matrixInput(inputId = 'data', label = 'Add/Remove Rows', data = df)
,
helpText("This table accepts user input into each cell. The number of rows may be controlled by pressing the +/- buttons.")
)
)
,
mainPanel(
wellPanel(
wellPanel(
h4("Output Table")
,
tableOutput(outputId = 'table.output')
,
helpText("This table displays the input matrix together with the product of the rows of the input matrix")
)
)
)
)
)
runApp(list(ui = ui, server = server))
The value should be entered by user from shiny widgets such as selectizeInput, checkboxInput or textInput and passed to the dataframe once the user click my actionButton. What I want is pretty similar to the combination of the above functions but I don't know how to do. Any suggestions?
Many thanks in advance.
Though I ended up using none of the two packages, this worked fine:
library(shiny)
server = shinyServer(function(input, output, session){
values <- reactiveValues()
values$DT <- data.frame(Name = NA,
status = NA,
compare = NA,
stringsAsFactors = FALSE)
newEntry <- observeEvent(input$addrow, {
newLine <- c(input$textIn, input$boxIn, input$selectIn)
values$DT <- rbind(values$DT, newLine)
})
newEntry <- observeEvent(input$revrow, {
deleteLine <- values$DT[-nrow(values$DT), ]
values$DT <- deleteLine
})
output$table <- renderTable({
values$DT
})
})
ui = shinyUI(navbarPage(
"Backtest System", inverse = TRUE, id = "navbar",
tabPanel("Strategy",
sidebarLayout(
sidebarPanel(
h4("Indicator"),
textInput("textIn", "Text", "try"),
checkboxInput("boxIn", "Box", TRUE),
selectizeInput("selectIn", "Select",
choices = c(">" = ">",
">=" = ">=",
"<" = "<",
"<=" = "<=")),
actionButton("addrow", "Add Row"),
actionButton("revrow", "Remove Row")
),
mainPanel(
tableOutput("table")
)
)
)
)
)
runApp(list(ui = ui, server = server))