├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── NAMESPACE ├── R └── dashtutorial.R ├── README.md ├── dashtutorial.Rproj └── inst ├── dashtutorial.Rproj ├── server ├── demos │ ├── 01_file_reader.R │ ├── 02_file_reader.R │ └── data.csv ├── exercises │ ├── helpers │ │ └── mockdaq.R │ ├── processing.R │ ├── processing.solution.R │ ├── reactive_poll.R │ ├── reactive_poll.solution.R │ └── reactive_poll.solution2.R └── slides │ ├── css │ ├── extras.css │ └── local-fonts.css │ ├── fonts │ ├── droid-serif-v6-latin-regular.eot │ ├── droid-serif-v6-latin-regular.svg │ ├── droid-serif-v6-latin-regular.ttf │ ├── droid-serif-v6-latin-regular.woff │ ├── droid-serif-v6-latin-regular.woff2 │ ├── source-code-pro-v6-latin-200.woff │ ├── source-code-pro-v6-latin-200.woff2 │ ├── source-code-pro-v6-latin-300.woff │ ├── source-code-pro-v6-latin-300.woff2 │ ├── source-code-pro-v6-latin-500.woff │ ├── source-code-pro-v6-latin-500.woff2 │ ├── source-code-pro-v6-latin-600.woff │ ├── source-code-pro-v6-latin-600.woff2 │ ├── source-code-pro-v6-latin-700.woff │ ├── source-code-pro-v6-latin-700.woff2 │ ├── source-code-pro-v6-latin-900.woff │ ├── source-code-pro-v6-latin-900.woff2 │ ├── source-code-pro-v6-latin-regular.woff │ ├── source-code-pro-v6-latin-regular.woff2 │ ├── yanone-kaffeesatz-v7-latin-200.eot │ ├── yanone-kaffeesatz-v7-latin-200.svg │ ├── yanone-kaffeesatz-v7-latin-200.ttf │ ├── yanone-kaffeesatz-v7-latin-200.woff │ ├── yanone-kaffeesatz-v7-latin-200.woff2 │ ├── yanone-kaffeesatz-v7-latin-300.eot │ ├── yanone-kaffeesatz-v7-latin-300.svg │ ├── yanone-kaffeesatz-v7-latin-300.ttf │ ├── yanone-kaffeesatz-v7-latin-300.woff │ ├── yanone-kaffeesatz-v7-latin-300.woff2 │ ├── yanone-kaffeesatz-v7-latin-700.eot │ ├── yanone-kaffeesatz-v7-latin-700.svg │ ├── yanone-kaffeesatz-v7-latin-700.ttf │ ├── yanone-kaffeesatz-v7-latin-700.woff │ ├── yanone-kaffeesatz-v7-latin-700.woff2 │ ├── yanone-kaffeesatz-v7-latin-regular.eot │ ├── yanone-kaffeesatz-v7-latin-regular.svg │ ├── yanone-kaffeesatz-v7-latin-regular.ttf │ ├── yanone-kaffeesatz-v7-latin-regular.woff │ └── yanone-kaffeesatz-v7-latin-regular.woff2 │ ├── libs │ ├── countdown_timer │ │ ├── countdown.css │ │ └── countdown.js │ ├── jquery │ │ └── jquery-1.12.0.min.js │ ├── remark-css │ │ └── example.css │ └── remark-latest.min.js │ ├── server.Rmd │ └── server.html └── ui ├── dashboard-tutorial-ui.pdf ├── flexdashboard ├── 01_vertical.Rmd ├── 02_horizontal.Rmd ├── 03_columns.Rmd ├── 04_rows.Rmd ├── 05_scrolling.Rmd ├── 06_components.Rmd ├── 07_flexdashboard-shiny.Rmd ├── 08_shiny-prerendered.Rmd ├── 09_reconnect.Rmd ├── 10_reconnect-prerendered.Rmd ├── 11_bookmark.Rmd └── 99_sidebar-static.Rmd └── shinydashboard ├── 01_basic └── app.R ├── 02_rows └── app.R ├── 03_cols └── app.R └── 04_sidebar └── app.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: dashtutorial 2 | Type: Package 3 | Title: Exercises, slides for rstudio::conf 2017 Shiny Dashboards tutorial 4 | Version: 1.0.0 5 | Author: RStudio, Inc. 6 | Maintainer: Joe Cheng 7 | Encoding: UTF-8 8 | LazyData: true 9 | Imports: shinydashboard, flexdashboard, dplyr, xml2, httr 10 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | exportPattern("^[[:alpha:]]+") 2 | -------------------------------------------------------------------------------- /R/dashtutorial.R: -------------------------------------------------------------------------------- 1 | summon <- function(dest_path = getwd(), overwrite = FALSE) { 2 | dest <- file.path(dest_path, "dashtutorial") 3 | message("Copying tutorial files to ", dest) 4 | dir.create(dest, recursive = TRUE) 5 | file.copy( 6 | system.file("ui", package = "dashtutorial"), 7 | dest, 8 | recursive = TRUE, 9 | overwrite = overwrite 10 | ) 11 | file.copy( 12 | system.file("server", package = "dashtutorial"), 13 | dest, 14 | recursive = TRUE, 15 | overwrite = overwrite 16 | ) 17 | file.copy( 18 | system.file("dashtutorial.Rproj", package = "dashtutorial"), 19 | dest, 20 | recursive = TRUE, 21 | overwrite = overwrite 22 | ) 23 | invisible() 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Shiny Dashboard Tutorial 2 | 3 | This package makes it easy to install all the dependencies you'll need for the Shiny Dashboard tutorial. It also contains all exercises and slides. 4 | 5 | ```r 6 | devtools::install_github("jcheng5/dashtutorial") 7 | ``` 8 | 9 | Once successfully installed, you can copy the slides and exercises to the current directory with: 10 | 11 | ```r 12 | dashtutorial::summon() 13 | ``` 14 | 15 | This will create a `dashtutorial` subdirectory in the current directory. 16 | -------------------------------------------------------------------------------- /dashtutorial.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /inst/dashtutorial.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /inst/server/demos/01_file_reader.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(ggplot2) 3 | 4 | ui <- fluidPage( 5 | plotOutput("plot"), 6 | tableOutput("table") 7 | ) 8 | 9 | server <- function(input, output, session) { 10 | dataset <- reactive({ 11 | read.csv("data.csv", stringsAsFactors = FALSE) 12 | }) 13 | 14 | output$plot <- renderPlot({ 15 | ggplot(dataset(), aes(Sepal.Width, Sepal.Length)) + 16 | geom_point() 17 | }) 18 | 19 | output$table <- renderTable({ 20 | dataset() 21 | }) 22 | } 23 | 24 | shinyApp(ui, server) -------------------------------------------------------------------------------- /inst/server/demos/02_file_reader.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(ggplot2) 3 | 4 | ui <- fluidPage( 5 | plotOutput("plot"), 6 | tableOutput("table") 7 | ) 8 | 9 | server <- function(input, output, session) { 10 | dataset <- reactiveFileReader(1000, session, "data.csv", 11 | read.csv, stringsAsFactors = FALSE) 12 | 13 | output$plot <- renderPlot({ 14 | ggplot(dataset(), aes(Sepal.Width, Sepal.Length)) + 15 | geom_point() 16 | }) 17 | 18 | output$table <- renderTable({ 19 | dataset() 20 | }) 21 | } 22 | 23 | shinyApp(ui, server) 24 | -------------------------------------------------------------------------------- /inst/server/demos/data.csv: -------------------------------------------------------------------------------- 1 | "Sepal.Length","Sepal.Width","Petal.Length","Petal.Width","Species" 2 | 5.1,3.5,1.4,0.2,"setosa" 3 | 4.9,3,1.4,0.2,"setosa" 4 | 4.7,3.2,1.3,0.2,"setosa" 5 | 4.6,3.1,1.5,0.2,"setosa" 6 | 5,3.6,1.4,0.2,"setosa" 7 | 5.4,3.9,1.7,0.4,"setosa" 8 | 4.6,3.4,1.4,0.3,"setosa" 9 | 5,3.4,1.5,0.2,"setosa" 10 | 4.4,2.9,1.4,0.2,"setosa" 11 | 4.9,3.1,1.5,0.1,"setosa" 12 | 5.4,3.7,1.5,0.2,"setosa" 13 | 4.8,3.4,1.6,0.2,"setosa" 14 | 4.8,3,1.4,0.1,"setosa" 15 | 4.3,3,1.1,0.1,"setosa" 16 | 5.8,4,1.2,0.2,"setosa" 17 | 5.7,4.4,1.5,0.4,"setosa" 18 | 5.4,3.9,1.3,0.4,"setosa" 19 | 5.1,3.5,1.4,0.3,"setosa" 20 | 5.7,3.8,1.7,0.3,"setosa" 21 | 5.1,3.8,1.5,0.3,"setosa" 22 | 5.4,3.4,1.7,0.2,"setosa" 23 | 5.1,3.7,1.5,0.4,"setosa" 24 | 4.6,3.6,1,0.2,"setosa" 25 | 5.1,3.3,1.7,0.5,"setosa" 26 | 4.8,3.4,1.9,0.2,"setosa" 27 | 5,3,1.6,0.2,"setosa" 28 | 5,3.4,1.6,0.4,"setosa" 29 | 5.2,3.5,1.5,0.2,"setosa" 30 | 5.2,3.4,1.4,0.2,"setosa" 31 | 4.7,3.2,1.6,0.2,"setosa" 32 | 4.8,3.1,1.6,0.2,"setosa" 33 | 5.4,3.4,1.5,0.4,"setosa" 34 | 5.2,4.1,1.5,0.1,"setosa" 35 | 5.5,4.2,1.4,0.2,"setosa" 36 | 4.9,3.1,1.5,0.2,"setosa" 37 | 5,3.2,1.2,0.2,"setosa" 38 | 5.5,3.5,1.3,0.2,"setosa" 39 | 4.9,3.6,1.4,0.1,"setosa" 40 | 4.4,3,1.3,0.2,"setosa" 41 | 5.1,3.4,1.5,0.2,"setosa" 42 | 5,3.5,1.3,0.3,"setosa" 43 | 4.5,2.3,1.3,0.3,"setosa" 44 | 4.4,3.2,1.3,0.2,"setosa" 45 | 5,3.5,1.6,0.6,"setosa" 46 | 5.1,3.8,1.9,0.4,"setosa" 47 | 4.8,3,1.4,0.3,"setosa" 48 | 5.1,3.8,1.6,0.2,"setosa" 49 | 4.6,3.2,1.4,0.2,"setosa" 50 | 5.3,3.7,1.5,0.2,"setosa" 51 | 5,3.3,1.4,0.2,"setosa" 52 | 7,3.2,4.7,1.4,"versicolor" 53 | 6.4,3.2,4.5,1.5,"versicolor" 54 | 6.9,3.1,4.9,1.5,"versicolor" 55 | 5.5,2.3,4,1.3,"versicolor" 56 | 6.5,2.8,4.6,1.5,"versicolor" 57 | 5.7,2.8,4.5,1.3,"versicolor" 58 | 6.3,3.3,4.7,1.6,"versicolor" 59 | 4.9,2.4,3.3,1,"versicolor" 60 | 6.6,2.9,4.6,1.3,"versicolor" 61 | 5.2,2.7,3.9,1.4,"versicolor" 62 | 5,2,3.5,1,"versicolor" 63 | 5.9,3,4.2,1.5,"versicolor" 64 | 6,2.2,4,1,"versicolor" 65 | 6.1,2.9,4.7,1.4,"versicolor" 66 | 5.6,2.9,3.6,1.3,"versicolor" 67 | 6.7,3.1,4.4,1.4,"versicolor" 68 | 5.6,3,4.5,1.5,"versicolor" 69 | 5.8,2.7,4.1,1,"versicolor" 70 | 6.2,2.2,4.5,1.5,"versicolor" 71 | 5.6,2.5,3.9,1.1,"versicolor" 72 | 5.9,3.2,4.8,1.8,"versicolor" 73 | 6.1,2.8,4,1.3,"versicolor" 74 | 6.3,2.5,4.9,1.5,"versicolor" 75 | 6.1,2.8,4.7,1.2,"versicolor" 76 | 6.4,2.9,4.3,1.3,"versicolor" 77 | 6.6,3,4.4,1.4,"versicolor" 78 | 6.8,2.8,4.8,1.4,"versicolor" 79 | 6.7,3,5,1.7,"versicolor" 80 | 6,2.9,4.5,1.5,"versicolor" 81 | 5.7,2.6,3.5,1,"versicolor" 82 | 5.5,2.4,3.8,1.1,"versicolor" 83 | 5.5,2.4,3.7,1,"versicolor" 84 | 5.8,2.7,3.9,1.2,"versicolor" 85 | 6,2.7,5.1,1.6,"versicolor" 86 | 5.4,3,4.5,1.5,"versicolor" 87 | 6,3.4,4.5,1.6,"versicolor" 88 | 6.7,3.1,4.7,1.5,"versicolor" 89 | 6.3,2.3,4.4,1.3,"versicolor" 90 | 5.6,3,4.1,1.3,"versicolor" 91 | 5.5,2.5,4,1.3,"versicolor" 92 | 5.5,2.6,4.4,1.2,"versicolor" 93 | 6.1,3,4.6,1.4,"versicolor" 94 | 5.8,2.6,4,1.2,"versicolor" 95 | 5,2.3,3.3,1,"versicolor" 96 | 5.6,2.7,4.2,1.3,"versicolor" 97 | 5.7,3,4.2,1.2,"versicolor" 98 | 5.7,2.9,4.2,1.3,"versicolor" 99 | 6.2,2.9,4.3,1.3,"versicolor" 100 | 5.1,2.5,3,1.1,"versicolor" 101 | 5.7,2.8,4.1,1.3,"versicolor" 102 | 6.3,3.3,6,2.5,"virginica" 103 | 5.8,2.7,5.1,1.9,"virginica" 104 | 7.1,3,5.9,2.1,"virginica" 105 | 6.3,2.9,5.6,1.8,"virginica" 106 | 6.5,3,5.8,2.2,"virginica" 107 | 7.6,3,6.6,2.1,"virginica" 108 | 4.9,2.5,4.5,1.7,"virginica" 109 | 7.3,2.9,6.3,1.8,"virginica" 110 | 6.7,2.5,5.8,1.8,"virginica" 111 | 7.2,3.6,6.1,2.5,"virginica" 112 | 6.5,3.2,5.1,2,"virginica" 113 | 6.4,2.7,5.3,1.9,"virginica" 114 | 6.8,3,5.5,2.1,"virginica" 115 | 5.7,2.5,5,2,"virginica" 116 | 5.8,2.8,5.1,2.4,"virginica" 117 | 6.4,3.2,5.3,2.3,"virginica" 118 | 6.5,3,5.5,1.8,"virginica" 119 | 7.7,3.8,6.7,2.2,"virginica" 120 | 7.7,2.6,6.9,2.3,"virginica" 121 | 6,2.2,5,1.5,"virginica" 122 | 6.9,3.2,5.7,2.3,"virginica" 123 | 5.6,2.8,4.9,2,"virginica" 124 | 7.7,2.8,6.7,2,"virginica" 125 | 6.3,2.7,4.9,1.8,"virginica" 126 | 6.7,3.3,5.7,2.1,"virginica" 127 | 7.2,3.2,6,1.8,"virginica" 128 | 6.2,2.8,4.8,1.8,"virginica" 129 | 6.1,3,4.9,1.8,"virginica" 130 | 6.4,2.8,5.6,2.1,"virginica" 131 | 7.2,3,5.8,1.6,"virginica" 132 | 7.4,2.8,6.1,1.9,"virginica" 133 | 7.9,3.8,6.4,2,"virginica" 134 | 6.4,2.8,5.6,2.2,"virginica" 135 | 6.3,2.8,5.1,1.5,"virginica" 136 | 6.1,2.6,5.6,1.4,"virginica" 137 | 7.7,3,6.1,2.3,"virginica" 138 | 6.3,3.4,5.6,2.4,"virginica" 139 | 6.4,3.1,5.5,1.8,"virginica" 140 | 6,3,4.8,1.8,"virginica" 141 | 6.9,3.1,5.4,2.1,"virginica" 142 | 6.7,3.1,5.6,2.4,"virginica" 143 | 6.9,3.1,5.1,2.3,"virginica" 144 | 5.8,2.7,5.1,1.9,"virginica" 145 | 6.8,3.2,5.9,2.3,"virginica" 146 | 6.7,3.3,5.7,2.5,"virginica" 147 | 6.7,3,5.2,2.3,"virginica" 148 | 6.3,2.5,5,1.9,"virginica" 149 | 6.5,3,5.2,2,"virginica" 150 | 6.2,3.4,5.4,2.3,"virginica" 151 | 5.9,3,5.1,1.8,"virginica" 152 | -------------------------------------------------------------------------------- /inst/server/exercises/helpers/mockdaq.R: -------------------------------------------------------------------------------- 1 | # A simulated stock price data source. 2 | # 3 | # md <- mockdaqConnect() 4 | # 5 | # > md$last_modified() 6 | # [1] "2017-01-06 18:42:30 PST" 7 | # 8 | # > md$fetch_prices() 9 | # symbol prev price 10 | # 1 BAC 22.68 22.94 11 | # 2 JCP 7.57 7.58 12 | # 3 NYRT 9.77 9.72 13 | # 4 F 12.76 12.83 14 | # 5 CHK 7.01 7.01 15 | # 6 T 41.32 41.34 16 | mockdaqConnect <- function() { 17 | # Starting values 18 | stocks <- read.csv(text = 'symbol,prev 19 | "BAC",22.68 20 | "JCP",7.57 21 | "NYRT",9.77 22 | "F",12.76 23 | "CHK",7.01 24 | "T",41.32', stringsAsFactors=FALSE) 25 | 26 | # Config parameters 27 | interval <- 5 # How many seconds to wait between updates 28 | drift_sd <- 0.005 # Std dev of how much to drift prices by 29 | 30 | # Mutable state 31 | last_modified <- Sys.time() - interval 32 | factors <- rep.int(1, nrow(stocks)) 33 | 34 | # Simulation loop 35 | run <- function() { 36 | while (last_modified + interval <= Sys.time()) { 37 | factors <<- factors + rnorm(nrow(stocks), sd = drift_sd) 38 | last_modified <<- last_modified + interval 39 | } 40 | } 41 | 42 | list( 43 | last_modified = function() { 44 | run() # Run simulation if necessary 45 | Sys.sleep(0.1) # Simulate latency 46 | last_modified 47 | }, 48 | fetch_prices = function() { 49 | run() # Run simulation if necessary 50 | Sys.sleep(0.7) # Simulate latency 51 | 52 | result <- cbind( 53 | stocks, 54 | price = round(pmax(0, stocks$prev * factors), digits = 2) 55 | ) 56 | result 57 | }, 58 | close = function() { 59 | } 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /inst/server/exercises/processing.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(dplyr) 3 | 4 | # This app shows an auto-updating table of fake stock data. 5 | # The table contains the following columns: 6 | # 7 | # - symbol (the ticker symbol) 8 | # - prev (the share price at the previous day's closing) 9 | # - price (the share price right now) 10 | # 11 | # It also shows the symbol and price of the stocks with the 12 | # highest and lowest share prices. 13 | # 14 | # ASSIGNMENT: 15 | # 16 | # Add two columns to the table output: 17 | # 18 | # - change (price change since previous day's closing, in $) 19 | # - change_pct (price change since previous day's closing, in %) 20 | # 21 | # Also, showing the stocks with highest and lowest share price 22 | # is pretty useless. Change those to the biggest gainer and loser, 23 | # by change_pct. 24 | 25 | source("helpers/mockdaq.R", local = TRUE) 26 | 27 | ui <- fluidPage( 28 | 29 | tableOutput("all"), 30 | 31 | div( 32 | strong("Highest price:"), 33 | textOutput("max_price", inline = TRUE) 34 | ), 35 | 36 | div( 37 | strong("Lowest price:"), 38 | textOutput("min_price", inline = TRUE) 39 | ) 40 | ) 41 | 42 | server <- function(input, output, session) { 43 | conn <- mockdaqConnect() 44 | session$onSessionEnded(function() { 45 | conn$close() 46 | }) 47 | 48 | raw_data <- reactivePoll(1000, session, 49 | conn$last_modified, 50 | conn$fetch_prices 51 | ) 52 | 53 | output$all <- renderTable({ 54 | raw_data() 55 | }) 56 | 57 | output$max_price <- renderText({ 58 | max_price <- raw_data() %>% top_n(1, price) 59 | paste(max_price$symbol, formatC(max_price$price)) 60 | }) 61 | 62 | output$min_price <- renderText({ 63 | min_price <- raw_data() %>% top_n(1, -price) 64 | paste(min_price$symbol, formatC(min_price$price)) 65 | }) 66 | 67 | } 68 | 69 | shinyApp(ui, server) -------------------------------------------------------------------------------- /inst/server/exercises/processing.solution.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(dplyr) 3 | 4 | source("helpers/mockdaq.R") 5 | 6 | ui <- fluidPage( 7 | tableOutput("all"), 8 | 9 | div( 10 | strong("Biggest gainer:"), 11 | textOutput("max_gainer", inline = TRUE) 12 | ), 13 | 14 | div( 15 | strong("Biggest loser:"), 16 | textOutput("max_loser", inline = TRUE) 17 | ) 18 | ) 19 | 20 | server <- function(input, output, session) { 21 | conn <- mockdaqConnect() 22 | session$onSessionEnded(function() { 23 | conn$close() 24 | }) 25 | 26 | raw_data <- reactivePoll(1000, session, 27 | conn$last_modified, 28 | conn$fetch_prices 29 | ) 30 | 31 | # Introduce a reactive expression to add the columns. 32 | # This is especially valuable because we're going to 33 | # use these columns from three different outputs. 34 | full_data <- reactive({ 35 | df <- raw_data() %>% mutate( 36 | change = price - prev, 37 | change_pct = (price - prev) / prev * 100 38 | ) 39 | }) 40 | 41 | output$all <- renderTable({ 42 | # Now using full_data() instead of raw_data() 43 | full_data() 44 | }) 45 | 46 | output$max_gainer <- renderText({ 47 | # Now using full_data() and change_pct 48 | max_gainer <- full_data() %>% top_n(1, change_pct) 49 | paste(max_gainer$symbol, formatC(max_gainer$change_pct)) 50 | }) 51 | 52 | output$max_loser <- renderText({ 53 | # Now using full_data() and change_pct 54 | max_loser <- full_data() %>% top_n(-1, change_pct) 55 | paste(max_loser$symbol, formatC(max_loser$change_pct)) 56 | }) 57 | 58 | } 59 | 60 | shinyApp(ui, server) -------------------------------------------------------------------------------- /inst/server/exercises/reactive_poll.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(httr) 3 | library(xml2) 4 | library(magrittr) 5 | 6 | feedUrl <- "https://jchengdemo.wordpress.com/feed/" 7 | # Or use http://lorem-rss.herokuapp.com/feed?unit=second&interval=5 for 8 | # more frequently updating (but less realistic) data 9 | 10 | ui <- fluidPage( 11 | tableOutput("entries") 12 | ) 13 | 14 | server <- function(input, output, session) { 15 | # Hint: httr::HEAD(feedUrl)$headers[c("last-modified", "etag")] 16 | 17 | # Retrieve the feed data and turn it into a data frame 18 | feed_xml <- read_xml(feedUrl) 19 | title <- xml_find_all(feed_xml, "/rss/channel/item/title") %>% xml_text() 20 | creator <- xml_find_all(feed_xml, "/rss/channel/item/dc:creator") %>% xml_text() 21 | date <- xml_find_all(feed_xml, "/rss/channel/item/pubDate") %>% xml_text() 22 | link <- xml_find_all(feed_xml, "/rss/channel/item/link") %>% xml_text() 23 | feed_data <- data.frame(stringsAsFactors = FALSE, 24 | title = title, creator = creator, date = date, link = link 25 | ) 26 | 27 | output$entries <- renderTable({ 28 | feed_data 29 | }) 30 | } 31 | 32 | shinyApp(ui, server) -------------------------------------------------------------------------------- /inst/server/exercises/reactive_poll.solution.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(httr) 3 | library(xml2) 4 | library(magrittr) 5 | 6 | feedUrl <- "https://jchengdemo.wordpress.com/feed/" 7 | # Or use http://lorem-rss.herokuapp.com/feed?unit=second&interval=5 for 8 | # more frequently updating (but less realistic) data 9 | 10 | ui <- fluidPage( 11 | tableOutput("entries") 12 | ) 13 | 14 | server <- function(input, output, session) { 15 | feed_data <- reactivePoll(5000, session, 16 | function() { 17 | httr::HEAD(feedUrl)$headers[c("last-modified", "etag")] 18 | }, 19 | function() { 20 | feed_xml <- read_xml(feedUrl) 21 | title <- xml_find_all(feed_xml, "/rss/channel/item/title") %>% xml_text() 22 | date <- xml_find_all(feed_xml, "/rss/channel/item/pubDate") %>% xml_text() 23 | link <- xml_find_all(feed_xml, "/rss/channel/item/link") %>% xml_text() 24 | data.frame(stringsAsFactors = FALSE, 25 | title = title, date = date, link = link 26 | ) 27 | } 28 | ) 29 | 30 | output$entries <- renderTable({ 31 | feed_data() 32 | }) 33 | } 34 | 35 | shinyApp(ui, server) -------------------------------------------------------------------------------- /inst/server/exercises/reactive_poll.solution2.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(httr) 3 | library(xml2) 4 | library(magrittr) 5 | 6 | feedUrl <- "https://jchengdemo.wordpress.com/feed" 7 | 8 | # Putting the data here means all sessions will share the same reactivePoll 9 | feed_data <- reactivePoll(5000, NULL, 10 | function() { 11 | httr::HEAD(feedUrl)$headers[c("last-modified", "etag")] 12 | }, 13 | function() { 14 | feed_xml <- read_xml(feedUrl) 15 | title <- xml_find_all(feed_xml, "/rss/channel/item/title") %>% xml_text() 16 | date <- xml_find_all(feed_xml, "/rss/channel/item/pubDate") %>% xml_text() 17 | link <- xml_find_all(feed_xml, "/rss/channel/item/link") %>% xml_text() 18 | data.frame(stringsAsFactors = FALSE, 19 | title = title, date = date, link = link 20 | ) 21 | } 22 | ) 23 | 24 | 25 | ui <- fluidPage( 26 | tableOutput("entries") 27 | ) 28 | 29 | server <- function(input, output, session) { 30 | output$entries <- renderTable({ 31 | feed_data() 32 | }) 33 | } 34 | 35 | shinyApp(ui, server) -------------------------------------------------------------------------------- /inst/server/slides/css/extras.css: -------------------------------------------------------------------------------- 1 | .smaller h3 { 2 | font-size: 28px; 3 | margin-bottom: 0em; 4 | } 5 | 6 | .smaller p { 7 | margin-top: 0.5em; 8 | } 9 | 10 | table.tabular { 11 | border: 1px solid #DDDDDD; 12 | border-collapse: collapse; 13 | } 14 | 15 | table.tabular td, table.tabular th { 16 | border: 1px solid #DDDDDD; 17 | padding: 8px; 18 | text-align: left; 19 | } 20 | 21 | textarea#source { 22 | display: none; 23 | } 24 | 25 | footer { 26 | position: fixed; 27 | bottom: 12px; 28 | left: 20px; 29 | opacity: 0.5; 30 | } 31 | footer a, footer a:active { 32 | color: black; 33 | text-decoration: underline; 34 | } 35 | 36 | .countdown-timer .countdown-timer-time { 37 | font-family: Source Sans Pro; 38 | font-size: 50pt; 39 | } 40 | .countdown-timer button { 41 | border: 0.25px solid #CCC; 42 | background-color: white; 43 | outline: none; 44 | } 45 | .countdown-timer button:active { 46 | border: 1px solid #CCC; 47 | background-color: silver; 48 | color: white; 49 | } 50 | -------------------------------------------------------------------------------- /inst/server/slides/css/local-fonts.css: -------------------------------------------------------------------------------- 1 | /* yanone-kaffeesatz-regular - latin */ 2 | @font-face { 3 | font-family: 'Yanone Kaffeesatz'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Yanone Kaffeesatz Regular'), local('YanoneKaffeesatz-Regular'), 7 | url('../fonts/yanone-kaffeesatz-v7-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 8 | url('../fonts/yanone-kaffeesatz-v7-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 9 | } 10 | 11 | 12 | /* droid-serif-italic - latin */ 13 | @font-face { 14 | font-family: 'Droid Serif'; 15 | font-style: italic; 16 | font-weight: 400; 17 | src: local('Droid Serif Italic'), local('DroidSerif-Italic'), 18 | url('../fonts/droid-serif-v6-latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 19 | url('../fonts/droid-serif-v6-latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 20 | } 21 | /* droid-serif-700 - latin */ 22 | @font-face { 23 | font-family: 'Droid Serif'; 24 | font-style: normal; 25 | font-weight: 700; 26 | src: local('Droid Serif Bold'), local('DroidSerif-Bold'), 27 | url('../fonts/droid-serif-v6-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 28 | url('../fonts/droid-serif-v6-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 29 | } 30 | /* droid-serif-700italic - latin */ 31 | @font-face { 32 | font-family: 'Droid Serif'; 33 | font-style: italic; 34 | font-weight: 700; 35 | src: local('Droid Serif Bold Italic'), local('DroidSerif-BoldItalic'), 36 | url('../fonts/droid-serif-v6-latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 37 | url('../fonts/droid-serif-v6-latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 38 | } 39 | /* droid-serif-regular - latin */ 40 | @font-face { 41 | font-family: 'Droid Serif'; 42 | font-style: normal; 43 | font-weight: 400; 44 | src: local('Droid Serif'), local('DroidSerif'), 45 | url('../fonts/droid-serif-v6-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 46 | url('../fonts/droid-serif-v6-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 47 | } 48 | 49 | 50 | /* source-code-pro-regular - latin */ 51 | @font-face { 52 | font-family: 'Source Code Pro'; 53 | font-style: normal; 54 | font-weight: 400; 55 | src: local('Source Code Pro'), local('SourceCodePro-Regular'), 56 | url('../fonts/source-code-pro-v6-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 57 | url('../fonts/source-code-pro-v6-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 58 | } 59 | /* source-code-pro-700 - latin */ 60 | @font-face { 61 | font-family: 'Source Code Pro'; 62 | font-style: normal; 63 | font-weight: 700; 64 | src: local('Source Code Pro Bold'), local('SourceCodePro-Bold'), 65 | url('../fonts/source-code-pro-v6-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 66 | url('../fonts/source-code-pro-v6-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 67 | } 68 | -------------------------------------------------------------------------------- /inst/server/slides/fonts/droid-serif-v6-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/droid-serif-v6-latin-regular.eot -------------------------------------------------------------------------------- /inst/server/slides/fonts/droid-serif-v6-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/droid-serif-v6-latin-regular.ttf -------------------------------------------------------------------------------- /inst/server/slides/fonts/droid-serif-v6-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/droid-serif-v6-latin-regular.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/droid-serif-v6-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/droid-serif-v6-latin-regular.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-200.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-200.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-200.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-200.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-300.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-300.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-500.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-500.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-600.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-600.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-600.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-700.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-700.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-900.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-900.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-regular.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/source-code-pro-v6-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/source-code-pro-v6-latin-regular.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.eot -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 16 | 18 | 21 | 25 | 29 | 30 | 32 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 45 | 47 | 50 | 52 | 54 | 56 | 57 | 60 | 62 | 63 | 65 | 66 | 67 | 68 | 71 | 76 | 78 | 80 | 82 | 84 | 85 | 86 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 99 | 101 | 103 | 106 | 108 | 110 | 111 | 112 | 114 | 117 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 130 | 132 | 134 | 136 | 139 | 141 | 145 | 147 | 148 | 150 | 153 | 154 | 156 | 158 | 160 | 162 | 164 | 165 | 167 | 169 | 170 | 172 | 175 | 177 | 179 | 181 | 184 | 185 | 188 | 190 | 191 | 192 | 194 | 196 | 198 | 200 | 204 | 205 | 208 | 211 | 214 | 215 | 218 | 219 | 221 | 223 | 225 | 228 | 229 | 231 | 233 | 234 | 236 | 238 | 240 | 243 | 246 | 249 | 253 | 256 | 259 | 261 | 264 | 267 | 270 | 273 | 275 | 279 | 281 | 283 | 285 | 287 | 289 | 291 | 293 | 295 | 297 | 300 | 303 | 306 | 309 | 312 | 315 | 316 | 319 | 321 | 323 | 325 | 327 | 329 | 331 | 334 | 337 | 340 | 344 | 348 | 351 | 354 | 358 | 361 | 364 | 367 | 371 | 374 | 376 | 378 | 380 | 382 | 385 | 388 | 391 | 394 | 397 | 400 | 403 | 405 | 408 | 410 | 412 | 414 | 416 | 419 | 421 | 424 | 425 | 426 | 427 | 428 | 429 | 431 | 433 | 435 | 436 | 438 | 440 | 441 | 442 | 443 | -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.ttf -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-200.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-300.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-300.eot -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-300.ttf -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-300.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-300.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-700.eot -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-700.ttf -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-700.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-700.woff2 -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-regular.eot -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-regular.ttf -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-regular.woff -------------------------------------------------------------------------------- /inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/server/slides/fonts/yanone-kaffeesatz-v7-latin-regular.woff2 -------------------------------------------------------------------------------- /inst/server/slides/libs/countdown_timer/countdown.css: -------------------------------------------------------------------------------- 1 | .countdown-timer .countdown-timer-time { 2 | font-family: monospace; 3 | font-size: 36px; 4 | } 5 | .countdown-timer.active .countdown-timer-time { 6 | color: green; 7 | } 8 | -------------------------------------------------------------------------------- /inst/server/slides/libs/countdown_timer/countdown.js: -------------------------------------------------------------------------------- 1 | function formatTime(millis) { 2 | var seconds = Math.round(millis / 1000); 3 | 4 | var minutes = Math.floor(seconds / 60); 5 | seconds = seconds - (minutes * 60); 6 | 7 | var hours = Math.floor(minutes / 60); 8 | minutes = minutes - (hours * 60); 9 | 10 | var days = Math.floor(hours / 24); 11 | hours = hours - (days * 24); 12 | 13 | var str = ""; 14 | 15 | if (days > 0) { 16 | str = str + days + ":"; 17 | } 18 | if (hours > 0) { 19 | if (hours < 10 && str !== "") 20 | str = str + "0"; 21 | str = str + hours + ":"; 22 | } 23 | if (minutes < 10 && str !== "") { 24 | str = str + "0"; 25 | } 26 | str = str + minutes + ":"; 27 | if (seconds < 10) { 28 | str = str + "0"; 29 | } 30 | str = str + seconds; 31 | return str; 32 | } 33 | 34 | $(document).on("click", ".countdown-timer-start", function(e) { 35 | var timerEl = $(e.target).parent(".countdown-timer"); 36 | var remaining = timerEl.data("countdown-timer-time"); 37 | if (typeof(remaining) == "undefined" || remaining === null) { 38 | remaining = +timerEl.data("timespan") * 1000; 39 | } 40 | var stop = new Date().getTime() + remaining; 41 | 42 | var handle = setInterval(function() { 43 | var left = Math.max(0, stop - new Date().getTime()); 44 | timerEl.data("countdown-timer-time", left); 45 | timerEl.trigger("countdown:time", {left: left / 1000}); 46 | timerEl.find(".countdown-timer-time").text(formatTime(left)); 47 | if (left === 0) { 48 | clearInterval(handle); 49 | timerEl.removeClass("active"); 50 | } 51 | }, 1000); 52 | timerEl.data("countdown-timer-handle", handle); 53 | timerEl.addClass("active"); 54 | }); 55 | $(document).on("click", ".countdown-timer-stop", function(e) { 56 | var timerEl = $(e.target).parent(".countdown-timer"); 57 | clearInterval(timerEl.data("countdown-timer-handle")); 58 | timerEl.removeClass("active"); 59 | }); 60 | $(document).on("click", ".countdown-timer-reset", function(e) { 61 | var timerEl = $(e.target).parent(".countdown-timer"); 62 | timerEl.find(".countdown-timer-stop").click(); 63 | var left = +timerEl.data("timespan") * 1000; 64 | timerEl.data("countdown-timer-time", left); 65 | timerEl.find(".countdown-timer-time").text(formatTime(left)); 66 | }); 67 | -------------------------------------------------------------------------------- /inst/server/slides/libs/remark-css/example.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); 2 | @import url(https://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic); 3 | @import url(https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700); 4 | 5 | body { font-family: 'Droid Serif', 'Palatino Linotype', 'Book Antiqua', Palatino, 'Microsoft YaHei', 'Songti SC', serif; } 6 | h1, h2, h3 { 7 | font-family: 'Yanone Kaffeesatz'; 8 | font-weight: normal; 9 | } 10 | a, a > code { 11 | color: rgb(249, 38, 114); 12 | text-decoration: none; 13 | } 14 | .footnote { 15 | position: absolute; 16 | bottom: 3em; 17 | padding-right: 4em; 18 | font-size: 90%; 19 | } 20 | .remark-code, .remark-inline-code { font-family: 'Source Code Pro', 'Lucida Console', Monaco, monospace; } 21 | .remark-code-line-highlighted { background-color: #ffff88; } 22 | 23 | .inverse { 24 | background-color: #272822; 25 | color: #777872; 26 | text-shadow: 0 0 20px #333; 27 | } 28 | .inverse h1, .inverse h2, .inverse h3 { 29 | color: #f3f3f3; 30 | line-height: 0.8em; 31 | } 32 | /* Two-column layout */ 33 | .left-column { 34 | color: #777; 35 | width: 20%; 36 | height: 92%; 37 | float: left; 38 | } 39 | .left-column h2:last-of-type, .left-column h3:last-child { 40 | color: #000; 41 | } 42 | .right-column { 43 | width: 75%; 44 | float: right; 45 | padding-top: 1em; 46 | } 47 | .pull-left { 48 | float: left; 49 | width: 47%; 50 | } 51 | .pull-right { 52 | float: right; 53 | width: 47%; 54 | } 55 | .pull-right ~ * { 56 | clear: both; 57 | } 58 | img { 59 | max-width: 100%; 60 | } 61 | -------------------------------------------------------------------------------- /inst/server/slides/server.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Tutorial: Dashboards in Shiny" 3 | subtitle: "rstudio::conf 2017" 4 | author: "Winston Chang and Joe Cheng" 5 | date: "January 13, 2017" 6 | always_allow_html: yes 7 | output: 8 | xaringan::moon_reader: 9 | lib_dir: libs 10 | nature: 11 | highlightStyle: github 12 | highlightLines: true 13 | mathjax: null 14 | chakra: "libs/remark-latest.min.js" 15 | css: 16 | - "default" 17 | - "css/local-fonts.css" 18 | - "css/extras.css" 19 | --- 20 | 21 | ```{r setup, include=FALSE} 22 | options(htmltools.dir.version = FALSE) 23 | ``` 24 | 25 | ## Before we begin 26 | 27 | Make sure you get an RStudio Server Pro account and log in! 28 | 29 | http://rstudio2017.joecheng.com/signup 30 | 31 | _(Supported browsers: Chrome, Firefox, Safari, IE10+. Sorry, no MS Edge.)_ 32 | 33 | --- 34 | layout: true 35 | 36 | 37 | 38 | --- 39 | 40 | ## Say hello 41 | 42 | #### Please take a moment to introduce yourself to your neighbors. 43 | 44 | - Name and affiliation 45 | - How much experience with R 46 | - How much experience with Shiny 47 | - How much experience building dashboards 48 | 49 | Over the course of the next two hours, we will be doing a bunch of coding exercises. Please help each other! 50 | 51 | ```{r echo=FALSE} 52 | countdown::countdown_timer(180) 53 | ``` 54 | 55 | --- 56 | 57 | ## What sets dashboards apart from other apps? 58 | 59 | - Automatic updating 60 | - Potentially many viewers looking at the same data 61 | - May or may not be interactive 62 | - "Ten-foot" user interface 63 | 64 | --- 65 | 66 | ## Today we're going to cover 67 | 68 | - Server: Reactivity concepts that are general, but particularly helpful for dashboarding (Joe Cheng) 69 | - UI: Styling your app to look like a traditional dashboard (Winston Chang) 70 | 71 | --- 72 | class: middle 73 | 74 | # Server 75 | 76 | --- 77 | 78 | ## Problem statement 79 | 80 | - New data comes in, either continuously or on a schedule (whether every 15s or every 24h) 81 | - All active sessions should automatically receive the new data 82 | -- 83 | 84 | - May need to be transformed, aggregated, summarized, etc. 85 | -- 86 | 87 | - Visualize data as 88 | - plots 89 | - tables 90 | - scalar values (e.g. "Total unique visitors today: 45,124") 91 | -- 92 | 93 | - Call attention to exceptional conditions (e.g. "Maximum capacity exceeded", "Monthly sales goal reached") 94 | 95 | --- 96 | 97 | ## Techniques we'll cover 98 | 99 | - Using reactiveFileReader/reactivePoll functions to automatically pick up new data 100 | - Creating networks of reactive expressions 101 | - Optimizing performance 102 | 103 | --- 104 | class: middle 105 | 106 | ## reactiveFileReader and reactivePoll 107 | 108 | --- 109 | 110 | ## User-driven events vs. data-driven events 111 | 112 | In most Shiny apps, reactivity is driven by **user gestures** (mouse clicks, keystrokes). 113 | 114 | In dashboards, we want to drive reactivity using **changing data sources** (new database records, file updates). 115 | 116 | -- 117 | 118 | Shiny comes with two functions designed to make it easy to treat changing data as reactive sources: 119 | 120 | - `reactiveFileReader` 121 | - `reactivePoll` 122 | 123 | --- 124 | 125 | ## But wait... 126 | 127 | Can't we just do this? 128 | 129 | ```{r eval=FALSE} 130 | dataset <- reactive({ 131 | result <- read.csv("data.csv") 132 | {{ invalidateLater(5000)}} 133 | result 134 | }) 135 | 136 | output$plot <- renderPlot({ 137 | plot(dataset()) # or whatever 138 | }) 139 | ``` 140 | 141 | -- 142 | 143 | #### This works, but is incredibly wasteful! 144 | 145 | Every 5 seconds we will read all the data, and replot it. If `data.csv` changes every 15 minutes on average, then 99.4% of that work will be wasted effort. 146 | 147 | Ideally, we only want `dataset` to invalidate when the file actually changes. 148 | 149 | --- 150 | 151 | ## reactiveFileReader 152 | 153 | - Reads the given file (`"data.csv"`) using the given function (`read.csv`) 154 | - Periodically (every 1000ms?) reads the last-modified time of the file 155 | - If the timestamp changes, then (and only then) re-reads the file 156 | 157 | `reactiveFileReader` wraps all of this logic up for you in an easy-to-use reactive expression object. 158 | 159 | --- 160 | class: middle 161 | 162 | ## Demo: tutorial_demos/01_file_reader.R 163 | 164 | --- 165 | 166 | ## reactiveFileReader 167 | 168 | Creates a reactive expression that always has the "up to date" version of your file data. 169 | 170 | ```{r eval=FALSE} 171 | dataset <- reactiveFileReader( 172 | intervalMillis = 1000, 173 | session = session, 174 | filePath = "data.csv", 175 | readFunc = read.csv 176 | ) 177 | 178 | output$plot <- renderPlot({ 179 | plot(dataset()) # or whatever 180 | }) 181 | ``` 182 | 183 | --- 184 | 185 | ## reactiveFileReader 186 | 187 | Creates a reactive expression that always has the "up to date" version of your file data. 188 | 189 | ```{r eval=FALSE} 190 | dataset <- reactiveFileReader( 191 | {{ intervalMillis = 1000,}} 192 | session = session, 193 | filePath = "data.csv", 194 | readFunc = read.csv 195 | ) 196 | ``` 197 | 198 | _How often to check if the file has changed (based on last-modified time)._ 199 | 200 | --- 201 | 202 | ## reactiveFileReader 203 | 204 | Creates a reactive expression that always has the "up to date" version of your file data. 205 | 206 | ```{r eval=FALSE} 207 | dataset <- reactiveFileReader( 208 | intervalMillis = 1000, 209 | {{ session = session,}} 210 | filePath = "data.csv", 211 | readFunc = read.csv 212 | ) 213 | ``` 214 | 215 | _The user session, or `NULL`._ 216 | 217 | _If non-null, then the reactive file reader will automatically stop when its session ends._ 218 | 219 | --- 220 | 221 | ## reactiveFileReader 222 | 223 | Creates a reactive expression that always has the "up to date" version of your file data. 224 | 225 | ```{r eval=FALSE} 226 | dataset <- reactiveFileReader( 227 | intervalMillis = 1000, 228 | session = session, 229 | {{ filePath = "data.csv",}} 230 | readFunc = read.csv 231 | ) 232 | ``` 233 | 234 | _The path to the data file. Must be a single file, not a directory name or glob pattern._ 235 | 236 | --- 237 | 238 | ## reactiveFileReader 239 | 240 | Creates a reactive expression that always has the "up to date" version of your file data. 241 | 242 | ```{r eval=FALSE} 243 | dataset <- reactiveFileReader( 244 | intervalMillis = 1000, 245 | session = session, 246 | filePath = "data.csv", 247 | {{ readFunc = read.csv}} 248 | ) 249 | ``` 250 | 251 | _The function that should be called (with `filePath` as an argument) whenever the file changes. The reactive expression then assumes the function's return value._ 252 | 253 | _We're using a function from `base` here, but you can use any function, including [anonymous functions](http://adv-r.had.co.nz/Functional-programming.html#anonymous-functions)._ 254 | 255 | --- 256 | 257 | ## reactiveFileReader 258 | 259 | Creates a reactive expression that always has the "up to date" version of your file data. 260 | 261 | ```{r eval=FALSE} 262 | dataset <- reactiveFileReader( 263 | intervalMillis = 1000, 264 | session = session, 265 | filePath = "data.csv", 266 | readFunc = read.csv, 267 | {{ stringsAsFactors = FALSE}} 268 | ) 269 | ``` 270 | 271 | _You can also add any other named arguments, and they'll be passed to `readFunc` whenever it's invoked._ 272 | 273 | --- 274 | 275 | ## reactivePoll 276 | 277 | **reactiveFileReader is limited to files on disk**. It doesn't work for non-file-based data sources like databases or APIs. 278 | 279 | -- 280 | 281 | **reactivePoll is a generalization of reactiveFileReader.** You give it two functions. 282 | 283 | --- 284 | 285 | ## reactivePoll 286 | 287 | Creates a reactive expression that always has the "up to date" version of your ~~file~~ data. 288 | 289 | ```{r eval=FALSE} 290 | dataset <- reactivePoll( 291 | intervalMillis = 1000, 292 | session = session, 293 | {{ checkFunc = ...,}} 294 | valueFunc = ... 295 | ) 296 | ``` 297 | 298 | _A function that can execute quickly, and merely determine if anything has changed._ 299 | 300 | --- 301 | 302 | ## reactivePoll 303 | 304 | Creates a reactive expression that always has the "up to date" version of your ~~file~~ data. 305 | 306 | ```{r eval=FALSE} 307 | dataset <- reactivePoll( 308 | intervalMillis = 1000, 309 | session = session, 310 | checkFunc = ..., 311 | {{ valueFunc = ...}} 312 | ) 313 | ``` 314 | 315 | _A function with the (potentially expensive) logic for actually reading the data._ 316 | 317 | --- 318 | 319 | ## Example: Database 320 | 321 | The "check" function might return the number of rows in a table, or the max value of a timestamp column. 322 | 323 | The "value" function actually returns the contents of the table (or some aggregation thereof). 324 | 325 | ```{r eval=FALSE} 326 | dataset <- reactivePoll(15000, session, 327 | checkFunc = function() { 328 | # Return the latest timestamp 329 | DBI::dbGetQuery(dbConn, "SELECT MAX(updated) FROM log;") 330 | }, 331 | valueFunc = function() { 332 | # Return the 20 most recently updated rows 333 | DBI::dbGetQuery(dbConn, 334 | "SELECT * FROM log ORDER BY updated DESC LIMIT 20;" 335 | ) 336 | } 337 | ) 338 | ``` 339 | 340 | --- 341 | 342 | ## Example: Let's reimplement reactiveFileReader 343 | 344 | ```{r eval=FALSE} 345 | myReader <- function(millis, session, filePath, readFunc, ...) { 346 | reactivePoll(millis, session, 347 | checkFunc = function() { 348 | file.info(filePath)$mtime 349 | }, 350 | valueFunc = function() { 351 | readFunc(filePath, ...) 352 | } 353 | ) 354 | } 355 | ``` 356 | 357 | --- 358 | 359 | ## checkFunc details 360 | 361 | **It is important that the `checkFunc` function actually be fast!** It will block the R process while it runs! 362 | 363 | (The slower it is, the greater you should make the polling interval.) 364 | 365 | -- 366 |

