Content from Introduction and Motivation


Last updated on 2025-06-03 | Edit this page

Overview

Questions

  • Why might you want to create an interactive data application or dashboard as a wrapper for your research data?
  • What is the Shiny package in R?
  • What are some examples of data applications or dashboards created with the Shiny package ecosystem?

Objectives

  • Survey published examples of Shiny applications and dashboards and explore some of their features and properties
  • Reflect on what an “ideal” application or dashboard would look like for your particular research data use case
  • Appreciate the relevance of data applications and dashboards for Open Science

Interactive Data Applications for Open Science


The practice of open science promotes a global “knowledge commons” that has the potential to support enlightened and informed public discourse. While open scholarly outputs such as open-access publications and research datasets are typically intended for scholarly audiences with specialized training and expertise, the broader public is a critical stakeholder in open science, and researchers may wish to invite non-specialist audiences to engage with their research data and findings for a variety of reasons, such as the following:

  • Some researchers may view it as a civic duty, to the extent that a significant share of scientific research is ultimately funded by the public
  • It offers academic researchers an opportunity to build their public profiles beyond their scholarly communities, which allows them to credibly share their expertise
  • It could help to build public appreciation for open science and academic research, which could have valuable downstream impacts (i.e. increased funding)
  • The research data may cast important light on issues of public interest or public policy, and making it available to a broad non-specialist audience in an accessible form could contribute to a more informed or enlightened public discourse

In short, the dissemination of research data to non-specialist audiences can be an important and valuable part of scientific communication in the context of open science. However, it can be challenging to present complex research datasets in ways that are intuitive, accessible, and engaging to a broader audience. One way to do so is through the use of digital applications that wrap research datasets into an interactive graphical-user-interface (GUI) that allows non-specialists to explore and query the underlying data. These applications can be constructed in any number of ways. Data dashboards are one example of a GUI-based data exploration application; typically, a dashboard offers an interactive visual display of multiple pieces of information in different formats, arrayed across different sections or “panels” of a rectangular display. One famous example of a data dashboard is the COVID-19 Dashboard, developed by the Center for Systems Science and Engineering at Johns Hopkins:

Figure 1. The Johns Hopkins COVID-19 Dashboard
Figure 1. The Johns Hopkins COVID-19 Dashboard

Data dashboards, and interactive data applications more generally, are frequently used in government, public health, and corporate contexts in which important and potentially complex information needs to be communicated with a broad audience in an accessible way. However, they are not used as frequently in the context of communicating information and insights derived from research data. There could be many potential reasons for this, one of which is that the creation of these applications can be time-consuming and resource-intensive for researchers and scientists who do not have previous experience with application development. For such researchers, who would like to develop simple applications that could help foster broader engagement with their research and data, but do not have software expertise or access to specialized programs that facilitate application or dashboard creation, the Shiny package ecosystem is an excellent option.

The Shiny Package Ecosystem for Interactive Application Development


The Shiny package facilitates the development of interactive data-oriented applications (such as data exploration tools, dashboards, and user interfaces for data) in programming languages familiar to empirical researchers and data scientists, such as R and Python, without requiring any knowledge of traditional application development tools HTML, CSS, JavaScript. Because Shiny applications are written using R or Python, they are also able to leverage the powerful data manipulation, visualization, and analysis capabilities of these scientific computing languages, while wrapping them into a responsive and intuitive interface that allows researchers to communicate insights and share their data in an engaging and accessible manner. Shiny apps can be deployed locally or hosted online, and allow you to create a useful bridge between data analysis and statistical computing work (which other Carpentries lessons introduce) and non-specialist downstream “consumers” of your research.

There are several adjacent packages that add to the core Shiny package’s functionality, and allow for the creation of more sophisticated applications. Examples of packages within this broader Shiny ecosystem include (but are not limited to):

  • shinydashboard offers functions that simplify the process of dashboard creation
  • shinyWidgets offers functions that allow one to enrich and customize the user interface of Shiny applications
  • shinythemes provides tools to customize the appearance of Shiny applications
  • rsconnect allows for the deployment of Shiny applications to sites such as shinyapps.io or Posit Connect.

Over the course of the Workshop, we will work with a variety of packages within the broader Shiny ecosystem.

Exploring Shiny Applications


Before proceeding to the next Episode, and getting started with the process of building Shiny applications for your data, it can be useful to survey some published applications created with Shiny.

Challenge 1: Explore Shiny Applications

Go to this online repository of published Shiny applications and look around. Are there any applications you find especially appealing? What are some of its relevant features and characteristics? After getting acquainted with some “real world” Shiny applications, sketch out a rough prototype for a Shiny application that could be relevant for your own work.

Discuss your observations and protoypes with a partner.

Key Points

  • Interactive applications and dashboards are valuable tools for presenting complex research data in an intuitive and engaging way, particularly for non-specialist audiences.
  • Open science benefits from such applications because they can help increase public understanding, enhance the visibility of research, and support informed discourse on topics of public interest.
  • Shiny is an R package that allows researchers to create interactive web applications and dashboards without needing to know web technologies like HTML, CSS, or JavaScript.

Content from Shiny Fundamentals


Last updated on 2025-06-03 | Edit this page

Overview

Questions

  • What are the different components of a Shiny application and how do they fit together?
  • How can we create, run, and view a simple Shiny application?
  • How can we create an interactive Shiny application that is responsive to user inputs or requests?
  • How can we customize the appearance of a simple Shiny application?

Objectives

  • Explain how to use markdown with the new lesson template
  • Demonstrate how to include pieces of code, figures, and nested challenge blocks

Preliminaries


Before beginning this episode, please load the following libraries”

R

# load libraries
library(shiny)
library(shinythemes)
library(shinyjs)

OUTPUT


Attaching package: 'shinyjs'

OUTPUT

The following object is masked from 'package:shiny':

    runExample

OUTPUT

The following objects are masked from 'package:methods':

    removeClass, show

The Structure of a Shiny Application


At the conclusion of the previous Episode, we examined some currently published Shiny applications. Now, we’ll begin peeking “under the hood” to see how these applications are put together, and how they work. Our starting point is the observation that all Shiny applications, no matter how complex, have three fundamental and interrelated building blocks:

  • A user interface (UI) that specifies the appearance and layout of the application
  • A server that defines how an application generates outputs in response to user inputs
  • A call to the shinyApp() function, which launches the application by bringing together the UI and server

The relationship between the user interface and server is dynamic and bi-directional; the server provides substantive content (created using R code) that populates the user interface, while the user interface defines how that content is organized and displayed to users. This may sound fairly abstract, but will hopefully come into focus as we proceed.

Our first application: Hello, World!


In this section, we’ll develop our very first Shiny application. There’s a good chance that the first (or one of the first) things you did in R was to print a “Hello, World” statement:

R

# prints "Hello, World"
print("Hello, World")

OUTPUT

[1] "Hello, World"

Our goal in this section is to wrap this statement into a “Hello, World” Shiny application.

A note on application files and directories

First, though, it is important to briefly discuss where to write and store your applications. Ideally, you should create a separate dedicated directory for each application you write. For now, we’ll create our application scripts, containing the UI and server code, in a familiar .R file (but R Studio does have a handy pre-built template for Shiny applications, which we’ll introduce later on). This application directory can also contain other elements relevant to or referenced by your application (such as image files, datasets, Readme files etc.). As applications grow in complexity, it could make sense for you to use subdirectories to organize your main application directory.

Each R script should only contain the code for a single application; trying to include the code for multiple applications in a single script can cause errors or undefined behavior when you try to launch an application (since Shiny may be confused about which application it’s supposed to run). That said, in this episode, we’ll be creating and running several simple “toy” applications to illustrate important Shiny features. You can either save each application in separate scripts, or write all of the applications in a single script while commenting out the code for the application(s) you are NOT running; that way, you can write code for several simple applications in one .R file, while ensuring that only one script at a time is “live” when you try to launch it.

Writing the “Hello, World” application

Let’s first write out our application’s skeletal structure, translating the three components we discussed above (the UI, server, and shinyApp()) into actual Shiny code. After getting this structure down, we’ll fill in elements to create the “Hello, World” application below. Recall from above that the application’s UI defines how the application looks (i.e. what are the inputs and outputs, and how are they visually displayed), the server defines what the application does and how it works (i.e. how inputs are processed to generate outputs), and the call to shinyApp() brings these elements together to launch the application.

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
}

# Launch the app 
shinyApp(ui, server)

Some aspects of this code require additional clarification.

  • The fluidPage() function used in the creation of the UI object is a Shiny layout function that creates a responsive web page layout that automatically adjusts to different screen sizes. The arguments to fluidPage(), will contain additional functions that define the user interface.
  • Within the server function, “input” is used to access values the user has entered or selected in the UI, while “output” is used to define the content that is displayed within the UI. Sometimes, in more complex applications with more sophisticated user interfaces, you will also see a “session” argument in the server function, but that is beyond the scope of our current workshop.

Again, this may still seem a little but abstract, but will hopefully come into focus as we proceed. You can launch an application from your script by simply running the UI and server code, along with with shinyApp(ui, server):

Figure 2. Launching an App
Figure 2. Launching an App

Since our application is still empty, launching it will generate a blank page that looks something like this:

Figure 3. A Blank Application
Figure 3. A Blank Application

Now, let’s go ahead and create our “Hello, World” app by adding this text to the application structure we’ve defined above. It may not seem intuitive at first, but we’ll unpack it after writing out the code and launching the application.

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  textOutput(outputId="greeting")  # Placeholder for text
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's text)
  output$greeting <- renderText({
    "Hello, World!"  # application content
  })
}

# Launch the app
shinyApp(ui, server)

When you go ahead and launch this application from your .R file, you’ll see something that looks like this:

Figure 4. The 'Hello, World!' Application
Figure 4. The ‘Hello, World!’ Application

Congratulations on writing your first Shiny application!

This application was simple–it’s just a blank web page with “Hello, World!” written on it–but it’s certainly less intuitive than printing that string to the console with Hello, World. The code above needs some unpacking:

  • textOutput() is one of Shiny’s UI output functions; it creates a placeholder in the user interface for a text string that will be defined in the server. The argument to this function, “greeting”, is an arbitrary identifier that will be used to link the text string defined in the server back to this placeholder in the user interface. To learn more about textOutput() please consult the documentation with ?textOutput().
  • After creating the placeholder for text output in the UI, the server defines what text will be displayed in the UI with output$greeting <- renderText({"Hello, World!"}). renderText() is a server-side output function used to generate the text displayed in the placeholder created by textOutput(). In Shiny, output() is a special object that is used to store content that will be displayed in the UI. Shiny uses dollar sign notation to assign the content in the output object to a specific UI placeholder, identified by its output ID. Here, output$greeting connects the rendered text to the UI placeholder identified by outputId="greeting".

Callout

In Shiny, output functions determine what gets displayed in the application. These are used to show text, plots, tables, or other content, either by reserving space in the UI (UI output functions) or by generating content in the server (server-side output functions).

  • Output functions in the UI are sometimes referred to as “placeholder functions” because they reserve space in the UI where content created in the server will appear.

  • Output functions in the server are sometimes referred to as “render functions”, because they generate (or “render”) the content that fills the placeholders defined in the UI.

Each render function in the server has a matching placeholder in the UI. For example, in the simple “Hello, World!” app, the placeholder function textOutput() reserves space in the UI, while the corresponding render function renderText() generates the text that will be shown there.

Shiny connects the two using output$id where “id” matches the identifier given to the placeholder.The content returned by the render function is displayed in the UI at the location specified by the placeholder.

Challenge 1: Make your own text app

Using what you’ve learned above, make your own basic Shiny application that communicates a message in simple text. Rather than simply modifying the “Hello, World!” app, write it from scratch; this will help you become familiar with Shiny’s syntax.

Displaying a plot in a Shiny application


Instead of printing a text string in our application, let’s instead create a Shiny application that displays a simple plot that’s generated with some R code. Below, we generate 1000 random values from a normal distribution with a mean of 0 and a standard deviation of 1, and assign the resulting vector to a new object named samples. We then plot a histogram of the samples data that’s divided into 30 bins:

