Previous chapter
Productionizing ShinyShiny Modules
Next chapter

Introduction

What is a Shiny module?

  • A module is a self-contained, composable component of a Shiny app self-contained like a function can be combined to make an app
  • Have their own UI and server (in addition to the app UI and server)

Modules

  • Useful for reusability
  • rather than copy and paste code, you can use modules to help manage the pieces that will be repeated throughout a single app or across multiple apps
  • can be bundled into packages
  • Essential for managing code complexity in larger apps

Limitations to Just Functionalizing

It’s possible to write UI-generating functions and call them from your app’s UI, and you write functions for the server that define outputs and create reactive expressions However you must make sure your functions generate input and output IDs that don’t collide since input and output IDs in Shiny apps share a global namespace, meaning, each ID must be unique across the entire app Solution: Namespaces! Modules add namespacing to Shiny UI and server logic

Ladder of Progression

  • Level 1. Use modules to break large monolithic apps into manageable pieces
  • Level 2. Create reusable modules Level 3. Combine/nest modules
  • Level 3. Combine/nest modules

Reading Materials

Read below further reading materials covering shiny modules:

Motivation: Gapminder App

Take a look at the Gapminder Shiny app below. Since each tab has the same functionality with a different parametrization it makes sense to only

  • Create a module with plotting functionality.
  • Parametrize the module by continent.
Gapminder App

Gapminder App

Anatomy of a Shiny module

A Shiny module consists of a user interface function and a corresponding server module. Indeed, the contents of your UI and server functions will look a lot like normal Shiny UI/server logic. But the packaging needs to differ in a few important ways:

  1. The UI function needs to have an id as its first parameter.
  2. The name of the UI function must end with UI.
  3. The name of the server module must be named as the UI module (without UI suffix)
  4. The function body starts with the statement ns <- NS(id). All UI function bodies should start with this line. It takes the string id and creates a namespace function.
  5. Anything input or output ID of any kind that appears in the function body needs to be wrapped in a call to ns(). This example shows inputId arguments being wrapped in ns(); you also want to use ns() when declaring a plotOutput brush ID, for example.
  6. The results are wrapped in tagList, instead of fluidPage, pageWithSidebar, etc. You only need to use tagList if you want to return a UI fragment that consists of multiple UI objects; if you were just returning a div or some specific input, you could skip tagList.

What’s in a module?

library(shiny)
name_of_module_UI <- function(id, ...) {
  # Create a namespace function using the provided id
  ns <- NS(id)
  # UI elements go here
  tagList(
... )
}
name_of_module <- function(input, output, session, ...) {
  # Server logic goes here
}

Shinymod Snippet

To make module creation even easier within RStudio you can also create a module template. This is of course optional and only pays off if you create modules on a regular basis.

  1. Use this RStudio snippet to do the boilerplate for you:
snippet shinymod
    ${1:name}UI <- function(id) {
        ns <- NS(id)
        tagList(
            ${0}
        )
    }
    
    ${1:name} <- function(input, output, session) {
        
    }
  1. Add snippet using: Preferences | Code | Edit Snippets, see also https://support.rstudio.com/hc/en-us/articles/204463668-Code-Snippets.

  1. Use snippet by typing shinymod [Tab], then type the name of your new module

User interface

Module UI

  • A function
  • Takes, as input, an id that gets pre- pended to all HTML element ids with a helper function: NS()
  • Can also have additional parameters

The module UI function is a vanilla R-function named with the suffix UI and taking the module id as its first argument. The crucial part is the specification of ns <- NS(id) which takes the input id and adds a prefix like prefix-id. It thus separates the namespace from the rest of the Shiny application and makes sure that only the corresponding server module is able to access its inputs.

name_of_module_UI <- function(id, ...) {
  # Create a namespace function using id
  ns <- NS(id)
  # UI elements go here
  tagList(
... )
}

Therefore, you must wrap all input and output IDs with ns() in the module UI:

You will often forget to do this! If your module doesn’t work as expected, double-check this first!

Server function

Module Server

  • Includes the code needed for your module
  • Looks almost identical to the app server function, except that you may have additional parameters
  • App server function is automatically invoked by Shiny; module server function must be invoked by the app author

The server module is named as the name of the Shiny module. Typically, the server module is written in the same file <name_of_module>.R as the module UI.

name_of_module <- function(input, output,
    session, ...) {
  # Server logic goes here
}

