52  a bit more about shiny

In this section, we are going to look at 2 important elements of a shiny app :

We will also introduce briefly the package bslib, to give to the shiny app a more modern look.

53 Reactive object

This part presents what is a reactive object : how it works and why it is (so!) useful.

In the previous examples, we created 3 simple output elements : a table, a chart, a text.

To create those visuals, we proceeded the same way, in 4 steps :

  • Get data.

  • Filter data.

  • Transform data.

  • Create the result (a table, a chart, a text).

Very often, the first 3 steps are the same. Then instead of repeating those steps several times, we can simply create a reactive object, and then use it as an input to create the visuals.

By doing so we keep the code easier to maintain (less lines), and faster to update. Instead of updating the 3 steps of each objects, we simply need to update the ones of the reactive object.

As described in the figure below, a reactive object is used as an input for a table and 2 charts.

Figure 86 : from a reactive object to charts

Furthermore, a reactive object can be combined with another one to create a visual, or another reactive object for instance.

This is very useful when we want :

  • to perform a calculation and then take its result (stored as a reactive object) to combine it with another set of data (another reactive object for example).

  • to use a “write back” capability :

    • the user inputs some data in the UI, and then those data can be used to update another table (in the server), to run some calculations or create some new visuals.

Figure 87 : combination of 2 reactive objects

Let’s illustrate how to create and use a reactive object, a data frame, continuing on the previous examples.

To create a reactive object, we need to write the following syntax : name_of_the_reactive_data <- reactive({ })

Within the 2 parentheses and curly bracket ({ }), we will :

  • proceed with the steps to import, filter and transform the data.

  • keep the final result, using the function return().

Then, to use this reactive data frame, we simply need to affect it to another object, within the 2 ({ }) of an output.

  • for example, to get some reactive data, we write : df1 <- name_of_the_reactive_data().

    • note : we must put 2 brackets after the name of the reactive object.
  • we can also make sure the object df1 is then a data frame, so we can add the syntax : df1 <- as.data.frame(df1) .

In the code below, we then notice that :

  • we start creating a reactive data frame, called react_selected_data.

  • this reactive object is then used to create to visuals : a table and a text.

We simplified the overall code : we just need to update the reactive object if we want to make some changes, for example adding a new filter or performing a transformation.

#----------------------
# Upload libraries
#----------------------

# shiny
library(shiny)
library(shinyWidgets)

# ETL
library(tidyverse)

# table
library(reactable)

# charts
library(highcharter)


#----------------------
# Upload data or create datasets
#----------------------

cities <- c("Taipei", "Tokyo", "Singapore", "Kuala Lumpur", "Jakarta")

population <- c(2494813, 14254039, 6110200, 2075600, 10684946)

my_data <- data.frame(cities,
                      population)





#------------------------------------------------
#
#                  Shiny app
#
#------------------------------------------------



#------------------------------------------------
#
#                  SHINY UI
#
#------------------------------------------------



# Define UI ----
ui <- fluidPage(
  
  # App title ----
  titlePanel("my app"),
  
  # Sidebar layout with input and output definitions ----
  sidebarLayout(
    
    # Sidebar panel for inputs ----
    sidebarPanel(
      
      conditionalPanel(condition = "input.tabselected == 1 || input.tabselected == 2",
                       
                       h4("Geography"),
                       
                       pickerInput("Selected.cities","Select city:",
                                   choices = sort(unique(my_data$cities),decreasing = FALSE),
                                   options = list(`actions-box` = TRUE,`live-search`=TRUE),multiple = T,
                                   selected = unique(my_data$cities)[1:1000]
                                   #selected = c("Taipei") # if we want a preselection
                       ),
                       
                       hr()
                       
                       
      ), # close conditionalPanel
      
      
      conditionalPanel(condition = "input.tabselected == 1",
                       
                       h4("Population"),
                       
                       sliderInput( 
                         "Selected.population", "Select population", 
                         min = 1000000, max = 20000000, 
                         value = c(1000000, 4000000) 
                       )
                       
                       
      ), # close conditionalPanel
      

      hr(),
      width = 2
      
    ), # end of SidebarPanel
    
    # Main panel for displaying outputs ----
    mainPanel(
      
      # Output: Tabset w/ plot, summary, and table ----
      tabsetPanel(type = "pills",
                  
                  tabPanel("summary", icon = icon("list"), value = 1,
                           
                           fluidRow(
                             
                             column(3,
                                    h4("Total Populations"),
                                    h5("of the selected cities"),
                                    h2(textOutput("total_cities_populations_TX"))
                                    ),
                             
                             column(4,
                                    reactableOutput("summary_RT")
                                    ),
                             
                             column(5)
                             
                             
                           ) # close fluidRow
                           
                           
                           
                           
                           ),
                  
                  tabPanel("analysis", icon = icon("chart-line"), value = 2,
                           
                           highchartOutput("cities_populations_HC")
                           ),
                  
                  
                  
                  
                  id = "tabselected"
                  ), # close tabsetPanel
      width = 10
      
    ) # end of mainPanel
  )
)












