Home > Back-end >  Text Inputs in Shiny that are mutually dependent
Text Inputs in Shiny that are mutually dependent

Time:01-07

I am building an app in shiny about biodiversity. The app has 2 text inputs (vernacularName and scientificName for a living being) and a select input for a year. I am struggling to;

  1. Link the 2 text inputs such that if the user types a vernacular name, the corresponding scientific name is populated in the scientific name text input.
  2. Likewise, if the user types the scientific name, the vernacular name box is automatically updated with the corresponding vernacular name.
  3. It would be a plus if a user starts typing a scientific or vernacular name, a list of all similar names appears that the user could pick from.

See my code I have so far.

The data is available here, please donload https://raw.githubusercontent.com/Karuitha/shiny_karuitha/master/final_data.csv

################################################################################
## Download and load packages manager pacman ----
if(!require(pacman)) {
    install.packages("pacman")
    library(pacman)
}

################################################################################
# Download and load required packages ----
pacman::p_load(
    shiny, glue, plotly, leaflet, 
    shinythemes, tidyverse
)

###############################################################################
if(!require(shiny.react)){
    remotes::install_github("Appsilon/shiny.react")}

if(!require(shiny.fluent)){
    remotes::install_github("Appsilon/shiny.fluent")}

################################################################################
## Load the pre-processed data ----
final_data <- read_csv("final_data.csv",
                       
                       col_types = 'ccddccdd')

################################################################################
## Create the UI ----
ui <- fluidPage(
    
    ## Header panel
    headerPanel(HTML("<h1 style='color: grey'>Prevalence of Selected Species in Poland and Germany</h1>")),
    
    ## Add a themes selector for the app
    shinythemes::themeSelector(),
    
    ## Side bar layout
    sidebarLayout(
        
        sidebarPanel(
            
            HTML("<h3>User Input</h3>"),
            
            ## User enters vernacular name 
            HTML("<h4>Enter Vernacular Name</h4>"),
            ## Create a drop down inputs selection
            textInput(inputId = "vernacularname", 
                      label = "Choose a Vernacular Name",
                      value = "Box bug",
                      #placeholder = "Norway Maple",
                      width = "100%"
            ),
            
            ## User has the choice to enter scientific name
            HTML("<h4>Enter Scientific Name</h4>"),
            ## Create a drop down inputs selection
            textInput(inputId = "scientificname", 
                      label = "Choose a Scientific Name",
                      value = "Acer platanoides",
                      #placeholder = "Norway Maple",
                      width = "100%"
                      
            ),
            
            ## User has the choice to enter scientific name
            HTML("<h4>Enter Year </h4>"),
            
            ## Create a slider input for the years
            selectInput(inputId = "year", 
                        label = "Choose year",
                        choices = sort(unique(final_data$year)),
                        selected = 2020,
                        multiple = FALSE)
            
        ),
        
        ## Main panel will contain the leaflet output
        mainPanel(
            
            
            leafletOutput("mymap"), width = "100%", height = "100%"
        )
        
    )
    
)

################################################################################
## Create the server with leaflet output ----
server <- function(input, output, session){
    
    
    ## Create a reactive for the current data ----
    this_data <- reactive({
        
        final_data %>% 
            
            filter(vernacularName == input$vernacularname,
                   
                   year == input$year)
    })
    
    
    ## Render an leaflet map
    output$mymap <- renderLeaflet(
        
        leaflet(
            
            this_data()
            
            
        ) %>%
            addProviderTiles('OpenStreetMap.HOT') %>%
            ## 
            ## Stamen.Toner
            addCircleMarkers(
                color = "red", 
                radius = ~ individualCount^0.3,
                stroke = TRUE,
                fillOpacity = 0.8,
                popup = ~paste(
                    
                    "<strong> Country: </strong>", country, "<br>",
                    "<strong> Locality: </strong>", locality, "<br>",
                    "<strong> Count: </strong>", individualCount, "<br>"
                )
                
                
            )
        
    )
    
    
    
}

################################################################################
## Run the application ----
shinyApp(ui, server)
################################################################################

CodePudding user response:

This app does the three things you wanted. However, first you must filter out the duplicated rows; so that only unique combinations of scientificNames and vernacularNames are present in data. This way, there is only one scientificName associated to the selected vernacularName and viceversa.

library(shiny)
library(shinyWidgets)
library(tidyverse)

data <- read.csv("https://raw.githubusercontent.com/Karuitha/shiny_karuitha/master/final_data.csv")

data <- data %>% group_by(vernacularName, scientificName) %>% distinct() %>% ungroup()

ui <- fluidPage(
  
  pickerInput("text1", "Vernacular Name", choices = sort(unique(data$vernacularName)), options = list(`live-search` = TRUE)),
  pickerInput("text2", "Scientific Name", choices = sort(unique(data$scientificName)), options = list(`live-search` = TRUE))
  
)

server <- function(input, output, session) {
  
  observeEvent(input$text1, {
    
    updatePickerInput(session = session, inputId = "text2",
                      choices = sort(unique(data$scientificName)), options = list(`live-search` = TRUE),
                      selected = unique(data$scientificName[data$vernacularName == input$text1]))
}, ignoreInit = TRUE)
  observeEvent(input$text2, {
    
    updatePickerInput(session = session, inputId = "text1",
                      choices = sort(unique(data$vernacularName)), options = list(`live-search` = TRUE),
                      selected = unique(data$vernacularName[data$scientificName == input$text2]))
    
  }, ignoreInit = TRUE)
}

shinyApp(ui, server)

CodePudding user response:

The risk of having two textInput fields inter-dependent is that you can get into a cycle of self-updating. One way around this is to track the last value and note which value is changing.

Here's a simpler example with local data to demonstrate the point. With this method, there are always two reactions to an entry, but the second is always a no-op: for instance, if we update Word, then it changes Synonym, which re-triggers the observe event; in the second observation, though, nothing is different from the stored change (in lasts$...), so it quickly escapes.

library(shiny)

words <- data.frame(
  word = c("apple", "pear"),
  synonym = c("fuji", "bosc")
)

shinyApp(
  ui = fluidPage(
    textInput("word", "Word"),
    textInput("synonym", "Synonym")
  ),
  server = function(input, output, session) {
    lasts <- reactiveValues(word = "", synonym = "")
    observeEvent({
      input$word
      input$synonym
    }, {
      if (!identical(lasts$synonym, input$synonym)) {
        # synonym changed
        validate(
          need(input$synonym %in% words$synonym,
               paste("Synonym needs to be one of:",
                     paste(sQuote(words$synonym, FALSE), collapse = ", ")))
        )
        newword <- words$word[match(input$synonym, words$synonym)]
        updateTextInput(session, "word", value = newword)
        lasts$word <- newword
        lasts$synonym <- input$synonym
      } else if (!identical(lasts$word, input$word)) {
        validate(
          need(input$word %in% words$word,
               paste("Word needs to be one of:",
                     paste(sQuote(words$word, FALSE), collapse = ", ")))
        )
        newsyn <- words$synonym[match(input$word, words$word)]
        updateTextInput(session, "synonym", value = newsyn)
        lasts$word <- input$word
        lasts$synonym <- newsyn
      }
    }) %>% throttle(1000)
    onSessionEnded(function() stopApp())
  }
)
  • Related