Site icon R-bloggers

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.

  • < section id="shiny-in-a-nutshell" class="level2">

    Shiny in a Nutshell

    < section id="video" class="level3">

    Video

    Checkout the full R-Shiny playlist here 👉 PLAYLIST

    < section id="code" class="level3">

    Code

    < details class="code-fold"> < summary>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()
    < section id="understanding-reactivity" class="level2">

    Understanding Reactivity

    < section id="video-1" class="level3">

    Video

    Checkout the full R-Shiny playlist here 👉 PLAYLIST

    < section id="code-1" class="level3">

    Code

    < details class="code-fold"> < summary>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()
    < section id="understanding-reactive-expressions" class="level2">

    Understanding Reactive Expressions

    < section id="video-2" class="level3">

    Video

    Checkout the full R-Shiny playlist here 👉 PLAYLIST

    < section id="code-2" class="level3">

    Code

    < details class="code-fold"> < summary>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()
    < section id="dynamicinteractive-ui-outputs" class="level2">

    Dynamic/Interactive UI Outputs

    < section id="video-3" class="level3">

    Video

    Checkout the full R-Shiny playlist here 👉 PLAYLIST

    < section id="code-3" class="level3">

    Code

    < details class="code-fold"> < summary>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()
    < section id="shiny-modules" class="level2">

    Shiny Modules

    < section id="video-4" class="level3">

    Video

    Checkout the full R-Shiny playlist here 👉 PLAYLIST

    < section id="code-4" class="level3">

    Code

    < details class="code-fold"> < summary>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()
    < section id="robust-shiny-apps-with-golem" class="level2">

    Robust Shiny Apps with Golem

    < section id="video-5" class="level3">

    Video

    Checkout the full R-Shiny playlist here 👉 PLAYLIST

    < section id="code-5" class="level3">

    Code

    Link to repository: repo

    < section id="client-side-code-dynamic-tables-with-shiny-and-gt" class="level2">

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

    < section id="video-6" class="level3">

    Video

    Checkout the full R-Shiny playlist here 👉 PLAYLIST

    < section id="code-6" class="level3">

    Code

    < details class="code-fold"> < summary>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

    < section id="code-modularized" class="level3">

    Code (modularized)

    < details class="code-fold"> < summary>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()
    < section id="advanced-widgets-with-shinywidgets" class="level2">

    Advanced Widgets with {shinywidgets}

    < section id="video-7" class="level3">

    Video

    Checkout the full R-Shiny playlist here 👉 PLAYLIST

    < section id="code-7" class="level3">

    Code

    < details class="code-fold"> < summary>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.
  • Exit mobile version