#------------------------------------------------
#
#
#                  SHINY SERVER
#
#
#------------------------------------------------


server <- function(input, output) {
  
  
  #------------------------------------------------
  #               Create reactive
  #------------------------------------------------
  
  react_selected_data <- reactive({
    
    
    #------------------
    # Get data
    df1 <- my_data
    
    #------------------
    # Filter
    
    # Select city
    df1 <- df1 |> filter(cities %in% input$Selected.cities)

    # Select population
    df1 <- df1 |> filter(population >= input$Selected.population[1] & population <= input$Selected.population[2])
    
    #------------------
    # Transform
    
    
    #------------------
    # keep results
    return(df1)
    
    
  })
  
  
  
  #------------------------------------------------
  
  #                Table 1
  #                Display of Summary table
  
  #------------------------------------------------
  
  
  output$summary_RT <- renderReactable({
    
    #------------------
    # Get data
    df1 <- react_selected_data()
    df1 <- as.data.frame(df1)
    

    #------------------
    # Table
    
    reactable(df1)
    
    
    
    })
  
  
  
  
  
  
  
  #------------------------------------------------
  
  #                Chart 1
  #                Display of the selected cities populations
  
  #------------------------------------------------
  
  
    output$cities_populations_HC <- renderHighchart({
    
    #------------------
    # Get data
    df1 <- my_data
    
    #------------------
    # Filter
    
    # Select city
    df1 <- df1 |> filter(cities %in% input$Selected.cities)

    
    
    
    #------------------
    # Transform
    
    
    #------------------
    # Table

    highchart() |> 
      hc_title(text = "Population by cities") |>
      hc_subtitle(text = "in nb of inhabitants") |> 
      hc_add_theme(hc_theme_google()) |>
      
      hc_xAxis(categories = df1$cities) |> 
      
      hc_add_series(name = "Population", 
                    color = "mediumseagreen",
                    dataLabels = list(align = "center", enabled = TRUE),
                    data = df1$population) |>
      
      hc_chart(type = "column") 
    
    
    
    })
  
  
  
  
  
  
  
  
  
  #------------------------------------------------
  
  #                Text 1
  #                Display the sum of the selected cities populations
  
  #------------------------------------------------
    
    
    output$total_cities_populations_TX <- renderText({
    
    #------------------
    # Get data
    df1 <- react_selected_data()
    df1 <- as.data.frame(df1)
    

    
    #------------------
    # Text
    
    value1 <- sum(df1$population)
    
    formatC(value1, format="d", big.mark=',')
    
    
    })
    
    
    
  
  
  
  
  
#------------------------------------------------
} # close server






#------------------------------------------------
#
#
#                  END of SHINY UI and SERVER
#
#
#------------------------------------------------


# Create Shiny app ----
shinyApp(ui, server)

54 Module

Now, let’s discover another very interesting feature : a module.

A module is a R file, which contains some code that we are going to import into our shiny app.

This code could be :

  • some functions that we create.

  • a UI.

  • some data.

Let’s import a module to create a KPI widget. This module contains a function called “KPI_diff_pc_ModuleUI”.

This function takes a data frame as an input (for example a reactive data frame) with 2 numeric columns. It will then return a KPI object, to display the difference between 2 quantities, in value (amount) and in percentage.

In this example, the module is the R file “module_KPI_box.R”. It is stored on a GitHub repository.

We will download it using the function source() and the syntax : source("https://raw.githubusercontent.com/nguyennico/shiny_intro/main/module_KPI_box.R")

#----------------------
# Upload libraries
#----------------------

# shiny
library(shiny)
library(shinyWidgets)

# ETL
library(tidyverse)
library(scales)

# Table
library(reactable)


#----------------------
# Upload Modules
#----------------------

# Source the R script from GitHub

# source the module
source("https://raw.githubusercontent.com/nguyennico/shiny_intro/main/module_KPI_box.R")




#----------------------
# Upload data or create datasets
#----------------------

product <- c("item 1", "item 2", "item 3", "item 4", "item 5")

ytd <- c(10, 20, 30, 40, 50)

lytd <- c(21, 15, 42, 33, 78)

my_data <- data.frame(product,
                      ytd,
                      lytd)



