My Shiny app uses open data from a bird atlas, including lat/lon coordinates by species. The bird species names come in different languages, plus as an acronym.
The idea is that the user first selects the language (or acronym). Based on the selection, Shiny renders a selectizeInput list of unique bird species names. Then, when one species is selected, a leaflet map is generated.
I've done a couple of Shiny apps but this time I miss something obvious. When the app starts, all is well. But, the selectizeInput list is not re-rendered when a new language is selected.
All the present code with some sample data is here as a GitHub Gist https://gist.github.com/tts/924b764e7607db5d0a57
If somebody could point to my problem, I'd be grateful.
The problem is that both the renderUI and the birds reactive blocks are dependent on the input$lan input.
If you add print(input$birds) in the birds block, you will see that it uses the name of the birds before renderUI has a chance to update them to fit the new language. The data you then pass the leaflet plot is empty.
Try adding isolate around input$lan in the birds expressions so that it is only dependent on input$birds:
birds <- reactive({
if( is.null(input$birds) )
return()
data[data[[isolate(input$lan)]] == input$birds, c("lon", "lat", "color")]
})
When you change the language, the renderUI will change the selectize, which will trigger the input$birds and update the data.
Instead of using renderUI, you could also create the selectizeInput in your ui.R using (replacing the uiOutput):
selectizeInput(
inputId = "birds",
label = "Select species",
multiple = F,
choices = unique(data[["englanti"]])
)
And in your server.R, update it using:
observe({
updateSelectizeInput(session, 'birds', choices = unique(data[[input$lan]]))
})
Related
I'm looking to make some picker inputs in Shiny for each of the 50 states, but I'd like to separate them into three different groups such that no group has the same state. I was just wondering if there was a way to ensure that the three picker inputs didn't both select the same state or if there was perhaps a better way of doing this in R that I was not aware of. Thank you!
It takes a bit of work to set up, but you can accomplish that by updating the
available choices for other inputs when one changes. If you only have two or
three inputs that should be linked like this, it may be tempting to just
write out the observers and be done with it. But really, this is a
generalizable pattern, so I think it makes sense to use a helper function
instead. That way, you can link however many inputs you need, and also re-use
the logic in different apps.
All that the helper function needs to know is the IDs of the participating
inputs, and the set of shared choices. It’s not strictly necessary here, but
also making the choices reactive lets them dynamically change.
selectPool <- function(inputIds, choices = reactive(NULL)) {
stopifnot(is.reactive(choices))
session <- getDefaultReactiveDomain()
input <- session$input
# Keep track of all selected values in the pool
alreadySelected <- reactive({
Reduce(union, lapply(inputIds, \(id) input[[id]]))
})
# ... and based on that, what's left to select from.
remainingChoices <- reactive({
setdiff(choices(), alreadySelected())
})
# When an input changes, update remaining choices for others
lapply(inputIds, \(id) {
observe({
lapply(setdiff(inputIds, id), \(otherId) {
otherSelected <- input[[otherId]]
updateSelectInput(
session = session,
inputId = otherId,
# Anything already selected must remain a choice
choices = c(remainingChoices(), otherSelected),
selected = otherSelected
)
})
}) |> bindEvent(input[[id]], ignoreNULL = FALSE)
})
}
Once we’ve taken the time to do that, it’s very straightforward to use in an app:
library(shiny)
ui <- fluidPage(
titlePanel("Star Wars Alliance Builder"),
selectInput("alliance1", "Alliance 1", NULL, multiple = TRUE),
selectInput("alliance2", "Alliance 2", NULL, multiple = TRUE),
selectInput("alliance3", "Alliance 3", NULL, multiple = TRUE),
)
server <- function(input, output, session) {
selectPool(
inputIds = c("alliance1", "alliance2", "alliance3"),
choices = reactive(unique(dplyr::starwars$species))
)
}
shinyApp(ui, server)
By "picker inputs" I assume you mean selectInput/selectizeInput.
There are multiple ways you could do this. One way would be to use updateSelectInput() to update the reminding inputs after the first/second has been selected. The possible states to choose from would then be all states except the one(s) already selected. This would make it impossible to choose the same state in multiple inputs from the UI.
However, this might be a bit involved for your need. In that case I suggest that you:
either replace your three inputs with one selectInput(..., multiple = TRUE), and use validate() to check that the user has selected exactly three states
or simply just use validate() to throw an error to the user if they have selected the same state more than once in any of the three inputs.
I am writing a web application in shiny and I am not to sure I am writing efficient code for conditional panels. What I want to happen is, if the user clicks "Yes" on the radioButton, then many more inputs will populate. I originally used "conditionalPanel" in my Ui followed buy 5 uiOutputs. However, I am noticing that the renderUi runs at the start of the app, even when the button does not say "Yes". I have changed the code so I do not need a conditional panel BUT I am seeing the same thing occur. Is this efficient? I will have multiple uiOutputs, here is just one for example.
UI.R
radioButtons("add_indicator1", "Add long indicators", choices = c("No", "Yes")),
uiOutput("long1"),
SERVER.R
button1 = reactive({
input$add_indicator1
})
output$long1 = renderUI({
if(button1() == "No"){NULL
}else{selectInput ("indicator1", "Select the first long indicator",
choices = c("Choices" = "", long_indicators))}
})
I am stuck in a small problem related to shiny/R.
I am reading in a text file and displaying selective column names returned by grep search into the shiny application on the fly. For this, I am using the dynamicUI.
After the file is read in, the following function runs in server.R. It checks for specific colnames and displays this on the UI using uiOutput. Whatever column names are selected by the user, they are sent to another function for data processing and the plot it returned on the mainPanel.
server.R
output$Bait <- renderUI({
data <- input.data();
if(is.null(data)) return()
colnames <- names(data)
colnames = colnames[grep("*LFQ*",colnames,ignore.case=TRUE)]
# Creating the checkboxes using the above colnames
checkboxGroupInput("bait", "Choose Bait LFQ columns",
choices = colnames,
selected = colnames)
})
ui.R
shinyUI(
sidebarPanel(
uiOutput("Bait"),
),
mainPanel(
plotOutput(outputId = 'plot'),
)
)
Everything is fine, what I am trying to create is an action button for the checkboxes. Some files are big and have a longer list of column names >60, so whenever a checkbox is clicked, the whole function runs for processing and displays a plot. This gets unnecessary when the user has to deselect/select more than 10 columns.
An easy fix is, I kept selected=NULL but what I want is to add an actionButton after the checkboxGroupInput, so that user can select as many as checkBoxes but the function only runs when the GO button is pressed via the actionButton. If add a actionButton control after the checkbocGroupInput, it doesnt' works.
Can someone guide me in this regard. After working on this for sometime, now I am bit lost.
Thanks
Did you look into ?isolate? Lets say i want function initialFunction() only be evaluated if input$actionButtonis clicked.
observe({
input$actionButton # everything that triggers initialFunction() should come before isolate()
isolate({
# everything that should not trigger initialFunction() should come inside isolate()
initialFunction()
})
})
I have built a shiny application which is located at the URL below.
https://hdoran.shinyapps.io/openAnalysis/
On the tab called "Choose CrossFit Open Data" I have a textInput() function that calls a function that uses grep(), which is used to find names in a data frame.
The first time the program loads and a name is entered, the search seemingly occurs quickly and names are returned. However, when I delete the name and type a second name, the search is seemingly very slow.
Is there something I can do to optimize this so that is performs quickly always?
I'm still quite new at shiny and am not sure if somehow making this a reactive expression would help. If so, I'm not quite sure how.
Thanks in advance.
The relevant portion of code in the ui.R file
textInput("name", label = 'Enter an athlete name and find your scores',
value = "Enter Name Here")
and the relevant portion of code in the server.R file is
output$myScores <- renderPrint({
df <- filedata()
df[grep(input$name, df$Competitor),]
})
And this portion is also in the ui.R file (though I'm not sure it is relevant to the problem)
verbatimTextOutput("myScores"),
If I understand your goal correctly, you want to give the user the ability to select an input variable based on searching a the competitor column of the dataframe called by filedata()? If so, selectizeInput() is what you are looking for, using server-side selection as outlined here.
Adapted to the code you provided:
ui.r
selectizeInput("name", choices = NULL, multiple = FALSE)
server.r
updateSelectizeInput(session, "name", choices = filedata()$competitor, server = TRUE)
output$myScores <- renderPrint({
df <- filedata()
subset(df, competitor==input$name)
})
I want to make a list for a selectInput from a CSV file, but from a subset made based on two previous selectInputs. This means that on my app:
the user chooses a species name from a list
radioButtons("species", "Which species are you workingwith?",
list("Caretta caretta"="Cc",
"Chelonia mydas"="Cm",
"Dermochelys coriacea"="Dc",
"Eretmochelys imbricata"="Ei",
"Lepidochelys kempii"="Lk",
"Lepidochelys olivacea"="Lo",
"Natator depressus"="Nd"))
the user chooses a nesting area (country) from a list based on the species:
conditionalPanel(
condition="input.country_type=='List' & input.species=='Cc'",
selectInput("country", "Country:",
choices=subset(NestingArea2, Sp=='Cc')$Country)),
conditionalPanel(
condition="input.country_type=='List' & input.species=='Cm'",
selectInput("country", "Country:",
choices=subset(NestingArea2, Sp=='Cm')$Country)),
......
and then the user must choose a RMU from a list, which is different for each "species" and "country". I have tried this and it didn't work:
selectInput("rmu", "RMU:",
choices=subset(
NestingArea2, Sp=='input.species', Country=='input.country')$RMU)
The .csv (NestingArea2) file has 3 columns as follows: Sp | Country | RMU
I could do what I've done on (2), but since there are many countries, I am searching for something easier.
Creating a conditionalPanel and selectInput for each country|RMU separately will be very tedious and (coding) error prone. What you are looking for is a dynamic UI where the choices in a selectInput depend on previous choices.
I haven't tested this because I don't have your data but the following should get you most of the way there. Put the two outputs below in server.R. Then put the uiOutputs in ui.R (note: add comma's as needed). Before even doing that however, make sure to read the Shiny documentation on dynamic ui linked above.
Put in server.R
output$countrySelect <- renderUI({
countryChoices <- subset(NestingArea2, Sp==input$species)$Country)
selectInput("country", "Country:", choices=countryChoices)
})
output$rmuSelect <- renderUI({
rmuChoices <- subset(NestingArea2, Sp==input$species, Country==input$country)$RMU
selectInput("rmu", "RMU:", choices=rmuChoices)
})
Put in ui.R
uiOutput('countrySelect'),
uiOutput('rmuSelect')