Module vs. App

  • Similarities:
  • Inputs in module UI can be accessed in module server with input$
  • Outputs in module UI can be defined in module server with output$
  • Can create reactive expressions, observers
  • Differences:
  • Inputs/outputs cannot be directly accessed from outside the module namespace
  • Module server function can take additional parameters, and can return a value to the caller

How it works

  • All UI input/output controls have IDs that are prefixed with "namespace-".
  • The server module function is passed special clones of input, output, and session that implicitly prefixes all IDs with "namespace-".
 # in module UI
numericInput(ns("rows"), "Row count", 5),
plotOutput(ns("plot"))
# in module server
output$plot <- renderPlot({
  plot(head(data, input$rows))
})
How it works

How it works

Calling the Module

  • In the app UI:
  • Include the module UI with name_of_module_UI("id", ...)
  • Can also include other UI elements that are not included in the module
  • In the app server:
  • Include the module server withcallModule(name_of_module, "id", ...)
    • DO NOT pass input, output, session to callModule()
  • Can define outputs that are not included in the module
  • The id must match and must be unique among other inputs/outputs/modules at the same “scope” (either top-level ui/server, or within a parent Shiny module)

Review: Gapminder App

Gapminder App

Gapminder App

Code UI

Download code here.

library(shiny)
library(dplyr)
source("data.R")
source("gapModule.R")

# UI --------------------------------------------------------------------------
ui <- fluidPage(
  tags$style(type="text/css", ".recalculating { opacity: 1.0; }"),
  titlePanel("Gapminder"),
  tabsetPanel(id = "continent", 
    tabPanel("All", gapModuleUI("all")),
    tabPanel("Africa", gapModuleUI("africa")),
    tabPanel("Americas", gapModuleUI("americas")),
    tabPanel("Asia", gapModuleUI("asia")),
    tabPanel("Europe", gapModuleUI("europe")),
    tabPanel("Oceania", gapModuleUI("oceania"))
  )
)

# Server ----------------------------------------------------------------------
server <- function(input, output) {
  callModule(gapModule, "all", all_data)
  callModule(gapModule, "africa", africa_data)
  callModule(gapModule, "americas", americas_data)
  callModule(gapModule, "asia", asia_data)
  callModule(gapModule, "europe", europe_data)
  callModule(gapModule, "oceania", oceania_data)    
}

# Run the application ---------------------------------------------------------
shinyApp(ui = ui, server = server)

Code Module

Download code here.

# Module UI -------------------------------------------------------------------
gapModuleUI <- function(id) {
  ns <- NS(id)
  
  tagList(
    plotOutput(ns("plot")),
    sliderInput(ns("year"), "Select Year", value = 1952, 
                min = 1952, max = 2007, step = 5,  
                animate = animationOptions(interval = 500))
  )
}

# Module server ---------------------------------------------------------------
gapModule <- function(input, output, session, data) {
  
  # Collect one year of data --------------------------------------------------
  ydata <- reactive({
    filter(data, year == input$year)
  })
  
  xrange <- range(data$gdpPercap)
  yrange <- range(data$lifeExp)
  
  output$plot <- renderPlot({
    
    # Draw background plot with legend ----------------------------------------
    plot(data$gdpPercap, data$lifeExp, type = "n", 
         xlab = "GDP per capita", ylab = "Life Expectancy", 
         panel.first = {
           grid()
           text(mean(xrange), mean(yrange), input$year, 
                col = "grey90", cex = 5)
         })
    
    legend("bottomright", legend = levels(data$continent), 
           cex = 1.3, inset = 0.01, text.width = diff(xrange)/5,
           fill = c("#E41A1C99", "#377EB899", "#4DAF4A99", 
                    "#984EA399", "#FF7F0099"))
    
    # Determine bubble colors -------------------------------------------------
    cols <- c("Africa" = "#E41A1C99",
              "Americas" = "#377EB899",
              "Asia" = "#4DAF4A99",
              "Europe" = "#984EA399",
              "Oceania" = "#FF7F0099")[ydata()$continent]
    
    # Add bubbles -------------------------------------------------------------
    symbols(ydata()$gdpPercap, ydata()$lifeExp, circles = sqrt(ydata()$pop),
            bg = cols, inches = 0.5, fg = "white", add = TRUE)
  })
}

Communicating with modules

