Ok. This was a fun one.
Major assumption: You can iteratively call your method. Such as run_iteration()
I let the print statements in the code so that you can see what is working when. Remove them when you feel comfortable with the code.
The main trick I used was to update your data object (dt) at each step and call invalidate later to trigger the next calculation step. Invalidate later will execute on the next "tick" within shiny. This allows the plot to be printed before the next iteration is calculated.
isolate() calls were used in the calculating observe to avoid a recursive trigger of the observer. Only a change to calculate should initially trigger the observer. The subsequent triggers are caused by the invalidateLater call.
library(shiny)
library(ggplot2)
# "optimization" code
run_iteration_i <- function(n, i = 1, current_data) {
cat("running iteration: ", i, "/", n, "\n")
# make new data
new_dt <- rbind(
current_data,
data.frame(x = i, y = rnorm(1, 5, 1))
)
# return new data
new_dt
}
# UI
ui <- fluidPage(
titlePanel("Incremental plotting"),
sidebarLayout(
sidebarPanel(
sliderInput("length_i",
"Steps to plot:",
min = 20,
max = 500,
value = 30),
textOutput("status")
),
# Plot where I would like to show points being plotted as the function generates them:
mainPanel(
plotOutput("livePlot")
)
)
)
# Server
server <- function(input, output) {
# data to print
dt <- reactiveVal(NULL)
observe({str(dt())})
# plot to print
p <- reactive({
# require data to not be NULL
req(dt())
ggplot(dt(), aes(x, y)) + geom_point() + xlim(1, input$length_i)
})
# print plot
output$livePlot <- renderPlot({
cat("making plot\n")
p()
})
# bool of "is calculating"
calculate <- reactiveVal(FALSE)
# iteration position
pos <- reactiveVal()
observe({cat("pos: ", pos(), "\n")})
# when the input changes, reset everything and start calculating
observeEvent({input$length_i}, {
print("resetting")
pos(1)
dt(NULL)
calculate(TRUE)
})
# Only called when calculate() changes or invalidateLater is triggered
observe({
if (!calculate()) {
req(FALSE)
}
# do not track any reactive changes that occur in here
isolate({
# run next step
new_data <- run_iteration_i(input$length_i, pos(), dt())
dt(new_data)
# increment position
pos(pos() + 1)
})
if (pos() > input$length_i) {
# stop calculating
calculate(FALSE)
} else {
# call the method again in 0.1 seconds... gives time for plot to appear
invalidateLater(0.2 * 1000)
}
})
output$status <- renderText({
if (calculate()) {
paste0("Working on: ", pos(), "/", input$length_i)
} else {
"Ready to calculate!"
}
})
}
shinyApp(ui = ui, server = server)