How to Create Dynamic Tabs With Plotly Plots in R Shiny

Customizing your application and giving a better user experience is crucial for a great application. In this tutorial, we will be creating dynamic tabs with R Shiny and the gapminder data set.

Here is the application we will be creating:

Let’s jump into the tutorial.

First, we will be loading the libraries.

library(plotly)
library(shiny)
library(gapminder)
library(tidyverse)
  • We are using plotly to create the plots in each tab
  • shiny to run the application
  • gapminder to load in the data set
  • tidyverse for data manipulation

We want to create an application where we want to plot life expectancy on the y-axis and the years on the x-axis. For each continent we are doing that and we want to create one tab for each continent.

Creating the UI for Dynamic Tabs in R Shiny

The UI is very simple and straight forward.

ui <- shiny::bootstrapPage(
    
    div(
        class = "container",
        style = "margin-top: 25px;",
        div(
            class = "row text-center",
            shiny::checkboxGroupInput(inputId = "continents",
                                      label   = "Choose continents",
                                      choices = unique(gapminder$continent) %>% 
                                          sort(),
                                      inline  = TRUE,
                                      selected = c("Africa", "Asia", "Oceania")),
            
            shiny::actionButton(inputId = "btn",
                                label   = "Create Dynamic Tabs")
        )
    ),
    
    div(
        class = "container",
        shiny::uiOutput("dynamic_tabs")
    )
    
)

We are creating a checkboxGroupInput where the user can select how many continents they want to render. After the desired continents have been checked, there is an action button that has to be clicked when one wants to create the tabs.

Lastly, there is the uiOutput() function which creates the tabs dynamically.

Creating the Server for Dynamic Tabs in R Shiny

The server side of things are always more complicated. First, we are creating a reactive value which holds the continents that the user has selected. Then we will update the reactive value depending on what the user has checked in the check box.

Reactive Values That Holds the Continents to be Created

rv <- shiny::reactiveValues(start = NULL)

shiny::observeEvent(input$btn, {

    rv$start <- input$continents

})

It is important to update the continents in an observer rather than in the renderUI() function so that the tabs only update once the action button has been clicked.

Creating the RenderUI Function Which Creates the Tabs Dynamically

Now, we can code the RenderUI function. This is the heart of the application where the tabs will be created dynamically. The function looks like this:

output$dynamic_tabs <- shiny::renderUI({
        
        rv$start %>% 
            purrr::map(~
                           shiny::tabPanel(
                               title = .x,
                               div(
                                   class = "panel", 
                                   div(
                                       class = "panel-header",
                                       tags$h3(.x)
                                   ),
                                   div(
                                       class = "panel-body",
                                       
                                       gapminder %>% 
                                           dplyr::filter(continent == .x) %>% 
                                           dplyr::group_by(continent, year) %>% 
                                           dplyr::summarise(life_exp = mean(lifeExp)) %>% 
                                           dplyr::ungroup() %>% 
                                           
                                           plotly::plot_ly(x = ~year, y = ~life_exp, 
                                                           mode = 'lines+markers',
                                                           type = "scatter") %>% 
                                           layout(legend = list(orientation = "h",
                                                                xanchor = "center",  
                                                                x = 0.5),
                                                  xaxis = list(title = ""),
                                                  yaxis = list(title = ""), height = 500)
                                   )
                               )
                           )
            ) -> gap
        
        do.call(what = shiny::tabsetPanel, 
                args = gap %>% 
                    append(list(type = "pills",
                                id   = "continent_tabs")))
        
    })

Whenever rv$start updates in the observer function, the renderUI function reacts to the change in continents and creates the tabs with the continents. rv$start holds a vector with continents. We are using purrr's map function to iterate over the vector with continents. ~ creates an anonymous function and .x is the function argument. In this case, a string. For each selected continent, we are creating a tab with shiny::tabPanel().

When creating the plot in the tab, we do not have to use any plotOutput() function. The plots will be created inside the tab. The most important part is the dplyr::filter(continent == ".x") which filters the gapminder data frame for a particular continent and then creates a plotly plot.

Then, we are using the do.call function where we use as first argument tabsetPanel as the function. The do.call function requires us to use a list as second argument. By default, purrr's map function gives us back a list where each element holds a tab with a plot. We will use this list as the second argument in the do.call function and then specify the type of tabs, in our case pills.

And boom! We have created dynamic tabs with just a few lines of code. However, you probably noticed that if we only add one more continent, the render function has to create all existing tabs again!! Moreover, when we want to only delete one tab, all other existing tabs will also be created when clicking the action button. This behavior might be alright for your app, however, when wanting to build an application that goes into production and serves a lot of users, this behavior is not desirable.

One option you have is using insertTab and removeTab. These two functions let you insert tabs and remove tabs without re-creating existing tabs. However, the logic for doing this gets a lot more complicated than just creating all tabs whenever rv$start changes. In the next tutorial, I will be going over this and will explain how to use insertTab and removeTab. The tutorial will is available here.

Here are some other R Shiny tutorials you might find helpful:

 

Post your comment