Passing Input to Modules

  • Sometimes, modules need to be informed by their callers
  • Access to simple parameter values
  • Access to reactive expressions
  • Access to inputs that are outside the module
  • You can accomplish this by adding parameters to your module server function (and callModule calls)

Example: Passing by value (static)

# Module server function
country_module <- function(input, output, session, country) {
  data <- countries %>% filter(Country == country)
}

# In app server function
callModule(country_module, id = "us", country = "United States")

Example: Passing a reactive expression (changes over time)

Pass reactives by reference (no parens) to preserve reactivity!

# Module server function
country_module <- function(input, output, session, selected_country) {
  data <- reactive({
    countries %>% filter(Country = selected_country())
  }) 
}

# In app server function
country <- reactive({ ... })
callModule(country_module, id = "us", selected_country = country)

Example: Passing a reactive input

Wrap in a new reactive expression

# Module server function
country_module <- function(input, output, session, selected_country) {
  data <- reactive({
    countries %>% filter(Country = selected_country())
  })
}

# In app server function
callModule(country_module, id = "us", country = reactive(input$country))

Returning Output from Module

  • Modules can also return (static or reactive) values back to their callers.
  • You can take advantage of this to make modules that represent reusable inputs, or reusable calculations/computations.

Example: Returning a reactive result

 # Module server function that returns a reactive( csv_upload <- function(input, output, session) {
r <- reactive({
    req(input$file) 
    read.csv(input$file$datapath)
  })
  r
}

# In app server function
upload <- callModule(csv_upload, "upload") 
output$table <- renderTable({ upload() })
  • If a module must return an input$value, wrap in a reactiv
  • If a module must return multiple values, return a named list
csv_upload <- function(input, output, session) {
  df <- reactive({
    req(input$file)
    read.csv(input$file$datapath)
  })

list(
  data = df,
  file = reactive(input$file),
  size = reactive(file.info(req(input$file$datapath))$length)
  )
}

Packaging modules

  • Inline code: Put the UI and server function code of the module directly in your app
  • If using app.R (single-file) syle, include module code in that file, before the app’s UI and server logic
  • If using ui.R / server.R (two-file) style, include module code in the global.R file
  • If defining many modules / modules containing a lot of code, may result in bloated global.R/app.R file.
  • Standalone R file:
  • Save module code in a separate .R file and then call source(“path-to-module.R”) from app.R or global.R
  • Probably the best approach for modules that won’t be reused across applications
  • R package: Useful if your modules are intended to be reused across apps
  • R package needs to export and document your module’s UI and server functions
  • R packages can include more than one module

Exercise: Modularize!

See the code for the movies app below for a simpler version of the movie browser:

  • only select x, y, and z (for colors) variables and alpha level and size of points
  • three tabs: one for each title type, showing a scatterplot and data table
  • Note that this app is created by repeating the plotting and data table code chunks three times each

Modularize the app using the Template code at the bottom of this page.

Movies App Code - Simplified

library(shiny)
library(ggplot2)
library(DT)
library(stringr)
library(dplyr)
library(tools)
load("movies.Rdata")

# Define UI for application that plots features of movies -----------
ui <- fluidPage(
  
  # Application title -----------------------------------------------
  titlePanel("Movie browser - without modules"),
  
  # Sidebar layout with a input and output definitions --------------
  sidebarLayout(
    
    # Inputs: Select variables to plot ------------------------------
    sidebarPanel(
      
      # Select variable for y-axis ----------------------------------
      selectInput(inputId = "y", 
                  label = "Y-axis:",
                  choices = c("IMDB rating" = "imdb_rating", 
                              "IMDB number of votes" = "imdb_num_votes", 
                              "Critics Score" = "critics_score", 
                              "Audience Score" = "audience_score", 
                              "Runtime" = "runtime"), 
                  selected = "audience_score"),
      
      # Select variable for x-axis ----------------------------------
      selectInput(inputId = "x", 
                  label = "X-axis:",
                  choices = c("IMDB rating" = "imdb_rating", 
                              "IMDB number of votes" = "imdb_num_votes", 
                              "Critics Score" = "critics_score", 
                              "Audience Score" = "audience_score", 
                              "Runtime" = "runtime"), 
                  selected = "critics_score"),
      
      # Select variable for color -----------------------------------
      selectInput(inputId = "z", 
                  label = "Color by:",
                  choices = c("Title Type" = "title_type", 
                              "Genre" = "genre", 
                              "MPAA Rating" = "mpaa_rating", 
                              "Critics Rating" = "critics_rating", 
                              "Audience Rating" = "audience_rating"),
                  selected = "mpaa_rating"),
      
      # Set alpha level ---------------------------------------------
      sliderInput(inputId = "alpha", 
                  label = "Alpha:", 
                  min = 0, max = 1, 
                  value = 0.5),
      
      # Set point size ----------------------------------------------
      sliderInput(inputId = "size", 
                  label = "Size:", 
                  min = 0, max = 5, 
                  value = 2),
      
      # Show data table ---------------------------------------------
      checkboxInput(inputId = "show_data",
                    label = "Show data table",
                    value = TRUE)
      
    ),
    
    # Output: -------------------------------------------------------
    mainPanel(
      
      # Show scatterplot --------------------------------------------
      tabsetPanel(id = "movies", 
                  tabPanel("Documentaries", 
                           plotOutput("scatterplot_doc"),
                           DT::dataTableOutput("moviestable_doc")),
                  tabPanel("Feature Films", 
                           plotOutput("scatterplot_feature"),
                           DT::dataTableOutput("moviestable_feature")),
                  tabPanel("TV Movies", 
                           plotOutput("scatterplot_tv"),
                           DT::dataTableOutput("moviestable_tv"))
      )
      
    )
  )
)

