I have a ui and a server module in shiny just like in an example in 'Mastering Shiny' by Hadley Wickham:
histogramUI <-
function(id){
tagList(
selectInput(NS(id, "var"), "Variable", choices = names(mtcars)),
numericInput(NS(id, "bins"), "bins", value = 10, min = 1),
plotOutput(NS(id, "hist"))
)
}
histogramServer <-
function(id){
moduleServer(id, function(input, output, session){
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
hist(data(), breaks = input$bins, main = input$var)
}, res = 96)
})
}
Now I want to create the app with two inputs and outputs named by "hist1" and "hist2".
That works fine with the following code:
histogrammApp <-
function(){
ui <- fluidPage(
histogramUI("hist1"),
histogramUI("hist2")
)
server <- function(input, output, session){
histogramServer("hist1")
histogramServer("hist2")
}
shinyApp(ui, server)
}
Each plot has its own input parameters.
Let's say I want to have a general input bins instead so that both plots will have the same amount of breaks in a numericInput. How could I achieve this?
My first attempt was to remove the line numericInput(NS(id, "bins"), "bins", value = 10, min = 1), and place the line tagList(numericInput("bins", "bins", value = 10, min = 1)), before the line histogramUI("hist1"), but this did not work. I get the following error: Invalid breakpoints produced by 'breaks(x)': NULL. input$bins is NULL, I guess. I think because it is in a different namespace? How could I come up with the problem?
You should consider passing the input$bins as a reactive to histogramServer("hist1",reactive(input$bins)). Try this
histogramUI <- function(id){
tagList(
selectInput(NS(id, "var"), "Variable", choices = names(mtcars)),
#numericInput(NS(id, "bins"), "bins", value = 10, min = 1),
plotOutput(NS(id, "hist"))
)
}
histogramServer <- function(id,bins){
moduleServer(id, function(input, output, session){
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
hist(data(), breaks = bins(), main = input$var)
}, res = 96)
})
}
#histogrammApp <- function(){
ui <- fluidPage(
numericInput("bins", "Bins", value = 10, min = 1),
histogramUI("hist1"),
histogramUI("hist2")
)
server <- function(input, output, session){
histogramServer("hist1",reactive(input$bins))
histogramServer("hist2",reactive(input$bins))
}
shinyApp(ui, server)
# }
#
# histogrammApp()
Related
I'm new to shiny.
When I try to handle a click or brush event without using modules, everything works ok.
What should I do to make the events work inside the module?
There is simple code
#MODULE
plUI <- function(id) {
tagList(
selectInput(NS(id, "var"), "Variable", choices = names(mtcars)),
numericInput(NS(id, "bins"), "bins", value = 10, min = 1),
plotOutput(NS(id, "hist"), click = "plot_click"),
verbatimTextOutput("info")
)
}
plServer <- function(id) {
moduleServer(id, function(input, output, session) {
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
plot(data(), breaks = input$bins, main = input$var)
}, res = 96)
output$info <- renderPrint({
req(input$plot_click)
x <- round(input$plot_click$x, 2)
y <- round(input$plot_click$y, 2)
cat("[", x, ", ", y, "]", sep = "")
})
})
}
#------------------------------UI-------------------------------------
ui <- fluidPage(
ui <- fluidPage(
plUI("p1"),
)
)
#------------------------------SERVER-------------------------------------
server <- function(input, output, session) {
plServer("p1")
}
shinyApp(ui, server)
Instead of NS(id, "var") for each id, you can do ns <- NS(id) and then use ns("var") and so on.
plot_click is also an id so wrap it in ns as well.
You had forgotten NS in verbatimTextOutput("info").
The main ui had two fluidPage that probably was by mistake so removed it.
library(shiny)
plUI <- function(id) {
ns <- NS(id)
tagList(
selectInput(ns("var"), "Variable", choices = names(mtcars)),
numericInput(ns("bins"), "bins", value = 10, min = 1),
plotOutput(ns("hist"), click = ns("plot_click")),
verbatimTextOutput(ns("info"))
)
}
plServer <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
plot(data(), breaks = input$bins, main = input$var)
}, res = 96)
output$info <- renderPrint({
req(input$plot_click)
x <- round(input$plot_click$x, 2)
y <- round(input$plot_click$y, 2)
cat("[", x, ", ", y, "]", sep = "")
})
})
}
#------------------------------UI-------------------------------------
ui <- fluidPage(
plUI("p1")
)
#------------------------------SERVER-------------------------------------
server <- function(input, output, session) {
plServer("p1")
}
shinyApp(ui, server)
I am trying to save and restore an app that uses modules which render UI outputs dynamically.
I hoped the bookmarking function would work with the app and I added the bookmarkButton and enabled bookmarking using enableBookmarking = "server". I've also made the ui a function. I learned that bookmarking works with modules, but I'm unable to find a way to get it working with dynamically created UI inputs and outputs. Only the last input and output are restored. The others are not restored.
Example app:
library(shiny)
histogramUI <- function(id) {
tagList(
fluidRow(column( 4, selectInput(NS(id, "var"), "Variable", choices = names(mtcars)),
numericInput(NS(id, "bins"), "bins", value = 10, min = 1)),
column(8, plotOutput(NS(id, "hist"))))
)
}
histogramServer <- function(id) {
moduleServer(id, function(input, output, session) {
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
hist(data(), breaks = input$bins, main = input$var)
}, res = 96)
})
}
ui <- function(request){
fluidPage(
bookmarkButton(),
actionButton("add", "Add"),
div(id = "add_here")
)
}
server <- function(input, output, session) {
observeEvent(input$add, {
histogramServer(paste0("hist_", input$add))
insertUI(selector = "#add_here", ui = histogramUI(paste0("hist_", input$add)))
})
}
shinyApp(ui, server, enableBookmarking = "server")
Only the last input and plot output are restored:
One would expect all module instances to be restored, but as you pointed out, only the last one is restored due to addbutton restoration.
As a workaround, you could store the module instances list stored in state$exclude with onBookmark and re-create the instances of the module with onRestore.
histogramUI was modified in order to accept var,bins as new parameters for creation of the modules.
Another important point is to use setBookmarkExclude so that the add button doesn't create the last module at restoration. As the button isn't anymore bookmarked, it's value should be also be saved with onBookmark.
Try:
library(shiny)
histogramUI <- function(id,var,bins) {
tagList(
fluidRow(column( 4, selectInput(NS(id, "var"), "Variable", choices = names(mtcars),selected=var),
numericInput(NS(id, "bins"), "bins", value = bins, min = 1)),
column(8, plotOutput(NS(id, "hist"))))
)
}
histogramServer <- function(id) {
moduleServer(id, function(input, output, session) {
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
hist(data(), breaks = input$bins, main = input$var)
}, res = 96)
})
}
ui <- function(request){
fluidPage(
bookmarkButton(),
actionButton("add", "Add Histogram"),
div(id = "add_here")
)
}
server <- function(input, output, session) {
add_id <- reactiveVal(0) # To save 'add' button state
setBookmarkExclude('add') # Don't add new module at restoration
observeEvent(input$add, {
histogramServer(paste0("hist_", input$add+add_id()))
insertUI(selector = "#add_here", ui = histogramUI(paste0("hist_", input$add+add_id()),'mpg',10))
})
onBookmark(function(state) {
modules <- state$exclude
state$values$modules <- modules[grepl("hist",modules)] # only 'hist' (without 'add')
state$values$add <- state$input$add + add_id() # add button state
})
onRestore(function(state){
# Restore 'add' last state
add_id(state$values$add)
# Restore 'hist' modules
modules <- state$values$modules
if (length(modules)>0) {
for (i in 1:(length(modules))) {
histogramServer(modules[i])
insertUI(selector = "#add_here", ui = histogramUI(modules[i],paste0(modules[i],"-var"),paste0(modules[i],"-bin")))
}
}
})
}
shinyApp(ui, server, enableBookmarking = "server")
Another way to do it:
library(shiny); library(purrr)
histogramUI <- function(id) {
ns <- NS(id)
tagList(
fluidRow(column( 4, selectInput(ns("var"), "Variable", choices = names(mtcars)),
numericInput(ns("bins"), "bins", value = 10, min = 1)),
column(8, plotOutput(ns("hist"))))
)
}
histogramServer <- function(id) {
moduleServer(id, function(input, output, session) {
vals <- reactiveValuesToList(input)
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
hist(data(), breaks = input$bins, main = input$var)
}, res = 96)
#to avoid inputs resetting after adding another.
if(length(vals) != 0) {
updateSelectInput(session, 'var', "Variable", choices = names(mtcars), selected = vals$var)
updateNumericInput(session, 'bins', "bins", value = input$bins, min = 1,)
}
})
}
ui <- function(request){
fluidPage(
bookmarkButton(),
actionButton("add", "Add"),
uiOutput('histogram_module')
)
}
server <- function(input, output, session) {
observeEvent(input$add, {
#the server module
map(1:input$add, ~histogramServer(paste0("hist_", .x)))
#the ui module
output$histogram_module <- renderUI({ map(1:input$add, ~histogramUI(id = paste0("hist_", .x))) })
})
}
shinyApp(ui, server, enableBookmarking = "server")
I'm trying to create multiple plots using modules, each plot with it's own input. But when I tried to run the app, only the inputs are added each time I add using insertUI and the plot output is blank.
I've tried connecting the ui and the server modules with the same id ("hist1") but it doesn't seem to connect each individual module.
histogramUI <- function(id) {
tagList(
selectInput(NS(id, "var"), "Variable", choices = names(mtcars)),
numericInput(NS(id, "bins"), "bins", value = 10, min = 1),
plotOutput(NS(id, "hist"))
)
}
histogramServer <- function(id) {
moduleServer(id, function(input, output, session) {
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
hist(data(), breaks = input$bins, main = input$var)
}, res = 96)
})
}
ui <- fluidPage(
actionButton("add", "Add"),
div(id = "add_here")
)
server <- function(input, output, session) {
histogramServer("hist1")
observeEvent(input$add, {
insertUI(selector = "#add_here", ui = histogramUI("hist1"))
})
}
shinyApp(ui,server)
Here is a solution where every time you click add you generate a new pair of histogramServer/histogramUI which have the same id (but a different one than the one before, because add gets incremented):
library(shiny)
histogramUI <- function(id) {
tagList(
selectInput(NS(id, "var"), "Variable", choices = names(mtcars)),
numericInput(NS(id, "bins"), "bins", value = 10, min = 1),
plotOutput(NS(id, "hist"))
)
}
histogramServer <- function(id) {
moduleServer(id, function(input, output, session) {
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
hist(data(), breaks = input$bins, main = input$var)
}, res = 96)
})
}
ui <- fluidPage(
actionButton("add", "Add"),
div(id = "add_here")
)
server <- function(input, output, session) {
observeEvent(input$add, {
histogramServer(paste0("hist_", input$add))
insertUI(selector = "#add_here", ui = histogramUI(paste0("hist_", input$add)))
})
}
shinyApp(ui,server)
When I try to feed to uiOutput to renderUI while using tabPanel in shiny I get an error in the first run. After switching tabs, the app runs ok.
Here is a minimal example that reproduces the error
library(shiny)
ui <- fluidPage(
tabsetPanel(
tabPanel("Data",
uiOutput("moreControls")
),
tabPanel("Research",
uiOutput("moreControls2")
)
),
plotOutput("plot1")
)
server <- function(input, output) {
output$moreControls <- renderUI({
tagList(
sliderInput("mean", "Mean", -10, 10, 1),
textInput("label", "Label")
)
})
output$moreControls2 <- renderUI({
tagList(
sliderInput("sd", "SD", 1, 50, 10),
textInput("label2", "Label2")
)
})
output$plot1 <- renderPlot({
hist(rnorm(n = 100,input$mean, input$sd) , xlim = c(-100, 100) )
})
}
shinyApp(ui, server)
#Vivek's answer is nice but here is another way:
server <- function(input, output) {
output$moreControls <- renderUI({
tagList(
sliderInput("mean", "Mean", -10, 10, 1),
textInput("label", "Label")
)
})
output$moreControls2 <- renderUI({
tagList(
sliderInput("sd", "SD", 1, 50, 10),
textInput("label2", "Label2")
)
})
outputOptions(output, "moreControls2", suspendWhenHidden = FALSE)
output$plot1 <- renderPlot({
req(input$mean, input$sd)
hist(rnorm(n = 100, input$mean, input$sd) , xlim = c(-100, 100) )
})
}
shinyApp(ui, server)
The input$mean is not available before the uiOutput renders, and input$sd too, but in addition input$sd is not available until you switch to the second tab, because the sliderInput is hidden.
The reason it doesn't work is because Shiny hasn't evaluated those values when your app runs. As such, the input values aren't actually available to renderPlot()
A good way to pass in values for the plot would be to use a reactive expression. In the code below I have used plot_params() and inside the reactive, I make a list which stores the parameters of producing your plot.
I'd also recommend using shiny::validate() to ensure that the input values are valid before rendering output. (See use below)
library(shiny)
ui <- fluidPage(
tabsetPanel(
tabPanel("Data",
uiOutput("moreControls")
),
tabPanel("Research",
uiOutput("moreControls2")
)
),
plotOutput("plot1")
)
server <- function(input, output) {
output$moreControls <- renderUI({
tagList(
sliderInput("mean", "Mean", -10, 10, 1),
textInput("label", "Label")
)
})
output$moreControls2 <- renderUI({
tagList(
sliderInput("sd", "SD", 1, 50, 10),
textInput("label2", "Label2")
)
})
# Reactive expression for plot parameters.
plot_params <- reactive({
list(
mean = input$mean,
sd = input$sd
)
})
output$plot1 <- renderPlot({
validate(
need(input$mean, 'Please check that mean is set!'),
need(input$sd, 'Please check that sd is set.')
)
hist(rnorm(n = 100, plot_params()$mean, plot_params()$sd) , xlim = c(-100, 100) )
})
}
shinyApp(ui, server)
I want an event to be triggered for the first time only by clicking a button. After that I want it to be reactive to the slider input.
I tried the following:
ui <- fluidPage(
actionButton("go", "Go"),
sliderInput("n", label = "Sample size", min = 1, max = 100, value = 10),
plotOutput('samples')
)
server <- function(input, output, session){
activate = reactive({FALSE})
activate = eventReactive(input$go, {
isolate(TRUE)
})
samples = eventReactive(activate(), {
rnorm(input$n)
})
output$samples <- renderPlot({ hist(samples()) })
}
shinyApp(ui = ui, server = server)
I hoped it would make it reactive to input$n after input$go has been clicked once. But it isn't and still needs input$go to be clicked every time.
There are several ways to achieve that.
One way would be to store the value in a reactiveValues() or just use req(), see below.
The problem with using eventReactive(activate(), ... is that it only triggers the code inside if activate() is executed, which only happens if you click input$go.
Reproducible example with req():
ui <- fluidPage(
actionButton("go", "Go"),
sliderInput("n", label = "Sample size", min = 1, max = 100, value = 10),
plotOutput('samples')
)
server <- function(input, output, session){
output$samples <- renderPlot({
req(input$go > 0)
hist(rnorm(input$n))
})
}
shinyApp(ui = ui, server = server)
Reproducible example with reactiveValues():
ui <- fluidPage(
actionButton("go", "Go"),
sliderInput("n", label = "Sample size", min = 1, max = 100, value = 10),
plotOutput('samples')
)
server <- function(input, output, session){
global <- reactiveValues(showPlot = FALSE)
observeEvent(input$go, {
global$showPlot <- TRUE
})
samples = reactive({
rnorm(input$n)
})
output$samples <- renderPlot({
req(global$showPlot)
hist(samples())
})
}
shinyApp(ui = ui, server = server)