How to preserve output and add multiple outputs with Shiny modules? - r

I would like to preserve the output from one input and then add more outputs.
Code:
if (interactive()) {
ui <- fluidPage(
sidebarPanel(
selectInput("plotType", "Plot Type",
c(Scatter = "scatter", Histogram = "hist")
),
# Only show this panel if the plot type is a histogram
conditionalPanel(
condition = "input.plotType == 'hist'",
selectInput(
"breaks", "Breaks",
c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
),
# Only show this panel if Custom is selected
conditionalPanel(
condition = "input.breaks == 'custom'",
sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
)
)
),
mainPanel(
plotOutput("plot")
)
)
server <- function(input, output) {
x <- rnorm(100)
y <- rnorm(100)
output$plot <- renderPlot({
if (input$plotType == "scatter") {
plot(x, y)
} else {
breaks <- input$breaks
if (breaks == "custom") {
breaks <- input$breakCount
}
hist(x, breaks = breaks)
}
})
}
shinyApp(ui, server)
}
Right now the output from "Scatter" disappears if for instance I select "Histogram". I would like to keep the scatter plot and add the histogram below it, and I would like to do this indefinitely.
I tried to do a few things but I didn't know what I needed to look for or learn.
Perhaps, I can add a button called Insert new plot which resets the inputs and saves the recently made plot, and then I can choose new inputs and generate a plot, and so on.
I merged two screenshots to create a picture of what I would like to achieve

Here is a solution that only shows the histogram when the plottype was selected, but keeps the scatter plot and also the histogram once it was selected. For this I use a second plot output and req with a flag if the histogram was already selected.
if (interactive()) {
ui <- fluidPage(
sidebarPanel(
selectInput("plotType", "Plot Type",
c(Scatter = "scatter", Histogram = "hist")
),
# Only show this panel if the plot type is a histogram
conditionalPanel(
condition = "input.plotType == 'hist'",
selectInput(
"breaks", "Breaks",
c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
),
# Only show this panel if Custom is selected
conditionalPanel(
condition = "input.breaks == 'custom'",
sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
)
)
),
mainPanel(
plotOutput("plot"),
plotOutput("plot_2")
)
)
server <- function(input, output) {
x <- rnorm(100)
y <- rnorm(100)
hist_flag <- FALSE
output$plot <- renderPlot({
plot(x, y)
})
output$plot_2 <- renderPlot({
req(input$plotType == "hist" || hist_flag)
breaks <- input$breaks
if (breaks == "custom") {
breaks <- input$breakCount
}
hist_flag <<- TRUE
hist(x, breaks = breaks)
})
}
shinyApp(ui, server)
}
Edit
The scenario you describe is a good use-case for modules. I've created a module that contains all the logic and ui to output one plot, and then add another module every time one clicks the button:
library(shiny)
one_plotUI <- function(id) {
ns <- NS(id)
plotOutput(ns("plot"))
}
one_plot <- function(id, x, y, type, breaks, break_counts) {
moduleServer(
id,
function(input, output, session) {
output$plot <- renderPlot({
if (type == "scatter") {
plot(x, y)
} else {
if (breaks == "custom") {
breaks <- break_counts
}
hist(x, breaks = breaks)
}
})
}
)
}
ui <- fluidPage(
sidebarPanel(
selectInput("plotType", "Plot Type",
c(Scatter = "scatter", Histogram = "hist")
),
# Only show this panel if the plot type is a histogram
conditionalPanel(
condition = "input.plotType == 'hist'",
selectInput(
"breaks", "Breaks",
c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
),
# Only show this panel if Custom is selected
conditionalPanel(
condition = "input.breaks == 'custom'",
sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
)
),
actionButton("make_plot", "Insert new plot")
),
mainPanel(
div(id = "add_here")
)
)
server <- function(input, output) {
x <- rnorm(100)
y <- rnorm(100)
counter_plots <- 1
observeEvent(input$make_plot, {
current_id <- paste0("plot_", counter_plots)
# call the logic for one plot
one_plot(id = current_id,
x = x,
y = y,
type = input$plotType,
breaks = input$breaks,
break_counts = input$breakCount)
# show the plot
insertUI(selector = "#add_here",
ui = one_plotUI(current_id))
# update the counter
counter_plots <<- counter_plots + 1
})
}
shinyApp(ui, server)