367 | 368 | **The `checkFunc` function should not return `TRUE` or `FALSE` for changed/unchanged.** 369 | 370 | Instead, just **return a value** (like the timestamp, or the count); it's reactivePoll's job, not yours, to keep track of whether that value is the same as the previous value or not. 371 | 372 | --- 373 | 374 | ## Example: Web API 375 | 376 | The `checkFunc` could do a partial HTTP request (called a `HEAD` request) that doesn't download actual data, but does tell us whether the data has changed since we last looked (using `Last-Modified` and/or `ETag` HTTP headers): 377 | 378 | ```{r eval=FALSE} 379 | checkFunc = function() { 380 | httr::HEAD(apiUrl)$headers[c("last-modified", "etag")] 381 | } 382 | ``` 383 | 384 | The `valueFunc` function does a full HTTP GET request: 385 | 386 | ```{r eval=FALSE} 387 | valueFunc <- function() { 388 | httr::GET(apiUrl) 389 | } 390 | ``` 391 | 392 | --- 393 | 394 | ## Your turn 395 | 396 | Open `tutorial_exercises/reactive_poll.R`. 397 | 398 | This app downloads an RSS feed and summarizes the results in a table. To refresh the results, you have to reload the page. (Ugh, so 2003!) 399 | 400 | Modify this app using `reactivePoll` so that the results are always fresh. 401 | 402 | ```{r echo=FALSE} 403 | countdown::countdown_timer(7 * 60) 404 | ``` 405 | 406 | --- 407 | 408 | ## Solution 409 | 410 | Open `tutorial_exercises/reactive_poll.solution.R`. 411 | 412 | --- 413 | class: middle 414 | 415 | ## Processing data 416 | 417 | --- 418 | 419 | ## Processing data 420 | 421 | Besides retrieving data, we might want to process it in other ways. 422 | 423 | - Filter 424 | - Summarize 425 | - Detect trends 426 | - Model fitting 427 | 428 | How can we perform these calculations against auto-updating data? 429 | 430 | --- 431 | 432 | ## Your turn 433 | 434 | Open `tutorial_exercises/processing.R`. 435 | 436 | Run this app; it shows an auto-updating table of fake stock data. It also shows the symbol and price of the stocks with the highest and lowest share prices. 437 | 438 | #### Assignment 439 | 440 | Add two columns to the table output: 441 | 442 | - `change` (price change since previous day's closing, in $) 443 | - `change_pct` (price change since previous day's closing, in %) 444 | 445 | Also, showing the stocks with highest and lowest share price 446 | is pretty useless. Make those fields show the biggest gainer and loser, 447 | by `change_pct`, instead. 448 | 449 | ```{r echo=FALSE} 450 | countdown::countdown_timer(10 * 60) 451 | ``` 452 | 453 | --- 454 | 455 | ## Solution 456 | 457 | Open `tutorial_exercises/processing.solution.R`. 458 | 459 | I solved this by introducing a reactive expression called `full_data`, which adds the desired columns. This guarantees that the `change` and `change_pct` calculations need only be performed once, no matter how may outputs are using it. 460 | 461 | ```{r eval=FALSE} 462 | raw_data <- reactivePoll(...) 463 | 464 | full_data <- reactive({ 465 | df <- raw_data() %>% mutate( 466 | change = price - prev, 467 | change_pct = (price - prev) / prev * 100 468 | ) 469 | }) 470 | 471 | output$all <- renderTable({ 472 | full_data() 473 | }) 474 | ``` 475 | 476 | --- 477 | 478 | ## Anti-solution 1 479 | 480 | ```{r eval=FALSE} 481 | output$all <- renderTable({ 482 | raw_data() %>% mutate( 483 | change = price - prev, 484 | change_pct = (price - prev) / prev * 100 485 | ) 486 | }) 487 | 488 | output$max_gainer <- renderText({ 489 | max_gainer <- raw_data() %>% mutate( 490 | change = price - prev, 491 | change_pct = (price - prev) / prev * 100 492 | ) %>% top_n(1, change_pct) 493 | paste(max_gainer$symbol, formatC(max_gainer$change_pct)) 494 | }) 495 | ``` 496 | 497 | --- 498 | 499 | ## Anti-solution 1 500 | 501 | ```{r eval=FALSE} 502 | output$all <- renderTable({ 503 | raw_data() %>% mutate( 504 | {{ change}} = price - prev, 505 | {{ change_pct}} = (price - prev) / prev * 100 506 | ) 507 | }) 508 | 509 | output$max_gainer <- renderText({ 510 | max_gainer <- raw_data() %>% mutate( 511 | {{ change}} = price - prev, 512 | {{ change_pct}} = (price - prev) / prev * 100 513 | ) %>% top_n(1, change_pct) 514 | paste(max_gainer$symbol, formatC(max_gainer$change_pct)) 515 | }) 516 | ``` 517 | 518 | Duplicate code! Bad for performance, readability, and maintainability. 519 | 520 | --- 521 | 522 | ## Anti-solution 2 523 | 524 | ```{r eval=FALSE} 525 | values <- reactiveValues(full_data = NULL) 526 | 527 | observeEvent(raw_data(), { 528 | values$full_data <- raw_data() %>% mutate( 529 | change = price - prev, 530 | change_pct = (price - prev) / prev * 100 531 | ) 532 | }) 533 | 534 | output$all <- renderTable({ 535 | values$full_data 536 | }) 537 | ``` 538 | 539 | The solution is _reactive_, this anti-solution is _imperative_; more intuitive for beginners to Shiny and reactive programming, but less efficient and impossible to reason about as apps get more complicated. 540 | 541 | See the videos on _Effective Reactive Programming_ from [ShinyConf 2016](https://www.rstudio.com/resources/webinars/shiny-developer-conference/) for much more. 542 | 543 | --- 544 | 545 | ## Takeaway 546 | 547 | Use reactive expressions (`reactive({...})`) to store calculations that need to be reused, as you would introduce a variable in a regular R script. 548 | 549 | --- 550 | 551 | ## Performance 552 | 553 | Options to improve performance and responsiveness: 554 | 555 | 1. Cache results 556 | 2. Speed up processing logic (Rcpp, parallel) 557 | 3. Scale hardware up/out 558 | 4. Remove functionality 559 | 560 | We'll focus on 1 today. 561 | 562 | --- 563 | 564 | ## Example: Performance 565 | 566 | `daily-job.R` 567 | ```{r eval=FALSE} 568 | {{A}} 569 | ``` 570 | 571 | `app.R` 572 | ```{r eval=FALSE} 573 | library(shiny) 574 | {{B}} 575 | 576 | ui <- ... 577 | 578 | server <- function() { 579 | {{ C}} 580 | output$plot <- renderPlot({ 581 | {{ D}} 582 | }) 583 | } 584 | ``` 585 | 586 | --- 587 | 588 | ## Example: Performance 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 |
DescriptionHow many
AData processing scriptOnce daily
BShiny R process4
CShiny session100 per process
DrenderPlot5 times per process
597 | 598 | --- 599 | 600 | ## Example: Performance 601 | 602 | `daily-job.R` 603 | ```{r eval=FALSE} 604 | {{1X}} 605 | ``` 606 | 607 | `app.R` 608 | ```{r eval=FALSE} 609 | library(shiny) 610 | {{4X}} 611 | 612 | ui <- ... 613 | 614 | server <- function() { 615 | {{ 400X}} 616 | output$plot <- renderPlot({ 617 | {{ 2,000X}} 618 | }) 619 | } 620 | ``` 621 | 622 | --- 623 | 624 | ## Takeaway 625 | 626 | Think carefully about how often your code needs to run, and move it to the scope that matches. 627 | 628 | -- 629 | 630 | Especially think about how to move your code out of your Shiny app and into preprocessing scripts, so you're not in a user's critical path. 631 | -------------------------------------------------------------------------------- /inst/server/slides/server.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tutorial: Dashboards in Shiny 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 669 | 670 | 675 | 676 | 677 | 678 | -------------------------------------------------------------------------------- /inst/ui/dashboard-tutorial-ui.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcheng5/dashtutorial/3e2b0f1e19ca06c0ee7d417dd30bf32ace807021/inst/ui/dashboard-tutorial-ui.pdf -------------------------------------------------------------------------------- /inst/ui/flexdashboard/01_vertical.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: “Vertical layout" 3 | output: flexdashboard::flex_dashboard 4 | --- 5 | 6 | ### Chart 7 | 8 | ```{r} 9 | plot(pressure) 10 | ``` 11 | 12 | ### Table 13 | 14 | ```{r} 15 | knitr::kable(pressure) 16 | ``` 17 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/02_horizontal.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: “Horizontal layout" 3 | output: flexdashboard::flex_dashboard 4 | --- 5 | 6 | Column 1 7 | ----------------- 8 | 9 | ### Chart 10 | 11 | ```{r} 12 | plot(pressure) 13 | ``` 14 | 15 | Column 2 16 | ----------------- 17 | 18 | ### Table 19 | 20 | ```{r} 21 | knitr::kable(pressure) 22 | ``` 23 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/03_columns.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Column layout" 3 | output: flexdashboard::flex_dashboard 4 | --- 5 | 6 | Column {data-width=4} 7 | ------------------------------------- 8 | 9 | ### Table 10 | 11 | ```{r} 12 | knitr::kable(pressure) 13 | ``` 14 | 15 | Column {data-width=6} 16 | ------------------------------------- 17 | 18 | ### Chart 1 19 | 20 | ```{r} 21 | plot(pressure) 22 | 23 | ``` 24 | 25 | ### Chart 2 26 | 27 | ```{r} 28 | hist(pressure$pressure) 29 | ``` 30 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/04_rows.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Row layout" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: rows 6 | --- 7 | 8 | Row 9 | ------------------------------------- 10 | 11 | ### Table 12 | 13 | ```{r} 14 | knitr::kable(pressure) 15 | ``` 16 | 17 | Row 18 | ------------------------------------- 19 | 20 | ### Chart 1 21 | 22 | ```{r} 23 | plot(pressure) 24 | 25 | ``` 26 | 27 | ### Chart 2 28 | 29 | ```{r} 30 | hist(pressure$pressure) 31 | ``` 32 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/05_scrolling.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Scrolling layout" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: columns 6 | vertical_layout: scroll 7 | --- 8 | 9 | Column 10 | ------------------------------------- 11 | 12 | ### Table 13 | 14 | ```{r} 15 | knitr::kable(cars) 16 | ``` 17 | 18 | Column 19 | ------------------------------------- 20 | 21 | ### Chart 1 {data-height=10} 22 | 23 | ```{r} 24 | plot(cars) 25 | ``` 26 | 27 | 28 | ### Chart 2 {data-height=50} 29 | 30 | ```{r} 31 | hist(cars$dist) 32 | ``` 33 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/06_components.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Components" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: rows 6 | --- 7 | 8 | ```{r} 9 | library(flexdashboard) 10 | ``` 11 | 12 | Row 13 | ------------------------------------- 14 | 15 | ### Flights 16 | 17 | ```{r} 18 | flights <- 23 19 | 20 | valueBox(flights, icon = "fa-plane") 21 | ``` 22 | 23 | ### Deliveries 24 | 25 | ```{r} 26 | deliveries <- 410 27 | 28 | valueBox(deliveries, color = "warning", icon = "fa-shopping-cart") 29 | ``` 30 | 31 | ### Rate 32 | 33 | ```{r} 34 | rate <- 85 35 | 36 | gauge(rate, min = 0, max = 100, symbol = '%', 37 | gaugeSectors(success = c(80, 100), warning = c(40, 79), danger = c(0, 39)) 38 | ) 39 | ``` 40 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/07_flexdashboard-shiny.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "flexdashboard + shiny" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: columns 6 | source_code: embed 7 | runtime: shiny 8 | --- 9 | 10 | ```{r global, include=FALSE} 11 | # load data in 'global' chunk so it can be shared by all users of the dashboard 12 | library(shiny) 13 | library(ggplot2) 14 | ``` 15 | 16 | Inputs {.sidebar} 17 | ----------------------------------------------------------------------- 18 | 19 | ```{r} 20 | checkboxGroupInput("cyl", "Cylinders", choices = c("4", "6", "8"), 21 | selected = c("4", "6", "8"), inline = TRUE 22 | ) 23 | 24 | sliderInput("hp", "Horsepower", 25 | min = min(mtcars$hp), max = max(mtcars$hp), 26 | value = range(mtcars$hp) 27 | ) 28 | ``` 29 | 30 | Outputs 31 | ----------------------------------------------------------------------- 32 | 33 | ### Scatterplot of weight and miles per gallon 34 | 35 | ```{r} 36 | mpg_subset <- reactive({ 37 | mtcars[mtcars$hp >= input$hp[1] & 38 | mtcars$hp <= input$hp[2] & 39 | mtcars$cyl %in% input$cyl, ] 40 | }) 41 | 42 | renderPlot({ 43 | ggplot(mpg_subset(), aes(x=wt, y=mpg, color=factor(cyl))) + 44 | geom_point() + 45 | coord_cartesian(xlim = range(mtcars$wt), ylim = range(mtcars$mpg)) 46 | }) 47 | ``` 48 | 49 | ### Histogram of weight 50 | 51 | ```{r} 52 | renderPlot({ 53 | ggplot(mpg_subset(), aes(x=wt)) + 54 | geom_histogram(binwidth = 0.25) + 55 | coord_cartesian(xlim = range(mtcars$wt)) 56 | }) 57 | ``` 58 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/08_shiny-prerendered.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "flexdashboard + shiny_prerendered" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: columns 6 | source_code: embed 7 | runtime: shiny_prerendered 8 | --- 9 | 10 | ```{r context="setup", include=FALSE} 11 | library(shiny) 12 | library(ggplot2) 13 | ``` 14 | 15 | Inputs {.sidebar} 16 | ----------------------------------------------------------------------- 17 | 18 | ```{r context="render"} 19 | checkboxGroupInput("cyl", "Cylinders", choices = c("4", "6", "8"), 20 | selected = c("4", "6", "8"), inline = TRUE 21 | ) 22 | 23 | sliderInput("hp", "Horsepower", 24 | min = min(mtcars$hp), max = max(mtcars$hp), 25 | value = range(mtcars$hp) 26 | ) 27 | ``` 28 | 29 | Outputs 30 | ----------------------------------------------------------------------- 31 | 32 | ### Scatterplot of weight and miles per gallon 33 | 34 | ```{r context="server"} 35 | mpg_subset <- reactive({ 36 | mtcars[mtcars$hp >= input$hp[1] & 37 | mtcars$hp <= input$hp[2] & 38 | mtcars$cyl %in% input$cyl, ] 39 | }) 40 | 41 | output$scatter <- renderPlot({ 42 | ggplot(mpg_subset(), aes(x=wt, y=mpg, color=factor(cyl))) + 43 | geom_point() + 44 | coord_cartesian(xlim = range(mtcars$wt), ylim = range(mtcars$mpg)) 45 | }) 46 | ``` 47 | 48 | ```{r context="render"} 49 | plotOutput("scatter") 50 | ``` 51 | 52 | ### Histogram of weight 53 | 54 | ```{r context="server"} 55 | output$hist <- renderPlot({ 56 | ggplot(mpg_subset(), aes(x=wt)) + 57 | geom_histogram(binwidth = 0.25, boundary = 0) + 58 | coord_cartesian( 59 | xlim = c(floor(mtcars$wt), ceiling(mtcars$wt)), 60 | ylim = c(0, 6) 61 | ) 62 | }) 63 | ``` 64 | 65 | ```{r context="render"} 66 | plotOutput("hist") 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/09_reconnect.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "flexdashboard + shiny + reconnect" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: columns 6 | source_code: embed 7 | runtime: shiny 8 | --- 9 | 10 | ```{r global, include=FALSE} 11 | library(shiny) 12 | library(ggplot2) 13 | ``` 14 | 15 | ```{r} 16 | # To test reconnections in a local R session, run: 17 | # rmarkdown::run("demos/09_reconnect.Rmd", shiny_args = list(launch.browser=FALSE)) 18 | 19 | # "force" is only for local testing. 20 | # In deployment, use session$allowReconnect(TRUE) 21 | session$allowReconnect("force") 22 | ``` 23 | 24 | Inputs {.sidebar} 25 | ----------------------------------------------------------------------- 26 | 27 | ```{r} 28 | checkboxGroupInput("cyl", "Cylinders", choices = c("4", "6", "8"), 29 | selected = c("4", "6", "8"), inline = TRUE 30 | ) 31 | 32 | sliderInput("hp", "Horsepower", 33 | min = min(mtcars$hp), max = max(mtcars$hp), 34 | value = range(mtcars$hp) 35 | ) 36 | ``` 37 | 38 | Outputs 39 | ----------------------------------------------------------------------- 40 | 41 | ### Scatterplot of weight and miles per gallon 42 | 43 | ```{r} 44 | mpg_subset <- reactive({ 45 | mtcars[mtcars$hp >= input$hp[1] & 46 | mtcars$hp <= input$hp[2] & 47 | mtcars$cyl %in% input$cyl, ] 48 | }) 49 | 50 | renderPlot({ 51 | ggplot(mpg_subset(), aes(x=wt, y=mpg, color=factor(cyl))) + 52 | geom_point() + 53 | coord_cartesian(xlim = range(mtcars$wt), ylim = range(mtcars$mpg)) 54 | }) 55 | ``` 56 | 57 | ### Histogram of weight 58 | 59 | ```{r} 60 | renderPlot({ 61 | ggplot(mpg_subset(), aes(x=wt)) + 62 | geom_histogram(binwidth = 0.25) + 63 | coord_cartesian(xlim = range(mtcars$wt)) 64 | }) 65 | ``` 66 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/10_reconnect-prerendered.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "flexdashboard + shiny_prerendered + reconnect" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: columns 6 | source_code: embed 7 | runtime: shiny_prerendered 8 | --- 9 | 10 | ```{r context="setup", include=FALSE} 11 | library(shiny) 12 | library(ggplot2) 13 | ``` 14 | 15 | ```{r context="server"} 16 | # To test reconnections in a local R session, run: 17 | # rmarkdown::run("demos/10_reconnect-prerendered.Rmd", shiny_args = list(launch.browser=FALSE)) 18 | 19 | # "force" is only for local testing. 20 | # In deployment, use session$allowReconnect(TRUE) 21 | session$allowReconnect("force") 22 | ``` 23 | 24 | Inputs {.sidebar} 25 | ----------------------------------------------------------------------- 26 | 27 | ```{r context="render"} 28 | checkboxGroupInput("cyl", "Cylinders", choices = c("4", "6", "8"), 29 | selected = c("4", "6", "8"), inline = TRUE 30 | ) 31 | 32 | sliderInput("hp", "Horsepower", 33 | min = min(mtcars$hp), max = max(mtcars$hp), 34 | value = range(mtcars$hp) 35 | ) 36 | ``` 37 | 38 | Outputs 39 | ----------------------------------------------------------------------- 40 | 41 | ### Scatterplot of weight and miles per gallon 42 | 43 | ```{r context="server"} 44 | mpg_subset <- reactive({ 45 | mtcars[mtcars$hp >= input$hp[1] & 46 | mtcars$hp <= input$hp[2] & 47 | mtcars$cyl %in% input$cyl, ] 48 | }) 49 | 50 | output$scatter <- renderPlot({ 51 | ggplot(mpg_subset(), aes(x=wt, y=mpg, color=factor(cyl))) + 52 | geom_point() + 53 | coord_cartesian(xlim = range(mtcars$wt), ylim = range(mtcars$mpg)) 54 | }) 55 | ``` 56 | 57 | ```{r context="render"} 58 | plotOutput("scatter") 59 | ``` 60 | 61 | ### Histogram of weight 62 | 63 | ```{r context="server"} 64 | output$hist <- renderPlot({ 65 | ggplot(mpg_subset(), aes(x=wt)) + 66 | geom_histogram(binwidth = 0.25, boundary = 0) + 67 | coord_cartesian( 68 | xlim = c(floor(mtcars$wt), ceiling(mtcars$wt)), 69 | ylim = c(0, 6) 70 | ) 71 | }) 72 | ``` 73 | 74 | ```{r context="render"} 75 | plotOutput("hist") 76 | ``` 77 | 78 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/11_bookmark.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "flexdashboard + shiny" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: columns 6 | source_code: embed 7 | runtime: shiny 8 | --- 9 | 10 | ```{r global, include=FALSE} 11 | # load data in 'global' chunk so it can be shared by all users of the dashboard 12 | library(shiny) 13 | library(ggplot2) 14 | ``` 15 | 16 | Inputs {.sidebar} 17 | ----------------------------------------------------------------------- 18 | 19 | ```{r} 20 | checkboxGroupInput("cyl", "Cylinders", choices = c("4", "6", "8"), 21 | selected = c("4", "6", "8"), inline = TRUE 22 | ) 23 | 24 | sliderInput("hp", "Horsepower", 25 | min = min(mtcars$hp), max = max(mtcars$hp), 26 | value = range(mtcars$hp) 27 | ) 28 | 29 | enableBookmarking("url") 30 | bookmarkButton() 31 | ``` 32 | 33 | Outputs 34 | ----------------------------------------------------------------------- 35 | 36 | ### Scatterplot of weight and miles per gallon 37 | 38 | ```{r} 39 | mpg_subset <- reactive({ 40 | mtcars[mtcars$hp >= input$hp[1] & 41 | mtcars$hp <= input$hp[2] & 42 | mtcars$cyl %in% input$cyl, ] 43 | }) 44 | 45 | renderPlot({ 46 | ggplot(mpg_subset(), aes(x=wt, y=mpg, color=factor(cyl))) + 47 | geom_point() + 48 | coord_cartesian(xlim = range(mtcars$wt), ylim = range(mtcars$mpg)) 49 | }) 50 | ``` 51 | 52 | ### Histogram of weight 53 | 54 | ```{r} 55 | renderPlot({ 56 | ggplot(mpg_subset(), aes(x=wt)) + 57 | geom_histogram(binwidth = 0.25) + 58 | coord_cartesian(xlim = range(mtcars$wt)) 59 | }) 60 | ``` 61 | -------------------------------------------------------------------------------- /inst/ui/flexdashboard/99_sidebar-static.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Sidebar layout" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: columns 6 | --- 7 | 8 | ```{r} 9 | library(crosstalk) 10 | 11 | # devtools::install_github("jcheng5/d3scatter") 12 | library(d3scatter) 13 | 14 | # This requires a development version of DT: 15 | # devtools::install_github("rstudio/DT") 16 | library(DT) 17 | ``` 18 | 19 | Inputs {.sidebar} 20 | ------------------------------------- 21 | 22 | ```{r} 23 | shared_mtcars <- SharedData$new(mtcars) 24 | filter_checkbox("cyl", "Cylinders", shared_mtcars, ~cyl, inline = TRUE) 25 | filter_slider("hp", "Horsepower", shared_mtcars, ~hp, width = "100%") 26 | ``` 27 | 28 | Column 29 | ------------------------------------- 30 | 31 | ### Chart 1 {data-height=10} 32 | 33 | ```{r} 34 | d3scatter(shared_mtcars, ~wt, ~hp, ~factor(cyl), width="100%") 35 | ``` 36 | 37 | 38 | ### Chart 2 {data-height=50} 39 | 40 | ```{r} 41 | d3scatter(shared_mtcars, ~wt, ~mpg, ~factor(cyl), width="100%") 42 | ``` 43 | 44 | Column 45 | ------------------------------------- 46 | 47 | ### Table 48 | 49 | ```{r} 50 | # Requires github version of DT 51 | # github::install_github("rstudio/DT") 52 | datatable(shared_mtcars, extensions="Scroller", style="bootstrap", 53 | class="compact", 54 | options=list(deferRender=TRUE, scrollY=300, scroller=TRUE)) 55 | ``` 56 | -------------------------------------------------------------------------------- /inst/ui/shinydashboard/01_basic/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinydashboard) 3 | 4 | ui <- dashboardPage( 5 | dashboardHeader(title = "Awesome dashboard"), 6 | dashboardSidebar(), 7 | dashboardBody() 8 | ) 9 | 10 | server <- function(input, output) { } 11 | 12 | shinyApp(ui, server) 13 | -------------------------------------------------------------------------------- /inst/ui/shinydashboard/02_rows/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinydashboard) 3 | 4 | ui <- dashboardPage( 5 | dashboardHeader(), 6 | dashboardSidebar(), 7 | dashboardBody( 8 | fluidRow( 9 | box( 10 | plotOutput("plot1") 11 | ), 12 | box( 13 | tableOutput("table1") 14 | ) 15 | ) 16 | ) 17 | ) 18 | 19 | server <- function(input, output) { 20 | output$plot1 <- renderPlot({ 21 | plot(cars) 22 | }) 23 | 24 | output$table1 <- renderTable({ 25 | cars 26 | }) 27 | } 28 | 29 | shinyApp(ui, server) 30 | -------------------------------------------------------------------------------- /inst/ui/shinydashboard/03_cols/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinydashboard) 3 | 4 | ui <- dashboardPage( 5 | dashboardHeader(), 6 | dashboardSidebar(), 7 | dashboardBody( 8 | fluidRow( 9 | column(width = 6, 10 | box(plotOutput("plot1"), width = NULL), 11 | box(plotOutput("plot2"), width = NULL) 12 | ), 13 | column(width = 6, 14 | box(tableOutput("table1"), width = NULL) 15 | ) 16 | ) 17 | ) 18 | ) 19 | 20 | server <- function(input, output) { 21 | output$plot1 <- renderPlot({ 22 | plot(cars) 23 | }) 24 | output$plot2 <- renderPlot({ 25 | plot(pressure) 26 | }) 27 | output$table1 <- renderTable({ 28 | cars 29 | }) 30 | } 31 | 32 | shinyApp(ui, server) 33 | -------------------------------------------------------------------------------- /inst/ui/shinydashboard/04_sidebar/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinydashboard) 3 | 4 | ui <- dashboardPage( 5 | dashboardHeader(), 6 | dashboardSidebar( 7 | sliderInput("s", "Slider", 1, 100, 20), 8 | dateInput("d", "Date") 9 | ), 10 | dashboardBody() 11 | ) 12 | 13 | server <- function(input, output) { 14 | output$plot1 <- renderPlot({ 15 | plot(cars) 16 | }) 17 | output$plot2 <- renderPlot({ 18 | plot(pressure) 19 | }) 20 | output$table1 <- renderTable({ 21 | cars 22 | }) 23 | } 24 | 25 | shinyApp(ui, server) 26 | --------------------------------------------------------------------------------