3

Is there any way I can dynamically create a number of renderPlot functions, based on the number of plots I have in a list of ggplots?

I have a Shiny app where instead of having a stable UI, and instead of using renderUI, I am relying on a user-supplied config file to tell Shiny how many plots to show. The config file also supplies data and pretty much helps do most of the heavy lifting.

After much battling, I'm mostly there. With the handy-dandy config file, I can build the correct UI, and generate the correct number of ggplots. The ggplots live in a list, creatively named list_of_ggplots.

But now, I'm at a point where I have a list of ggplots, and I need to allow them to be plotted by using them like this:

  output$plot1 <- renderPlot({
  print(list_of_ggplots[[1]])
})

But now I have an existentialist crisis -- I can't do it like this, since the user-supplied config file tells me how many plots I have. I can no longer hard code the renderPlot call like is usually done in Shiny, since the number of these functions needed is defined in the config file.

Given my list of ggplots, I need some way to generate the renderPlot calls.

Has anyone done this or have any ideas? Much appreciated.

Here's my code:

SERVER.R:

library(shiny)
library(ggplot2)

# 3 simple plots of different colors -- used here instead of all the complicated stuff
# where someone uses the config file that specified 3 plots, with data, etc.

ggplot_names <- c("p1", "p2", "p3")
ggplot_colors <- c("red", "blue", "green")
list_of_ggplots <- list()
j = 1
for (i in ggplot_names){
  i <- ggplot(data.frame(x = c(-3, 3)))         
  i <- i + aes(x)
  i <- i + stat_function(fun = dnorm, colour=ggplot_colors[[j]])
  list_of_ggplots[[j]] <- i
  j <- j+ 1
}

## here's the problem -- the user specified 3 plots. 
## I can't hardcode the following shinyServer functions!!!
## What if tomorrow, the user specifies 2 plots instead?
shinyServer(function(input, output) { 

  output$plot1 <- renderPlot({
  print(list_of_ggplots[[1]])
})

  output$plot2 <- renderPlot({
  print(list_of_ggplots[[2]])
})

  output$plot3 <- renderPlot({
  print(list_of_ggplots[[3]])
})
})

UI.R

## this top part is actually sourced from the config file
## since Shiny needs to know how many tabPages to use,
## names for the tabs, etc

number_of_tabPages <- 3
tab_names <- c("", "Tab1", "Tab2", "Tab3")
tabs<-list()
tabs[[1]]=""
for (i in 2:(number_of_tabPages+1)){
  tabs[[i]]=tabPanel(tab_names[i],plotOutput(paste0("plot",i-1)))}

## Here's the familiar UI part
shinyUI(fluidRow(

        column(12,
               "", 
               do.call(navbarPage,tabs)
              ) 
                )
      )
tumultous_rooster
  • 12,150
  • 32
  • 92
  • 149
  • 1
    You might get some inspiration from this question: http://stackoverflow.com/questions/27930144/reactivity-in-r-shiny-with-toy-example – Marat Talipov Feb 17 '15 at 03:16
  • It looks like he's using RenderUI and uiOutput...I am not using these functions. – tumultous_rooster Feb 17 '15 at 04:01
  • Do you have a particular reason not to use them? – Marat Talipov Feb 17 '15 at 04:04
  • A few reasons...I don't want to those controls in the interface because the number of plots is not something to be a variable -- I want it to be fixed, yet different depending on the needs of the user. I also have so much other cutomization stuff going on for a user that a config file makes way too much sense. – tumultous_rooster Feb 17 '15 at 05:08
  • Another difference between me and the link you provided, is it looks like he's displaying all the plots on one renderPlot, whereas I have these tabPages to contend with... – tumultous_rooster Feb 17 '15 at 05:15

1 Answers1

1

You can use this solution (I modified only the shinyServer part of your scripts, so I don't list the repeating code here):

 shinyServer(function(input, output) {

 observe(
     lapply(seq(3),function(i) output[[paste0("plot",i)]] <- renderPlot(list_of_ggplots[[i]]))
 )

 })

Of course, you can replace 3 by a variable.

Marat Talipov
  • 13,064
  • 5
  • 34
  • 53
  • Thanks for the answer. It works perfectly. Except now I'm wondering what to do with my reactive functions I've defined. Not sure where they need to live now that observe has entered the picture. – tumultous_rooster Feb 17 '15 at 06:11
  • You mean the definitions like `output$plot1` that you had in your server.R? There is no need in keeping them, as they are defined within `observe` now (but it is well possible that I've misunderstood you) – Marat Talipov Feb 17 '15 at 06:17
  • no...I had some functions that would subset the dataframe if necessary, that were defined immediately after shinyServer(function(input, output) and were called in the renderPlot. Is there any reason renderPlot won't take these functions at it exists right now? Maybe I'm missing a curly brace or two. – tumultous_rooster Feb 17 '15 at 06:23
  • 1
    Yeah, I removed some curly brackets, you might want to put them back, e.g. `renderPlot({subset_your_data; plot_subsetted})` – Marat Talipov Feb 17 '15 at 06:30