returning a ggplot() from a shiny module

Hello everyone,

I'm new to shiny and recently started learning about modules. After playing around for a good time, I still struggle to understand what I can return from a moduleServer().

Problem
I created a small shiny module setup, where a slider changes the plot limits. In Case 1, the ggplot() is rendered inside the module and returned afterward. In Case 2, the ggplot() gets returned and rendered afterward. I would expect both setups to work the same, yet Case 1 does work while Case 2 does not.

Questions
Question 1: Where is the mistake?
Question 2: What would a working Case 2 look like, where a server module returns a ggplot()?

Thanks in advance,
Simon

Setup

library(tidyverse)
library(shiny)

x <- 1:100
y <- rnorm(x)
my_data <- data.frame(x, y)
my_plot <- ggplot(my_data, aes(x, y)) + geom_point()

my_plot

sliderUI <- function (id) {
  ns <- NS(id)
  fluidRow(sliderInput(inputId = ns("slider"), label = NULL, min = 1, max = 100, value = c(1, 100)))
}

ui <- fluidPage(
  sliderUI("my_slider_plot"),
  plotOutput("plot")
)

Case 1: works

# reactive(ggplot()) -> renderPlot() -> return()
sliderServer <- function (id, plot) {
  moduleServer(
    id,
    module = function (input, output, session) {

      sliderReactive <- reactive({
        plot + xlim(c(input$slider[1], input$slider[2]))
      })

      return(renderPlot(sliderReactive()))

    }
  )
}

server <- function (input, output) {
  output$plot <- sliderServer("my_slider_plot", plot = my_plot)
}

shinyApp(ui, server)

Case 2: does not work

# reactive(ggplot()) -> return() -> renderPlot()
sliderServer <- function (id, plot) {
  moduleServer(
    id,
    module = function (input, output, session) {

      sliderReactive <- reactive({
        plot + xlim(c(input$slider[1], input$slider[2]))
      })

      return(sliderReactive())

    }
  )
}


server <- function (input, output) {
  output$plot <- renderPlot(sliderServer("my_slider_plot", plot = my_plot))
}


shinyApp(ui, server)

I'm not going to pretend I fully understand Shiny modules, though I use them all the time. My gut feeling is that in the second case renderPlot is outside the module, so it cannot track changes inside the module. I asked ChatGPT and here is the answer:

You're returning sliderReactive itself from the module, and then you're trying to use renderPlot on the result of sliderServer in the main server function. This doesn't correctly set up the dependency, because by the time the renderPlot function is called, the sliderReactive expression has already been evaluated, and any changes to the slider input won't trigger a reevaluation.

It confirms my guess. If someone can explain it better, I would be glad to hear it!

you dont want the module to return some concrete unchanging value, you want it to return a reactiveValue something that can change, and can trigger other things to recalculate/redraw.
Thereof drop the brackets
return(sliderReactive))

Then on the server side, you need to have the server module bound to some name that can appear in the shiny reactive tree.

server <- function (input, output) {
  output$plot <- renderPlot(sliderServer("my_slider_plot", plot = my_plot))
}

becomes



server <- function (input, output) {
  slideresult <- sliderServer("my_slider_plot", plot = my_plot)
  output$plot <- renderPlot(
    req(slideresult())
  )
}
1 Like

Thanks for your explanation. That's a very insightful lesson. So basically, reactive() produces a container of sorts, that only keeps its abilities when packaged. By adding brackets, I unpack the container and with it, I lose the reactive abilities.

So in case 1, I did

Container <- reactive()
return(renderPlot(unpackedContainer = Container()))

where adding brackets unpacks the container.

In case 2 however,

Container <- reactive()
renderPlot(return(unpackedContainer = Container()))

returns something independent of the sliderInput, since the reactive() is unpacked before renderPlot().

A working case 2 would be

Container <- return(reactive())
renderPlot(unpackedContainer = Container())

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.