Here is a way to create an arbitrary number of plots.
library(tidyverse)
library(shiny)
library(glue)
ui <- fluidPage(actionButton("add_sepal_length", "Add Sepal.Length Histogram"),
actionButton("add_sepal_width", "Add Sepal.Width Histogram"),
uiOutput("plot_ui"))
server <- function(input, output, session){
number_of_plots <- reactiveVal(0L)
output$plot_ui <- renderUI({
req(number_of_plots()>0)
seq(number_of_plots(),1) %>% map(~plotOutput(glue("plot_{.}")))
})
observeEvent(input$add_sepal_length,{
output[[glue("plot_{number_of_plots() +1}")]] <- renderPlot(hist(iris$Sepal.Length))
number_of_plots(number_of_plots() + 1L)
})
observeEvent(input$add_sepal_width,{
output[[glue("plot_{number_of_plots() +1}")]] <- renderPlot(hist(iris$Sepal.Width))
number_of_plots(number_of_plots() + 1L)
})
}
shinyApp(ui = ui, server = server)
The basic idea is that you can use renderPlot to add to ouput$ by string. The observeEvent creates the plot on demand. Put whatever plotting logic you want in here. This takes care of the server part. The UI is handled by a single renderUI which returns a list of plotOutputs.
My logic here is very simple. I use a map() function which only needs to know the id. You could do something fancy involving a whole lot of parameters. One time I did a project where I had arbitrarily many selectInputs. I stored the parameters in a tibble inside of a reactiveVal and used pmap() inside the renderUI.

Related

How to save and load state with insertUI modules?

I'm trying to save and load state of a shiny app using bookmarks. However, it doesn't work and I wonder whether it is because of inserting dynamic UI. If there are other ways to save and load dynamically rendered ui and resulting output, that would be great too. I don't know where to start and this is as far as I could come.
Simple example
library(shiny)
ui <- function(request){fluidPage(
actionButton("add", "Add UI"),
bookmarkButton()
)}
# Server logic
server <- function(input, output, session) {
observeEvent(input$add, {
insertUI(
selector = "#add",
where = "afterEnd",
ui = textInput(paste0("txt", input$add),
"Insert some text")
)
})
}
# Complete app with UI and server components
shinyApp(ui, server, enableBookmarking = "server")
Complex example
library(shiny)
one_plotUI <- function(id) {
ns <- NS(id)
plotOutput(ns("plot"))
}
one_plot <- function(id, x, y, type, breaks, break_counts) {
moduleServer(
id,
function(input, output, session) {
output$plot <- renderPlot({
if (type == "scatter") {
plot(x, y)
} else {
if (breaks == "custom") {
breaks <- break_counts
}
hist(x, breaks = breaks)
}
})
}
)
}
ui <- fluidPage(
sidebarPanel(
bookmarkButton(),
selectInput("plotType", "Plot Type",
c(Scatter = "scatter", Histogram = "hist")
),
# Only show this panel if the plot type is a histogram
conditionalPanel(
condition = "input.plotType == 'hist'",
selectInput(
"breaks", "Breaks",
c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
),
# Only show this panel if Custom is selected
conditionalPanel(
condition = "input.breaks == 'custom'",
sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
)
),
actionButton("make_plot", "Insert new plot")
),
mainPanel(
div(id = "add_here")
)
)
server <- function(input, output) {
x <- rnorm(100)
y <- rnorm(100)
counter_plots <- 1
observeEvent(input$make_plot, {
current_id <- paste0("plot_", counter_plots)
# call the logic for one plot
one_plot(id = current_id,
x = x,
y = y,
type = input$plotType,
breaks = input$breaks,
break_counts = input$breakCount)
# show the plot
insertUI(selector = "#add_here",
ui = one_plotUI(current_id))
# update the counter
counter_plots <<- counter_plots + 1
})
}
shinyApp(ui, server, enableBookmarking = "server")
edit: Found another solution emulating what insertUI does but with renderUI:
library(shiny)
library(purrr)
ui <- function(request){fluidPage(
actionButton("add", "Add UI"),
uiOutput('dynamic_ui'),
bookmarkButton()
)}
# Server logic
server <- function(input, output, session) {
input_contents <- reactive({reactiveValuesToList(input)})
observeEvent(input$add, {
# a new ui will be rendered with one extra input each time add button is pressed
output$dynamic_ui <- renderUI({
map(1:input$add, ~textInput(inputId = paste0("txt", .x), label = paste0("txt", .x) ))
})
#add the old values, otherwise all the inputs will be empty agin.
input_contents() %>%
names() %>%
map(~ updateTextInput(session = session, inputId = .x, label = .x, value = input_contents()[[.x]]))
})
}
# Complete app with UI and server components
shinyApp(ui, server, enableBookmarking = "server")
insertUI might be broken. The only way i could "fix" it was to drop function(request) of the ui, that caused that all the values in the inputs have to be saved between stances (in state$values$input_restore). Also a warning is showed in the console, but it doesn't affect the functionality.
library(shiny)
library(tidyverse)
library(stringr)
ui <- fluidPage(
actionButton("add", "Add UI"),
uiOutput('restored_ui'), #this is very important
bookmarkButton())
# Server logic
server <- function(input, output, session) {
counter <- reactiveValues()
counter$n <- c(0) #This value is only used to initialize the object.
total_ui_count <- reactiveValues()
total_ui_count$info <- 0 #because input$add will reset to zero this will count the number of uis to remember.
#When bookmark button is pressed
onBookmark(function(state) {
state$values$currentCounter <- counter$n
state$values$input_restore <- reactiveValuesToList(input)
print(names(input) %>% str_subset('^txt'))
state$values$total_uis_to_restore <- counter$n[[length(counter$n)]]
})
#rerender the previous outputs and their values
onRestore(function(state) {
#restore values from previous state
counter$n <- state$values$currentCounter
vals <- state$values$input_restore
print(str_subset(names(vals), '^txt.*$')) #for debugging
total_ui_count$info <- state$values$total_uis_to_restore
print(total_ui_count$info)
#render back a ui with the previous values.
output$restored_ui <- renderUI({
str_subset(names(vals), '^txt.*$') %>%
sort(decreasing = TRUE) %>% #to avoid order reversal of the inputs
map(~ textInput(.x, label = .x, value = vals[[.x]])) #render the last inputs
})
})
observeEvent(input$add, {
#input$add starts as 1 in the next state (because ui is not wrapped in function(request)) that's why total_ui_count is present
counter$n <- c(counter$n, input$add + total_ui_count$info)
print(counter$n) #for debugging
insertUI(
selector = "#add",
where = "afterEnd",
ui = textInput(inputId = paste0("txt", counter$n[[length(counter$n)]]),
label = "Insert some text")
)})
}
# Complete app with UI and server components
shinyApp(ui, server, enableBookmarking = "server")

