rggplot2shinygganimate

Can gganimate be implemented in Shiny


This is my dataframe:

df <- data.frame(
      Date_Time = c(
        "2023-06-14 09:43:06",
        "2023-06-14 09:43:09",
        "2023-06-14 09:43:12",
        "2023-06-14 09:43:16",
        "2023-06-14 09:43:19"
      ),
      Specific_Heat_Capacity = c(159.65, 129.65, 190.65, 116.03, 180.71)
      
    )
df$Date_Time <- as.POSIXct(df$Date_Time, format = "%Y-%m-%d %H:%M:%S")

This is my code which generates a circle where the values inside it changes based on Date_Time using gganimate :

plot <- ggplot(df, aes(x = 0, y = 0)) +
  coord_fixed() +
  theme_void() +
  geom_point(aes(x = 0, y = 0), size = 60, shape = 21, fill = "#11141a", color = "#6cc3fa", stroke = 2) +

  geom_text(aes(x = 0, y = 0, label = paste("Specific Heat\n", Specific_Heat_Capacity)), color = "white", size = 5)+ 
  xlim(-4, 4) + ylim(-4, 4)

animated_plot <- plot +
  transition_time(Date_Time) +
  ease_aes('linear')

animate(animated_plot,   fps = 24)

Ouput: enter image description here

How can I integrate the above code in Shiny


Solution

  • Here is a way but it is slow. I'm not aware of another way.

    I made the plot reactive to the choice of the size of the circle for the illustration.

    The app saves the animation in a GIF file with gganimate::anim_save and then converts this file in base64 encoding, and then inserts the image. The slow part is anim_save.

    library(gganimate)
    library(shiny)
    library(base64enc)
    
    df <- data.frame(
      Date_Time = c(
        "2023-06-14 09:43:06",
        "2023-06-14 09:43:09",
        "2023-06-14 09:43:12",
        "2023-06-14 09:43:16",
        "2023-06-14 09:43:19"
      ),
      Specific_Heat_Capacity = c(159.65, 129.65, 190.65, 116.03, 180.71)
    )
    df$Date_Time <- as.POSIXct(df$Date_Time, format = "%Y-%m-%d %H:%M:%S")
    
    Anim <- function(size) {
      plot <- ggplot(df, aes(x = 0, y = 0)) +
        coord_fixed() +
        theme_void() +
        geom_point(
          aes(x = 0, y = 0), size = size, shape = 21, 
          fill = "#11141a", color = "#6cc3fa", stroke = 2
        ) +
        geom_text(
          aes(
            x = 0, y = 0, 
            label = paste("Specific Heat\n", Specific_Heat_Capacity)
          ), 
          color = "white", size = 5
        ) + 
        xlim(-4, 4) + ylim(-4, 4)
      
      plot +
        transition_time(Date_Time) +
        ease_aes('linear')
    }
    
    ui <- fluidPage(
      sidebarLayout(
        sidebarPanel(
          sliderInput("size", "Size", min = 40, max = 100, value = 80, step = 5)
        ),
        mainPanel(
          uiOutput("animation")
        )
      )
    )
    
    server <- function(input, output, session) {
     
      AnimB64 <- eventReactive(input[["size"]], {
        anim <- Anim(input[["size"]])
        tmpFile <- tempfile(fileext = ".gif")
        anim_save(tmpFile, anim)
        dataURI(file = tmpFile, mime = "image/gif")
      })
     
      output[["animation"]] <- renderUI({
        tags$div(
          tags$img(src = AnimB64(), width="100%"),
          style = "width: 400px;"
        )
      })
      
    }
    
    shinyApp(ui, server)
    

    Edit : a JavaScript+CSS solution

    Since this is slow with the GIF, I would rather use a JavaScript+CSS solution. Something like that:

    library(shiny)
    
    js <- '
    $(document).ready(function() {
      Shiny.addCustomMessageHandler("animation", function(numbers) {
        var n = numbers.length;
        var i = 0;
        var interval = setInterval(function() {
          $("#numb").html(numbers[i]);
          i = (i + 1) % n;
        }, 500);
        $("#btn").on("click", function() {
          clearInterval(interval);
        });
      });
    });
    '
    
    css <- '
    .circle {
      text-align: center;
      height: 135px;
      width: 135px;
      background-color: #ccc;
      border-radius: 50%;
      outline: 3px solid blue;
      display: flex;
      flex-direction:column;
      justify-content: center;
    }
    #circletitle {
      font-size: 20px;
    }
    #numb {
      font-size : 16px;
    }
    '
    
    ui <- fluidPage(
      tags$head(
        tags$script(HTML(js)),
        tags$style(HTML(css))
      ),
      
      br(),
      
      actionButton(
        "btn",
        "Generate data"
      ),
      
      br(), br(),
      
      tags$span(
        class = "circle",
        tags$p(
          id = "circletitle",
          "Specific heat"
        ),
        tags$p(
          id = "numb"
        )
      )
      
    )
    
    server <- function(input, output, session) {
      
      Numbers <- eventReactive(input[["btn"]], {
        round(cumsum(rgamma(20, 50, 1)), 2)
      })
      
      observeEvent(Numbers(), {
        session$sendCustomMessage("animation", Numbers())
      })
      
    }
    
    shinyApp(ui, server)
    

    enter image description here