Simplifying Parts Of A Shiny App by Creating Functions

October 10, 2022 By Pascal Schmidt R R Shiny

Until now, the Shiny app we created has all the HTML in the UI. On top of that, we have repeated code that we can stick into a function to make the app smaller and to be able to test the functions we are creating.

Here are Part 1 and Part 2 of the app.

The UI Shiny Part

Right now, the UI part of the shiny app looks like this:

ui <- fluidPage(

    br(),
    br(),

    div(id = "placeholder"),
    shiny::tagList(

        # first viz
        div(
            class = "class_a",
            div(
                class = "col-md-6",
                div(
                    class = "panel panel-default",
                    div(
                        class = "panel-heading clearfix",
                        tags$h2("Visualization 1", class = "pull-left panel-title"),
                        div(
                            class = "pull-right",
                            shiny::actionButton(
                                inputId = "a",
                                label = "",
                                class = "btn-danger delete",
                                icon = shiny::icon("minus")
                            )
                        )
                    ),
                    div(
                        class = "panel-body",
                        plotly::plot_ly(mtcars, x = ~mpg, y = ~wt)
                    )
                )
            )
        ),

        # second viz
        div(
            class = "class_b",
            div(
                class = "col-md-6",
                div(
                    class = "panel panel-default",
                    div(
                        class = "panel-heading clearfix",
                        tags$h2("Visualization 2", class = "pull-left panel-title"),
                        div(
                            class = "pull-right",
                            shiny::actionButton(
                                inputId = "b",
                                label = "",
                                class = "btn-danger delete",
                                icon = shiny::icon("minus")
                            )
                        )
                    ),
                    div(
                        class = "panel-body",
                        plotly::plot_ly(mtcars, x = ~mpg, y = ~wt)
                    )
                )
            )
        ),

        # third viz
        div(
            class = "class_c",
            div(
                class = "col-md-6",
                div(
                    class = "panel panel-default",
                    div(
                        class = "panel-heading clearfix",
                        tags$h2("Visualization 3", class = "pull-left panel-title"),
                        div(
                            class = "pull-right",
                            shiny::actionButton(
                                inputId = "c",
                                label = "",
                                class = "btn-danger delete",
                                icon = shiny::icon("minus")
                            )
                        )
                    ),
                    div(
                        class = "panel-body",
                        plotly::plot_ly(mtcars, x = ~mpg, y = ~wt)
                    )
                )
            )
        )


    ),

    shiny::includeScript(here::here("GA_dashboard/part_2/www/scripts.js"))

)

The code produces three visualizations. Instead of copying and pasting the code three times, we are going to write a function that produces the three visualizations.

All we need to do is to identify the lines that are changing and turn them into arguments.

The things that are unique to each visualization are:

  • The class inside the first div
  • The h2 header in the panel heading
  • The input id of the action button
  • The class of the action button and color are the same but in case we want to change them we will turn them into arguments as well

Creating a Function for the Shiny UI

Putting everything together, the function looks like this:

google_analytics_viz <- function(title = NULL, btn_id, class_all, class_specific, color) {
  shiny::tagList(
    div(
      class = class_specific,
      div(
        class = "col-md-6",
        div(
          class = "panel panel-default",
          div(
            class = "panel-heading clearfix",
            tags$h2(title, class = "panel-title pull-left"),
            div(
              class = "pull-right",
              shiny::actionButton(
                inputId = btn_id,
                label = "",
                class = stringr::str_glue("btn-{color} {class_all}"),
                icon = shiny::icon("minus")
              )
            )
          ),
          div(
            class = "panel-body",
            plotly::plot_ly(mtcars, x = ~mpg, y = ~wt)
          )
        )
      )
    )
  )
}

What we can do next is to replace the code inside the fluidPage with an uiOutput function and then put a renderUI inside the server.

The shiny UI looks like this now:

ui <- fluidPage(

    br(),
    br(),

    div(id = "placeholder"),
    shiny::tagList(
        shiny::uiOutput(
            outputId = "first"
        )
    ),

    shiny::includeScript(here::here("GA_dashboard/part_2/www/scripts.js"))

)

And the server side looks like this:

server <- function(input, output) {


    output$first <- shiny::renderUI({

        pmap(
            list(x = c("a", "b", "c"), y = c("Viz 1", "Viz 2", "Viz 3")),
            function(x, y) {
                google_analytics_viz(
                    title = y,
                    viz = y,
                    btn_id = x,
                    df = NULL,
                    class_all = "delete",
                    class_specific = paste0("class_", x),
                    color = "danger"
                )
            }
        )
    })

    # run when we add visualization
    shiny::observeEvent(input$add_btn_clicked, {

        # clicked id
        panel <- input$add_btn_clicked

        panel_plot_item <-
            google_analytics_viz(
                title = input$header,
                viz = NULL,
                df = NULL,
                btn_id = panel,
                class_all = "delete",
                class_specific = paste0("class_", panel),
                color = "danger"
            )

        css_selector <- ifelse(input$last_panel == "#placeholder",
                               "#placeholder",
                               paste0(".", input$last_panel)
        )

        shiny::insertUI(
            selector = css_selector,
            "afterEnd",
            ui = panel_plot_item
        )
    })

}

Conclusions

We significantly reduced the lines of code in the shiny application. As a result of that, the code is more readable and clearer, and functions can also be unit tested.

Tags: R R Shiny YouTube

Post your comment