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)
How can I integrate the above code in Shiny
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)
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)