Shiny app logic I

ui (User Interface)

The container, whatever sees the user

server

It gets the work done

server and ui talks to each other in a flow:

ui (input) \(\rightarrow\) server (elaborate and compute) \(\rightarrow\) ui (output)

  • Define something in the ui and not using in the server: nothing happens, it just doesn’t exist

  • Calling something in the server that has not been defined in the ui: errors everywhere

Shiny app logic II

First things first:

install.packages("shiny") # install
library(shiny) # use it

And then:

ui = fluidPage(          # Define the ui
   [...]
  )

server = function(input, output){ # Define the server
  [...]
}

shinyApp(ui, server)  # create the App

Yes… it’s that easy

Be tidy, be consistent, save time

  • Use R projects with the default for shiny app creations

  • 10 minutes coding \(=\) 1 hour of debugging

  • If the app is particularly complex and long, it’s better to save ui and server in two different scripts

To each its own: Input in UI

sliderInput()     # Slider input widget
numericInput()    # Numeric input control
selectInput()     # Select list input control
checkboxInput()   # Checkbox input control
checkboxGroupInput()
dateInput()       # Date input
fileInput()       # File upload control
radioButtons()    # Radio buttons
textInput()       # Text input control
passwordInput()   # Password input control
actionButton()    # Action button
dateInput()       # Date input
dateRangeInput()  # Input a data range

Complete list (with code)

selectInput("mySelection", label = h3("Select box"), 
    choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3), 
    selected = 1)

To each its own: Output in UI

plotOutput()          # Plot output element
textOutput()          # Text output element
verbatimTextOutput()  # Verbatim text output element
tableOutput()         # Table output element
dataTableOutput()     # Data table output element
htmlOutput()          # HTML output element
uiOutput()            # user interface element
downloadButton()      # Download button
Progress()            # Reporting progress (object oriented)
withProgress()        # Reporting progress (functional)
outputOptions()       # Set options for an output object

Labels:

verbatimTextOutput(
  outputId = "myOutput"
)

To each its own: Output containers in server

renderPlot()        # Plot output
renderText()        # Text output
renderPrint()       # Printable output
renderTable()       # Table output
renderDataTable()   # Data table output
renderImage()       # Image file output
renderUI()          # UI output
downloadHandler()   # File downloads
server = function(input, output) {
  output$myOutput = renderText({
    paste(paste("This is my choice"), input$mySelection)
  })
}

App layout

fluidPage()   # rows with columns within 12-unit 
              # wide grid

fluidRow(      # custom the page in two columns
  column(6, ), # of width 6 out of the 12-unit
  column(6)    # on a single row
)    

sidebarLayout(     # automatic layout with
sidebarPanel(...), # sidebar and
mainPanel(...)    # main area
)

# and many more!

Complete list (look for “Application layout”)

Let’s start with an easy (ugly) one: Practice

Let’s start with an easy (ugly) one: Code

shinyApp(

  ui = fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput(inputId = "dataset", # name of the input (for the server)
                  label = "Choose a dataset:", # name of the input (for the users)
                  choices = c("rock", "pressure", "cars")) # options (for  both 
                                                          # users & server)
    ),

    mainPanel(
      plotOutput( # define the graphical output (we're telling R that this output 
        "graph"   # container must contain a plot)
      ),
      verbatimTextOutput( # define the graphical output (we're telling R that
        "summary"   # this output container must contain a Verbatim output)
      )
    ) 
  )
),

server = function(input, output){
  output$graph <- renderPlot({
    if(input$dataset == "rock"){ # call the input and its options with their label
      data <- rock
    } else if (input$dataset == "pressure" ){
      data <- pressure
    } else if (input$dataset == "cars") {
      data <- cars
    }
    plot(data[, c(1:2)])
  })

  output$summary <- renderPrint({
    if(input$dataset == "rock"){
      data <- rock
    } else if (input$dataset == "pressure" ){
      data <- pressure
    } else if (input$dataset == "cars") {
      data <- cars
    }
    summary(data[, c(1:2)])
  })
},


  options = list(height = 800)
)

