Search code examples
rshinyplotlyc3.jsgauge

Is there a way to animate a gauge in R, according to specific values from a vector?


I am trying to create an animated gauge chart that changes according to specific values defined in a vector. This is for a shiny app in R. I am currently using the C3 library, but there is no restraint. I do want code this in R with shiny.

The code below does something similar, but the animation runs with random values. I want to set specific values for each frame of the animation.

runApp(list(
ui = bootstrapPage(
# example use of the automatically generated output function
column(6, C3GaugeOutput("gauge1"))
),
server = function(input, output) {

    #riskList <- c(10,20,30,40,50,60,70,80,90,100)


    # reactive that generates a random value for the gauge
    value = reactive({
        invalidateLater(1000)
        round(runif(1,min=0,max=100),2)
    })

    # example use of the automatically generated render function
    output$gauge1 <- renderC3Gauge({ 
        # C3Gauge widget
        C3Gauge(value())
    })
}
))

The output should be similar to what we get when using animated charts in plotly with the frame parameter. I should have an input vector (c(10,20,30,40,50), for example), a play button and a gauge chart as output. I want the gauge to show and output of 10, then 20, then 30 and so on, once I click the button.


Solution

  • Try this approach using application state variables and a constantly re-validating gauge_value.

    library(c3)
    library(shiny)
    
    ui <- fluidPage(
      c3Output("gauge1"), 
      actionButton("start_button", "Start Engines")
    )
    
    server <- function(input, output, session) {
      gauge_breaks <- seq(0, 100, 10)
      is_animating <- FALSE
      current_index <- 1
    
      observeEvent(input$start_button, {
        current_index <<- 0
        is_animating <<- TRUE
      })
    
      gauge_value = reactive({
        invalidateLater(1000)
    
        if(is_animating)
          current_index <<- current_index + 1
    
        if(current_index == length(gauge_breaks))
          is_animating <<- FALSE
    
        data.frame(rpm = as.numeric(gauge_breaks[current_index]))
      })
    
      output$gauge1 <- renderC3({
        c3_gauge(c3(gauge_value()))
      })
    }
    

    While I do not like using the <<- operator, I found this version to be easier to understand than another version which stored the current index in a numericInput hidden inside an always hidden conditionalPanel. That approach required an isolate() in order to adhere to the invalidateLater() as opposed to updating the gauge_value() immediately every time the index changed.