R

# create a vector of 1000 randomly generated values from a normal distribution with a mean of 0 and SD of 1
samples <- rnorm(1000, mean = 0, sd = 1)

# make a histogram of "samples" data
hist(samples, breaks = 30, col = "skyblue",
     main = "Histogram of Normal Samples",
     xlab = "Value")

Let’s wrap this histogram into a simple Shiny app; we’ll do so in much the same way we wrapped “Hello, World” into a Shiny app, with some minor adjustments to account for the fact that we want to display a plot, rather than text. In particular, we’ll use a new set of output functions designed specifically for plots (rather than output functions designed for text, as above). Let’s start by first creating the skeletal structure for a blank application:

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
}

# Launch the app 
shinyApp(ui, server)

Now, let’s begin populating the application:

  • In the “Hello, World!” application, we didn’t make a title for the application. That can be a useful thing to do, so here, we’ll create a title for the application using Shiny’s titlePanel() function. We’ll name the application “Exploring the Normal Distribution”, which we can pass as an argument to titlePanel() in the UI.
  • We’ll create a placeholder for the plot in the UI using Shiny’s plotOutput() function, and use “normal_plot” as the output ID. Note that different elements in the UI are separated by a comma (in this case, a comma separates the titlePanel() and plotOutput() functions)
  • Then, in the server, we’ll wrap the code to make our plot within the renderPlot() function, and assign this output back to the UI using the “normal_plot” ID.