# Define server function required to create the scatterplot ---------
server <- function(input, output, session) {
  
  # Create subsets for various title types --------------------------
  docs <- reactive({
    filter(movies, title_type == "Documentary")
  })
  
  features <- reactive({
    filter(movies, title_type == "Feature Film")
  })
  
  tvs <- reactive({
    filter(movies, title_type == "TV Movie")
  })
  
  
  # Scatterplot for docs --------------------------------------------
  output$scatterplot_doc <- renderPlot({
    ggplot(data = docs(), aes_string(x = input$x, y = input$y, color = input$z)) +
      geom_point(alpha = input$alpha, size = input$size) +
      labs(x = toTitleCase(str_replace_all(input$x, "_", " ")),
           y = toTitleCase(str_replace_all(input$y, "_", " ")),
           color = toTitleCase(str_replace_all(input$z, "_", " "))
      )
  })
  
  # Scatterplot for features ----------------------------------------
  output$scatterplot_feature <- renderPlot({
    ggplot(data = features(), aes_string(x = input$x, y = input$y, color = input$z)) +
      geom_point(alpha = input$alpha, size = input$size) +
      labs(x = toTitleCase(str_replace_all(input$x, "_", " ")),
           y = toTitleCase(str_replace_all(input$y, "_", " ")),
           color = toTitleCase(str_replace_all(input$z, "_", " "))
      )    
  })
  
  # Scatterplot for tvs ---------------------------------------------
  output$scatterplot_tv <- renderPlot({
    ggplot(data = tvs(), aes_string(x = input$x, y = input$y, color = input$z)) +
      geom_point(alpha = input$alpha, size = input$size) +
      labs(x = toTitleCase(str_replace_all(input$x, "_", " ")),
           y = toTitleCase(str_replace_all(input$y, "_", " ")),
           color = toTitleCase(str_replace_all(input$z, "_", " "))
      )    
  })
  
  # Table for docs --------------------------------------------------
  output$moviestable_doc <- DT::renderDataTable(
    if(input$show_data){
      DT::datatable(data = docs()[, 1:7], 
                    options = list(pageLength = 10), 
                    rownames = FALSE)
    }
  )
  
  # Table for features ----------------------------------------------
  output$moviestable_feature <- DT::renderDataTable(
    if(input$show_data){
      DT::datatable(data = features()[, 1:7], 
                    options = list(pageLength = 10), 
                    rownames = FALSE)
    }
  )  
  
  # Table for tvs ---------------------------------------------------
  output$moviestable_tv <- DT::renderDataTable(
    if(input$show_data)
      DT::datatable(data = tvs()[, 1:7],
                    options = list(pageLength = 10),
                    rownames = FALSE
                    
      )
  )
  
}


# Run the app ------------------------------------------------------
shinyApp(ui = ui, server = server)

Template

# Module UI ---------------------------------------------------------
movies_module_UI <- function(id) {
  ns <- NS(id)
  
  tagList(
    ### add UI elements ###
  )
  
}

