class: center, middle, inverse, title-slide .title[ # Shiny: Part I ] .author[ ### MACS 40700
University of Chicago ] --- class: inverse # Agenda * What is Shiny * Examples * NEED THE FOLLOWING: * relevant packages: `shiny`, `vidiris`, `ggrepel`, `shinythemes` * github repo for exercises: ```r usethis::use_course("MACS40700/shiny") ``` * start with **sandbox** --- class: middle, inverse # Setup --- ## Setup ``` r # load packages library(viridis) library(tidyverse) library(ggrepel) library(shinythemes) library(shiny) # set default theme for ggplot2 ggplot2::theme_set(ggplot2::theme_minimal(base_size = 16)) # set default figure parameters for knitr knitr::opts_chunk$set( fig.width = 8, fig.asp = 0.618, fig.retina = 2, dpi = 150, out.width = "60%" ) # dplyr print min and max options(dplyr.print_max = 10, dplyr.print_min = 10) ``` --- class: middle, inverse # Shiny: High level view --- class: middle, center .center[ Every Shiny app has a webpage that the user visits, <br> and behind this webpage there is a computer that serves this webpage by running R. ] <img src="images/high-level-1.png" width="80%" style="display: block; margin: auto;" /> --- class: middle, center .center[ When running your app locally, the computer serving your app is your computer. ] <img src="images/high-level-2.png" width="100%" style="display: block; margin: auto;" /> --- class: middle, center .center[ When your app is deployed, the computer serving your app is a web server. ] <img src="images/high-level-3.png" width="100%" style="display: block; margin: auto;" /> --- class: middle, center <img src="images/high-level-4.png" width="100%" style="display: block; margin: auto;" /> --- class: inverse, middle # Shiny Repos! --- ## Shiny very basic example: <iframe src="https://gallery.shinyapps.io/001-hello/" width="80%" height="400px" data-external="1"></iframe> [https://github.com/rstudio/shiny-examples?tab=readme-ov-file](repo with examples) --- # Let's look at some examples! https://shiny.posit.co/r/gallery/ <iframe src="https://shiny.posit.co/r/gallery/" width="80%" height="400px" data-external="1"></iframe> --- class: middle, inverse # Anatomy of a Shiny app --- ## What's in an app? .pull-left[ ``` r library(shiny) ui <- fluidPage() server <- function(input, output, session) {} shinyApp(ui = ui, server = server) ``` ] .pull-right[ - **User interface** controls the layout and appearance of app - **Server function** contains instructions needed to build app ] --- ## Add elements to app inside `fluidPage()` ```r library(shiny) ui <- fluidPage("Hello Data Viz!") server <- function(input, output) {} shinyApp(ui = ui, server = server) ``` --- ## Add elements to app inside `fluidPage()` ```r fluidPage( h1("My Shiny app"), "Hello Data Viz!" ) ``` --- ## Add HTML to `fluidPage()` * The UI simply creates HTML * [Can use any HTML tags](http://shiny.rstudio.com/articles/tag-glossary.html) * `h1()` = header1 * `br()` = line break * `strong()` = bold text * Any HTML tag can be accessed using `tags` object * `h1` = `tags$h1()`, `br` = `tags$br()` * Common tags can be accessed without `tags` --- ## Add HTML to `fluidPage()` ```r fluidPage( h1("My Shiny app"), h3("Subtitle"), "Hello", "Data Viz!", br(), strong("bold text") ) ``` --- ## Use a layout * By default, all elements stack up one after the other * [Can use different layouts](http://shiny.rstudio.com/articles/layout-guide.html) * We’ll use `sidebarLayout()` --- ## `sidebarLayout()` .panelset[ .panel[.panel-name[Code] ```r fluidPage( titlePanel("My Shiny app"), sidebarLayout( sidebarPanel( "This is a side panel" ), mainPanel( "And this is the main stuff" ) ) ) ``` ] .panel[.panel-name[Output] <img src="images/shiny-sidebarlayout.png" width="80%" style="display: block; margin: auto;" /> ] ] --- ## Inputs and outputs * For interactivity, app needs inputs and outputs * **Inputs** - things user can toggle/adjust * **Output** - R objects user can see, often depend on inputs --- ## Inputs ```r library(shiny) ui <- fluidPage( sliderInput( inputId = "num", label = "Choose a number", min = 0, max = 100, value = 20) ) server <- function(input, output) {} shinyApp(ui = ui, server = server) ``` --- ## Inputs ```r sliderInput(inputId = "num", label = "Choose a number", min = 0, max = 100, value = 20) ``` ``` ## <div class="form-group shiny-input-container"> ## <label class="control-label" id="num-label" for="num">Choose a number</label> ## <input class="js-range-slider" id="num" data-skin="shiny" data-min="0" data-max="100" data-from="20" data-step="1" data-grid="true" data-grid-num="10" data-grid-snap="false" data-prettify-separator="," data-prettify-enabled="true" data-keyboard="true" data-data-type="number"/> ## </div> ``` --- ## Inputs <img src="images/shiny-inputs.png" width="80%" style="display: block; margin: auto;" /> --- ## Inputs .pull-left[ ```r sliderInput( inputId = "num", label = "Choose a number", min = 0, max = 100, value = 20 ) ``` ] .pull-right[ * Input name * Label to display * Input-specific arguments ] --- ## Outputs Function | Outputs ---------|--------- `plotOutput()` | plot `tableOutput()` | table `uiOutput()` | Shiny UI element `textOutput()` | text * Plots, tables, text - anything that R creates and users see * Initialize as empty placeholder space until object is created --- # Finalizing inputs Going to have a bit of a frivolous example so you can see how it can go/look. ```r library(shiny) library(tidyverse) library(shiny) ui <- fluidPage( sliderInput( inputId = "num", label = "How many observations? Choose a number from 0 to 87", min = 1, max = 87, value = 20), plotOutput("myplot") ) server <- function(input, output) {} shinyApp(ui = ui, server = server) ``` --- # Outputs: cont'd (we're going to reuse the ui from the prior page) ```r server <- function(input, output) { select_data <- reactive({ data("starwars") # a bit random: star wars dataset starwars[0:input$num,] # plot the first (selected) rows }) output$myplot <-renderPlot({ hist(select_data()$birth_year, # make it into a histogram main = "Histogram of Birth year", xlab = "birth year", col = "orchid") }) } shinyApp(ui = ui, server = server) ``` --- # Add a twist: incorporate side panel We're going to demonstrate how annoying it is to change an app mid-stream. Add a side panel in your code. ```r titlePanel(), sidebarLayout( sidebarPanel(), mainPanel(), position = ("right") ) ) ``` --- class: center, middle # Application: Baby Names! --- # Baby Names <iframe src="https://jclip.shinyapps.io/baby-names/?showcase=0" width="80%" height="650px" data-external="1"></iframe> --- ## Strategies for success .pull-left[ <img src="https://media.giphy.com/media/14jQC2AONxNBHq/giphy.gif?cid=ecf05e47e049ai9thudi6hxkf7xwqpdsgqfkzylp1lvbghdv&ep=v1_gifs_search&rid=giphy.gif&ct=g" width="80%" style="display: block; margin: auto;" /> ] -- .pull-right[ * Make a plan * Sketch it out * Start with the 'chunks' of where things will go (ui) * Test plots in ggplot before putting in * Figure out an indenting strategy that works for you * Understand that it's a different land from 'typical' R in that the order of operations is different * PATIENCE ] --- # Recap * Shiny is yet another layer-ish * **How**: User Interface: how it is going to look (overall structure) * Getting info: * Slider * Drop-down menus * Entry box * Check box * Shaping interactions: * Structure: one page vs many * Menu options and placement * Themes and color * **What**: Server: The things that 'make' it -- visualizations --- ## Cautionary tales: * What role is this going to serve? * You lose control (users can play around): think HARD about what to include and how to visualize/convey -- <div class="figure" style="text-align: center"> <img src="https://media.giphy.com/media/jL0c2TrfDUz9S/giphy.gif?cid=790b7611j2e6l95hb8bbvbisuzyisfabrww0mjdo0lg29ly2&ep=v1_gifs_search&rid=giphy.gif&ct=g" alt="Users exploring your Shiny app" width="35%" /> <p class="caption">Users exploring your Shiny app</p> </div> --- # App code: sidepanel ```r library(shiny) ui <- fluidPage( # Application title titlePanel("My App!"), sidebarLayout( # Sidebar with a slider input sidebarPanel( sliderInput( inputId = "num", label = "How many observations? Choose a number from 0 to 87", min = 1, max = 87, value = 20)), mainPanel( plotOutput("myplot")))) server <- function(input, output) { select_data <- reactive({ data("starwars") # a bit random: star wars dataset starwars[0:input$num,] # plot the first (selected) rows }) output$myplot <-renderPlot({ hist(select_data()$birth_year, # make it into a histogram main = "Histogram of Birth year", xlab = "birth year", col = "orchid") }) } shinyApp(ui = ui, server = server) ``` --- # TASKS: * Go to MPG example * Change dataset to dplyr::starwars * Choose THREE factor variables to include * Update title * Change plot color --- # Get inspired! ``` r knitr::include_url("https://posit.co/blog/winners-of-the-2024-shiny-contest/") ``` <iframe src="https://posit.co/blog/winners-of-the-2024-shiny-contest/" width="80%" height="400px" data-external="1"></iframe> <!-- class: inverse, middle --> <!-- ```{r echo = FALSE, out.width = "70%"} --> <!-- include_graphics(path = "https://media.giphy.com/media/SRkvcNk9BIeAX2gCFX/giphy.gif") --> <!-- ``` --> <!-- --- --> <!-- ## Data: Ask a manager --> <!-- Source: Ask a Manager Survey via [TidyTuesday](https://github.com/rfordatascience/tidytuesday/tree/master/data/2021/2021-05-18) --> <!-- > This data does not reflect the general population; it reflects Ask a Manager readers who self-selected to respond, which is a very different group (as you can see just from the demographic breakdown below, which is very white and very female). --> <!-- Some findings [here](https://www.askamanager.org/2021/05/some-findings-from-24000-peoples-salaries.html). --> <!-- --- --> <!-- ## Data: Ask a manager --> <!-- ```{r message = FALSE} --> <!-- manager <- read_csv(here::here("15-interactive-reporting", "data/survey.csv")) --> <!-- manager --> <!-- ``` --> <!-- --- --> <!-- class: middle --> <!-- #livecoding --> <!-- .task[ --> <!-- Go to the `shiny` project and code along in `manager-survey/app.R`. --> <!-- ] -->