Don’t repeat yourself, use the “shelf” solution

shinyApp(

  ui = fluidPage( # the UI didn't change at all
  sidebarLayout(
    sidebarPanel(
      selectInput(inputId = "dataset",
                  label = "Choose a dataset:",
                  choices = c("rock", "pressure", "cars"))
    ),

    mainPanel(
      plotOutput(
        "graph"
      ),
      verbatimTextOutput(
        "summary"
      )
    ) # display output
  )
),

server = function(input, output){ 

values <- reactiveValues() # create the shelf with reactive values 
  dataInput <- reactive({ # create a reactive envrinoment (it reacts to whatever input receives)
    if(input$dataset == "rock"){ # this part is the same as before
      data <- rock
    } else if (input$dataset == "pressure" ){
      data <- pressure
    } else if (input$dataset == "cars") {
      data <- cars
    }
  })
  observe({                      # ta-da, the shelf is in action. To make it works,telling R 
    values$data <- data.frame(dataInput()) #  it has to be called inside an observe function, 
  })                                       # we're using a reactive object

    output$graph <- renderPlot({
       plot(values$data[, c(1:2)]) # call the shelf and you're all set
    })

    output$summary <- renderPrint({
    summary(values$data)
    })
},


  options = list(height = 800)
)

Take things slowly and use the action buttons

There are two ways of using the action buttons:

  1. Use eventReactive()
  2. Use the shelf we have seen before and adding the action button

(Throughout the course \(\rightarrow\) solution number 2)

Action button solution 1: Practice

Action button solution 1: Code

shinyApp(
  ui = fluidPage( # same as before
    sidebarLayout(
      sidebarPanel(
        selectInput(inputId = "dataset",
                    label = "Choose a dataset:",
                    choices = list("rock" = 1, # we add numbers to the choices 
                                   "pressure" = 2, # (more convenient)
                                   "cars" = 3)), 
        actionButton("load", "Select dataset")
      ),
      
      
      mainPanel(
        plotOutput( 
          "graph"   
        ), 
        verbatimTextOutput( 
          "summary"   
        )))), 
  server = function(input, output){         # new shelf 
    dataInput = eventReactive(input$load, { # here's the action for the button
      if(input$dataset == 1){ # call the input and its options with their label
        data <- rock
      } else if (input$dataset == 2 ){
        data <- pressure
      } else if (input$dataset == 3) {
        data <- cars
      } 
      
    } 
    )
    output$graph <- renderPlot({
      data = dataInput()
      plot(data[, c(1:2)])
    })
    
    output$summary <- renderPrint({
      data = dataInput()
      summary(data[, c(1:2)])
    })
  }, 
  options = list(height = 800)
  
)

Action button solution 2: Practice

Action button solution 2: Code

shinyApp(

  ui = fluidPage(
   sidebarLayout(
    sidebarPanel(
      selectInput(inputId = "dataset",
                  label = "Choose a dataset:",
                  choices = list("rock" = 1, 
                                   "pressure" = 2,
                                   "cars" = 3)),

      actionButton("load", "Upload data")
      ),
    mainPanel(
      plotOutput(
        "graph"
      ),
      verbatimTextOutput(
        "summary"
      )
    ) # display output
  )
),

server = function(input, output){
 values <- reactiveValues()
  dataInput <- reactive({
    if(input$dataset == 1){
      data <- rock
    } else if (input$dataset == 2 ){
      data <- pressure
    } else if (input$dataset == 3) {
      data <- cars
    }
  })
  observeEvent(input$load, { # Use the observeEvent function to make the UI and 
    values$data <- data.frame(dataInput()) # server communicate again
  })

  output$graph <- renderPlot({
    plot(values$data[, c(1:2)])
  })

  output$summary <- renderPrint({
    summary(values$data)
  })
},


  options = list(height = 800)
)

Don’t scare the users: Practice

Don’t scare the users: Code

