How to Create Dynamic Tabs With Plotly Plots in R Shiny
December 20, 2020 By Pascal Schmidt R 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 applicationgapminder
to load in the data settidyverse
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:
DataTable
andDataTable
proxy. How to efficiently update values in a table without re-rendering the entire table when a value updates.- Communicating between R Shiny modules.
Recent Posts
Recent Comments
- Kardiana on The Lasso – R Tutorial (Part 3)
- Pascal Schmidt on RSelenium Tutorial: A Tutorial to Basic Web Scraping With RSelenium
- Pascal Schmidt on Dynamic Tabs, insertTab, and removeTab For More efficient R Shiny Applications
- Gisa on Persistent Data Storage With a MySQL Database in R Shiny – An Example App
- Nicholas on Dynamic Tabs, insertTab, and removeTab For More efficient R Shiny Applications