#----------------------
# UI
#----------------------

ui <- fluidPage(
  
  headerPanel("a KPI module example"), # title of the app
  
  sidebarPanel(
    
    pickerInput("selected_product", "Select Product", 
            choices = unique(as.character(my_data$product)),
            options = list(`actions-box` = TRUE),multiple = T,
            selected = unique(as.character(my_data$product))[1:20]
            #selected = c("item 1") # we also can preselect one or more value
                       )
    
    
  ), # close sidebarPanel
      
  mainPanel(
    
    KPI_diff_pc_ModuleUI("KPI_YTD_vs_LYTD"),
    
    reactableOutput("my_table")
    
  ) # close mainPanel

) # close ui


#----------------------
# Server
#----------------------

server <- function(input, output) {

  
  #----------------------
  #       Create Reactive
  #----------------------
  
  my_reactive_data <- reactive({
    
    #------------------
    # Get data
    df1 <- my_data
    
    #------------------
    # Filters
    
    # select product
    df1 <- df1 |> filter(product %in% input$selected_product)
    
    #------------------
    # Table
    return(df1)
    
  })
  
  
  
  
  #----------------------
  #       Table 1
  #----------------------
  
  output$my_table <- renderReactable({
    
    #------------------
    # Get data
    df1 <- my_reactive_data()
    df1 <- as.data.frame(df1)
    
    #------------------
    # Table
    reactable(df1)
    
  })
  
  
  
  

  #----------------------
  #       KPI 1
  #       YTD vs LYTD
  #----------------------
  
  
  # call Module
  KPI_diff_pc_ModuleServer("KPI_YTD_vs_LYTD",
                           data = my_reactive_data, 
                           previous = "lytd",
                           new = "ytd",
                           title = "YTD vs LYTD sales",
                           subtitle = "vs LYTD :",
                           uom = "units"
                           )
  
  


} # close server

shinyApp(ui = ui, server = server)

In a nutshell : Using modules in R Shiny applications offers several benefits, particularly when it comes to organizing, maintaining, and scaling your applications.

Here are some key advantages:

  • Reusability:

    • Modules allow you to encapsulate functionality that can be reused across different parts of your application or even in different applications.

    • This reduces code duplication and makes it easier to maintain consistent behavior across your projects.

  • Encapsulation:

    • By using modules, you can encapsulate the internal workings of a component, exposing only the necessary inputs and outputs.

    • This leads to cleaner and more modular code, as each module can be developed and tested independently.

  • Scalability:

    • As your application grows, modules make it easier to scale by allowing you to add new features or modify existing ones without affecting other parts of the application.

    • This modular approach is particularly beneficial for large applications.

  • Maintenance, Debugging and Testing:

    • Modules can be developed and tested separately, which simplifies debugging.

    • You can isolate issues within a module without having to sift through the entire application code.

  • Collaboration:

    • When working in teams, modules allow different developers to work on different parts of the application simultaneously.

    • Each module can be assigned to different team members, facilitating parallel development.

55 Modern boostrap with bslib

What we have seen so far in terms of design is using Bootstrap 3. It’s the by default design of the shiny package. We can make it more modern, using the bslib package.

The bslib package in R is designed to enhance the styling and theming capabilities of Shiny applications by leveraging Bootstrap, a popular front-end framework.

Here are some advantages of using bslib:

  • Customizable Themes:

    • bslib allows to easily customize Bootstrap themes using variables such as colors, fonts, and spacing.

    • This enables us to create a unique look and feel for your shiny applications without needing extensive CSS knowledge.

  • Responsive Design:

    • Bootstrap is known for its responsive design capabilities, and bslib makes it easy to integrate these features into your shiny apps.

    • This ensures that our applications look good on a variety of devices, from desktops to tablets to smartphones.

  • Integration with Bootstrap 4 and 5:

    • bslib supports both Bootstrap 4 and 5, allowing you to leverage the latest features and improvements in the Bootstrap framework.

    • This includes new components, utilities, and improved grid systems.

  • Dynamic Theming:

    • You can create dynamic themes that change based on user input or other reactive conditions within your app.

    • This is useful for applications that need to adapt their appearance in real-time.

  • Easy to Use:

    • The package provides a user-friendly interface for theme customization, making it accessible even to those who are not familiar with web development.

    • Functions like bs_theme() and bs_add_variables() simplify the process of setting and modifying theme variables.

  • Enhanced Aesthetic Control:

    • With bslib, you have greater control over the aesthetics of your Shiny apps.

    • You can fine-tune aspects like typography, button styles, and layout, which can significantly improve the user experience.

To know more about bslib, let’s refer to the website of posit which presents it.