shinyApp(

  ui = fluidPage( # same as before
   sidebarLayout(
    sidebarPanel(
      selectInput(inputId = "dataset",
                  label = "Choose a dataset:",
                  choices = list("rock" = 1, 
                                   "pressure" = 2,
                                   "cars" = 3)),

      actionButton("load", 
                   "Upload data")
      ),
    mainPanel(
      plotOutput(
        "graph"
      ),
      verbatimTextOutput(
        "summary"
      )
    ) # display output
  )
),

server = function(input, output){
   values <- reactiveValues()
    dataInput <- reactive({
      if(input$dataset == 1){
        data <- rock
      } else if (input$dataset == 2 ){
        data <- pressure
      } else if (input$dataset == 3) {
        data <- cars
      }
    })
    observeEvent(input$load, {
      values$data <- data.frame(dataInput())
    })

  output$graph <- renderPlot({
    validate( # validate function: I need something to be validated for letting 
      need(input$load > 0, # you see what's hidden behind me
           "Waiting for data") # If it's not validated, I'll show you this message
    )
    plot(values$data[, c(1:2)])
  })

  output$summary <- renderPrint({
    validate(
      need(input$load > 0, "Waiting for data") # Waiting message
    )
    summary(values$data)
  })
},


  options = list(height = 800)
)

Let the users use their own data: Practice

Let the users use their data: Code

shinyApp(

  ui = fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput(inputId = "dataset",
                  label = "Choose a dataset:",
                  choices = list("rock" = 1, 
                                 "pressure" = 2,
                                 "cars" = 3,
                                 "I want to use my data!!" =4)), # we add an option
      # Let me introduce you Mr. Conditional Panel
      conditionalPanel(
        condition = "input.dataset == '4'", # What is the condition for which I should show up?
        # What should be displayed inside me once I show up?
        fileInput("example",  # label for the server
                  "", # no displayed label for users
                  accept = c("csv")) # what format do we accept?
      ),
      actionButton("load", "Upload data")
    ),

    mainPanel(
      plotOutput(
        "graph"
      ),
      verbatimTextOutput(
        "summary"
      )
    ) # display output
  )
),

server = function(input, output){
    values <- reactiveValues()
  dataInput <- reactive({
    if(input$dataset == 1){ # now we use the number associated with each dataset 
      data <- rock
    } else if (input$dataset == 2 ){
      data <- pressure
    } else if (input$dataset == 3) {
      data <- cars
    } else if (input$dataset == 4) {
      data <- read.csv(input$example$datapath) # we call the input for the data 
            #import by its label and we ask for the data path selected by the user
    }
  })
  observeEvent(input$load, # we load the data set (whatever it is only once the 
                                       #  user has hit the button)
               {
    values$data <- data.frame(dataInput())
  })

  output$graph <- renderPlot({
    validate(
      need(input$load > 0, "Waiting for data")
    )
    if (any(colnames(values$data) == "condition") ){ # I had to change the code 
      #just for the new data set, which is different from all the others
      # check if there are characters
      if (any(sapply(values$data, is.character)) == TRUE) {
  values$data[, sapply(values$data, is.character) == T] = lapply(values$data[, sapply(values$data, is.character) == T], 
                                                 as.factor)
} else {
  values$data = values$data
}
      plot(values$data$tr ~ values$data$condition,
           xlab = "Condition", ylab = "TR")
    } else {
      plot(values$data[, c(1:2)])
    }
  })

  output$summary <- renderPrint({
    validate(
      need(input$load > 0, "Waiting for data")
    )
    if (any(colnames(values$data) == "condition") ){
      summary(values$data[, c(2:3)])
    } else {
      summary(values$data[, c(1:2)])
    }
  })
},


  options = list(height = 800)
)

Code has to be fluid and adapt to everything (like a cat)

We have the code for uploading a data frame.

The code I provided for the previous app is specifically designed to work for that specific data frame

If only we had a way to explore the data frame columns and choose the ones we would like to plot 🤔