How do I access a variable from another output

I'm trying to build my first Shiny app at the moment and and having some issues. Is it possible to get access to a variable from a different output object? I'm trying to print the table in the first tab and show the individual plots on separate tabs, even better if I can show all 3 on 1 tab.
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Table", tableOutput("dataTable")),
tabPanel("xy Chart", plotOutput("xyChart")),
tabPanel("yz Chart", plotOutput("yzChart"))
)
)
)
)
)
server <- function(input, output) {
output$dataTable <- renderTable({
x <- rnorm(100, mean = 1)
y <- rnorm(100, mean = 0)
z <- rnorm(100, mean = 0.5)
dataTable <- cbind(x,y,z)
})
output$xyChart <- renderPlot({
plot(x,y)
})
If you haven't already, would take a look at the shiny tutorials available.
Instead of including your data in a single output inside of server, you could declare these variables elsewhere. Since you are creating a shiny app, you might be interested in changing these variables, and having the other outputs automatically update.
If that is true, you might want to use reactiveValues or create a reactive function.
Here's an example below. By using reactiveValues, when you read a value from it (like x, y, or z) the calling expression takes a reactive dependency on that value (and will update with changes made to it). Whenever you modify those values, it will notify all reactive functions that depend on that value.
library(shiny)
ui <- fluidPage(
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)
)
)
server <- function(input, output) {
my_data <- reactiveValues(
x = rnorm(100, mean = 1),
y = rnorm(100, mean = 0),
z = rnorm(100, mean = 0.5)
)
output$table <- renderTable({
data.frame(my_data$x, my_data$y, my_data$z)
})
output$plot <- renderPlot({
plot(my_data$x, my_data$y)
})
output$summary <- renderText({
"Summary Goes Here"
})
}
shinyApp(ui = ui, server = server)
And if you want all 3 on one panel (as described in comments), use this for your ui:
ui <- fluidPage(
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("All 3",
plotOutput("plot"),
verbatimTextOutput("summary"),
tableOutput("table")
)
)
)
)
If you want to include your input$nRV (as mentioned in comments), use a reactive expression, and call it as my_data():
server <- function(input, output) {
my_data <- reactive({
a = rnorm(input$nRV, mean = 2)
b = rnorm(input$nRV, mean = 5)
x = rnorm(input$nRV, mean = 3)
y = rnorm(input$nRV, mean = 0)
z = rnorm(input$nRV, mean = 0.5)
data.frame(a, b, x, y, z)
})
output$table <- renderTable({ data.frame(my_data()$x, my_data()$y, my_data()$z)
})
output$plot <- renderPlot({ plot(my_data()$x, my_data()$y) })
}

