I'm building a shiny app with a leaflet map that updates based on user selection. When I got to working on the labels for shiny, I noticed my performance dropped upon hitting my action button to refresh the map. It takes about 10 seconds
Digging into it, it takes 10 seconds regardless of its rendering 1 point or 3000 points. I've strangely been able to troubleshoot and find that if I remove the HTML function (to render an unformatted label) it instantly renders any number of points upon refresh.
Any ideas why rendering HTML labels takes so much longer than unformatted labels, regardless of the number of points?
global.R
# Packages
library(shiny)
library(sf)
library(leaflet)
library(maps)
library(dplyr)
library(htmltools)
# Initialize Data
print('Read in Data')
df <- read.delim("inst/Sample Data.csv", na.strings="")%>%
mutate(SurfaceHoleLongitude=as.numeric(substr(SurfaceHoleLongitude,2,length(SurfaceHoleLongitude))))%>%
filter(is.na(SurfaceHoleLongitude)==FALSE & is.na(SurfaceHoleLatitude)==FALSE)%>%
filter(SurfaceHoleLongitude!='NA' & SurfaceHoleLatitude!='NA')%>%
as.data.frame()
print('Create Labels')
df <- df %>%
mutate(pointlabel=paste0(`Lease.Name`,
"<br>", County,", ",State,
"<br> Operator: ", Operator,
"<br> Customer Name: ", Customer.Name,
"<br> Reservoir: ", Reservoir#,
# "<br>BOKF Exposure", `BOKF.Exposure`,
# "<br>DROI", `CEResults.DROI`,
# "<br>Outstanding Percent", `Outstanding Percent`
))%>%
rowwise()%>%
mutate(pointlabel=HTML(pointlabel))
print('Finished Making Labels')
app.R
# Tabset of hideable filters
parameter_tabs <- tabsetPanel(
id = "filterTabset",
type = "hidden",
tabPanel("Operator",
selectInput(
selected = 'BCE Mach III LLC',
inputId='Filter1', multiple = TRUE, label='Operator',
choices=df%>%select(Operator)%>%unique()%>%pull())),
tabPanel("Customer Name",
selectInput(
inputId='Filter2', multiple = TRUE, label='Customer Name',
choices=df%>%select(Customer.Name)%>%unique()%>%pull())),
tabPanel("Region",
selectInput(
inputId='Filter3', multiple = TRUE, label='Region',
choices=df%>%select(Region)%>%unique()%>%pull())),
tabPanel("County",
selectInput(
inputId='Filter4', multiple = TRUE, label='County',
choices=df%>%select(County)%>%unique()%>%pull())),
tabPanel("State",
selectInput(
inputId='Filter5', multiple = TRUE, label='State',
choices=df%>%select(State)%>%unique()%>%pull())),
tabPanel("Reservoir",
selectInput(
inputId='Filter6', multiple = TRUE, label='Reservoir',
choices=df%>%select(Reservoir)%>%unique()%>%pull()))
)
# Define UI for app
ui <- fluidPage(
# App title ----
titlePanel("Engineering Toolkit"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input 1: selectInput to choose a filter to appear in 2nd selectInput ----
selectInput(
inputId='FilterFieldSelection',
label='Filter Field',
choices=c('Operator','Customer Name','Region','County','State','Reservoir'),
selected = 'Operator',
multiple = FALSE#,
# selectize = TRUE,
# width = NULL,
# size = NULL
),
# Input 2: Specific selectInput to actually filter data
parameter_tabs,
# Input 3: Action button to load map
actionButton('button','Load Map')
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Map ----
leafletOutput("WellMap")
)
)
)
# Define Server
server <- function(input, output) {
# Reactive (lazy) expression for getting the data subsetted to what the user selected
filteredData <- reactive({
df%>%
# Filtering only occurs if the there is a selection (length>0)
filter(case_when(length(input$Filter1)>0 ~ Operator %in% input$Filter1,TRUE~1==1))%>%
filter(case_when(length(input$Filter2)>0 ~ Customer.Name %in% input$Filter2,TRUE~1==1))%>%
filter(case_when(length(input$Filter3)>0 ~ Region %in% input$Filter3,TRUE~1==1))%>%
filter(case_when(length(input$Filter4)>0 ~ County %in% input$Filter4,TRUE~1==1))%>%
filter(case_when(length(input$Filter5)>0 ~ State %in% input$Filter5,TRUE~1==1))%>%
filter(case_when(length(input$Filter6)>0 ~ Reservoir %in% input$Filter6,TRUE~1==1))
})
# Fire this when 1st selectInput "FilterOfFilters" is changed,
observeEvent(input$FilterFieldSelection, {
# Update choices of 2nd selectInput "SpecificFilter"
updateTabsetPanel(inputId = "filterTabset", selected = input$FilterFieldSelection)
})
# Output to UI
output$WellMap <-
# Output Leaflet map
renderLeaflet({
# Draw Map layers (not points)
counties.sf <- st_as_sf(map("county", plot = FALSE, fill = TRUE))
counties.latlong<-st_transform(counties.sf,crs = "+init=epsg:4326")
leaflet() %>%
addTiles() %>%
addPolygons(weight=1,fill=FALSE,color='black',data=counties.latlong) %>%
addCircles(lat= ~SurfaceHoleLatitude,lng= ~SurfaceHoleLongitude,label= ~pointlabel,
group = 'pointLayer',data=df%>%filter(Operator=='BCE Mach III LLC'),
radius=0.5,color='red',opacity=0.5,fill=FALSE,stroke = TRUE,weight=5)
})
# Perform this only on button press
observeEvent(input$button,{
# Clear points on map
leafletProxy("WellMap", data = filteredData())%>%
clearGroup('pointLayer')%>%
# Update map with Updated Data
addCircles(lat= ~SurfaceHoleLatitude,lng= ~SurfaceHoleLongitude,
group = 'pointLayer',radius=0.5,label= ~pointlabel,
color ='red',opacity=0.5,fill=FALSE,stroke = TRUE,weight=5)
})
}
# Run App
shinyApp(ui = ui, server = server)
I heard data.table was faster than data.frame from tidyverse and so I passed my filteredData %>% data.table() and now it loads instantly again. Interesting, as a huge tidyverse fan
Related
I made a flowchart that changes based on numeric input the user provides. Now I want the user to be able to select a row from a table, and depending on what the user selected, the value of the numeric input changes.
In this example, selecting a movie title would change the numeric input "ticket price" based on a data frame with movie information. However, the user would be free to simply ignore the selectInput if they want to.
I already have the select input widget. I want it so that the widget reads the information of movie titles from the data frame with movie information.
I made this working example:
# Packages
library(shiny)
library(DiagrammeR) # creates flowchart
### Define UI ###
ui <- fluidPage(
# Sidebar with select input and numeric input
sidebarLayout(
sidebarPanel(h3("Flowchart creator with select Input"),
#User can choose movie title (optional)
selectInput(inputId = 'title',
label = "Movie title",
choices = c("","Matrix","Inception")),
#User can manually type in ticket price
numericInput("inputb",
"Ticket price is:",
value = 60)
),
# Show DiagrammeR plot
mainPanel(
grVizOutput("dg",width ="95%",height="95%")
)
)
)
### Define server ###
server <- function(input, output) {
#Creates tribble with movie information.
df <- as.data.frame(tribble(
~movie,~cost,~duration,
"Matrix",10.00,136,
"Inception",12.00,158,
"Titanic",15.00,194)) #end movie tribble
rv <<- reactive({
#Creates tibble. DiagrammeR reads values inside the tibble.
tibble::tibble(
price.tickets = input$inputb
) #End of tibble
})
#Define outputs to show in flowchart based on values on tibble
#The flowchart can't read inputs. It can only read values on the tibble.
output$price.tickets = renderText({rv()$price.tickets })
# Create flowchart with DOT language with DiagrammeR
output$dg <- renderGrViz({
grViz(diagram = "digraph flowchart {
legend [label = '##2']
price [label = '##1']
legend->price
}
[1]: rv()$price.tickets
[2]: 'ticket price'
") #end of flowchart in DOT language
})
}
# Run the application
shinyApp(ui = ui, server = server)
So it's two things:
Define selectInput choices based on the data.frame with movie information
Update the numericInput value based on the cost of the movie selected by the user, if the user chooses a movie.
Thank you!
We can combine datatable with updateNumericInput and updateSelectInput.
output$table_movies <- renderDT({
datatable(df,selection = 'single')
})
observeEvent(input$table_movies_rows_selected, {
rws <- input$table_movies_rows_selected
updateNumericInput(session = session,inputId = 'inputb',value = df[rws, 'cost'])
updateSelectInput(session = session, inputId = 'title', selected = df[rws, 'movie'])
})
Now every time a row is selected the inputs will "auto-select" that information and also trigger the flowchart to change.
App:
library(shiny)
library(DiagrammeR)
library(DT)
library(tidyverse)
#Creates tribble with movie information.
df <- as.data.frame(tribble(
~movie,~cost,~duration,
"Matrix",10.00,136,
"Inception",12.00,158,
"Titanic",15.00,194)) #end movie tribble
### Define UI ###
ui <- fluidPage(
# Sidebar with select input and numeric input
sidebarLayout(
sidebarPanel(h3("Flowchart creator with select Input"),
#User can choose movie title (optional)
selectInput(inputId = 'title',
label = "Movie title",
choices = df$movie),
#User can manually type in ticket price
numericInput("inputb",
"Ticket price is:",
value = 60),
DTOutput('table_movies')
),
# Show DiagrammeR plot
mainPanel(
grVizOutput("dg",width ="95%",height="95%")
)
)
)
### Define server ###
server <- function(input, output, session) {
rv <<- reactive({
#Creates tibble. DiagrammeR reads values inside the tibble.
tibble::tibble(
price.tickets = input$inputb
) #End of tibble
})
output$table_movies <- renderDT({
datatable(df,selection = 'single')
})
observeEvent(input$table_movies_rows_selected, {
rws <- input$table_movies_rows_selected
updateNumericInput(session = session,inputId = 'inputb',value = df[rws, 'cost'])
updateSelectInput(session = session, inputId = 'title', selected = df[rws, 'movie'])
})
#Define outputs to show in flowchart based on values on tibble
#The flowchart can't read inputs. It can only read values on the tibble.
output$price.tickets = renderText({rv()$price.tickets })
# Create flowchart with DOT language with DiagrammeR
output$dg <- renderGrViz({
grViz(diagram = "digraph flowchart {
legend [label = '##2']
price [label = '##1']
legend->price
}
[2]: 'ticket price'
") #end of flowchart in DOT language
})
}
# Run the application
shinyApp(ui = ui, server = server)
In my shiny app, I need to filter the data according to:
Date
Parameter
But not all parameters are available for all dates, so once date is selected I need to update the list of parameters available.
MRE:
# Libraries
library(shiny)
library(bslib)
library(plotly)
library(modeldata)
library(DataExplorer)
library(tidyverse)
library(ggplot2)
library(httr)
library(jsonlite)
library(data.table)
library(tidyjson)
library(dplyr)
require(reshape2)
library(purrr)
library(sp)
library(leaflet)
library(RColorBrewer)
library(shinyWidgets)
library(conflicted)
# Make API call to get locations
res1 <- GET("http://environment.data.gov.uk/water-quality/id/sampling-point?&area=1-1") # area=1-1: East Anglia # nolint
data1 <- fromJSON(rawToChar(res1$content), flatten = TRUE)
items1 <- data1$items
coords <- select(items1, c("notation", "label", "lat", "long"))
df1 <- data.frame(coords)
# Make API call to get data for all locations in df1
ids <- df1$notation
url <- "http://environment.data.gov.uk/water-quality/data/measurement?"
df2 <- data.frame()
for (id in ids) {
res2 <- GET(url = url, query = list(samplingPoint = toString(id))) # nolint
data2 <- fromJSON(rawToChar(res2$content), flatten = TRUE)
items2 <- data2$items
values <- select(items2, c("sample.samplingPoint.notation", "sample.samplingPoint.label", "sample.sampleDateTime", "determinand.label", "result", "determinand.unit.label")) # nolint
df <- data.frame(values)
df2 <- rbind(df2, df)
}
# Change df2 colnames
colnames(df2) <- c("notation", "label", "date", "determinand", "value", "unit")
# Add lat and long values to df2 from df1
temp <- left_join(df1,df2, on='notation')
master <- data.table(temp[, c("notation","label","lat","long","date","determinand","value","unit")])
determinands <- as.vector(unique(master$determinand))
# Shiny app
ui <- fluidPage(
titlePanel("Environment Agency sampling sites in East Anglia"),
hr(),
sidebarLayout(
sidebarPanel(
h4("Select the date:"),
tags$head(tags$style('.selectize-dropdown {z-index: 10000}')),
sliderInput("date", "Date", min=as.Date(min(master$date)), max=as.Date(max(master$date)), value=as.Date(min(master$date))),
hr(),
h4("Select the determinand:"),
selectizeInput("select", "Determinand", choices = sort(determinands), options=NULL, multiple=FALSE)
),
mainPanel(
h4("Output:"),
textOutput("points"),
leafletOutput("mymap")
)
)
)
server <- function(input, output, session) {
# Filter data according to determinand and time
data <- reactive({
master[master$determinand==input$select & master$date==input$date]
})
# Update dropdown menu with list of available determinands for the selected date
observeEvent(input$date,
{possible_determinands <- master[master$determinand==input$select & master$date==input$date, "determinand"]
updateSelectizeInput(session, "select", choices=possible_determinands, server=TRUE)}
)
points <- reactive({nrow(data())})
output$points <- renderText({paste(points(), "sites available out of", length(unique(master$label)))})
output$mymap <- renderLeaflet({
# Check if dataframe is empty
if(points()>0){
binpal <- colorBin("RdBu", data()$value, n=5, pretty=TRUE)
radius=200*data()$value
leaflet() %>%
addTiles() %>%
addRectangles(
lng1=1.5, lat1=51.3,
lng2=-1.5, lat2=53.3,
fillColor = "transparent",
color="#000000",
weight=2,
opacity=1
) %>%
addCircles(lng=data()$long,lat=data()$lat, radius=radius, color=binpal(data()$value), label=data()$label, opacity=1, fillOpacity=0.5) %>%
addLegend("bottomright", pal=binpal, values=data()$value, opacity=1)
}
else {
radius=100
leaflet() %>%
addTiles() %>%
addRectangles(
lng1=1.5, lat1=51.3,
lng2=-1.5, lat2=53.3,
fillColor = "transparent",
color="#000000",
weight=2,
opacity=1
) %>%
addCircles(lng=master$long,lat=master$lat, radius=radius, color="#000000", label=master$label, opacity=1, fillOpacity=0.5)
}
})
}
runApp(shinyApp(ui, server))
However, if I run this, R complains saying: Warning: Error in : Operation not allowed without an active reactive context.
When calling choices=..., how can I reference the updated list of unique parameters available?
EDIT
Using
observe({ updateSelectizeInput(session, "select", choices=unique(mydata()$parameter), server=TRUE) })
returns an empty dropdown menu when launching the app.
You need to add an observer to tell the updateSelectizeInput function when to update. In your case, it probably makes sense to listen to changes in data.
observeEvent(data(), {
updateSelectizeInput(session, "select", choices=data()$parameter, server=TRUE)
})
However, you need to make sure that you don't run into loops (because select gets updated, data gets updated etc.) I'm not sure if this is the case for your example. If yes and the calculation of data is not too expensive, you can directly do it in the observer and just listen to changes in input$date:
observeEvent(input$date, {
possible_params <- mydata[mydata$parameter==input$select & mydata$date==input$date,
"parameter"]
updateSelectizeInput(session, "select", choices=possible_params, server=TRUE)
})
I'm thinking about this empty dropdown menu and I think the problem could be with this:
selectizeInput("select", "Parameter", choices = sort(parameters), options=NULL, multiple=FALSE)
connected with this:
data <- reactive({
mydata[mydata$parameter==input$select & mydata$date==input$date]
})
observe({ updateSelectizeInput(session, "select", choices=unique(mydata()$parameter), server=TRUE) })
Because you said you want to filter the data based on date and show only parameters linked to this date. But what you actually do is you are filtering data based on date AND parameter, so - as I think - you don't need this mydata$parameter==input$select (EDIT: I understand you may need to filter the dataset by parameter, but I think you need at least two steps - one to filter dataset only by date to display limited set of parameters and then filer again [already filterd dataset by date] the dataset by chosen parameter).
It also feels somehow quite bad that you are trying to update dropdown menu based on the values chosen in the same dropdown menu (because you are updating selectizeInput with id "select" and uses the same selectizeInput with id "select" to filter the dataset and display new values in the same selectizeInput with id "select")
I'm working on a shiny app and I'm running into difficulty with observeEvent() function when creating a complex expression of multiple inputs that all derive from selectInput().
My issue is some of the expressions within the observeEvent() function are triggered at startup, causing the event to prematurely execute (i.e. my actionButton() is disabled at startup, as it should be, but becomes enabled when at least one of the inputs are selected when ideally I would want it to become enabled only when ALL inputs are selected). As seen below:
observeEvent({
#input$cohort_file
input$cohort_IDvar
input$cohort_index_date
input$cohort_EOF_date
input$cohort_EOF_type
input$cohort_Y_name
input$cohort_L0
}, {
enable("set_cohort_button")
})
For reference, I'm using the shinyjs package by #daattali found on github to enable/disable actionButton().
All but the last input (i.e. input$cohort_L0) appear to be initialized at startup so observeEvent() enables actionButton only when input$cohort_L0 is selected. If you run my app and select input in sequential order from top to bottom, it appears that observeEvent() is working as intended. I only discovered that it wasn't working as intended when I decided to choose inputs at random and discovered that selecting input$cohort_L0 was the only input I needed to select to enable actionButton().
The UI portion of the code looks like this:
# Variable selection
selectInput('cohort_IDvar', 'ID', choices = ''),
selectInput('cohort_index_date', 'Index date', choices = ''),
selectInput('cohort_EOF_date', 'End of follow-up date', choices = ''),
selectInput('cohort_EOF_type', 'End of follow-up reason', choices = ''),
selectInput('cohort_Y_name', 'Outcome', choices = ''),
selectInput('cohort_L0', 'Baseline covariate measurements', choices = '', multiple=TRUE, selectize=TRUE),
And I'm using observe() to collect the column names of an upload data-set to direct them to selectInput() as follows:
### Collecting column names of dataset and making them selectable input
observe({
value <- c("",names(cohort_data()))
updateSelectInput(session,"cohort_IDvar",choices = value)
updateSelectInput(session,"cohort_index_date",choices = value)
updateSelectInput(session,"cohort_EOF_date",choices = value)
updateSelectInput(session,"cohort_EOF_type",choices = value)
updateSelectInput(session,"cohort_L0",choices = value)
})
I've looked into using the argument ignoreInit = TRUE but it does nothing for my case of having multiple expressions within observeEvent(). I've also looked into forcing no default selection in selectInput() but had no luck with that.
So my two-part question is how can I execute observEvent() when only ALL inputs are selected/how do I stop from the inputs from being initialized at startup?
My entire code:
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
navbarPage("Test",
tabPanel("Cohort",
sidebarLayout(
sidebarPanel(
fileInput("cohort_file", "Choose CSV File",
multiple = FALSE,
accept = c("text/csv",
"text/comma-separated-values,text/plain",
".csv")),
# Horizontal line ----
tags$hr(),
# Variable selection
selectInput('cohort_IDvar', 'ID', choices = ''),
selectInput('cohort_index_date', 'Index date', choices = ''),
selectInput('cohort_EOF_date', 'End of follow-up date', choices = ''),
selectInput('cohort_EOF_type', 'End of follow-up reason', choices = ''),
selectInput('cohort_Y_name', 'Outcome', choices = ''),
selectInput('cohort_L0', 'Baseline covariate measurements', choices = '', multiple=TRUE, selectize=TRUE),
# Horizontal line ----
tags$hr(),
disabled(
actionButton("set_cohort_button","Set cohort")
)
#actionButton("refresh_cohort_button","Refresh")
),
mainPanel(
DT::dataTableOutput("cohort_table"),
tags$div(id = 'cohort_r_template')
)
)
)
)
)
server <- function(input, output, session) {
################################################
################# Cohort code
################################################
cohort_data <- reactive({
inFile_cohort <- input$cohort_file
if (is.null(inFile_cohort))
return(NULL)
df <- read.csv(inFile_cohort$datapath,
sep = ',')
return(df)
})
rv <- reactiveValues(cohort.data = NULL)
rv <- reactiveValues(cohort.id = NULL)
rv <- reactiveValues(cohort.index.date = NULL)
rv <- reactiveValues(cohort.eof.date = NULL)
rv <- reactiveValues(cohort.eof.type = NULL)
### Creating a reactiveValue of the loaded dataset
observeEvent(input$cohort_file, rv$cohort.data <- cohort_data())
### Displaying loaded dataset in UI
output$cohort_table <- DT::renderDataTable({
df <- cohort_data()
DT::datatable(df,options=list(scrollX=TRUE, scrollCollapse=TRUE))
})
### Collecting column names of dataset and making them selectable input
observe({
value <- c("",names(cohort_data()))
updateSelectInput(session,"cohort_IDvar",choices = value)
updateSelectInput(session,"cohort_index_date",choices = value)
updateSelectInput(session,"cohort_EOF_date",choices = value)
updateSelectInput(session,"cohort_EOF_type",choices = value)
updateSelectInput(session,"cohort_L0",choices = value)
})
### Creating selectable input for Outcome based on End of Follow-Up unique values
observeEvent(input$cohort_EOF_type,{
updateSelectInput(session,"cohort_Y_name",choices = unique(cohort_data()[,input$cohort_EOF_type]))
})
### Series of observeEvents for creating vector reactiveValues of selected column
observeEvent(input$cohort_IDvar, {
rv$cohort.id <- cohort_data()[,input$cohort_IDvar]
})
observeEvent(input$cohort_index_date, {
rv$cohort.index.date <- cohort_data()[,input$cohort_index_date]
})
observeEvent(input$cohort_EOF_date, {
rv$cohort.eof.date <- cohort_data()[,input$cohort_EOF_date]
})
observeEvent(input$cohort_EOF_type, {
rv$cohort.eof.type <- cohort_data()[,input$cohort_EOF_type]
})
### ATTENTION: Following eventReactive not needed for example so commenting out
### Setting id and eof.type as characters and index.date and eof.date as Dates
#cohort_data_final <- eventReactive(input$set_cohort_button,{
# rv$cohort.data[,input$cohort_IDvar] <- as.character(rv$cohort.id)
# rv$cohort.data[,input$cohort_index_date] <- as.Date(rv$cohort.index.date)
# rv$cohort.data[,input$cohort_EOF_date] <- as.Date(rv$cohort.eof.date)
# rv$cohort.data[,input$cohort_EOF_type] <- as.character(rv$cohort.eof.type)
# return(rv$cohort.data)
#})
### Applying desired R function
#set_cohort <- eventReactive(input$set_cohort_button,{
#function::setCohort(data.table::as.data.table(cohort_data_final()), input$cohort_IDvar, input$cohort_index_date, input$cohort_EOF_date, input$cohort_EOF_type, input$cohort_Y_name, input$cohort_L0)
#})
### R code template of function
cohort_code <- eventReactive(input$set_cohort_button,{
paste0("cohort <- setCohort(data = as.data.table(",input$cohort_file$name,"), IDvar = ",input$cohort_IDvar,", index_date = ",input$cohort_index_date,", EOF_date = ",input$cohort_EOF_date,", EOF_type = ",input$cohort_EOF_type,", Y_name = ",input$cohort_Y_name,", L0 = c(",paste0(input$cohort_L0,collapse=","),"))")
})
### R code template output fo UI
output$cohort_code <- renderText({
paste0("cohort <- setCohort(data = as.data.table(",input$cohort_file$name,"), IDvar = ",input$cohort_IDvar,", index_date = ",input$cohort_index_date,", EOF_date = ",input$cohort_EOF_date,", EOF_type = ",input$cohort_EOF_type,", Y_name = ",input$cohort_Y_name,", L0 = c(",paste0(input$cohort_L0,collapse=","),"))")
})
### Disables cohort button when "Set cohort" button is clicked
observeEvent(input$set_cohort_button, {
disable("set_cohort_button")
})
### Disables cohort button if different dataset is loaded
observeEvent(input$cohort_file, {
disable("set_cohort_button")
})
### This is where I run into trouble
observeEvent({
#input$cohort_file
input$cohort_IDvar
input$cohort_index_date
input$cohort_EOF_date
input$cohort_EOF_type
input$cohort_Y_name
input$cohort_L0
}, {
enable("set_cohort_button")
})
### Inserts heading and R template code in UI when "Set cohort" button is clicked
observeEvent(input$set_cohort_button, {
insertUI(
selector = '#cohort_r_template',
ui = tags$div(id = "cohort_insertUI",
h3("R Template Code"),
verbatimTextOutput("cohort_code"))
)
})
### Removes heading and R template code in UI when new file is uploaded or when input is changed
observeEvent({
input$cohort_file
input$cohort_IDvar
input$cohort_index_date
input$cohort_EOF_date
input$cohort_EOF_type
input$cohort_Y_name
input$cohort_L0
}, {
removeUI(
selector = '#cohort_insertUI'
)
})
}
# Run the application
shinyApp(ui = ui, server = server)
The code chunk that you're passing to the observeEvent as the trigger event is
{
input$cohort_IDvar
input$cohort_index_date
input$cohort_EOF_date
input$cohort_EOF_type
input$cohort_Y_name
input$cohort_L0
}
This means that, just like any other reactive code block, when ANY of these values changes, that reactive block is considered invalidated and therefore the observer will trigger. So the behaviour you're seeing makes sense.
It sounds like what you want is to execute only when all values are set. That sounds like a great use of the req() function! Try something like this:
observe({
req(input$cohort_IDvar, input$cohort_index_date, input$cohort_EOF_date, ...)
enable("set_cohort_button")
})
Note that for shinyjs::enable() specifically, you can instead use the shinyjs::toggleState() function. I think in this case the req() function is the better option though.
I am writing a shiny app to realize the following effects:
Whenever I choose variable included by categoryname, the web will generate the slider which provides a divider. It divides the selected variable into 2 groups and form a new column containing the group name added to the original data set.
People here helped me solve the problem using conditional panel in this question but now I use renderUI combined with shinyjs cause conditional panel doesn't work in my large project.
I am stuck by a tiny bug (seems):
Error in data.frame: arguments imply differing number of rows: 32, 0
The following is my code, how to change it so that the function works?
library(shiny)
library(shinyjs)
library(stringr)
categoryname = c("mpg_group", "disp_group")
MT_EG = mtcars[,1:5]
# Define UI for application that draws a histogram
ui <- fluidPage(
useShinyjs(),
# Application title
titlePanel("Mtcars Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
selectInput(inputId = "arm",
label = "ARM VARIABLE",
choices = c("mpg_group", "cyl", "disp_group", "hp", "drat"),
selected = "cyl"),
# conditionalPanel(
# #condition = "categoryname.includes(input.arm)",
# condition = "input.arm == 'disp_group' | input.arm == 'mpg_group'",
#
# #sliderInput("divider", "divide slider", 1, 100, 20)
# optionalSliderInputValMinMax("divider", "divide slider", c(50,0,100), ticks = FALSE)
# )
uiOutput("divider")
),
# Show a plot of the generated distribution
mainPanel(
uiOutput("data")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output, session) {
output$divider <- renderUI({
if (input$arm %in% categoryname){
show("divider")
}
else{
hide("divider")
}
sliderInput("divider", "divide slider", 0, 100, 50)
})
observeEvent(
input$arm,
observe(
{
if (input$arm %in% categoryname){
#browser()
# start over and remove the former column if exists
MT_EG = MT_EG[, !(colnames(MT_EG) %in% input$arm)]
id_arm_var <- input$arm
id_arm <- unlist(str_split(id_arm_var,'_'))[1]
# change the range of the slider
val <- input$divider
mx = max(MT_EG[[id_arm]])
mn = min(MT_EG[[id_arm]])
updateSliderInput(session, inputId = "divider", min=floor(mn/2),max = mx + 4,step = 1,value = input$divider)
# generate a new column and bind
divi <- data.frame(id_arm_var = MT_EG[[id_arm]]>input$divider)
divi$id_arm_var[divi$id_arm_var==TRUE] <- paste0(id_arm_var, " Larger")
divi$id_arm_var[divi$id_arm_var==FALSE] <- paste0(id_arm_var, " Smaller")
colnames(divi) <- id_arm_var
MT_EG <- cbind(MT_EG,divi)
}
output$data=renderTable(MT_EG)
}
)
)
}
# Run the application
shinyApp(ui = ui, server = server)
I simply use mtcars dataset so that all of you can get access to
observeEvent and renderUI both depend on "or triggered by" input$arm. What happened is observeEvent ask for input$divider before renderUI rendered probably in the UI and input$divider becomes available for observeEvent which as I mentioned above input$divider ends up with value NULL.
To solve this problem just add req(input$divider) before MT_EG = MT_EG[, !(colnames(MT_EG) %in% input$arm)]. Also change output$divider to output$dividerUI since Shiny doesn't allow input and output with the same id.
See ?shiny::req for more details.
I am trying to dynamically populate the values of the selectInput from the data file uploaded by the user. The selectInput must contain only numeric columns.
Here is my code snippet for server.R
...
idx <- sapply(data.file, is.numeric)
numeric_columns <- data.file[, idx]
factor_columns <- data.file[, !idx]
updateSelectInput(session, "bar_x", "Select1", choices = names(numeric_columns))
updateSelectInput(session, "bar_y", "Select2", choices = names(factor_columns))
...
Corresponding ui.r
...
selectInput("bar_x", "Select1", choices = NULL),
selectInput("bar_y", "Select2", choices = NULL)
...
The code works fine as long as there are more than one values in any dropdown. However, it fails as soon as it encounters only one value to be displayed in the selectInput.
How can I handle this specific condition, given that the data is uploaded and it cannot be controlled if there is just one column as numeric?
It appears that in 2019, this issue still exists. The issue that I have seen is that when there is only one option in the dropdown, the name of the column is displayed instead of the one option.
This appears to only be a graphical problem, as querying the value for the selectInput element returns the correct underlying data.
I was unable to figure out why this problem exists, but an easy way around this bug is to simply change the name of the column so that it looks like the first element in the list.
library(shiny)
ui <- fluidPage(
selectInput("siExample",
label = "Example Choices",
choices = list("Loading...")),
)
server <- function(input, output, session) {
# load some choices into a single column data frame
sampleSet <- data.frame(Example = c("test value"))
# rename the set if there is only one value
if (length(sampleSet$Example) == 1) {
# This should only be done on a copy of your original data,
# you don't want to accidentally mutate your original data set
names(sampleSet) <- c(sampleSet$Example[1])
}
# populate the dropdown with the sampleSet
updateSelectInput(session,
"siExample",
choices = sampleSet)
}
shinyApp(ui = ui, server = server)
Info: Code was adapted by OP to make error reproducible.
To solve your issue use val2 <- val[,idx, drop = FALSE]
You dropped the column names by subsetting the data.frame().
To avoid this use drop = FALSE; see Keep column name when select one column from a data frame/matrix in R.
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
# drj's changes START block 1
#selectInput('states', 'Select states', choices = c(1,2,4))
selectInput('states', 'Select states', choices = NULL)
# drj's changes END block 1
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output, session) {
observe({
#drj's changes START block 2
#val <- c(1,2,3)
#names(val) <- c("a","b","c")
#updateSelectInput(session, 'states', 'Select states', choices = names(val[1]))
val <- as.data.frame(cbind(c("_1","_2","_3"), c(4, 4, 6)))
names(val) <- c("a","b")
val$b <- as.numeric(val$b)
idx <- sapply(val, is.numeric)
val2 <- val[,idx, drop = FALSE]
updateSelectInput(session, 'states', 'Select states', choices = names(val2))
#drj's changes END block 2
})
}
# Run the application
shinyApp(ui = ui, server = server)