Our application code should look something like this:

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  plotOutput(outputId = "normal_plot")
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  output$normal_plot<-renderPlot({
    # create vector
    samples <- rnorm(1000, mean = 0, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = 30, col = "skyblue",
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

When you’re ready, go ahead and launch the application. It will look something like this:

Figure 5. The Normal Plot Application
Figure 5. The Normal Plot Application

Applications with multiple outputs


The “Hello, World!” application displayed a text output, while the app we just created displayed a plot output. There’s no reason why a Shiny application cannot include many different types of outputs (indeed, most “real world” Shiny apps do!). Let’s now make a slightly more complex application that incorporates both text and plot outputs; we’ll use the same plot as above, and add some text to provide some additional context, using textOutput() to create a placeholder in the UI, and the renderText() function to create the message we’d like to display. The output created using renderText() is assigned back to the UI placeholder by using dollar sign notation to reference the output ID specified in textOutput(). Your application code should look something like the following:

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  textOutput(outputId="context_discussion"),
  plotOutput(outputId = "normal_plot")
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  
  # creates text output
  output$context_discussion<-renderText({
    "This histogram shows 1,000 values randomly drawn from a standard normal distribution. Most values fall between -3 and 3, with a peak around 0."
  })
  
  # creates plot output
  output$normal_plot<-renderPlot({
    # create vector
    samples <- rnorm(1000, mean = 0, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = 30, col = "skyblue",
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

Now, let’s go ahead and launch our application, and see what it looks like:

Figure 6. The Normal Plot Application With Text
Figure 6. The Normal Plot Application With Text

Note the text above the plot in this modified application.

Challenge 2: Change an application’s layout

In the application we just created, the written text providing relevant context is situated above the plot. Modify the application code so that it’s instead below the plot.

To make this change, you can simply move textOutput() function below the plotOutput() function in the UI. There is no need to change the order of anything in the server; the order in which elements is displayed is solely governed by the UI code. The code for a modified application with the text element below the plot looks as follows:

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  plotOutput(outputId = "normal_plot"),
  textOutput(outputId="context_discussion")
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  
  # creates text output
  output$context_discussion<-renderText({
    "This histogram shows 1,000 values randomly drawn from a standard normal distribution. Most values fall between -3 and 3, with a peak around 0."
  })
  
  # creates plot output
  output$normal_plot<-renderPlot({
    # create vector
    samples <- rnorm(1000, mean = 0, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = 30, col = "skyblue",
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

A conceptual overview of interactive applications


So far, we have learned about the basic structure of Shiny applications and how to populate them with content by linking output functions in the UI (placeholder functions) with output functions in the server (render functions). You may have noticed that the applications we have built so far are static, in the sense that they don’t respond dynamically to user input. However, static applications have limited utility, and much of the power and usefulness of Shiny applications comes from their ability to dynamically respond to user input.

Input Functions

To build dynamic, interactive applications, we need to introduce a new class of Shiny functions: input functions. Input functions are UI functions that create interactive elements where users can enter or select information (such as text boxes, sliders, check boxes, or radio buttons). This user input can then determine what gets displayed in the app.

For example, imagine an app that contains some text output in the UI, and radio buttons that let the user choose a language. If the user selects “English”, the text appears in English; if they select “Spanish”, it appears in Spanish. The function that creates these radio buttons in Shiny is considered an input function.

Reactivity

In Shiny, applications respond to user input by automatically recalculating and updating outputs whenever their underlying inputs change. For example, if a user adjusts a slider input specifying a date range, any outputs depending on that slider (i.e. a plot, text, table etc.) will automatically update without needing to refresh or re-run the app. This concept is known as reactivity.

Reactivity is built into Shiny functions. For example, the server-side renderPlot() function can explicitly reference input values (we’ll see how to do this below); the plot will automatically re-render whenever the user changes a relevant input value. In other words, render functions “listen” for changes in inputs and update their output value accordingly.

It’s worth highlighting how unique reactive behavior in Shiny is, compared to the way the R programming language works more generally. In particular, traditional R code is emphatically not reactive. To make this more concrete, let’s consider an example. First, we’ll define two new objects, x and y

R

# defines objects x and y
x<-5
y<-x+1

As expected, we can see the value of y is 6:

R

# prints value of y
y

OUTPUT

[1] 6

Now, let’s change the value of of x to 10:

R

# assigns new value of 10 to object x
x<-10

Now, what is the value of y? As you likely know from your previous experience with R, the value of y would remain unchanged:

R

# prints value of y
y

OUTPUT

[1] 6

This is the essence of non-reactive behavior: R does not automatically re-calculate the value of y after the change in x. In order for the updated value of x to be reflected in the value of y, it would be necessary to re-run y<-x+1. However, in a reactive context such as Shiny, outputs automatically update whenever their dependent inputs change. For example, if x were controlled by a user input (e.g. a slider), and y was displayed in the app using renderText(), then an app user updating the slider would automatically trigger a recalculation of y, without needing to re-run any code. This is the essence of reactivity in Shiny: it allows your applications to respond automatically to user input, without manual intervention or re-running code.

Writing interactive applications


With those concepts in mind, let’s now turn to writing some simple interactive applications that use input functions and utilize Shiny’s reactive capabilities. We’ll start by modifying our previous “Hello, World!” application. Rather than having the application always display the same static “Hello, World!” greeting, we’ll allow the user to enter their own greeting using a text box. By default, the app will display “Hello, World!”, but users can replace this with a custom message-making the application dynamic and interactive (though still, of course, very simple). We can generate such an application with the following:

Interactive Greeting Applications

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  titlePanel("Greeting Application"),
  
  # Input: Text box for user to input their own message
  textInput(inputId = "user_input", label = "Enter your greeting:", value = "Hello, World!"),
  
  # Output: Display the greeting
  textOutput(outputId = "greeting")
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  
  # Reactive output based on user input
  output$greeting <- renderText({
    paste0(input$user_input)  # Dynamically update the greeting based on input
  })
}

# Launch the app 
shinyApp(ui, server)

Let’s unpack the code above:

  • We use the titlePanel() function in the UI to give the application a title, “Greeting Application”.
  • We then call textInput() within the UI. This is an input function that creates a text box where users can type in a custom greeting. Just as output placeholder functions have identifiers that can be used to refer to them in the server, so do input functions. The first argument, inputId="user_input", gives the input a unique ID. The second argument specifies a label that users will see above the text box. Finally, the “value” argument specifies the default starting value for the text box.
  • The final element of the UI is a call to textOutput(), which reserves space in the UI for displaying the greeting. The argument to this function is an ID used in the server to link the actual output to this placeholder.
  • In the server, we use renderText() to specify the output text that will be shown. We reference the user’s input using input$user_input and return it using paste0() to create the final output. The use of input$user_input to refer to the user-supplied input within the renderText() function ensures the output updates automatically whenever the user changes their input. This demonstrates reactivity in action.

When you run the application, you’ll initially get a result that looks like this:

Figure 7. The Interactive Greeting App with Default Value
Figure 7. The Interactive Greeting App with Default Value

When you change the value in the text box, the output text will also change. For example, if we go ahead and change the greeting in the text box to “Hi there :)”, this change will be reflected in the output:

Figure 8. The Interactive Greeting App with a new user-supplied input
Figure 8. The Interactive Greeting App with a new user-supplied input

Let’s now tweak this “greeting” application, which will help you become familiar with another input function, and get some more practice writing reactive code. In particular, instead of letting users write any text they wish into the text box, let’s modify the app to invite users to select a greeting from a drop-down menu that presents a few different greetings to choose from; the chosen greeting will be printed as the output. We can keep all aspects of the code for the initial greeting application the same; we just have to change the input function from textInput() (which creates a text box) to selectInput() which creates a drop-down menu with choices defined within one of the function’s arguments. For more information, check the selectInput() function’s documentation. The modified code will look something like this:

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  titlePanel("Greeting Application"),
  
  # Input: Dropdown menu for user to select a greeting
  selectInput(
    inputId = "user_input",
    label = "Choose your greeting:",
    choices = c("Hello, World!", "Hi there :)", "What's up?", "Nice to meet you"),
    selected = "Hello, World!"
  ),
  
  # Output: Display the selected greeting
  textOutput(outputId = "greeting")
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  
  # Reactive output based on selected greeting
  output$greeting <- renderText({
    input$user_input  # Display the selected greeting
  })
}

# Launch the app 
shinyApp(ui, server)

When it is launched, the app looks something like this:

Figure 9. The Interactive Greeting App with dropdown Menu
Figure 9. The Interactive Greeting App with dropdown Menu

The user can select the desired greeting from the drop-down menu; let’s say they want the “Nice to meet you” greeting:

Figure 10. Selecting an option from the interactive greeting app's dropdown menu
Figure 10. Selecting an option from the interactive greeting app’s dropdown menu

After the selection is made, the corresponding text is printed as text output:

Figure 11. Text output after selecting an input from the dropdown menu
Figure 11. Text output after selecting an input from the dropdown menu

Challenge 3: Modify the greeting application to use radio buttons

Replace the drop down menu in the app we just created with radio buttons, using the radioButtons() input function. You may have to consult the function’s documentation to specify the necessary arguments. Make sure to create a label, and a default selection. Use the same greeting options we used for the dropdown menu above.

To make this change, you can replace selectInput() with radioButtons(), while keeping the remaining code unchanged. It will look something like this:

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  titlePanel("Greeting Application"),
  
  # Input: Radio buttons for user to select a greeting
  radioButtons(
    inputId = "user_input",
    label = "Choose your greeting:",
    choices = c("Hello, World!", "Hi there :)", "What's up?", "Nice to meet you"),
    selected = "Hello, World!"
  ),
  
  # Output: Display the selected greeting
  textOutput(outputId = "greeting")
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  
  # Reactive output based on selected greeting
  output$greeting <- renderText({
    input$user_input
  })
}

# Launch the app 
shinyApp(ui, server)

Once launched, the modified application will look something like this:

Figure 12. Greeting App with Radio Buttons
Figure 12. Greeting App with Radio Buttons

Let’s now create a slightly different greeting app in which the user provides their name, and the application responds with a personal greeting. We can develop this application with some now-familiar functions:

R

# UI
ui <- fluidPage(
  titlePanel("Personal Greeting App"),

  # Text input for the user's name
  textInput(inputId="name", label="What is your name?"),

  # Output: Greeting text
  textOutput(outputId="greeting")
)

# Server
server <- function(input, output) {
  output$greeting <- renderText({
    paste0("Hello, ", input$name, "! Nice to meet you.")
  })
}

# Run the app
shinyApp(ui = ui, server = server)

When you launch the application, it looks something like this:

Figure 13. Personal Greeting App
Figure 13. Personal Greeting App

When a name is typed into the text box, the output updates accordingly:

Figure 14. Personal Greeting App Output
Figure 14. Personal Greeting App Output

One potential design limitation of this simple application is that before the name is entered into the text box, the output “Hello, ! Nice to meet you.” looks awkward. It may be desirable to hide this output before the name is entered, and only show the complete output once the name is actually entered into the box. We can accomplish this by inserting the conditional statement if (input$name == "") return(NULL) in the renderText() function before the paste0() function. This essentially says “If the name field is blank, do not return an output.” The modified script looks like this:

R

# UI
ui <- fluidPage(
  titlePanel("Personal Greeting App"),

  # Text input for the user's name
  textInput(inputId="name", label="What is your name?"),

  # Output: Greeting text
  textOutput(outputId="greeting")
)

# Server
server <- function(input, output) {
  output$greeting <- renderText({
   if (input$name == "") return(NULL)
    paste0("Hello, ", input$name, "! Nice to meet you.")
  })
}

# Run the app
shinyApp(ui = ui, server = server)

Now, when we launch the app, we’ll see an interface that looks like this:

Figure 15. Launching the Revised Personal Greeting App
Figure 15. Launching the Revised Personal Greeting App

The application will return the full output in response to the user entering their name in the text field:

Figure 16. User-Prompted Output in the Revised Personal Greeting App
Figure 16. User-Prompted Output in the Revised Personal Greeting App

Let’s build on this to make a slighly more complex personal greeting application. In particular, we’ll write an application that requests two different inputs from the user, using two different methods. We’ll ask the user their name, which they’ll supply in a text box. We’ll also ask the user if this is their first Carpentries workshop, which they’ll answer by filling in a radio button (Yes/No). If it is their first Carpentries workshop, the following text output is returned: “Hello, . Welcome to the Carpentries!”. If it is NOT their first Carpentries workshop, the following text output is returned: “Hello, . Welcome back to the Carpentries!”.

This interactive application is more complex than the others we’ve written so far, but uses techniques and functions we’re already familiar with. The UI includes two input functions, textInput() and radioButtons that are referenced in the server, within conditional statements in the renderText() function:

R

# UI
ui <- fluidPage(
  titlePanel("Carpentries Greeting"),

  # Ask for user's name
  textInput(inputId = "name", label="What is your name?"),

  # Ask if they're new
  radioButtons(inputId = "new", 
               label = "Are you new to the Carpentries?",
               choices = c("Yes", "No"), 
                selected = character(0)), # No default selection

  # Display greeting
  textOutput(outputId = "greeting")
)

# Server
server <- function(input, output) {
  output$greeting <- renderText({
     # Don't show anything unless both inputs are filled
    if (input$name == "" || is.null(input$new)) return(NULL)

    # Build greeting based on response
    if (input$new == "Yes") {
      paste0("Hello ", input$name, ", welcome to the Carpentries.")
    } else if (input$new == "No") {
      paste0("Hello ", input$name, ", welcome back to the Carpentries.")
    } else {
      NULL  # If no radio button is selected yet
    }
  })
}

# Run the app
shinyApp(ui = ui, server = server)

When the resulting app is launched, it looks like this:

Figure 17.Carpentries Introduction App
Figure 17.Carpentries Introduction App

After the user provides their name and clicks the relevant radio button, the text output responds with the appropriate message:

Figure 18.Carpentries Introduction App Text Output
Figure 18.Carpentries Introduction App Text Output

Challenge 4: Explain the logic of the Carpentries greeting app in your own words

Work with a partner, and in your own words, take turns explaining how the application we’ve just written is put together. Focus on input functions, output functions, and the server logic.

Here is a sample explanation: This Shiny app has two main parts: the UI (user interface) and the server (logic). In the UI, we use textInput() to ask the user for their name and radioButtons() to ask whether they are new to the Carpentries. We set selected = character(0) in the radio button to ensure that no option is selected by default. We also include textOutput() as a placeholder where the final greeting message will appear.

In the server function, we use renderText() to generate the greeting based on the user’s input. Inside renderText(), the line if (input$name == "" || is.null(input$new)) return(NULL) plays an important role: it tells the app to hold off on showing any message until both the name and the radio button response are provided. The input$name == "" part checks whether the name text box is empty. The is.null(input$new) part checks whether the user has not yet selected a radio button. The || means “or” — so if either of these conditions is true, the function returns NULL (i.e., nothing is shown). Once both inputs are present, the app uses paste0() to construct a personalized greeting, depending on whether the user selected “Yes” or “No”, and displays it using the textOutput() placeholder.

Interactive Plot Applications

Having explored several interactive variations on our static “Hello, World!” application, let’s now turn to our static plot application above, and turn it into an interactive application using Shiny’s reactive functions. Let’s modify the code we used to create the static plot, and use reactive input functions to allow users to specify the number of bins in the histogram. To do so, we’ll use numericInput() within the UI, which gives users a text box in which they can specify the desired number of bins; in the server, we’ll replace the number 30 with input$desired_bins, which sets the “breaks” argument equal to the number specified by the user (using dollar sign notation to reference this number using the numericInput function’s input ID):

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  numericInput(inputId = "desired_bins", 
               label="Please enter the desired number of bins",
               value=30),
  plotOutput(outputId = "normal_plot"),
  textOutput(outputId="context_discussion")
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  
  # creates text output
  output$context_discussion<-renderText({
    "This histogram shows 1,000 values randomly drawn from a standard normal distribution. Most values fall between -3 and 3, with a peak around 0."
  })
  
  # creates plot output
  output$normal_plot<-renderPlot({
    # create vector
    samples <- rnorm(1000, mean = 0, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = input$desired_bins, col = "skyblue",
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

When we launch the application, we see a numeric textbox with the default value set to 30 (the number of bins we had in our static application):

Figure 19.Launching the interactive plot application
Figure 19.Launching the interactive plot application

However, the desired number of bins can now be changed by the user, and the plot will respond accordingly. For example, let’s change the number of bins to 100:

Figure 20. Increasing the number of bins to 100
Figure 20. Increasing the number of bins to 100

Let’s add another interactive dimension to this application by allowing users to change the color of the plot. We’ll create a text input field where a user can type in a color, based on color codes used in R. We’ll place this text box below the numeric input box, and create it using the textInput() function; we’ll refer back to the user specified color in the server using dollar sign notation in conjunction with the ID argument passed to textInput():

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  numericInput(inputId = "desired_bins", 
               label="Please enter the desired number of bins",
               value=30),
  textInput(inputId="desired_color",
            label="Please enter the desired color for the histogram",
            value="skyblue"),
  plotOutput(outputId = "normal_plot"),
  textOutput(outputId="context_discussion")
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  
  # creates text output
  output$context_discussion<-renderText({
    "This histogram shows 1,000 values randomly drawn from a standard normal distribution. Most values fall between -3 and 3, with a peak around 0."
  })
  
  # creates plot output
  output$normal_plot<-renderPlot({
    # create vector
    samples <- rnorm(1000, mean = 0, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = input$desired_bins, col = input$desired_color,
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

Let’s launch the app, set the number of bins to 60, and the color to “orangered”. It will look something like this:

Figure 21. Interactively changing number of bins and colors
Figure 21. Interactively changing number of bins and colors

Challenge 5: Modify the interactive plot application

Modify the interactive plot application in the following ways:

  • Instead of inviting users to specify their desired color in a textbox, constrain their choices by asking them to select one of the following colors from a dropdown menu: skyblue, orangered, violet, lightcyan, or lawngreen.
  • Remove the contextual discussion (i.e. “This histogram shows…)
  • Create a numeric input field where users can specify a desired mean for the sample.

Your script should look something like this:

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  numericInput(inputId = "desired_bins", 
               label="Please enter the desired number of bins",
               value=30),
  selectInput(inputId="desired_color",
            label="Please select the desired color for the histogram",
            choices=c("skyblue", "orangered", "violet", "lightcyan", "lawngreen")),
  numericInput(inputId="desired_mean", label="Please enter the desired population mean", value=0),
  plotOutput(outputId = "normal_plot"),
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  
  # creates plot output
  output$normal_plot<-renderPlot({
    # create vector
    samples <- rnorm(1000, mean = input$desired_mean, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = input$desired_bins, col = input$desired_color,
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

Once the app is launched, if we were to set the number of bins to 100, select “lawngreen” as the color, and choose a mean of 45, we will get something that looks like the following:

Figure 21. Interactive plot with user inputs for color, number of bins, and mean
Figure 21. Interactive plot with user inputs for color, number of bins, and mean

Layouts and Themes


We’ll use the app you just created in the exercise above to explore some basic tools that can be used to lay out the elements within your application, as well as style the application as a whole.

First, note that the user inputs in the app you created were laid out on top of the plot. Often, it can be preferable to situate these elements towards the side. Indeed, one of the most useful Shiny application layouts is known as the sidebar layout, in which there is a sidebar for inputs, and a “main” panel for outputs. To see how the sidebar layout works, let’s wrap the application we wrote in the previous exercise into a sidebar layout, with the inputs on the side, and the plot in the main panel:

R

ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  
  sidebarLayout(
    sidebarPanel(
      numericInput(inputId = "desired_bins", 
                   label = "Please enter the desired number of bins",
                   value = 30),
      
      selectInput(inputId = "desired_color",
                  label = "Please select the desired color for the histogram",
                  choices = c("skyblue", "orangered", "violet", "lightcyan", "lawngreen")),
      
      numericInput(inputId = "desired_mean", 
                   label = "Please enter the desired population mean", 
                   value = 0)
    ), # closes sidebarPanel
    
    mainPanel(
      plotOutput(outputId = "normal_plot")
    ) # closes mainPanel
  ) # closes sidebarLayout
) # closes fluidPage


# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  
  # creates plot output
  output$normal_plot <- renderPlot({
    # create vector
    samples <- rnorm(1000, mean = input$desired_mean, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = input$desired_bins, col = input$desired_color,
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

As you can see, the sidebarLayout() function declares a sidebar structure; within this function, relevant input widgets are placed within sidebarPanel(), and the outputs to be displayed within the main panel are placed within sidebarPanel(). Once this sidebar structure is in place, the revised app will look like the following:

Figure 23. Sidebar layout
Figure 23. Sidebar layout

If you wanted to place the input widgets to the right of the main panel, you can do so by specifying position="right" withinsidebarLayout(), right before callingsidebarPanel()```. The modified script would look like this:

R

ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  
  sidebarLayout(
    position="right",
    sidebarPanel(
      numericInput(inputId = "desired_bins", 
                   label = "Please enter the desired number of bins",
                   value = 30),
      
      selectInput(inputId = "desired_color",
                  label = "Please select the desired color for the histogram",
                  choices = c("skyblue", "orangered", "violet", "lightcyan", "lawngreen")),
      
      numericInput(inputId = "desired_mean", 
                   label = "Please enter the desired population mean", 
                   value = 0)
    ), # closes sidebarPanel
    
    mainPanel(
      plotOutput(outputId = "normal_plot")
    ) # closes mainPanel
  ) # closes sidebarLayout
) # closes fluidPage


# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  
  # creates plot output
  output$normal_plot <- renderPlot({
    # create vector
    samples <- rnorm(1000, mean = input$desired_mean, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = input$desired_bins, col = input$desired_color,
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

Yielding an application with the inputs on the right, as expected:

Figure 24. Sidebar layout with inputs on the right
Figure 24. Sidebar layout with inputs on the right

Recall, from above, that if we want the input widgets below the main plot, we could remove the sidebar layout, and place the input widgets below the plot output in the UI:

R

# UI: Layout and inputs/outputs go here
ui <- fluidPage(
  # Add UI code (specifying application's inputs, outputs, and layouts) here
  titlePanel("Exploring the Normal Distribution"),
  plotOutput(outputId = "normal_plot"),
  numericInput(inputId = "desired_bins", 
               label="Please enter the desired number of bins",
               value=30),
  selectInput(inputId="desired_color",
            label="Please select the desired color for the histogram",
            choices=c("skyblue", "orangered", "violet", "lightcyan", "lawngreen")),
  numericInput(inputId="desired_mean", label="Please enter the desired population mean", value=0),
)

# Server: Logic and reactivity go here
server <- function(input, output) {
  # Add server code (specifying application's content) here
  
  # creates plot output
  output$normal_plot<-renderPlot({
    # create vector
    samples <- rnorm(1000, mean = input$desired_mean, sd = 1)
    
    # make a histogram of "samples" data
    hist(samples, breaks = input$desired_bins, col = input$desired_color,
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
}

# Launch the app 
shinyApp(ui, server)

This minor adjustment in the UI leads to an app with the following appearance:

Figure 25. Inputs on the bottom
Figure 25. Inputs on the bottom

Tabs

So far, our apps have been fairly simple, but as they grow in complexity and the amount of information displayed increases, it can be helpful to use tabs as an organizational tool. Tabs allow us to distribute content across multiple panels, reducing clutter and creating a more streamlined and user-friendly design. In Shiny, we can use the tabsetPanel() function to organize the content into tabs. Each individual tab is then defined using the tabPanel() function, where the material for that tab is placed. Below, we demonstrate the use of tabs by placing the plot in one tab (the tab is named “Histogram”), and then place some contextual information in another tab (named “Explanation”).

R

# UI
ui <- fluidPage(
  titlePanel("Exploring the Normal Distribution"),
  
  sidebarLayout(
    sidebarPanel(
      numericInput(inputId = "desired_bins", 
                   label = "Please enter the desired number of bins",
                   value = 30),
      
      selectInput(inputId = "desired_color",
                  label = "Please select the desired color for the histogram",
                  choices = c("skyblue", "orangered", "violet", "lightcyan", "lawngreen")),
      
      numericInput(inputId = "desired_mean", 
                   label = "Please enter the desired population mean", 
                   value = 0)
    ), # closes sidebarPanel
    
    mainPanel(
      tabsetPanel(
        tabPanel(title="Histogram", 
                 plotOutput(outputId = "normal_plot")),
        tabPanel(title="Explanation", 
                 textOutput(outputId = "context_discussion"))
      ) # closes tabsetPanel
    ) # closes mainPanel
  ) # closes sidebarLayout
) # closes fluidPage

# Server
server <- function(input, output) {
  
  output$normal_plot <- renderPlot({
    samples <- rnorm(1000, mean = input$desired_mean, sd = 1)
    
    hist(samples, breaks = input$desired_bins, col = input$desired_color,
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
  
  output$context_discussion <- renderText({
    "This histogram shows 1,000 values drawn from a normal distribution with the specified mean. 
    You can change the number of bins and the histogram color using the controls on the right."
  })
}

# Run the app
shinyApp(ui = ui, server = server)

When launched, this tab-based app looks something like this; note the “Histogram” tab is live:

Figure 26. Tab based app with Histogram tab open
Figure 26. Tab based app with Histogram tab open

When the user toggles to the “Explanation” tab, it looks like this:

Figure 27. Tab based app with Explanation tab open
Figure 27. Tab based app with Explanation tab open

Themes

Issues of application design and style are complex issues, and many of them are beyond the scope of our Workshop. However, we will briefly introduce themes, which can help shape the visual appearance of your applications with relatively little effort. Shiny themes come from the shinythemes package, and you can add a theme to your app with one line of code. Before deciding on a theme, you’d probably want to preview various options, which you can do with the themeSelector() function. Let’s explore various themes that could be applied to the tab-based app we made above:

R

# UI
ui <- fluidPage(
  themeSelector(),
  titlePanel("Exploring the Normal Distribution"),
  sidebarLayout(
    sidebarPanel(
      numericInput(inputId = "desired_bins", 
                   label = "Please enter the desired number of bins",
                   value = 30),
      
      selectInput(inputId = "desired_color",
                  label = "Please select the desired color for the histogram",
                  choices = c("skyblue", "orangered", "violet", "lightcyan", "lawngreen")),
      
      numericInput(inputId = "desired_mean", 
                   label = "Please enter the desired population mean", 
                   value = 0)
    ), # closes sidebarPanel
    
    mainPanel(
      tabsetPanel(
        tabPanel(title="Histogram", 
                 plotOutput(outputId = "normal_plot")),
        tabPanel(title="Explanation", 
                 textOutput(outputId = "context_discussion"))
      ) # closes tabsetPanel
    ) # closes mainPanel
  ) # closes sidebarLayout
) # closes fluidPage

# Server
server <- function(input, output) {
  
  output$normal_plot <- renderPlot({
    samples <- rnorm(1000, mean = input$desired_mean, sd = 1)
    
    hist(samples, breaks = input$desired_bins, col = input$desired_color,
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
  
  output$context_discussion <- renderText({
    "This histogram shows 1,000 values drawn from a normal distribution with the specified mean. 
    You can change the number of bins and the histogram color using the controls on the right."
  })
}

# Run the app
shinyApp(ui = ui, server = server)

When you launch this app, it will launch with a drop-down menu that allows you to select and preview how various themes look in the context of your app:

Figure 28. Shiny theme selector
Figure 28. Shiny theme selector

After previewing the various themes, let’s say you decide you want to select the “slate” theme. To apply that theme, replace themeSelector() in the code with theme=shinytheme("superhero"):

R

# UI
ui <- fluidPage(
  theme=shinytheme("superhero"),
  titlePanel("Exploring the Normal Distribution"),
  sidebarLayout(
    sidebarPanel(
      numericInput(inputId = "desired_bins", 
                   label = "Please enter the desired number of bins",
                   value = 30),
      
      selectInput(inputId = "desired_color",
                  label = "Please select the desired color for the histogram",
                  choices = c("skyblue", "orangered", "violet", "lightcyan", "lawngreen")),
      
      numericInput(inputId = "desired_mean", 
                   label = "Please enter the desired population mean", 
                   value = 0)
    ), # closes sidebarPanel
    
    mainPanel(
      tabsetPanel(
        tabPanel(title="Histogram", 
                 plotOutput(outputId = "normal_plot")),
        tabPanel(title="Explanation", 
                 textOutput(outputId = "context_discussion"))
      ) # closes tabsetPanel
    ) # closes mainPanel
  ) # closes sidebarLayout
) # closes fluidPage

# Server
server <- function(input, output) {
  
  output$normal_plot <- renderPlot({
    samples <- rnorm(1000, mean = input$desired_mean, sd = 1)
    
    hist(samples, breaks = input$desired_bins, col = input$desired_color,
         main = "Histogram of Normal Samples",
         xlab = "Value")
  })
  
  output$context_discussion <- renderText({
    "This histogram shows 1,000 values drawn from a normal distribution with the specified mean. 
    You can change the number of bins and the histogram color using the controls on the right."
  })
}

# Run the app
shinyApp(ui = ui, server = server)

Now, when the app is launched, you’ll see the theme is applied:

Figure 29. Shiny superhero theme
Figure 29. Shiny superhero theme

Advanced Reactivity


As we noted above, in a reactive programming context, a change in an input (triggered by a user) automatically triggers an update in all downstream outputs that depend on that input. Shiny’s suite of input and output functions, which we are becoming familiar with, facilitate this reactivity, making it possible to develop interactive applications in a relatively straightforward way. Now that we have some more experience in Shiny’s reactive programming context, this section will introduce some more advanced tools and concepts related to reactivity.

Reactive conductors and the reactive() function

In considering reactive programming within Shiny, it’s helpful to distinguish between the following:

  • Reactive Sources are the values provided by the user through UI widgets created by input functions
  • Reactive Endpoints are outputs that depend on reactive sources. These include render functions like renderText() or renderPlot(), which respond to input changes and update the UI accordingly.
  • Reactive conductors sit in between sources and endpoints. They are created using the reactive() function and are used to process or transform input values before they reach the endpoints. They can help reduce code duplication, and improve efficiency.

These reactive conductors are particularly useful when multiple outputs depend on the same derived value. Without a conductor, you’d have to repeat the same calculation in every render function. For example, in a Fahrenheit-to-Celsius conversion app with multiple outputs that display the Celsius temperature in different ways, it would be inefficient to recompute the conversion in each output. Instead, you can use a reactive conductor to perform the conversion once, and then reference it in each render function. This makes your code cleaner, avoids duplication, and improves efficiency.

Let’s consider an example. We’ll make a tip calculator application that takes as input your bill, and the desired tip (as a percentage). It then returns the dollar value of the tip (based on the bill and desired tip percentage), and the total value of the bill (including the tip). Note that in the process we’ll also introduce a new input widget, the slider bar, which is created with sliderInput():

R

# Define the user interface
ui <- fluidPage(
  titlePanel("Tip Calculator"),  # App title displayed at the top
  
  sidebarLayout(
    sidebarPanel(  # Sidebar contains input controls
      numericInput("bill", "Bill amount ($):", value = 50, min = 0),  # Input for bill amount
      sliderInput("tip", "Tip percentage:", min = 0, max = 30, value = 15)  # Slider for tip %
    ),
    
    mainPanel(  # Main panel displays output
      textOutput("tip_amount"),   # Displays computed tip amount
      textOutput("total_amount")  # Displays computed total amount (bill + tip)
    ) # closes mainPanel
  ) # closes sidebarLayout
) # closes fluidPage

# Define the server logic
server <- function(input, output) {
  
  # Calculate and display the tip amount
  output$tip_amount <- renderText({
    tip_amt <- input$bill * input$tip / 100  # Calculate tip as a percentage of the bill
    paste("Tip amount: $", round(tip_amt, 2))  # Create a string to display the result
  })
  
  # Calculate and display the total amount
  output$total_amount <- renderText({
    tip_amt <- input$bill * input$tip / 100         # Recalculate tip again (duplicated logic)
    total_amt <- input$bill + tip_amt               # Add tip to bill for total
    paste("Total amount: $", round(total_amt, 2))   # Display total
  })
}

# Run the Shiny app
shinyApp(ui, server)

This yields an application that looks like the following:

Figure 30. Tip calculator
Figure 30. Tip calculator

The app looks and works as expected, but note that the code used to create it involved some duplication (which the comments in the code call attention to). In particular tip_amt <- input$bill * input$tip / 100 is repeated in both the tip_amount and total_amount endpoints. You might wonder why tip_amt can’t be reused after it’s initially defined in output$tip_amount; the reason is that the value of tip_amt calculated inside the first renderText() id local to that block of code. It is not remembered or shared across other render functions. More generally, each render function is its own reactive context; variables defined within one of these contexts only exist there.

Given that’s the case, you might wonder whether you can define a function in the server to calculate tip_amt, and then use that function within render functions to avoid code duplication. The answer is that this won’t work; in particular, regular user-defined functions are not reactive, so they won’t automatically update when input values change.

The solution to avoid code duplication of this sort is the reactive() function. Below, we’ll rewrite the script above, this time using the reactive() function to calculate tip_amt, which we’ll then subsequently use in different endpoints without having to recalculate anything:

R

ui <- fluidPage(
  titlePanel("Tip Calculator"),
  sidebarLayout(
    sidebarPanel(
      numericInput("bill", "Bill amount ($):", value = 50, min = 0),
      sliderInput("tip", "Tip percentage:", min = 0, max = 30, value = 15)
    ),
    mainPanel(
      textOutput("tip_amount"),
      textOutput("total_amount")
    ) # closes mainPanel
  ) # closes sidebarLayout
) # closes fluidPage

server <- function(input, output) {
  
  # Reactive conductor: compute tip once and reuse it
  tip_amt <- reactive({
    input$bill * input$tip / 100
  })
  
  output$tip_amount <- renderText({
    paste("Tip amount: $", round(tip_amt(), 2))
  })
  
  output$total_amount <- renderText({
    total <- input$bill + tip_amt()
    paste("Total amount: $", round(total, 2))
  })
}

shinyApp(ui, server)

Above, tip_amount() is turned into a reactive expression by being enclosed in reactive() and is subsequently used in both of the subsequent render functions, which helps avoid code duplication. Note that when referring to a reactive expression, the name of the expression must be followed by (), as in tip_amt().

Consider the benefits of using reactive(), even in this simple application:

  • It avoids duplication and increases efficiency: Tip amount is calculated once in the reactive() expression, instead of being repeated in each output block. This can improve app performance, particularly in more complex settings.
  • Improves maintainability: If you need to change how the tip is calculated, you only need to update it in one place

The ability to define custom reactive conductors with reactive() is especially useful when creating applications based on datasets, which we will explore in the next episode. If you are still a little hazy on what exactly reactive() does or why it’s useful, it will become clearer then.

Controlling Reactivity with eventReactive()

In the apps we’ve created so far, application outputs automatically update whenever an input changes. Often, however, it can be useful to control or delay reactivity, such that an update only happens when a specific event occurs (such as the user clicking a button). Delaying reactivity in this way can improve a user experience and conserve computational resources.

A useful Shiny function for controlling reactivity in this way is eventReactive(). Below, we modify the tip calculator application to only run when the user clicks a button that says “Calculate Tip”. To do so, we make two changes:

  • We include the actionButton() function in the UI, which creates the button users must click to trigger a calculation
  • Instead of using reactive() to calculate the tip, we wrap the calculation in eventReactive(). This tells Shiny to update the tip (and all downstream outputs) only when the Calculate Tip button is clicked.

R

ui <- fluidPage(
  titlePanel("Tip Calculator"),
  sidebarLayout(
    sidebarPanel(
      numericInput("bill", "Bill amount ($):", value = 50, min = 0),
      sliderInput("tip", "Tip percentage:", min = 0, max = 30, value = 15),
      actionButton("calc_btn", "Calculate Tip")
    ),
    mainPanel(
      textOutput("tip_amount"),
      textOutput("total_amount")
    ) # closes mainPanel
  ) # closes sidebarLayout
) # closes fluidPage

server <- function(input, output) {
  
  # Reactive conductor: compute tip once and reuse it
  tip_amt <- eventReactive(input$calc_btn, {
    input$bill * input$tip / 100
  })
  
  # create output of tip amount
  output$tip_amount <- renderText({
    paste("Tip amount: $", round(tip_amt(), 2))
  })
  
  # create output of total amount
  output$total_amount <- renderText({
    total <- input$bill + tip_amt()
    paste("Total amount: $", round(total, 2))
  })
}

shinyApp(ui, server)

When you launch the newly revised application, it will look something like this:

Figure 31. Tip calculator with activation button
Figure 31. Tip calculator with activation button

Now, the application will only “run” (i.e. return the relevant outputs) after the user sets the input parameters and clicks the “Calculate Tip” button.

Observers

In Shiny, observers are functions that watch for changes in inputs, and then trigger actions that don’t directly produce outputs shown in the app’s user interface, but still affect how the app behaves. These actions are called “side effects” because they influence the app in ways other than updating visible output (for example, showing a message, resetting a form, saving data to a file etc). Unlike reactive expressions, which calculate values to update the UI, observers don’t return values. Instead, they simply carry out a task when something changes. Shiny provides two kinds of observers: observe(), which runs code whenever any of its inputs change, and observeEvent(), which waits for a specific event (like a button click) before running. We can think of observe() and observeEvent() as counterparts to reactive() and observeReactive(), but instead of computing values for outputs, they trigger side effects that change the app’s behavior.

To make this more concrete, let’s see how observe() and observeEvent() can be used to enrich our tip calculator app. In particular, we’ll use observe() to grey out the “Calculate Tip” button if the Bill amount entered by the user is 0. We’ll also use observeEvent() to reset the input parameters to their default state when a “Reset” button is clicked; in addition, when “Reset” is clicked and the input parameters are reset to their default, the output values in the app are erased; when the “Calculate” button is clicked again, the output values are once again displayed:

R

ui <- fluidPage(
  useShinyjs(),  # Initialize shinyjs
  titlePanel("Tip Calculator"),
  sidebarLayout(
    sidebarPanel(
      numericInput("bill", "Bill amount ($):", value = 50, min = 0),
      sliderInput("tip", "Tip percentage:", min = 0, max = 30, value = 15),
      actionButton("calc_btn", "Calculate Tip"),
      actionButton("reset_btn", "Reset")
    ),
    mainPanel(
      textOutput("tip_amount"),  
      textOutput("total_amount")
    )
  )
)

server <- function(input, output, session) {
  
  # Reactive conductor: calculate tip when button is clicked
  tip_amt <- eventReactive(input$calc_btn, {
    input$bill * input$tip / 100
  })
  
  # Disable "Calculate Tip" button when bill is 0
  observe({
    if (input$bill == 0) {
      shinyjs::disable("calc_btn")
    } else {
      shinyjs::enable("calc_btn")
    }
  })
  
  # create output of tip amount
  output$tip_amount <- renderText({
    paste("Tip amount: $", round(tip_amt(), 2))
  })
  
  # create output of total amount
  output$total_amount <- renderText({
    total <- input$bill + tip_amt()
    paste("Total amount: $", round(total, 2))
  })
  
  # Reset form and clear outputs when the "reset" button is clicked
  observeEvent(input$reset_btn, {
    updateNumericInput(session, "bill", value = 50)  # Reset bill to 50
    updateSliderInput(session, "tip", value = 15)    # Reset tip to 15
    shinyjs::hide("tip_amount")                     # Hide tip output
    shinyjs::hide("total_amount")                   # Hide total output
  })
  
  # Show outputs when "calculate" button is clicked 
  observeEvent(input$calc_btn, {
    shinyjs::show("tip_amount")    # Show tip output
    shinyjs::show("total_amount")  # Show total output
  })
}

shinyApp(ui, server)

A couple of points are worth highlighting about these changes to the code.

  • You’ll notice that we use functions from the shinyjs package inside our observers. Shinyjs is a package within the tidyverse ecosystem that offers a variety of functions that can improve the user experience. We used the functions enable() and disable() to enable or disable the “Calculate Tip” button depending on a condition. We also used it to hide outputs after a user resets the application, and show outputs after a user clicks the “Calculate Tip” button again.
  • Note the tasks the observers are accomplishing, such as disabling or enabling buttons or resetting the form to default values. This underscores our earlier point that observers perform tasks in response to inputs, and thereby shape the app’s behavior; however, they do not actually compute values used in outputs (as reactive expressions do). Note, also, the difference between observe() and observeEvent() in action; the former produces its side-effect (i.e. disabling the button) whenever the input value for the bill is 0, while the latter produces its side effects (i.e. resetting the form and clearing or restoring outputs) in response to a specific trigger.
  • In response to a user clicking the reset button, code within an observer function hides output, and restores this output when the “Calculate Tip” button is again clicked. This may seem to contradict our earlier point, that observers do not actually impact outputs, but trigger side effects that are relevant for the app’s behavior. It’s important to note, though, that these “hiding” and “showing” effects modify the UI’s appearance via JavaScript/CSS (via the shinyjs functions) rather than altering the reactive values or computations that drive the outputs, and are therefore best understood as side effects.
  • Note that we included the “session” argument to the server function this time, because of the way in which the side effects of the observer function impact the UI. The details are too technical to get into here, but the basic point is that in apps with multiple concurrent users, the session object ensures that updates (triggered, for example, by the reset button) are applied to the correct user’s instance of the app.

Callout

One advanced reactivity function that we won’t cover in detail, but which you should be aware of, is isolate(). The isolate() function lets you access an input value without making your code react to it. In other words, isolate() lets you use an input while disabling Shiny’s default behavior of automatically updating outputs in response to input changes. This is helpful when you want to use an input—like a name or a comment—in your output, but don’t want changes to that input to automatically rerun your calculations or update your results. Just like other reactivity functions, isolate() gives you more control over how and when your app responds to changes. You’ll want to be aware of isolate(), particularly if you go on to use Shiny extensively in the future, and anticipate developing sophisticated applications; for our purposes now, however, the reactivity functions we’ve already covered are more fundamental.

Challenge 6: Create a button-activated Fahrenheit to Celsius converter application

Write a Fahrenheit to Celsius converter application in which the user enters a Fahrenheit temperature, and can convert this value to its Celsius equivalent by clicking a button.

Your application code should look something like this:

R

ui <- fluidPage(
  titlePanel("Fahrenheit to Celsius Converter"),
  sidebarLayout(
    sidebarPanel(
      numericInput("temp_f", "Temperature in Fahrenheit:", value = NULL),
      actionButton("convert_btn", "Convert")
    ),
    mainPanel(
      textOutput("result")
    )
  )
)

server <- function(input, output, session) {
  temp_celsius <- eventReactive(input$convert_btn, {
    (input$temp_f - 32) * 5 / 9
  })
  
  output$result <- renderText({
    paste("Temperature in Celsius:", round(temp_celsius(), 1), "°C")
  })
}

shinyApp(ui, server)

Once the app is launched, it looks something like this:

Figure 32. The Fahrenheit to Celsius Converter App with Activation Button
Figure 32. The Fahrenheit to Celsius Converter App with Activation Button

Entering a temperature value in the text box, and then clicking “Convert”, should trigger the application to make the required conversion display the Celsius temperature equivalent.

Key Points

  • The fundamental building blocks of all Shiny applications are the user interface (UI) and the server. Shiny apps are launched by combining the UI and server with the shinyApp() function.
  • Output functions serve two purposes: in the UI, they act as placeholders for content; in the server, their corresponding render functions generate content to be displayed. It’s possible to build a static Shiny application using only outputs, without any inputs.
  • Input functions create various widgets (such as text boxes, sliders, menus etc.) that allow users to interact with an application by specifying input values. By default, changes in inputs automatically trigger updates in outputs—this behavior is called reactivity. Input values are referenced in the server using their unique IDs.
  • Reactive expressions (also known as reactive conductors) created with reactive() sit between inputs and outputs, allowing you to process or transform input values before they’re displayed. They help reduce duplication and improve performance.
  • Observers created with observe() let you perform side effects—tasks that respond to changes but do not return values (e.g., resetting inputs, enabling/disabling buttons, or saving files). They affect app behavior rather than generating output.
  • Functions such as eventReactive() and observeEvent() provide more control over when reactivity occurs. They are useful for delaying updates until a specific event (like a button click) happens. Use eventReactive() when producing outputs and observeEvent() for behavior changes that don’t involve rendering output.
  • Layouts and themes help organize and style your app. Use sidebar layout and tabs to arrange components clearly. Style templates from the shinythemes package can give your app a polished look with minimal effort.

Content from Shiny Data Applications


Last updated on 2025-06-03 | Edit this page

Overview

Questions

  • How do you create Shiny applications that allow users to query and explore datasets in an intuitive and interactive manner?

Objectives

  • Apply what you learned about general principles of Shiny application development in the previous Episode to make simple but powerful Shiny applications that facilitate interactive data exploration
  • Continue to learn new tools, concepts, and functions related to Shiny application development

Preliminaries


Before beginning this episode, please ensure you have the following libraries installed and loaded:

R

library(shiny)
library(shinyjs)
library(nycflights13)
library(tidyverse)
library(DT)
library(shinythemes)

Introduction


Now that we are oriented to some of the fundamental principles of application development in Shiny, we’ll use what we learned to develop simple applications that can facilitate interactive and intuitive data exploration. By showcasing how Shiny applications can function as interactive portals into static datasets, this Episode aims to stimulate ideas about how you might use Shiny to make your own datasets more broadly accessible to non-specialist audiences.

This Episode uses a “case-study” approach to demonstrating how the basic principles we learned in the previous Episode can be applied in the context of developing dataset-based applications. Certain new features of Shiny will be introduced as we go, but for the most part, the focus will be on applying and reinforcing what we have already learned about Shiny, but in an applied context that is more relevant for researchers and data scientists than application developers.

Shiny script template


Before proceeding to our first data application, we’ll briefly introduce R Studio’s Shiny template, which you can use to develop the applications in this Episode. In the previous Episode, we wrote out our simple Shiny applications in regular R scripts. Writing Shiny code from scratch offered good practice when starting out, but it’s useful to know that R Studio comes with a prebuilt shiny template that can make it easier to develop applications. This template is actually a simple Shiny app, and we can swap out the elements in this template with our own as we go; this allows us to focus on our inputs and outputs, rather than the app’s structure. To access this template go to File, then select New File followed by Shiny Web App:

Figure 33. Accessing the Shiny Template
Figure 33. Accessing the Shiny Template

This will open up a small dialog box in which you’re asked to name the application and specify a directory; R Studio will create a new sub-directory within the specified directory with the name you provide. This sub-directory can be used to house all of your application materials. R Studio automatically deposits a file within this sub-directory called app.R, which is the template. The template will also automatically be opened within R Studio after you close the dialog box. It looks something like this:

Figure 34. The Shiny Template File
Figure 34. The Shiny Template File

The template actually provides the script for a simple Shiny application, which you can launch by highlighting the script and clicking the Run App button in the menu bar right about the script. You can develop your own original applications by swapping in (and/or adding) your own title and application elements (inputs, outputs etc.) as you go. Using this template allows you to focus on these application elements, instead of worrying about writing the broader skeletal structure of the app from scratch.

Our first data application: querying flight delays


In your previous work learning about data analysis in R, you may have encountered a dataset of flight departures from New York City airports in 2013 from the nycflights13 dataset:

R

# prints flights dataset
nycflights13::flights

OUTPUT

# A tibble: 336,776 × 19
    year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
   <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
 1  2013     1     1      517            515         2      830            819
 2  2013     1     1      533            529         4      850            830
 3  2013     1     1      542            540         2      923            850
 4  2013     1     1      544            545        -1     1004           1022
 5  2013     1     1      554            600        -6      812            837
 6  2013     1     1      554            558        -4      740            728
 7  2013     1     1      555            600        -5      913            854
 8  2013     1     1      557            600        -3      709            723
 9  2013     1     1      557            600        -3      838            846
10  2013     1     1      558            600        -2      753            745
# ℹ 336,766 more rows
# ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
#   tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
#   hour <dbl>, minute <dbl>, time_hour <dttm>

This dataset is intuitive, interesting, and possibly familiar from your previous education in R. It theerfore offers a good opportunity to develop our very first data based application. Imagine you are a transportation analyst who has been tasked with developing a simple application that allows end users to specify a month and date, and view a table containing information about the five flights with the longest departure delays on that day, along with a plot that allows them to visualize these delays. Such an application could be useful to a senior-level decisionmaker that may not necessarily have data science expertise, but needs to quickly query the dataset to get a sense of day-by-day delay patterns. It could also be useful on a public-facing website.

Before starting to write your app (with the aid of the prebuilt Shiny template), it can be useful to consider briefly outline the app in plain language, which can subsequently help you to write out the code.

  • The app will have a table and a plot as outputs, so we will need to reserve space in the UI for these elements using output placeholder functions. Recall from the previous episode that the placeholder function for plots is plotOutput(). We didn’t work with data tables in the previous episode, but Shiny’s native data table output function is tableOutput(). However, Shiny’s native table functions are somewhat limited; they are static, when it is frequently the case that dynamic tables (which allow for scrolling) are preferable. The DT package has table functions that can be used in Shiny, and which support tables that have more functionality and which are aesthetically more appealing than native Shiny tables. As a result, we’ll use these DT functions in our app. The relevant table output placeholder function from DT is dataTableOutput().
  • These placeholder functions will need to be accompanied by corresponding output render functions in the server; these render functions contain the code necessary to create the outputs that will populate the UI. Our plot will be created within the renderPlot() function, which we used in the previous episode. The DT package’s render function, used in conjunction with dataTableOutput() is renderDataTable().
  • We will use the selectInput() function in the UI to create a drop-down menu where users can select the day and month of interest.
  • Turning to the server logic, it makes sense to create a reactive expression that extracts all of the records associated with the user’s desired month and day, and feed this information into the render functions, where the records with the five highest delays can be subsetted using dplyr’s slice_head() function. In the renderPlot() function, this information will be used to make a plot (in ggplot2) of the five longest delays for that month/day.

Now, let’s translate this into Shiny code by populating the template with these functions and the relevant server logic:

R

ui <- fluidPage(
  titlePanel("Top 5 Departure Delays"),
  sidebarLayout(
    sidebarPanel(
      selectInput(inputId = "month", label = "Select Month", choices = 1:12, selected = 1),
      selectInput(inputId = "day", label = "Select Day", choices = 1:31, selected = 1)
    ),
    mainPanel(
      DT::dataTableOutput(outputId = "delay_table"),
      plotOutput(outputId = "delay_plot")
    )
  )
)

server <- function(input, output) {
  
  # reactive expression to extract records associated with the month/day desired by the user
  daily_flights <- reactive({
    flights %>%
      filter(month == input$month, day == input$day) %>%
      filter(!is.na(dep_delay))
  })
  
  # code to create table of the flights with the five longest delays on the user-specified day;
  # populates "dataTableOutput" in the UI
  output$delay_table <- DT::renderDataTable({
    daily_flights() %>%
      arrange(desc(dep_delay)) %>%
      slice_head(n = 5) %>%
      select(dep_time, sched_dep_time, dep_delay, carrier, flight, dest)
  }, options = list(pageLength = 5, scrollX = TRUE)) # specifies behavior and appearance of table
  
  # creates inverted bar plot of flights with the five longest delays on the user-specified day
  output$delay_plot <- renderPlot({
    top5 <- daily_flights() %>%
      arrange(desc(dep_delay)) %>%
      slice_head(n = 5)
    
    ggplot(top5, aes(x = reorder(paste(carrier, flight), dep_delay), y = dep_delay)) +
      geom_col(fill = "darkorange") +
      coord_flip() +
      labs(title = "Top 5 Departure Delays", x = "Flight", y = "Delay (min)") +
      theme_minimal()
  })
}

shinyApp(ui, server)

When you launch the app, it will look something like this:

Figure 35. Flight Delay Application
Figure 35. Flight Delay Application

Once it’s launched, you should change the inputs and confirm that the outputs are responsive to these inputs.

Challenge 1: Enhance the flight delay app

In this challenge, you will enhance the app we just created, both functionally and stylistically, using your knowledge of Shiny’s features and tools. Please modify and/or add to the flight delay app’s code in order to accomplish the following:

  • Add a button labeled “Display” that users must press after specifying their desired input parameters in order for the app to update.
  • Add an additional input filter (you can choose a widget of your choice) that allows users to filter observations by the originating airport (the “origin” field)
  • Apply a theme of your choice from the shinythemes package to improve the appearance of the app.

The code below makes these changes. It uses the “paper” theme, check box filters to capture user preferences on the airport(s) of interest, and uses eventReactive() to activate updates when the button, created by actionButton(), is clicked.

R

ui <- fluidPage(
  theme = shinytheme("paper"),  # paper theme
  
  titlePanel("Top 5 Departure Delays"),
  
  sidebarLayout(
    sidebarPanel(
      selectInput("month", "Select Month", choices = 1:12, selected = 1),
      selectInput("day", "Select Day", choices = 1:31, selected = 1),
      # adds check box input to select airport; default is to have all options selected
      checkboxGroupInput("origin", "Filter by Origin Airport",
                         choices = unique(flights$origin),
                         selected = unique(flights$origin)),
      # adds "Display" button
      actionButton("go", "Display")  # Action button
    ),
    
    mainPanel(
      DT::dataTableOutput("delay_table"),
      plotOutput("delay_plot")
    )
  )
)

server <- function(input, output) {
  
  # Use eventReactive to delay updates until button is clicked; extract records associated with the   # month/day/airport desired by the user
  filtered_flights <- eventReactive(input$go, {
    flights %>%
      filter(month == input$month, day == input$day) %>%
      filter(origin %in% input$origin) %>%
      filter(!is.na(dep_delay))
  })
   # creates table of the flights with the five longest delays on the user-specified day and     
   # populates "dataTableOutput" in the UIairport(s);
  output$delay_table <- DT::renderDataTable({
    filtered_flights() %>%
      arrange(desc(dep_delay)) %>%
      slice_head(n = 5) %>%
      select(dep_time, sched_dep_time, dep_delay, carrier, flight, origin, dest)
  }, options = list(pageLength = 5, scrollX = TRUE)) # specifies behavior and appearance of table
  
   # creates inverted bar plot of flights with the five longest delays on the user-specified day and    # for specified airport(s)
  output$delay_plot <- renderPlot({
    top5 <- filtered_flights() %>%
      arrange(desc(dep_delay)) %>%
      slice_head(n = 5)
    
    ggplot(top5, aes(x = reorder(paste(carrier, flight), dep_delay), y = dep_delay)) +
      geom_col(fill = "darkorange") +
      coord_flip() +
      labs(title = "Top 5 Departure Delays", x = "Flight", y = "Delay (min)") +
      theme_minimal()
  })
}

shinyApp(ui, server)

When launched, the app will look something like this:

Figure 36. Enhanced Flight Delay Application
Figure 36. Enhanced Flight Delay Application

Did you know?

You can configure your Shiny apps to allow users to download data from your app, based on their queries. For example, using the downloadButton() (in the UI), and downloadHandler() in the server, you could create an application feature that allows users to download a CSV file of the top 5 delays based on their selected filters. If you’d like to learn more, consult the documentation for these functions, and see if you can add this feature to the app.

test


Key Points

  • Shiny applications can offer an accessible and interactive entry point into tabular datasets; you can build such applications using only the concepts and tools introduced in the previous Episode
  • R Studio’s Shiny web application template can help save you time in writing applications; start with the template, and swap in the functions needed for your specific applications
  • It can be helpful to verbally conceptualize and outline your application before writing the relevant code
  • To add tabular information to the Shiny application UI, it’s recommended that you use the DT package’s dataTableOutput() (in the UI) and renderDataTable() (in the server) functions.

Content from Shiny Data Dashboards


Last updated on 2025-06-03 | Edit this page

Overview

Questions

  • What is the relationship between the data applications we created in the previous episode, and data dashboards (which we’ll create in this one)?
  • What is shinydashboard, and what is it’s role in the Shiny ecosystem?
  • How can shinydashboard simplify the dashboard creation process?

Objectives

  • Explore shinydashboard and gain practice in using this package to make simple data dashboards.
  • Connect the process of making a dashboard with shinydashboard to more general principles of application development in Shiny (introduced in previous episodes)

Preliminaries


Before proceeding with this Episode, please install and load the following libraries (if you haven’t already done so):

R

# load libraries
library(shiny)
library(shinydashboard)

OUTPUT


Attaching package: 'shinydashboard'

OUTPUT

The following object is masked from 'package:graphics':

    box

R

library(tidyverse)

OUTPUT

── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     

OUTPUT

── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

R

library(nycflights13)
library(DT)

OUTPUT


Attaching package: 'DT'

The following objects are masked from 'package:shiny':

    dataTableOutput, renderDataTable

Introduction


A Shiny data application is an interactive web application lets users explore or manipulate data, including those without data science expertise. In the previous episode, we gained experience in developing such applications by applying some fundamental principles of Shiny application development that we learned earlier. We can think of a data dashboard as a specific kind of Shiny data application, one that emphasizes the display of multiple interrelated pieces of information in a structured, systematic, and visually appealing way. Dashboards can often convey more information than simpler applications, and are well-suited to providing important context and supporting decisionmaking.

By itself, the base shiny package we’ve been working with has all the tools and functions necessary to create more structured dashboards, but doing so is non-trivial, and requires fairly advanced knowledge of the Shiny environment. That’s where the shinydashboard package comes in. Part of the broader Shiny ecosystem (i.e. packages adjacent to the core shiny package that extend and broaden its functionality, including those we’ve worked with such as shinythemes and shinyjs), the shinydashboard package provides a suite of functions that make it relatively straighforward to build dashboards using the principles of Shiny application development. The package does much of the work involved in getting from basic Shiny data applications to more multidimensional and context-rich dashboards, which makes it easier than it otherwise would be to make data dashboards using the Shiny tools and principles we’ve been learning.

At this point, your knowledge of Shiny is sufficiently advanced that picking up shinydashboard will be quite manageable; it involves learning some new functions, but as you’ll see, the underlying principles at work are the same as the principles that underlie Shiny. Now that you’ve done the heavy lifting in learning the essentials of Shiny, and have developed some Shiny data applications, that learning curve for shinydashboard will be less steep, but still yield a significant payoff.

Getting oriented to shinydashboard


As a way of getting oriented to shinydashboard, consider the following script, which creates a basic dashboard structure using functions from the shinydashboard package.

R

ui <- dashboardPage(                     

  dashboardHeader(                    
    title = "My Dashboard"               
  ),                                    

  dashboardSidebar(                      
    sidebarMenu(                         
      
      menuItem(                          
        "Dashboard",                   
        tabName = "dashboard",         
        icon = icon("dashboard")       
      ),                                 

      menuItem(                          
        "About",                        
        tabName = "about",               
        icon = icon("info-circle")       
      )                                 

    )                                   
  ),                                     

  dashboardBody(                         
    tabItems(                         

      tabItem(                           
        tabName = "dashboard",           
        h2("Main Dashboard"),            
        plotOutput("myplot")            
      ),                                 

      tabItem(                           
        tabName = "about",              
        h2("About This App"),            
        p("This app was built using shinydashboard.")  
      )                                  
    )                                 
  )                                    

)                                     

# Define the server-side logic
server <- function(input, output) {      

  output$myplot <- renderPlot({          
    hist(rnorm(100))                    
  })                               

}                                        

# Run the Shiny application
shinyApp(ui, server)                     

When you launch the dashboard application, you will see something that looks like this:

Figure 37. Empty Dashboard Application
Figure 37. Empty Dashboard Application

Challenge 1: Understand the code to create a dashboard

After launching the (mostly) empty dashboard application structure above, closely examine the dashboard and connect its various features to the parts of the code that created it. Then, as a way of solidifying your understanding, comment the code. Finally, identify some similarities and differences between the code to create a more general Shiny application, and to create a dashboard.

A thoroughly commented version of the script above would look something like this:

R

# Define the User Interface (UI)
ui <- dashboardPage(                     # Start the overall dashboard page layout

  dashboardHeader(                       # Define the top header of the dashboard
    title = "My Dashboard"               # Title displayed in the dashboard header
  ),                                     # End of dashboardHeader

  dashboardSidebar(                      # Start the sidebar layout
    sidebarMenu(                         # Create a menu container for sidebar items
      
      menuItem(                          # Create first menu item
        "Dashboard",                     # Text shown in sidebar
        tabName = "dashboard",           # Identifier used to link to tab content
        icon = icon("dashboard")         # Display an icon (text input; still allowed here)
      ),                                 # End of first menuItem

      menuItem(                          # Create second menu item
        "About",                         # Text shown in sidebar
        tabName = "about",               # Identifier for linking to about tab
        icon = icon("info-circle")       # Icon to accompany label
      )                                  # End of second menuItem

    )                                    # End of sidebarMenu
  ),                                     # End of dashboardSidebar

  dashboardBody(                         # Start the main content body of the dashboard
    tabItems(                            # Container that holds tab panels

      tabItem(                           # First tab panel
        tabName = "dashboard",           # Must match tabName in sidebar menu item
        h2("Main Dashboard"),            # Large heading for this tab
        plotOutput("myplot")             # Placeholder for plot to be rendered in server
      ),                                 # End of first tabItem

      tabItem(                           # Second tab panel
        tabName = "about",               # Must match second menu item's tabName
        h2("About This App"),            # Heading for the About section
        p("This app was built using shinydashboard.")  # Informative paragraph
      )                                  # End of second tabItem

    )                                    # End of tabItems container
  )                                      # End of dashboardBody

)                                        # End of dashboardPage definition

# Define the server-side logic
server <- function(input, output) {      # Start server function

  output$myplot <- renderPlot({          # Define a plot output
    hist(rnorm(100))                     # Generate a histogram of 100 random values
  })                                     # End of renderPlot

}                                        # End of server function

# Run the Shiny application
shinyApp(ui, server)                     # Launch the app using the UI and server

You’ll notice that in broad terms, the code to create a standard application is quite similar to this code to create a dashboard using shinydashboard functions. Notice, for example, the structure of the code, which is divided into a section for the UI and one for the server; as before, the application is launched by bringing these elements together within shinyApp. The basic logic of a Shiny app is also on display here; space for outputs is reserved in the UI, and these outputs are then populated in the server code. For example, space for the plot created within the renderPlot() function is reserved with plotOutput("myplot"), and the plot created in the server is linked back to its UI placeholder using dollar sign notation to reference the output ID. Right now, the dashboard isn’t an interactive one that takes in user inputs and automatically updates the outputs in accordance with the principle of reactivity, but you may be able to anticipate this possibility, based on the overall structure of the dashboard application.

The main difference you’ll notice is that the user interface is more structured (with sections such as a sidebar menu and tabbed content) than the interface of a regular application, and this structured interface is implemented using shinydashboard functions that help generate a dashboard layout “off the shelf”. Note, for example, the use of dashboardPage() rather than fluidPage() as the base-level UI function.

The UI is organized by dashboardPage() into three main sections:

  • Header, which shows the dashboard title at the top via dashboardHeader()
  • Sidebar which shows a vertical panel with a navigation menu on the side, via dashboardSidebar()
  • A Body, where associated with each tab is displayed, via dashboardBody()

In this example, the sidebar menu of the dashboard includes two menu items:

  • One is called “Dashboard”, and labeled internally with tabName="dashboard"
  • The other is called “About”, and labelled internally with tabName="about"

The body of the dashboard has two items, with each one linked to a tab in the sidebar menu.

  • The “dashboard” item the body contains a title and plot output.
  • the “about” item in the body contains a title and a short paragraph of text.

The items in the dashboard’s boy are linked back to their tabs in the sidebar (so that clicking one of the tabs brings the user to the appropriate item in the body) via their internal identifiers. For example, the “dashboard” item in the body is linked to its corresponding tab in the sidebar via tabName = "dashboard".

The syntax on the server side is identical to what you’ve seen in previous episodes.

Building a simple flight delay dashboard


Now that we’re oriented to to the basic layout of shinydashboard and the code used to generate it, let’s get some practice developing a more substantive (yet still relatively simple) dashboard that features an interactive dimension. As in the previous Episode, we’ll use flights data from the nycflights13 package. Building a data dashboard of flight delays will allow us to compare the dashboard to the application we built in the previous lesson, and appreciate the richer options for the display of information offered by the dashboard format.

As before, we’ll start by verbally outlining the dashboard, which we’ll name “NYC Flights Dashboard”:

  • For simplicity, we’ll only include one menu item in the sidebar, named “Dashboard”, corresponding to the main dashboard page. On the sidebar, we’ll also include our Month and Day input widgets.
  • The body of the dashboard has a single page containing a variety of plots, tables, and value boxes The first row of this page will contain two value boxes containing information on the number of total departing flights, and the average flight delay (we’ll reserve space for these boxes using the valueBoxOutput() placeholder. The second row will contain a plot of the top 10 departure delays for the selected time frame, and a table of the top 5 departure delays; space for the plot is reserved in the UI using plotOutput(), while space for the table is reserved using tableOutput() (this time, unlike the previous episode, we’ll use Shiny’s in-built table output functions rather than those from DT, so you can see the difference in appearance). Finally, in the third row of the dashboard page, we’ll reserve space for a plot of the average delay by destination. * In the server, we’ll first create a reactive expression that filters the dataset based on user specifications, and use this expression within a renderValueBox() function to create the code to display the total number of flights on the selected day. We’ll also create a second reactive expression that takes the first reactive expression, and removes the non-delayed flights. This expression will be used to calculate the value for the second value box, i.e. the average delay.
  • This second reactive expression will also be used in the subsequent render functions that calculate the output values for the plots and tables specified in the UI.

When we translate this outline into code, we get something that looks like the following:

R

# Define UI
ui <- dashboardPage(
  dashboardHeader(title = "NYC Flights Dashboard"),
  
  dashboardSidebar(
    sidebarMenu(
      menuItem("Dashboard", tabName = "dashboard", icon = icon("plane")),
      selectInput("month", "Month", choices = 1:12, selected = 1), # month selector
      selectInput("day", "Day", choices = 1:31, selected = 1) # day selector
    ) # closes sidebar menu
  ), # closes dashboard sidebar
  
  dashboardBody(
    tabItems(
      tabItem(tabName = "dashboard",
              fluidRow(
                valueBoxOutput("flight_count"),
                valueBoxOutput("avg_delay")
              ), # closes first fluidRow
              fluidRow( # opens second row
                box(title = "Top 10 Departure Delays", width = 6,
                    plotOutput("delay_plot")),
                box(title = "Top 5 Delayed Flights", width = 6,
                    tableOutput("top_delay_table"))
              ), # closes second row
              fluidRow( # opens third row
                box(title = "Average Delay by Destination", width = 12,
                    plotOutput("dest_delay_plot"))
              ) # closes third fluidRow
      ) # closes tabItem 
    ) # closes tabItems
  ) # closes dashboardBody
) # closes dashboardPage

# Define server logic
server <- function(input, output) {
  
  # Reactive dataset for all flights (no dep_delay filter)
  all_flights <- reactive({
    flights %>%
      filter(month == as.numeric(input$month), 
             day == as.numeric(input$day))
  })
  
  # Reactive dataset for flights with non-missing dep_delay
  filtered_flights <- reactive({
    all_flights() %>%
      filter(!is.na(dep_delay))
  })
  
  # Value box for total flights (all flights, including NA dep_delay)
  output$flight_count <- renderValueBox({
    count <- nrow(all_flights())
    valueBox(count, "Flights", icon = icon("plane-departure"), color = "blue")
  })
  
  # Value box for average departure delay
  output$avg_delay <- renderValueBox({
    avg <- round(mean(filtered_flights()$dep_delay, na.rm = TRUE), 1)
    valueBox(paste0(avg, " min"), "Avg. Departure Delay", icon = icon("clock"), color = "orange")
  })
  
  # Bar plot for top 10 departure delays
  output$delay_plot <- renderPlot({
    top10 <- filtered_flights() %>%
      arrange(desc(dep_delay)) %>%
      slice_head(n = 10)
    
    ggplot(top10, aes(x = reorder(paste(carrier, flight), dep_delay), y = dep_delay)) +
      geom_col(fill = "tomato") +
      coord_flip() +
      labs(title = "Top 10 Departure Delays", x = "Flight", y = "Delay (min)") +
      theme_minimal()
  })
  
  # Table of top 5 delayed flights
  output$top_delay_table <- renderTable({
    filtered_flights() %>%
      arrange(desc(dep_delay)) %>%
      slice_head(n = 5) %>%
      select(carrier, flight, dep_delay, dest) %>%
      rename(Airline = carrier, Flight = flight, Delay = dep_delay, Destination = dest)
  })
  
  # Bar plot for average delay by destination
  output$dest_delay_plot <- renderPlot({
    filtered_flights() %>%
      group_by(dest) %>%
      summarise(avg_delay = mean(dep_delay, na.rm = TRUE), n = n()) %>%
      filter(n >= 3) %>%  # Require at least 3 flights for reliability
      ggplot(aes(x = reorder(dest, avg_delay), y = avg_delay)) +
      geom_col(fill = "steelblue") +
      coord_flip() +
      labs(title = "Average Departure Delay by Destination", x = "Destination", y = "Avg. Delay (min)") +
      theme_minimal()
  })
}

# Run the app
shinyApp(ui, server)

Some additional things to note about this script:

  • Note that within the dashboard’s body, tab item is organized into rows using fluidRow(). Each row spans a total width of 12 units. You can arrange content within these rows using boxes of varying widths (for example, three boxes of width 4 or two boxes of width 6, as long as the total does not exceed 12). These boxes function as “containers” for UI elements such as plots, tables, value boxes etc.
  • To correctly display value boxes, it’s not enough to compute a value inside renderValueBox() and expect it to appear automatically via its identifier. You must use the valueBox() function inside renderValueBox() to explicitly define what should be displayed and how—this ensures the value appears in the intended format in the UI.

When the app is launched, it will look something like this:

Figure 38. Flight Delay Dashboard
Figure 38. Flight Delay Dashboard

Challenge 2: Enhance the flight delay dashboard

Please add a checkbox input to the dashboard that allows users to select their airport(s) of interest. Make sure that the app’s outputs update in response to the user’s selection of the airport(s).

To add a checkbox-based “airport selector”, use checkboxGroupInput() in the UI; to ensure that this filter is appropriately used in the calculation of the outputs, make sure to update the argument to the filter() function in the first reactive expression in the server. The revised code will look something like this:

R

# Define UI
ui <- dashboardPage(
  dashboardHeader(title = "NYC Flights Dashboard"),
  
  dashboardSidebar(
    sidebarMenu(
      menuItem("Dashboard", tabName = "dashboard", icon = icon("plane")),
      selectInput("month", "Month", choices = 1:12, selected = 1), # month selector
      selectInput("day", "Day", choices = 1:31, selected = 1), # day selector
      checkboxGroupInput("origin", "Airport of Origin", # airport selector
                         choices=unique(flights$origin), 
                         selecte=unique(flights$origin))
    ) # closes sidebar menu
  ), # closes dashboard sidebar
  
  dashboardBody(
    tabItems(
      tabItem(tabName = "dashboard",
              fluidRow(
                valueBoxOutput("flight_count"),
                valueBoxOutput("avg_delay")
              ), # closes first fluidRow
              fluidRow( # opens second row
                box(title = "Top 10 Departure Delays", width = 6,
                    plotOutput("delay_plot")),
                box(title = "Top 5 Delayed Flights", width = 6,
                    tableOutput("top_delay_table"))
              ), # closes second row
              fluidRow( # opens third row
                box(title = "Average Delay by Destination", width = 12,
                    plotOutput("dest_delay_plot"))
              ) # closes third fluidRow
      ) # closes tabItem 
    ) # closes tabItems
  ) # closes dashboardBody
) # closes dashboardPage

# Define server logic
server <- function(input, output) {
  
  # Reactive dataset for all flights (no dep_delay filter)
  all_flights <- reactive({
    flights %>%
      filter(month == as.numeric(input$month), 
             day == as.numeric(input$day),
             origin %in% input$origin)
  })
  
  # Reactive dataset for flights with non-missing dep_delay
  filtered_flights <- reactive({
    all_flights() %>%
      filter(!is.na(dep_delay))
  })
  
  # Value box for total flights (all flights, including NA dep_delay)
  output$flight_count <- renderValueBox({
    count <- nrow(all_flights())
    valueBox(count, "Flights", icon = icon("plane-departure"), color = "blue")
  })
  
  # Value box for average departure delay
  output$avg_delay <- renderValueBox({
    avg <- round(mean(filtered_flights()$dep_delay, na.rm = TRUE), 1)
    valueBox(paste0(avg, " min"), "Avg. Departure Delay", icon = icon("clock"), color = "orange")
  })
  
  # Bar plot for top 10 departure delays
  output$delay_plot <- renderPlot({
    top10 <- filtered_flights() %>%
      arrange(desc(dep_delay)) %>%
      slice_head(n = 10)
    
    ggplot(top10, aes(x = reorder(paste(carrier, flight), dep_delay), y = dep_delay)) +
      geom_col(fill = "tomato") +
      coord_flip() +
      labs(title = "Top 10 Departure Delays", x = "Flight", y = "Delay (min)") +
      theme_minimal()
  })
  
  # Table of top 5 delayed flights
  output$top_delay_table <- renderTable({
    filtered_flights() %>%
      arrange(desc(dep_delay)) %>%
      slice_head(n = 5) %>%
      select(carrier, flight, dep_delay, dest) %>%
      rename(Airline = carrier, Flight = flight, Delay = dep_delay, Destination = dest)
  })
  
  # Bar plot for average delay by destination
  output$dest_delay_plot <- renderPlot({
    filtered_flights() %>%
      group_by(dest) %>%
      summarise(avg_delay = mean(dep_delay, na.rm = TRUE), n = n()) %>%
      filter(n >= 3) %>%  # Require at least 3 flights for reliability
      ggplot(aes(x = reorder(dest, avg_delay), y = avg_delay)) +
      geom_col(fill = "steelblue") +
      coord_flip() +
      labs(title = "Average Departure Delay by Destination", x = "Destination", y = "Avg. Delay (min)") +
      theme_minimal()
  })
}

# Run the app
shinyApp(ui, server)

The result will look something like this:

Figure 39. Flight Delay Dashboard With Airport Selector
Figure 39. Flight Delay Dashboard With Airport Selector

Key Points

  • shinydashboard is a package that makes it easier to develop data dashboards using Shiny principles
  • We can think of a data dashboard as a specific type of data application with a more structured layout, and the ability to display larger amounts of information in context
  • The basic principles involved in building a dashboard with shinydashboard are the same as those for any Shiny application, but shinydashboards has unique functions associated with the dashboard layout
  • In shinydashboard, the UI is divided into three main parts: dashboardHeader(), dashboardSidebar(), and dashboardBody(). These allow for a clear separation of the header, navigation sidebar, and main content body, which contributes to a structured, user-friendly layout.
  • shinydashboard makes it easy to organize content using boxes, value boxes, and fluid rows. Each of these can be customized with specific widths to ensure an optimal layout for displaying information like charts, tables, or textual content.
  • Just like any other Shiny application, dashboards can include interactive elements like inputs (e.g., sliders, dropdowns, checkboxes) to filter or modify the displayed data in real time. You can create dynamic outputs using render functions, such as renderPlot() that are familiar from before, as well as render functions unique to shinydashboard such as renderValueBox().

Content from Publishing Shiny Applications


Last updated on 2025-06-03 | Edit this page

Overview

Questions

  • What are some ways you can publish your Shiny web applications, and make them available to a wider audience?

Objectives

  • Survey different options for publishing your Shiny web applications, and considerations associated with these options.
  • Deploy a web application to shinyapps.io, an online platform hosted by Posit (the developers of R Studio).

Preliminaries


Please load (or if necessary, install) the following package, which is necessary to deploy your applications to shinyapps.io.

R

library(rsconnect)

Introduction


Now that you have some experience creating simple data applications and dashboard using Shiny, you may be wondering how these applications can be deployed online, so that those interested in your work can interact with your applications. After all, these applications have the potential to play an important role in the open source ecosystem, by allowing researchers to share their research data with non-specialist audiences in accessible ways. This final Episode will therefore suggest some ways to put your applications out into the world.

Publishing Shiny Applications: Surveying the Options


There are a variety of possible ways for you to disseminate your Shiny applications. Note that our assumption, in presenting these options, is that you would like to deploy your application(s) in your capacity as an individual. There are enterprise-level deployment solutions, such as Posit Connect and the open-source Shiny Server,that are beyond our scope, but which may be useful, depending on your particular professional or organizational context. Also, please note that the options below are not exhaustive, even when limiting our concern to individual-level deployment options; if none of these options meet your needs, you may want to do some research and investigate possible alternatives that do. That said, the possibilities below are good potential starting points.

Posit, which is the developer of R Studio, offers the most user-friendly and intuitive way to publish your Shiny Apps in the form of shinyapps.io). We will discuss this platform and hosting service at greater length below. If you do not want to use Posit’s service, you might consider a containerized deployment, in which you package your app and environment in a Docker container and deploy it to a cloud hosting service such as AWS, Google Cloud, or Azure; of course, this requires meaningful technical expertise, and would be a considerably more complex process than deploying to shinyapps.io. However, these alternative cloud deployments are likely to give you greater flexibility and control in managing the deployment of your app.

It is also possible to share your applications in static (non-interactive ways), and leave it to your users to launch your application locally, based on the application code. Github repositories can be an excellent place to store your application code and related materials, where they can be accessed by others. Of course, this requires some expertise on the part of the end-users (i.e. knowing how to clone a Github repository and launch a Shiny application locally), and sharing your applications this way may not allow you to realize the goal of making your dataset(s) accessible to broad non-specialist audiences, including the broader public. Of course, the choice between sharing your applications in non-interactive formats (i.e. via an application code repository on Github) or deploying to the web, is not necessarily either/or. Indeed, it makes a lot of sense to deploy your application to the web using a service such as shinyapps.io, but also maintain a Github repository for your code, which can facilitate code sharing, teaching, and research reproducibility. Hosting your application code on Github can also be a way to assign a DOI to your application and dashboard projects (via Github’s integration with Zenodo), which facilitates discoverability and long-term preservation.

Deploying to shinyapps.io


In this section, we’ll go into a bit more detail on deploying your Shiny applications on Posit’s shinyapps.io) service, since that offers the easiest way for you to publish your applications online, where they can be accessed by anyone with an internet connection.

Setting up and configuring your shinyapps account and rsconnect

In order to set up and configure rsconnect, which is needed to deploy your Shiny apps to shinyapps.io, please take the following steps:

  1. Got toshinyapps.io), and if you haven’t already, create an account and log in.
  2. Install and load the rsconnect package in your local instance of R Studio (as you would any other package)
  3. Once you are within your shinyapps.io account, click your user name in the top right of the page, and select the Tokens button.
  4. On the Tokens page, you will see a window that looks something like this:
Figure 40. Authorizing rsconnect (source: Posit documentation)
Figure 40. Authorizing rsconnect (source: Posit documentation)

Copy that command, paste it into your R console, and run it. 5. At this point, you have rsconnect configured, and can use it to deploy Shiny apps saved locally to shinyapps.io.

Deploying your Shiny applications

In order to deploy your applications, follow these steps:

  1. Make sure your Shiny application code, and related materials, are stored in a dedicated directory. The .R file containing your code must be saved as app.R.
  2. Identify the file path to the directory with your application code and related materials (such as the data needed to run your application)
  3. Pass the file path as an argument to rsconnect’s deployApp() function, i.e. rsconnect::deployApp(<file path>. This will deploy your application to shinyapps.io, where you can find the url to your application and visit your application online. Anybody with an internet connection and this url can access your Shiny app, and interact with it in the way you designed.

Within your shinyapps.io account, which is itself laid out as a dashboard, you can see a list of your deployed dashboards by clicking the Applications tab on the left. Within that page, clicking on the link to your deployed application will take you to an application-specific page (known as “Application View”), which contains the application url, and usage statistics. From this page, you can also archive or delete your applications.

Note that if you are on the free tier, you can deploy up to five applications at a time, but other usage limits apply. To see more information about pricing and usage, please go to the shinyapps.io page and scroll down. In general, the free tier is sufficient for most research-related use cases (especially if you carefully manage deployments), but in some cases it may be worthwhile to pay for increased deployment and usage limits, as well as increased security

Much of this information rehashes the shinyapps.io documentation and user guide, which is a good place to go if you have further questions or need additional guidance.

Deploy an application to shinyapps.io

Take an application or dashboard that you created in one of the previous episodes, and publish it to shinyapps.io. Copy the link to your application, share it with a partner, and have them confirm your application is working.

Key Points

  • There are a variety of ways to publish your Shiny applications and dashboards to the web, depending on your needs
  • The most intuitive and beginner-friendly publication platform is Posit’s shinyapps.io
  • Before being able to deploy your apps to shinyapps.io, you must set up an account, and configure rsconnect
  • After this initial set up, you can deploy your application by passing the filepath to the application directory (which contains the app.R file containing your application code, as well as any other relevant dependencies) as an argument to rsconnect’s deployApp() function.
  • Once the application is deployed, you can go to your shinyapps.io account page (and particularly the page’s “Application View”) to retrieve the published application’s url, track usage statistics, and manage your application (for example, archive or delete it when you no longer want it publicly available).