Multiple plots according to checkboxGroupInput

I am trying to create an easy application with R shiny. However I could not get the desired output I want. I am neither experienced in shiny nor an expert of R. Here is the code:
library(shiny)
ui <- fluidPage(
headerPanel("deneme"),
checkboxGroupInput("plots", "draw plots:",
choices=list("histogram", "qq","both"),
selected="histogram"),
sidebarPanel(
numericInput("mean", "rn mean", value=seq(0:5), min=0, max=5),
numericInput("sd","standart deviation",value=seq(0:5),min=0,max=5),
numericInput("n", " number of observations ", value=seq(30,50))
),
mainPanel(
textOutput("text1"),
fluidRow(splitLayout(cellWidths = c("60%", "40%"),
plotOutput("graph1"), plotOutput("graph2")))
)
)
server <- function(input, output) {
norm<-reactive({
set.seed(6)
rnorm(input$n,mean=input$mean,sd=input$sd)
})
output$text1<-renderText({
paste("A random normal distrubution of",
input$n, "observations is generated with parameters mean",
input$mean,"and standart deviation", input$sd)
})
output$graph1<-renderPlot({
if(identical(input$plots,"histogram")){
req(norm())
hist(norm())
}
})
output$graph2<- renderPlot({
if(identical(input$plots,"qq")) {
req(norm())
qqnorm(norm(), pch = 1, frame = FALSE)
qqline(norm(), col = "steelblue", lwd = 2)
}
})
observe({
if(identical(input$plots,"both")) {
req(norm())
output$graph1<- renderPlot({
hist(norm())
})
output$graph2<- renderPlot({
qqnorm(norm(), pch = 1, frame = FALSE)
qqline(norm(), col = "steelblue", lwd = 2)
})
}
})
}
shinyApp(ui = ui, server = server)
I want the plot layout change dynamically according to selection of checkboxGroupInput. When I click histogram or qq I want it to plot an unsplit frame, into only one plotting frame. Whereas when I click both I want the plots to be seen together in a split frame of two rows. When unclicked the layout must be reset to one frame again. I know I am not doing it right by splitting the layout in ui first. I saw something about renderUI function but could not understand how it works. Thanks in advance.
Also I got some error related to if statement:
Warning in if (!is.na(attribValue)) { :
the condition has length > 1 and only the first element will be used
Warning in charToRaw(enc2utf8(text)) :
argument should be a character vector of length 1
all but the first element will be ignored
Here is a start, you don't need the observer, you can just add an if statement to each renderPlot.
Update: The trick to getting the plots to update dynamically is to assign them into a list and then render the list of plots with renderUI, the only caveat to this is that I am unaware of a way to get these plots to render side-by-side at the moment, it probably has something to do with adding some div tags...
Update 2: To get the plots side by side we just need to wrap the plotOutput in column
library(shiny)
ui <- fluidPage(
headerPanel("deneme"),
checkboxGroupInput("plots", "draw plots:",
choices=list("histogram", "qq"),
selected="histogram"),
sidebarPanel(
numericInput("mean", "rn mean", value=1, min=0, max=5),
numericInput("sd","standart deviation",value=1,min=0,max=5),
numericInput("n", " number of observations ", value=30)
),
mainPanel(
textOutput("names"),
textOutput("text1"),
fluidRow(uiOutput("plot_list"))
)
)
server <- function(input, output) {
norm<-reactive({
set.seed(6)
rnorm(input$n,mean=input$mean,sd=input$sd)
})
output$text1<-renderText({
paste("A random normal distribution of",
input$n, "observations is generated with parameters mean",
input$mean,"and standart deviation", input$sd)
})
output$histogram <- renderPlot({
req(norm())
if("histogram" %in% input$plots){
hist(norm())
}
})
output$qq <- renderPlot({
req(norm())
if("qq" %in% input$plots){
qqnorm(norm(), pch = 1, frame = FALSE)
qqline(norm(), col = "steelblue", lwd = 2)
}
})
output$plot_list <- renderUI({
plot_output_list <- lapply(input$plots,
function(plotname) {
column(width=5, plotOutput(plotname)) ##wrap the plotOutput in column to render side-by-side
})
# Convert the list to a tagList - this is necessary for the list of items
# to display properly.
do.call(tagList, plot_output_list)
})
}
shinyApp(ui = ui, server = server)
You can have a single plotOutput and use mfrow to split it into two panels, like this:
library(shiny)
ui <- fluidPage(
headerPanel("deneme"),
radioButtons("plots", "draw plots:",
choices=list("histogram", "qq","both"),
selected="histogram"),
sidebarPanel(
numericInput("mean", "rn mean", value=seq(0:5), min=0, max=5),
numericInput("sd","standart deviation",value=seq(0:5),min=0,max=5),
numericInput("n", " number of observations ", value=seq(30,50))
),
mainPanel(
textOutput("text1"),
plotOutput("graph")
)
)
server <- function(input, output) {
norm<-reactive({
set.seed(6)
rnorm(input$n,mean=input$mean,sd=input$sd)
})
output$text1<-renderText({
paste("A random normal distrubution of",
input$n, "observations is generated with parameters mean",
input$mean,"and standart deviation", input$sd)
})
output$graph = renderPlot({
if(input$plots == "both") {
par(mfrow = c(1, 2))
}
if(is.element(input$plots, c("histogram", "both"))) {
req(norm())
hist(norm())
}
if(is.element(input$plots, c("qq", "both"))) {
req(norm())
qqnorm(norm(), pch = 1, frame = FALSE)
qqline(norm(), col = "steelblue", lwd = 2)
}
})
}
shinyApp(ui = ui, server = server)
If you want two rows instead of two columns, just change par(mfrow = c(1, 2)) to par(mfrow = c(2, 1)).
(I'm still getting the error on if too, but it doesn't seem to affect the functioning of the app, at least as far as the graphs are concerned. I'm not sure where it's coming from.)

Interacting with checkboxes in Rshiny for overlapping plots

I am struggling trying to graph two overlaying plots and add checkboxes fro displaying them in my Rshiny app. I am using the following code:
library(shiny)
library(shinyjs)
mpgData <- mtcars
ui <- fluidPage(
# Application title
titlePanel("Test"),
# Sidebar with checkboxes to select plot
sidebarLayout(
sidebarPanel(
helpText("Select type of plot:"),
checkboxGroupInput("checkPlot",
label = ("Plots"),
choices=c("Baseline","Scenario A"),
selected = c("Baseline","Scenario A")
)
),
mainPanel(
textOutput("overview"),
plotOutput("plot")
)
)
)
server <- function(input, output, session) {
#get Check group input (type of plot)
checkedVal <- reactive({
as.vector(input$checkPlot)
})
#plot
output$plot <- renderPlot({
if(("Baseline" %in% checkedVal()) & ("Scenario A" %in% checkedVal()))
# first plot
plot(mpgData$mpg, mpgData$cyl, type='l',col="steelblue",ylim=range(c(mpgData$cyl,mpgData$disp)))
# second plot
par(new = TRUE)
plot(mpgData$mpg, mpgData$disp, type = "l",col="red", ylim=range(c(mpgData$cyl,mpgData$disp)), axes = FALSE, xlab = "", ylab = "")
if ("Baseline" %in% checkedVal())
plot(mpgData$mpg, mpgData$cyl, type='l',col = "steelblue")
if ("Scenario A" %in% checkedVal())
plot(mpgData$mpg, mpgData$disp, type='l',col = "red")
})
}
shinyApp(ui, server)
My checkboxes seem to be working out alright when I just want one graph to be displayed, however, there's definitely an issue when I want to display both on the same axes. Most examples I saw were a little too complex for me to understand and break down, so I tired to infer from previous R knowledge, but clearly I'm off.
any help is much appreciated !
If you were trying to add the baseline on the existing graph, you could use the lines function, as below. Although if for your particular data-set, the base line is really negligible compared to the original plot, you need to use a package other than 'base', like 'ggplot'.
library(shiny)
library(shinyjs)
mpgData <- mtcars
ui <- fluidPage(
# Application title
titlePanel("Test"),
# Sidebar with checkboxes to select plot
sidebarLayout(
sidebarPanel(
helpText("Select type of plot:"),
checkboxGroupInput("checkPlot",
label = ("Plots"),
choices=c("Baseline","Scenario A"),
selected = c("Baseline","Scenario A")
)
),
mainPanel(
textOutput("overview"),
plotOutput("plot")
)
)
)
server <- function(input, output, session) {
#get Check group input (type of plot)
checkedVal <- reactive({
as.vector(input$checkPlot)
})
#plot
# first plot
output$plot <- renderPlot({
if(("Baseline" %in% checkedVal()) & ("Scenario A" %in% checkedVal()))
{ plot(mpgData$mpg, mpgData$cyl, type='l',col="steelblue",ylim=range(c(mpgData$cyl,mpgData$disp)))
lines(mpgData$mpg, mpgData$disp, type = "l",col="red")
}
else if("Baseline" %in% checkedVal())
{
plot(mpgData$mpg, mpgData$cyl, type='l',col = "steelblue")
}
else if("Scenario A" %in% checkedVal())
{
plot(mpgData$mpg, mpgData$disp, type='l',col = "red")
}
})
}
shinyApp(ui, server)
Please let me know if this works for you.

how to delete warnings in reactive inputs in shiny

Could anyone can tell me why I get an error when I change a dataset in first selectInput widget? When I change a dataset from diamonds to mtcars I get an error Could not find 'carat' in input$bins and in the plot just for one second and after that everything works fine. Why it happened?
library(shiny)
library(ggplot2)
data(diamonds)
data(mtcars)
ui <- fluidPage(
column(3,
selectInput("data", "", choices = c('mtcars', 'diamonds')),
uiOutput('server_cols'),
uiOutput('server_bins')
),
column(9,
plotOutput("plot")
)
)
server <- function(input, output) {
data <- reactive({
switch(input$data,
diamonds = diamonds,
mtcars = mtcars)
})
output$server_cols <- renderUI({
data <- data()
nam <- colnames(data)
selectInput('cols', "Choose numeric columns:", choices = nam[sapply(data, function(x) is.numeric(x))])
})
output$server_bins <- renderUI({
if (!is.null(input$cols)) {
df <- data()
x <- eval(input$cols)
max_value <- max(df[,x])
sliderInput('bins','Choose number of bins:', min = 0.1,
max = max_value,
value = max_value/2)
}
})
output$plot <- renderPlot({
if (!is.null(input$cols) & !is.null(input$bins)) {
basicData <- data()
var <- eval(input$cols)
ggplot(basicData, aes_string(var)) +
geom_histogram(binwidth = input$bins, color = 'white', fill = 'red')
}
})
}
shinyApp(ui, server)
Your respective output objects respond to any changes of your input variables. Thus, when you change your dataset via input$data, the plot rebuilds itself, although input$cols did not yet adjust. Actually, try inserting some print("a") inside the output$plot to see that it is called up to three times if you change input$data.
The fix is to rethink your reaction logic and let your elements respond only to specific changes, to get some kind of response "thread".
For example, input$data should only trigger output$server_cols. And output$server_bins should only be triggered by input$cols (because this already implies that input$data changed earlier). Ultimately, output$plot just has to listen to changes of input$bins (because changes in input$cols and input$data always result in changes of input$bins since it is at the end of the thread).
Here is my suggestion using isolate.
library(shiny)
library(ggplot2)
data(diamonds)
data(mtcars)
ui <- fluidPage(
column(3,
selectInput("data", "", choices = c('mtcars', 'diamonds')),
uiOutput('server_cols'),
uiOutput('server_bins')
),
column(9,
plotOutput("plot")
)
)
server <- function(input, output) {
data <- reactive({
switch(input$data, diamonds = diamonds, mtcars = mtcars)
})
output$server_cols <- renderUI({
data <- data()
nam <- colnames(data)
selectInput('cols', "Choose numeric columns:", choices = nam[sapply(data, function(x) is.numeric(x))])
})
output$server_bins <- renderUI({
if (!is.null(input$cols)) {
df <- isolate(data())
x <- eval(input$cols)
max_value <- max(df[,x])
sliderInput('bins','Choose number of bins:', min = 0.1, max = max_value, value = max_value/2)
}
})
output$plot <- renderPlot({
if (!is.null(isolate(input$cols)) & !is.null(input$bins)) {
basicData <- isolate(data())
var <- eval(isolate(input$cols))
ggplot(basicData, aes_string(var)) +
geom_histogram(binwidth = input$bins, color = 'white', fill = 'red')
}
})
}
shinyApp(ui, server)
You might also want to look into updateSelectInput and updateSliderInput if you want to alter Input Elements depending on other input.

Resources