# Module server -----------------------------------------------------
movies_module <- function(input, output, session, [add other necessary inputs] ) {
  
  # Select movies with given title type -----------------------------
  ### add UI elements ###

  
  # Create scatterplot object the plotOutput function is expecting --
  ### add plotting code ###

  
  # Print data table if checked -------------------------------------
  ### add data table code ###
  
}

Solution: Modularize!

Movies App

library(shiny)
library(ggplot2)
library(DT)
library(stringr)
library(dplyr)
library(tools)
load("movies.Rdata")
source("moviesmodule.R")

# Define UI for application that plots features of movies -----------
ui <- fluidPage(
  
  # Application title -----------------------------------------------
  titlePanel("Movie browser - without modules"),
  
  # Sidebar layout with a input and output definitions --------------
  sidebarLayout(
    
    # Inputs: Select variables to plot ------------------------------
    sidebarPanel(
      
      # Select variable for y-axis ----------------------------------
      selectInput(inputId = "y", 
                  label = "Y-axis:",
                  choices = c("IMDB rating" = "imdb_rating", 
                              "IMDB number of votes" = "imdb_num_votes", 
                              "Critics Score" = "critics_score", 
                              "Audience Score" = "audience_score", 
                              "Runtime" = "runtime"), 
                  selected = "audience_score"),
      
      # Select variable for x-axis ----------------------------------
      selectInput(inputId = "x", 
                  label = "X-axis:",
                  choices = c("IMDB rating" = "imdb_rating", 
                              "IMDB number of votes" = "imdb_num_votes", 
                              "Critics Score" = "critics_score", 
                              "Audience Score" = "audience_score", 
                              "Runtime" = "runtime"), 
                  selected = "critics_score"),
      
      # Select variable for color -----------------------------------
      selectInput(inputId = "z", 
                  label = "Color by:",
                  choices = c("Title Type" = "title_type", 
                              "Genre" = "genre", 
                              "MPAA Rating" = "mpaa_rating", 
                              "Critics Rating" = "critics_rating", 
                              "Audience Rating" = "audience_rating"),
                  selected = "mpaa_rating"),
      
      # Set alpha level ---------------------------------------------
      sliderInput(inputId = "alpha", 
                  label = "Alpha:", 
                  min = 0, max = 1, 
                  value = 0.5),
      
      # Set point size ----------------------------------------------
      sliderInput(inputId = "size", 
                  label = "Size:", 
                  min = 0, max = 5, 
                  value = 2),
      
      # Show data table ---------------------------------------------
      checkboxInput(inputId = "show_data",
                    label = "Show data table",
                    value = TRUE)
      
    ),
    
    # Output: -------------------------------------------------------
    mainPanel(
      
      # Show scatterplot ------------------------------------------------------
      tabsetPanel(id = "movies", 
                  tabPanel("Documentaries", movies_module_UI("doc")),
                  tabPanel("Feature Films", movies_module_UI("feature")),
                  tabPanel("TV Movies", movies_module_UI("tv"))
      )
      
    )
  )
)

# Define server function required to create the scatterplot ---------
server <- function(input, output, session) {
  
  x     <- reactive(input$x)
  y     <- reactive(input$y)
  z     <- reactive(input$z)
  alpha <- reactive(input$alpha)
  size  <- reactive(input$size)
  show_data <- reactive(input$show_data)
  
  # Create the scatterplot object the plotOutput function is expecting --------
  callModule(movies_module, "doc", data = movies, mov_title_type = "Documentary", x, y, z, alpha, size, show_data)
  callModule(movies_module, "feature", data = movies, mov_title_type = "Feature Film", x, y, z, alpha, size, show_data)
  callModule(movies_module, "tv", data = movies, mov_title_type = "TV Movie", x, y, z, alpha, size, show_data)
  
}


# Run the app ------------------------------------------------------
shinyApp(ui = ui, server = server)

Movies Module

# Module UI -------------------------------------------------------------------
movies_module_UI <- function(id) {
  ns <- NS(id)
  
  tagList(
    plotOutput(ns("scatterplot")),
    DT::dataTableOutput(ns("moviestable"))
  )
  
}

