WebDev4R: Shiny Explained

[This article was first published on Albert Rapp, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Shiny in a Nutshell

Video

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code

Press to unfold code
library(shiny)

ui <- bslib::page_fluid(
  sliderInput(
    'my_slider', # Input ID
    'Select your number', # Input label
    # Input-specifc stuff
    min = 0,
    max = 1,
    value = 0.5,
    step = 0.1
  ),

  selectInput(
    'my_dropdown_menu', # Input ID
    'Pick your color', # Input label
    # Input-specifc stuff
    choices = c('red', 'green', 'blue')
  ),

  textOutput('my_generated_text')
)

server <- function(input, output, session) {
  output$my_generated_text <- renderText({
    paste(
      input$my_dropdown_menu,
      input$my_slider
    )
  })
}

shinyApp(ui, server) |> print()

Understanding Reactivity

Video

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code

Press to unfold code
library(shiny)
ui <- bslib::page_fluid(
  sliderInput(
    'slider',
    'Slider',
    min = 1,
    max = 100,
    value = 50
  ),
  actionButton('button', 'Button'),
  textOutput('text')
)

server <- function(input, output, session) {
  observeEvent(
    input$button,
    {
      output$text <- renderText({
        print(input$button)
        isolate(input$slider)
      })
    }
  )
}

shinyApp(ui, server) |> print()

Understanding Reactive Expressions

Video

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code

Press to unfold code
library(tidyverse)
library(shiny)

ui <- bslib::page_fluid(
  bslib::layout_column_wrap(
    selectInput(
      'species',
      'Choose your species',
      unique(palmerpenguins::penguins$species)
    ),
    selectInput(
      'island',
      'Choose your island',
      unique(palmerpenguins::penguins$island)
    )
  ),
  bslib::layout_column_wrap(
    plotOutput('plot'),
    DT::dataTableOutput('tbl')
  )
)

server <- function(input, output, session) {
  filtered_data <- reactiveValues()
  observe({
    dat <- palmerpenguins::penguins |>
      filter(
        !is.na(sex),
        species == input$species,
        island == input$island
      )

    filtered_data$dat <- dat
  })

  output$tbl <- DT::renderDT({
    filtered_data$dat
  })

  output$plot <- renderPlot({
    filtered_data$dat |>
      ggplot(
        aes(
          x = flipper_length_mm,
          y = bill_depth_mm
        )
      ) +
      geom_point(size = 3)
  })
}

# Print inside Positron
shinyApp(ui, server) |> print()

Dynamic/Interactive UI Outputs

Video

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code

Press to unfold code
library(shiny)

ui <- bslib::page_fluid(
  actionButton('button', 'Move slider to 7'),
  sliderInput(
    'slider',
    'Move me!',
    min = 1,
    max = 10,
    value = 5
  )
)

server <- function(input, output, session) {
  observe({
    updateSliderInput(
      inputId = 'slider',
      value = 7,
      label = 'This has been moved!'
    )
  }) |>
    bindEvent(input$button)
}

# Print to display in Positron
shinyApp(ui, server) |> print()

Shiny Modules

Video

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code

Press to unfold code
library(shiny)
library(ggplot2)
plot_UI <- function(id) {
  ns <- NS(id)
  tagList(
    plotOutput(ns("plot"))
  )
}

plot_Server <- function(id, shared_data) {
  moduleServer(
    id,
    function(input, output, session) {
      output$plot <- renderPlot({
        if (shared_data$dropdown_choice == "bar") {
          p <- ggplot(
            mtcars,
            aes(x = factor(cyl))
          ) +
            geom_bar(fill = 'dodgerblue4')
        } else {
          p <- ggplot(
            mtcars,
            aes(x = factor(cyl), y = mpg)
          ) +
            geom_boxplot(
              color = 'dodgerblue4',
              linewidth = 1.5
            )
        }

        p +
          theme_minimal(base_size = 16)
      })
    }
  )
}

ui <- bslib::page_fluid(
  selectInput(
    "dropdown",
    "Choose a chart",
    choices = c("bar", "boxplot")
  ),
  plot_UI("plot")
)

server <- function(input, output, session) {
  # shared_data <- reactiveValues()
  # observe({
  #   shared_data$dropdown_choice <- input$dropdown
  # })
  shared_data <- list(dropdown_choice = 'bar')
  plot_Server("plot", shared_data)
}

shinyApp(ui, server) |> print()

Robust Shiny Apps with Golem

Video

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code

Link to repository: repo

Client-side Code & Dynamic Tables with {shiny} and `{gt}

Video

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code

Press to unfold code
library(shiny)
library(gt)


df_pizza <- gt::pizzaplace |>
  dplyr::slice(1:500) |>
  dplyr::mutate(
    cryptic_id = purrr::map_chr(id, \(x) {
      sample(c(letters, LETTERS, 0:9), size = 30, replace = TRUE) |>
        paste0(collapse = '')
    }),
  ) |>
  dplyr::mutate(
    cryptic_id_link = purrr::map_chr(
      cryptic_id,
      \(x) {
        a(
          '👀 Show me',
          onclick = glue::glue(
            'Shiny.setInputValue("clicked_id", ""); 
            Shiny.setInputValue("clicked_id", "{x}")'
          ),
          style = htmltools::css(
            background = '#F2FDFF',
            border = '1px solid #101935',
            border_radius = '5px',
            padding = '5px',
            cursor = 'pointer'
          )
        ) |>
          as.character()
      }
    )
  )

ui <- bslib::page_fluid(
  h3('Dynamic Tables'),
  gt_output('tbl')
)

server <- function(input, output, session) {
  output$tbl <- render_gt({
    df_pizza |>
      dplyr::select(date, time, price, cryptic_id_link) |>
      gt() |>
      fmt_markdown(
        columns = 'cryptic_id_link'
      ) |>
      opt_interactive(page_size_default = 5)
  })

  observe({
    print(input$clicked_id)

    showModal(
      modalDialog(
        df_pizza |>
          dplyr::filter(cryptic_id == input$clicked_id) |>
          dplyr::select(-c(cryptic_id, cryptic_id_link)) |>
          tidyr::pivot_longer(
            cols = tidyr::everything(),
            values_transform = as.character
          ) |>
          gt()
      )
    )
  }) |>
    bindEvent(input$clicked_id)
}

shinyApp(ui, server) |> print()

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code (modularized)

Press to unfold code
library(shiny)
library(gt)

df_pizza <- gt::pizzaplace |>
  dplyr::slice(1:500) |>
  dplyr::mutate(
    cryptic_id = purrr::map_chr(id, \(x) {
      sample(c(letters, LETTERS, 0:9), size = 30, replace = TRUE) |>
        paste0(collapse = '')
    }),
  )


tbl_UI <- function(id) {
  ns <- NS(id)
  tagList(
    gt_output(ns('tbl'))
  )
}

tbl_Server <- function(id) {
  moduleServer(
    id,
    function(input, output, session) {
      ns <- session$ns
      df_pizza <- df_pizza |>
        dplyr::mutate(
          cryptic_id_link = purrr::map_chr(
            cryptic_id,
            \(x) {
              a(
                '👀 Show me',
                onclick = glue::glue(
                  'Shiny.setInputValue("{ns("clicked_id")}", ""); 
                  Shiny.setInputValue("{ns("clicked_id")}", "{x}")'
                ),
                style = htmltools::css(
                  background = '#F2FDFF',
                  border = '1px solid #101935',
                  border_radius = '5px',
                  padding = '5px',
                  cursor = 'pointer'
                )
              ) |>
                as.character()
            }
          )
        )

      output$tbl <- render_gt({
        df_pizza |>
          dplyr::select(date, time, price, cryptic_id_link) |>
          gt() |>
          fmt_markdown(
            columns = 'cryptic_id_link'
          ) |>
          opt_interactive(page_size_default = 5)
      })

      observe({
        print(input$clicked_id)

        showModal(
          modalDialog(
            df_pizza |>
              dplyr::filter(cryptic_id == input$clicked_id) |>
              dplyr::select(-c(cryptic_id, cryptic_id_link)) |>
              tidyr::pivot_longer(
                cols = tidyr::everything(),
                values_transform = as.character
              ) |>
              gt()
          )
        )
      }) |>
        bindEvent(input$clicked_id)
    }
  )
}


ui <- bslib::page_fluid(
  h3('Dynamic Tables'),
  tbl_UI('tbl')
)

server <- function(input, output, session) {
  tbl_Server('tbl')
}

shinyApp(ui, server) |> print()

Advanced Widgets with {shinywidgets}

Video

Checkout the full R-Shiny playlist here 👉 PLAYLIST

Code

Press to unfold code
library(shiny)
library(bslib)
library(ggplot2)

df_pizza <- gt::pizzaplace |>
  dplyr::mutate(date_sold = readr::parse_date(date)) |>
  dplyr::select(-c(date, time))


plot_revenue_by_timeframe <- function(
  df,
  timeframe,
  primary_color = '#007bc2'
) {
  if (!(timeframe %in% c('month', 'quarter', 'week'))) {
    cli::cli_abort('Unsupported timeframe')
  }
  if (timeframe == 'month') {
    fn_aggregate <- lubridate::month
  }
  if (timeframe == 'quarter') {
    fn_aggregate <- lubridate::quarter
  }
  if (timeframe == 'week') {
    fn_aggregate <- lubridate::week
  }

  df |>
    dplyr::mutate(timeframe = fn_aggregate(date_sold)) |>
    dplyr::summarize(
      price = sum(price),
      .by = timeframe
    ) |>
    ggplot(aes(x = timeframe, y = price)) +
    geom_col(fill = primary_color) +
    labs(x = element_blank(), y = element_blank()) +
    scale_y_continuous(labels = scales::label_dollar()) +
    theme_minimal(base_size = 24, base_family = 'Source Sans Pro') +
    theme(
      panel.grid.major.x = element_blank(),
      panel.grid.minor = element_blank(),
    )
}

ui <- page_navbar(
  title = 'My {bslib} App',
  nav_panel(
    'Stats',
    page_sidebar(
      shinyWidgets::useSweetAlert(),
      sidebar = sidebar(
        sliderInput(
          'slider_timepoint',
          'Timeframe',
          min = min(df_pizza$date_sold),
          max = max(df_pizza$date_sold),
          value = range(df_pizza$date_sold),
          width = 225
        ),
        width = 300
      ),
      layout_column_wrap(
        value_box(
          'Pizzas sold',
          value = textOutput('nmbr_pizzas_sold', inline = TRUE),
          showcase = shiny::icon('pizza-slice')
        ),
        value_box(
          'Revenue generated',
          value = textOutput('nmbr_revenue_genrated', inline = TRUE),
          showcase = shiny::icon('sack-dollar')
        ),
        fill = FALSE,
        width = '300px',
        min_height = '100px'
      ),
      card(
        card_header(
          'Revenue by month'
        ),
        card_body(
          plotOutput('plot_by_month')
        ),
        full_screen = TRUE
      ),
      navset_card_tab(
        nav_panel(
          'Revenue by week',
          card(
            card_body(plotOutput('plot_by_week')),
            full_screen = TRUE
          )
        ),
        nav_panel(
          'Revenue by quarter',
          card(
            card_body(plotOutput('plot_by_quarter')),
            full_screen = TRUE
          )
        ),
        nav_spacer(),
        nav_item(
          actionLink(
            'btn_settings',
            label = 'Settings',
            icon = shiny::icon('gear')
          )
        )
      )
    )
  ),
  nav_panel(
    'Other Stuff',
    'Here is where your content could live.'
  )
)

server <- function(input, output, session) {
  df_filtered_pizza <- reactive({
    df_pizza |>
      dplyr::filter(
        date_sold >= input$slider_timepoint[1],
        date_sold <= input$slider_timepoint[2],
      )
  })

  output$nmbr_pizzas_sold <- renderText({
    df_filtered_pizza() |>
      dplyr::pull(price) |>
      length() |>
      scales::number(big.mark = ',')
  })
  output$nmbr_revenue_genrated <- renderText({
    df_filtered_pizza() |>
      dplyr::pull(price) |>
      sum() |>
      scales::dollar()
  })

  output$plot_by_month <- renderPlot({
    req(df_filtered_pizza)
    plt <- df_filtered_pizza() |>
      plot_revenue_by_timeframe(timeframe = 'month')
    plt + scale_x_continuous(breaks = 1:12)
  })

  output$plot_by_week <- renderPlot({
    req(df_filtered_pizza)
    plt <- df_filtered_pizza() |>
      plot_revenue_by_timeframe(timeframe = 'week')
    plt + scale_x_continuous(breaks = 1:53)
  })

  output$plot_by_quarter <- renderPlot({
    req(df_filtered_pizza)
    plt <- df_filtered_pizza() |>
      plot_revenue_by_timeframe(timeframe = 'quarter')
    plt + scale_x_continuous(breaks = 1:4)
  })

  observe({
    shinyWidgets::show_alert(
      title = 'Hooraay!',
      text = 'You clicked something',
      type = 'success'
    )
  }) |>
    bindEvent(input$btn_settings)
}

shinyApp(ui, server) |> print()
To leave a comment for the author, please follow the link and comment on their blog: Albert Rapp.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)