Practice

Code

shinyApp(

 ui = fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput(inputId = "dataset",
                  label = "Choose a dataset:",
                  choices = list("rock" = 1,
                                 "pressure" = 2,
                                 "cars" = 3,
                                 "I want to use my data!!" =4)),
      
      conditionalPanel(
        condition = "input.dataset == '4'",
        fileInput("example",
                  "", accept = c("csv"))
      ), 
      actionButton("load", "Upload data"), # This is the button for uploading 
                                            # the data
      conditionalPanel(                    # It appears only when the data are 
        condition = "input.load >= '1'",   # loaded
        uiOutput("var1"),                 # contains the name for variable 1
        uiOutput("var2"),                 # contains the name for variable 2
        actionButton("select", "Select & Display") # This is the button for 
      ),                              # selecting the variables and actually see
                                      # something
    ),
    
    mainPanel(
      plotOutput(
        "graph"
      ),
      verbatimTextOutput(
        "summary"
      )
    ) # display output
  )
),

server = function(input, output){
  values <- reactiveValues()
  dataInput <- reactive({
    if(input$dataset == 1){
      data <- rock
    } else if (input$dataset == 2 ){
      data <- pressure
    } else if (input$dataset == 3) {
      data <- cars
    } else if (input$dataset == 4) {
      data <- read.csv(input$example$datapath)
    }
  })
  observeEvent(input$load, {
    values$data <- data.frame(dataInput())
    # check the characters
    if (any(sapply(values$data, is.character)) == TRUE) {
      values$data[, sapply(values$data, is.character) == T] = lapply(values$data[, sapply(values$data, is.character) == T], as.factor)
    } else {
      values$data = values$data
      }
  })
  
  output$var1 <- renderUI({    # remember variable 1? here it is how we extract it
    nam <- colnames(values$data) # from the data set
    selectInput("var1", label = "Select x:", # create the input
                choices = c(nam), multiple = FALSE,
                selected = nam[1])
  })
  
  output$var2 <- renderUI({
    nam2 <- colnames(values$data) # create the input for variable 2
    selectInput("var2", label = "Select y:",
                choices = c(nam2), multiple = FALSE,
                selected = nam2[1])
  })
  
  newdata <- observeEvent(input$select, # use observe event so that the app will 
                          { # wait for you to decide before acting
                            # Besides, you're creating a new (smaller) object
                            values$df <- values$data[c(input$var1, input$var2)]
                          })
  output$graph <- renderPlot({
    validate(
      need(input$select > 0, "Waiting for data") # I changed the validation from
    )                                        # load to select
    
    df <- values$df # store the new object into an R object
    plot(df[, c(1:2)]) # use it normally


  })

  output$summary <- renderPrint({
    validate(
      need(input$select > 0, "Waiting for data")
    ) 
    df <- values$df # same 
    summary(df[, c(1:2)])

  })
},


  options = list(height = 800)
)

Interactive plots: Practice

Interactive plots: Code

library(shinyjs)
shinyApp(

 ui = fluidPage(
      useShinyjs(),  # Set up shinyjs (this is just for nice visual effects)
  sidebarLayout(
    sidebarPanel(style = "background-color:         #e1e9f9;",
                    a(id = "imp_det", 
                      h3("Choose a dataset", 
                         style = "font-style: normal; font-size: 14pt;"), 
                      href = "#"),
          shinyjs::hidden(div(
            id = "details_import",
            helpText(
              h5("You can also upload your data!")
            )
          )),
      selectInput(inputId = "dataset",
                  label = "",
                  choices = list("rock" = 1,
                                 "pressure" = 2,
                                 "cars" = 3,
                                 "I want to use my data!!" =4)),

      conditionalPanel(
        condition = "input.dataset == '4'",
        fileInput("example",
                  "", accept = c("csv"))
      ),
      actionButton("load", "Upload data"), 
      conditionalPanel(                    
        condition = "input.load >= '1'",  
        uiOutput("var1"),                
        uiOutput("var2"),                 
        actionButton("select", "Select & Display") 
      ),                              
                                      
    ),

    mainPanel(
      plotOutput(
        "graph",
        click = clickOpts(id = "plot_click"), # when we click we select a point
        brush = brushOpts(id = "plot_brush") # when we highlight an area we select
      ),     # many rows
      fluidRow( # it displays on the same row multiple arguments
      column(4, # first column with the summary verbatim output
             verbatimTextOutput(
        "summary"
      )),
      column(4,  # second column with another verbatim output for the points
             verbatimTextOutput(
               "points"
             )
             ),
      column(4,
             verbatimTextOutput(
               "brush"
             ))
      )

    ) # display output
  )
),

server = function(input, output){
  values <- reactiveValues()
  dataInput <- reactive({
    if(input$dataset == 1){
      data <- rock
    } else if (input$dataset == 2 ){
      data <- pressure
    } else if (input$dataset == 3) {
      data <- cars
    } else if (input$dataset == 4) {
      data <- read.csv(input$example$datapath)
    }
  })
   observeEvent(input$load, {
    values$data <- data.frame(dataInput())
    # check the characters
    if (any(sapply(values$data, is.character)) == TRUE) {
      values$data[, sapply(values$data, is.character) == T] = lapply(values$data[, sapply(values$data, is.character) == T], as.factor)
    } else {
      values$data = values$data
      }
  })
   shinyjs::onclick("imp_det",  # here the nice visual effect
                   shinyjs::toggle(id = "details_import", anim = TRUE))
  output$var1 <- renderUI({    
    nam <- colnames(values$data) 
    selectInput("var1", label = "Select x:", 
                choices = c(nam), multiple = F,
                selected = nam[1])
  })

  output$var2 <- renderUI({
    nam2 <- colnames(values$data) 
    selectInput("var2", label = "Select y:",
                choices = c(nam2), multiple = F,
                selected = nam2[1])
  })

  newdata <- observeEvent(input$select, 
                          { # wait for you to decide before acting
                            # Besides, you're creating a new (smaller) object
                            values$df <- values$data[c(input$var1, input$var2)]
                          })
  output$graph <- renderPlot({
    validate(
      need(input$select > 0, "Waiting for data") 
    )                                        # load to select
    df <- values$df # store the new object into an R object
    plot(df[, c(1:2)]) # use it normally


  })

  output$summary <- renderPrint({
    validate(
      need(input$select > 0, "Waiting for data")
    )
    df <- values$df # same
    summary(df[, c(1:2)])

  })

  output$points <- renderPrint({
    df <- values$df # store the data frame in an object
    pointID <- nearPoints(df, # the data frame
                          input$plot_click, # the command for a reaction
                           xvar = names(df)[colnames(df) == input$var1], # xvar of the graph
                          yvar = names(df)[colnames(df) == input$var2], # yvar of the graph,
                          addDist = FALSE)
    validate(
      need(nrow(pointID) != 0, "Click on a point") # Waiting message
    )
    pointID
  })

   output$brush <- renderPrint({
    df <- values$df # store the data frame in an object
    brushID <- brushedPoints(df,# the  data frame
                             input$plot_brush, # the command for a reaction
                          xvar = names(df)[colnames(df) == input$var1], # xvar of the graph
                          yvar = names(df)[colnames(df) == input$var2], # yvar of the graph
                             )
    validate(
      need(nrow(brushID) != 0, "Highlight Area") # Waiting message
    )
      brushID
  })

},


  options = list(height = 800)
)

Useful things

  • Shiny Gallery: You can find a gallery of different apps developed and deployed with shiny
  • Shiny widgets: All the possible inputs you can use in shiny
  • Shiny server: How to deploy your shiny app
  • Shiny server for dummies How to deploy your shiny app for free on your own server (but not necessarily in an easy way, sorry)
  • shinjs: Animate your shiny app with some Javascript without knowing anything about Java
  • CSS for dummies: Make your app looks better with css