Skip to Content
Course content

36: Building a Simple Shiny Dashboard

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably spent most of your time in R writing scripts that run linearly—top to bottom. But once you move into Shiny, you're shifting from a "script" mindset to an "app" mindset. The most frustrating part of this transition is usually the reactivity. I can't tell you how many times I've stared at a blank white screen in my browser, wondering why my plot isn't appearing, only to realize I've made a fundamental mistake in how the UI and Server talk to each other.

Take a look at this snippet. This is a classic "Day One" Shiny bug. The developer is trying to create a simple dashboard to filter the mtcars dataset by cylinder count.

library(shiny)

ui <- fluidPage(
  selectInput("cyl_select", "Select Cylinders:", choices = c(4, 6, 8)),
  plotOutput("carPlot")
)

server <- function(input, output) {
  output$carPlot <- {
    # The goal: Plot MPG vs HP for the selected cylinder count
    filtered_data <- mtcars[mtcars$cyl == input$cyl_select, ]
    plot(filtered_data$hp, filtered_data$mpg, main = "HP vs MPG")
  }
}

shinyApp(ui = ui, server = server)

The 'Invisible Plot' and the Missing Renderer

If you run this, the app will load, but the plot area will stay stubbornly empty. There are no errors in the console, which is the worst kind of bug. What's happening here?

In Shiny, the ui defines where things go (the plotOutput), but the server defines how to create them. However, you can't just assign a plot directly to an output object. Shiny doesn't know that the code inside that block is supposed to be a plot that needs to be redrawn whenever the input changes. You've provided the logic, but you haven't provided the renderer.

Wrapping Logic in renderPlot

To fix this, we have to wrap the plotting code in renderPlot({}). This tells Shiny: "Watch the reactive inputs inside this block; whenever input$cyl_select changes, re-execute this code and send the resulting image to the UI."

server <- function(input, output) {
  output$carPlot <- renderPlot({
    # Now Shiny knows this is a reactive plot
    filtered_data <- mtcars[mtcars$cyl == input$cyl_select, ]
    plot(filtered_data$hp, filtered_data$mpg, 
         xlab = "Horsepower", ylab = "MPG", 
         main = paste("Cars with", input$cyl_select, "Cylinders"))
  })
}

Notice how I also added paste() to the title. This is a nice touch for dashboards—it gives the user immediate confirmation that the app is actually responding to their input.

Organizing the Dashboard Layout

Using fluidPage is fine for a quick test, but real dashboards usually need a bit more structure. I generally prefer sidebarLayout because it separates the controls (the "knobs and dials") from the results (the "gauges").

When you use sidebarLayout, you get two main sections: the sidebarPanel and the mainPanel. This keeps your UI from looking like a giant vertical list of widgets. Here is how I would structure a clean, professional version of that same app:

ui <- fluidPage(
  titlePanel("Motor Trend Car Research"),
  
  sidebarLayout(
    sidebarPanel(
      helpText("Adjust the filter to update the plot."),
      selectInput("cyl_select", "Cylinders:", choices = c(4, 6, 8))
    ),
    
    mainPanel(
      plotOutput("carPlot")
    )
  )
)

Understanding the Input-Output Bridge

The most important thing to remember as you build more complex dashboards is the naming convention. The string you put in the UI (like "cyl_select") is the exact key you use in the server (input$cyl_select). If you have a typo in one, the bridge collapses, and your app will either crash or simply stop updating.

I always recommend naming your IDs descriptively. "input1" and "plot1" are fine for a 10-line script, but once you have five filters and three plots, you'll spend half your day hunting for which "input" is which. Use names like "date_range_filter" or "species_dropdown". Your future self will thank you.




📋 Practical Task

Build a Diamond Cut Analysis Dashboard

Using the diamonds dataset (from the ggplot2 package), create a Shiny dashboard that allows a user to explore the relationship between carat weight and price based on the quality of the diamond's cut.

Your dashboard must include:

  • A titlePanel that clearly names the application.
  • A sidebarLayout containing a sidebarPanel and a mainPanel.
  • A selectInput in the sidebar that lets the user choose a cut (Fair, Good, Very Good, Premium, Ideal).
  • A plotOutput in the main panel that displays a scatter plot of carat (x-axis) vs price (y-axis).
  • A server function that uses renderPlot to filter the diamonds dataset based on the user's selection and render the plot.

Pro Tip: Since the diamonds dataset is quite large, make sure your filtering logic happens inside the renderPlot call so the plot updates reactively!

Rating
0 0

There are no comments for now.

to be the first to leave a comment.