# Module server ---------------------------------------------------------------
movies_module <- function(input, output, session, data, mov_title_type, x, y, z, alpha, size, show_data) {
  
  # Select movies with given title type ----------------------------------------
  movies_with_type <- reactive({
    filter(data, title_type == as.character(mov_title_type))
  })
  
  # Create the scatterplot object the plotOutput function is expecting --------
  output$scatterplot <- renderPlot({
    ggplot(data = movies_with_type(), aes_string(x = x(), y = y(), color = z())) +
      geom_point(alpha = alpha(), size = size()) +
      labs(x = toTitleCase(str_replace_all(x(), "_", " ")),
           y = toTitleCase(str_replace_all(y(), "_", " ")),
           color = toTitleCase(str_replace_all(z(), "_", " "))
      )
  })
  
  # Print data table if checked -----------------------------------------------
  output$moviestable <- DT::renderDataTable(
    if(show_data()){
      DT::datatable(data = movies_with_type()[, 1:7], 
                    options = list(pageLength = 10), 
                    rownames = FALSE)
    }
  )
  
}

Combining Modules

  • When building an app that uses modules that depend on each other, avoid violating the sanctity of the module’s namespace (similar to a function’s local environment)
  • If results of Module 1 will be used as inputs in Module 2, then Module 1 needs to return those results as an output, so that Module 2 does not have to “reach in and grab them”

DEMO: left_right_01

Module 1: Dataset chooser

# Module 1 UI
dataset_chooser_UI <- function(id) {
  ns <- NS(id)

  tagList(
    selectInput(ns("dataset"), "Choose a dataset", c("pressure", "cars")),
    numericInput(ns("count"), "Number of records to return", 10)
  )
}

# Module 1 Server
dataset_chooser <- function(input, output, session) {
  dataset <- reactive({
    req(input$dataset)
    get(input$dataset, pos = "package:datasets")
  })
  
  return(list(
    dataset = dataset,
    count = reactive(input$count)
  ))
}

Module 2: Dataset summarizer

# Module 2: Dataset summarizer ------------------------------------------------

# Module 2 UI
dataset_summarizer_UI <- function(id) {
  ns <- NS(id)
  
  verbatimTextOutput(ns("summary"))
}

# Module 2 Server
dataset_summarizer <- function(input, output, session, dataset, count) {
  output$summary <- renderPrint({
    summary(head(dataset(), count()))
  })
}

App combining the two modules

# App combining Module 1 and Module 2 -----------------------------------------

# App UI
ui <- fluidPage(
  fluidRow(
    column(6,
      dataset_chooser_UI("left_input"),
      dataset_summarizer_UI("left_output")
    ),
    column(6,
      dataset_chooser_UI("right_input"),
      dataset_summarizer_UI("right_output")
    )
  )
)

# App server
server <- function(input, output, session) {
  left_result <- callModule(dataset_chooser, "left_input")
  right_result <- callModule(dataset_chooser, "right_input")
  
  callModule(dataset_summarizer, "left_output", dataset = left_result$dataset, count = left_result$count)
  callModule(dataset_summarizer, "right_output", dataset = right_result$dataset, count = right_result$count)
}

shinyApp(ui, server)

Exercise: Combining Modules

  • Start with the left_right code in the previous section or download here
  • Update the 2nd module, dataset summarizer, so that it also returns mean of each of the two variables
  • Add a 3rd module, dataset plotter, that makes a scatterplot of the selected dataset, and overlays a red point at (mean of x, mean of y) that it takes from the results of the dataset summarizer
  • Add this module into your app such that the plot is printed underneath the summary

Desired Outcome

Solution: Combining Modules

Download full code here.

Module 2: Dataset summarizer (updated)

# Module 2 UI
dataset_summarizer_UI <- function(id) {
  ns <- NS(id)
  
  verbatimTextOutput(ns("summary"))
}

# Module 2 Server
dataset_summarizer <- function(input, output, session, dataset, count) {
  selected_data <- reactive({ head(dataset(), count()) })

  output$summary <- renderPrint({
    summary( selected_data() )
  })
  
  mean_x <- reactive({ mean(selected_data()[,1]) })
  mean_y <- reactive({ mean(selected_data()[,2]) })
  
  return(list(
    mean_x = mean_x,
    mean_y = mean_y
  ))
  
}

Module 3: Dataset plotter (new)

# Module 3 UI
dataset_plotter_UI <- function(id) {
  ns <- NS(id)
  
  plotOutput(ns("scatterplot"))
}

# Module 3 Server
dataset_plotter <- function(input, output, session, dataset, count, mean_x, mean_y) {
  output$scatterplot <- renderPlot({
    plot(head(dataset(), count()))
    points(x = mean_x(), y = mean_y(), pch = 19, col = "red")
  })
}