A quick zoom in shiny makes one lose control

Viewed 48

I would like to make a plot where you can zoom in and zoom out with the keyboard arrows up and down. This is what I have done. It worked pretty well, but there is still a major problem.

library(shiny)
library(dplyr)
library(ggplot2)

ui <- fluidPage(
    tags$script(
        '$(document).on("keyup", function(e) {
    if(e.keyCode == 38){
    Shiny.onInputChange("up", Math.random());
    }
    if(e.keyCode == 40){
    Shiny.onInputChange("down", Math.random());
    }
    });'
    ),
    uiOutput("whole_page")
)

server <- function (input, output, session) {
    min <- 0
    max <- 1000000
    view_size <- reactiveVal(max - min)
    view_center <- reactiveVal(mean(c(max, min)))
    position <- reactiveVal(c(min, max))
    
    observeEvent(c(view_size(), view_center()), {
        from <- (view_center() - (view_size() / 2))
        to <- (view_center() + (view_size() / 2))
        c(from, to) %>% position()
    })
    
    output$whole_page <- renderUI({
        fluidPage(
            sliderInput("slider", "range:", min = min, max= max, value = position(), step = 1),
            plotOutput("plot")
        )
    })

    output$plot <- renderPlot({
        ggplot(data = tibble(pos = position())) +
            geom_point(aes(x = pos, y = 0))
    })
    
    observeEvent(input$slider, {
        input$slider %>% position()
    })
    
    observeEvent(position(), {
        position() %>%
            mean() %>%
            view_center()
        position() %>%
            diff() %>%
            view_size()
    })
    
    observeEvent(input$up, {
        (view_size() / 2) %>%
            view_size()
    })
    
    observeEvent(input$down, {
        (view_size() * 2) %>%
            view_size()
    })
}

shinyApp(ui, server)

The remaining problem is that when you quickly click on the upper arrow several times, it makes the application crash, it starts to continuously switch from one zoom level to the other and you have to restart the application.

Can someone help me solve this problem and allow fast zooming?

Note: This is my first post on this forum, as I usually find the answers to my questions in the previously asked questions. This forum is really a goldmine for code-related questions.

3 Answers

You can use debounce to filter too quick input changes :

library(shiny)
library(dplyr)
library(ggplot2)

ui <- fluidPage(
  tags$script(
    '$(document).on("keyup", function(e) {
    if(e.keyCode == 38){
    Shiny.onInputChange("up", Math.random());
    }
    if(e.keyCode == 40){
    Shiny.onInputChange("down", Math.random());
    }
    });'
  ),
  uiOutput("whole_page")
)

server <- function (input, output, session) {
  min <- 0
  max <- 1000000
  view_size <- reactiveVal(max - min)
  view_center <- reactiveVal(mean(c(max, min)))
  position <- reactiveVal(c(min, max))
  
  observeEvent(c(view_size(), view_center()), {
    from <- (view_center() - (view_size() / 2))
    to <- (view_center() + (view_size() / 2))
    c(from, to) %>% position()
  })
  
  output$whole_page <- renderUI({
    fluidPage(
      sliderInput("slider", "range:", min = min, max= max, value = position(), step = 1),
      plotOutput("plot")
    )
  })
  
  output$plot <- renderPlot({
    ggplot(data = tibble(pos = position())) +
      geom_point(aes(x = pos, y = 0))
  })
  
  observeEvent(input$slider, {
    input$slider %>% position()
  })
  
  observeEvent(position(), {
    position() %>%
      mean() %>%
      view_center()
    position() %>%
      diff() %>%
      view_size()
  })
  
  up_d <- debounce(reactive({input$up}),500)
  down_d <- debounce(reactive({input$down}),500)
  
  observeEvent(up_d(), {
    (view_size() / 2) %>%
      view_size()
  })
  
  observeEvent(down_d(), {
    (view_size() * 2) %>%
      view_size()
  })
}

shinyApp(ui, server)

Waldi's answer is exactly what I was looking for. However, I have done other tests and I want to share this one if it can be useful to someone. I replaced the slider with a second graph like a global view and a zoomed view:

library(shiny)
library(dplyr)
library(ggplot2)

ui <- fluidPage(
    tags$script(
        '$(document).on("keyup", function(e) {
    if(e.keyCode == 38){
    Shiny.onInputChange("up", Math.random());
    }
    if(e.keyCode == 40){
    Shiny.onInputChange("down", Math.random());
    }
    });'
    ),
    uiOutput("whole_page")
)

server <- function (input, output, session) {
    min <- 0
    max <- 1000000
    view_size <- reactiveVal(max - min)
    view_center <- reactiveVal(mean(c(max, min)))
    position <- reactiveVal(c(min, max))
    
    observeEvent(c(view_size(), view_center()), {
        from <- (view_center() - (view_size() / 2))
        to <- (view_center() + (view_size() / 2))
        c(from, to) %>% position()
    })
    
    output$whole_page <- renderUI({
        fluidPage(
           plotOutput("global_plot", height = "100px"),
            plotOutput("zoom_plot", height = "100px")
        )
    })

    output$global_plot <- renderPlot({
        ggplot(data = tibble(pos = c(min, max))) +
            geom_point(aes(x = pos, y = 0))+
            geom_rect(aes(xmin = position()[1], xmax = position()[2], ymin = 0, ymax = 1))
    })
    
    output$zoom_plot <- renderPlot({
        ggplot(data = tibble(pos = c(min, max))) +
            geom_point(aes(x = pos, y = 0))+
            coord_cartesian(position())
    })
    
    observeEvent(position(), {
        position() %>%
            mean() %>%
            view_center()
        position() %>%
            diff() %>%
            view_size()
    })
    
    observeEvent(input$up, {
        (view_size() / 2) %>%
            view_size()
    })
    
    observeEvent(input$down, {
        (view_size() * 2) %>%
            view_size()
    })
}

shinyApp(ui, server)

Pressing an up/down arrow triggers an observer, which triggers an observer, which triggers an observer, .... When you quickly press an arrow two times the second arrow event is triggered before the first cycle has finished, and this causes a mess.

Here is a lighter version of your server logic, I didn't face any issue with this one:

server <- function (input, output, session) {
  min <- 0
  max <- 1000000
  position <- reactiveVal(c(min, max))
  view <- reactiveVal(list(center = mean(c(max, min)), size = max-min))
  
  observeEvent(view(), {
    from <- with(view(), center - size/2)
    to <- with(view(), center + size/2)
    updateSliderInput(session, "slider", value = c(from,to))
  }, ignoreInit = TRUE)
  
  output$whole_page <- renderUI({
    fluidPage(
      sliderInput("slider", "range:", min = min, max= max, value = c(min,max), step = 1),
      plotOutput("plot")
    )
  })
  
  output$plot <- renderPlot({
    ggplot(data = tibble(pos = position())) +
      geom_point(aes(x = pos, y = 0))
  })
  
  observeEvent(input$slider, {
    input$slider %>% position()
    list(center = mean(position()), size = diff(position())) %>% view()
  })
  
  observeEvent(input$up, {
    with(view(), list(center = center, size = size/2)) %>% view()
  })
  
  observeEvent(input$down, {
    with(view(), list(center = center, size = size*2)) %>% view()
  })
}

shinyApp(ui, server)
Related