Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

suspendWhenHidden is not sufficient to be able to render elements immediately #1409

Closed
bborgesr opened this issue Oct 6, 2016 · 9 comments · Fixed by #3740
Closed

suspendWhenHidden is not sufficient to be able to render elements immediately #1409

bborgesr opened this issue Oct 6, 2016 · 9 comments · Fixed by #3740

Comments

@bborgesr
Copy link
Contributor

bborgesr commented Oct 6, 2016

suspendWhenHidden = FALSE works great after an element has already been rendered before and it should be re-rendered eagerly, even when hidden. This can be useful when, for example, there are several tabs dependent on the same inputs and we want to re-render them all whenever these change (so the user does not notice any lag after the initial time). However, this will not work if the elements have never been rendered before (e.g. on app startup).

Reproducible example:

library(shiny)
library(ggplot2)

ui <- shinyUI(fluidPage(
tabsetPanel(
tabPanel("Daily",
plotOutput("DailyCandles")
),
tabPanel("Weekly",
plotOutput("WeeklyCandles")
)
))
)

# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {

Sims = 50000

df <- data.frame(x = 1:Sims, y= rnorm(Sims))

output$DailyCandles <- renderPlot({
ggplot(df,aes(x=x,y=y)) + geom_line()
})

output$WeeklyCandles <- renderPlot({
ggplot(df,aes(x=x,y=y)) + geom_line()
})

outputOptions(output, "WeeklyCandles", suspendWhenHidden = FALSE)

})

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

We can either make it so that suspendWhenHidden also works here, or we can create a new construct for this (e.g. using Joe's reactiveTrigger?)

Source: Mauro Herrada's comment on Disqus.

@bborgesr bborgesr added the P2 label Oct 6, 2016
@jcheng5
Copy link
Member

jcheng5 commented Oct 6, 2016

Specifically talking about plots, the plots cannot render in advance because they have no width and height. So the renderPlot does run, it just gives up before getting to the user code. I believe you can confirm this by passing an explicit width and height to the renderPlot function--then it should execute. (You'd want to do plotOutput("foo", height="auto") in this case)

If it's really important to run eagerly and maintain auto-resizing plots, then you can probably do that by using a combination of an eager reactive expression (a regular reactive expression that you set up a dedicated observer that does nothing but invoke it) that does the plotting in a png device and returns recordPlot, then your renderPlot would retrieve that value from the reactive expression and run replayPlot. I would give you sample code but I'm on my phone at the moment, let me or @wch know if you can't figure it out.

@jcheng5
Copy link
Member

jcheng5 commented Oct 6, 2016

On second thought if we felt this was a common case we could handle it in renderPlot. If we detect that the size info is zero or missing and suspendWhenHidden=FALSE we could use some default size.

@bborgesr
Copy link
Contributor Author

bborgesr commented Oct 6, 2016

@jcheng5, I think I just found a second bug because plotOutput("foo", height="auto") actually causes the plot never to render (!?) It's something about the "auto" part because this plotOutput("foo", height="300px") works fine.

However, I can confirm that exactly the same behavior (initial lag) occurs even when you specify size. E.g:

library(shiny)
library(ggplot2)

ui <- shinyUI(fluidPage(
  numericInput("nrow", "Number of obs", 50000),
  tabsetPanel(
    tabPanel("Daily",
             plotOutput("DailyCandles")
    ),
    tabPanel("Weekly",
             plotOutput("WeeklyCandles", height = "300px")
    )
  ))
)

# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
  df <- reactive({ data.frame(x = 1:input$nrow, y= rnorm(input$nrow)) })

  output$DailyCandles <- renderPlot({
    ggplot(df(),aes(x=x,y=y)) + geom_line()
  })

  output$WeeklyCandles <- renderPlot({
    ggplot(df(),aes(x=x,y=y)) + geom_line()
  })

  outputOptions(output, "WeeklyCandles", suspendWhenHidden = FALSE)
})

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

@bborgesr
Copy link
Contributor Author

bborgesr commented Oct 6, 2016

But you are right that this is a plot/image-specific issue. I.e. with a table, for example, it works fine:

library(shiny)
library(ggplot2)

ui <- shinyUI(fluidPage(
  numericInput("nrow", "Number of obs", 50000),
  tabsetPanel(
    tabPanel("Daily",
             plotOutput("DailyCandles")
    ),
    tabPanel("Weekly",
             tableOutput("WeeklyCandles")
    )
  ))
)

# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
  df <- reactive({ data.frame(x = 1:input$nrow, y= rnorm(input$nrow)) })

  output$DailyCandles <- renderPlot({
    ggplot(df(),aes(x=x,y=y)) + geom_line()
  })

  output$WeeklyCandles <- renderTable({
    head(df(), 10000)
    #ggplot(df(),aes(x=x,y=y)) + geom_line()
  })

  outputOptions(output, "WeeklyCandles", suspendWhenHidden = FALSE)
})

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

@bborgesr
Copy link
Contributor Author

bborgesr commented Oct 6, 2016

Also, for the suggested hack of using recordPlot and replayPlot, I think I didn't follow you completely. Here's my sad attempt:

library(shiny)
library(ggplot2)

ui <- shinyUI(fluidPage(
  numericInput("nrow", "Number of obs", 50000),
  tabsetPanel(
    tabPanel("Daily",
             plotOutput("DailyCandles")
    ),
    tabPanel("Weekly",
             plotOutput("WeeklyCandles", height = 300)
    )
  ))
)

# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
  df <- reactive({ data.frame(x = 1:input$nrow, y= rnorm(input$nrow)) })

  drawPlot <- reactive({
    ggplot(df(),aes(x=x,y=y)) + geom_line()
    recordPlot() 
  })
  observe(drawPlot())

  output$DailyCandles <- renderPlot({
    ggplot(df(),aes(x=x,y=y)) + geom_line()
  })

  output$WeeklyCandles <- renderPlot({
    plt <- isolate(drawPlot())
    replayPlot(plt)
  })

  outputOptions(output, "WeeklyCandles", suspendWhenHidden = FALSE)
})

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

@jcheng5
Copy link
Member

jcheng5 commented Oct 6, 2016

The initial lag should go away when you put the explicit width and height on renderPlot, not plotOutput. Sorry, still on my phone. :)

@bborgesr
Copy link
Contributor Author

bborgesr commented Oct 6, 2016

Ok, got it. This works:

library(shiny)
library(ggplot2)

ui <- shinyUI(fluidPage(
  numericInput("nrow", "Number of obs", 50000),
  tabsetPanel(
    tabPanel("Daily",
             plotOutput("DailyCandles")
    ),
    tabPanel("Weekly",
             plotOutput("WeeklyCandles")
    )
  ))
)

# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
  df <- data.frame(x = 1:50000, y= rnorm(50000))

  output$DailyCandles <- renderPlot({
    ggplot(df,aes(x=x,y=y)) + geom_line()
  })

  output$WeeklyCandles <- renderPlot({
    ggplot(df,aes(x=x,y=y)) + geom_line()
  }, height = 450, width = 600)

  outputOptions(output, "WeeklyCandles", suspendWhenHidden = FALSE)
})

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

So now, the only questions are:

  1. if we want to enable this by default (i.e. if we detect that the size info is zero or missing and suspendWhenHidden=FALSE we could use some default size)
  2. if we want to at least have an example of how you can get around this (i.e. if you wan to run eagerly and maintain auto-resizing plots, show how to use an eager reactive expression and recordPlot/replayPlot).. This could be a gallery app example or something...

@bborgesr
Copy link
Contributor Author

bborgesr commented Oct 9, 2016

We could make a simple change in renderPlot, to check for NULL size and have a default if suspendWhenHidden = FALSE. It would be similar to what we do starting in this line -- e.g:

if (is.null(width))
  width <- 500
if (is.null(height))
  height <- 400

@wch and I talked about doing this, but we worried it may break in edge cases and this is not a big enough priority to be working on it now...

@bborgesr bborgesr added P3 and removed P2 labels Oct 9, 2016
@wch wch added the backlog label Nov 17, 2016
@wch wch removed backlog labels Jun 13, 2018
cpsievert added a commit that referenced this issue Nov 21, 2022
…derPlot() early if height/width of a plot aren't yet defined
cpsievert added a commit that referenced this issue Nov 21, 2022
…derPlot() early if height/width of a plot aren't yet defined
@cpsievert
Copy link
Collaborator

#3739 didn't actually close this (#3740 will)

cpsievert added a commit that referenced this issue Dec 3, 2022
* Close #1409: don't supply width/height to the device if they aren't defined

* Update news

* Update unit tests to reflect that plotPNG()/startPNG() now handles NULL dimensions

* Add a note about NULL dimensions on plotPNG() help page

* Update news
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants