1

I am writing a small shiny app to interactively display filtered data. I want to animate the transition in the data and in the axis bounds. No matter what I do I can't get the axis bounds to animate smoothly. Does anyone know how to do this?

# herd testing shiny app
version <- "v0.2"

library(shiny)
library(shinyjs)
library(readr)
library(dplyr)
library(stringr)
library(plotly)
library(purrr)

# notin function
"%notin%" <- function(x,y)!("%in%"(x,y))

# avoid as.numeric coercion warnings
as_numeric <- function(x, default=NA_real_){
  suppressWarnings(if_else(is.na(as.numeric(x)), default, as.numeric(x)))
}
as_integer <- function(x, default=NA_integer_){
  suppressWarnings(if_else(is.na(as.integer(x)), default, as.integer(x)))
}

# range including zero and handling NA
zrange <- function(x){
  c(min(c(0, x), na.rm=TRUE), max(c(0, x), na.rm=TRUE))
}

# test data for reprex
data <- data.frame(
  herd = rep(LETTERS, each=10),
  year = rep(2010:2019, times=26),
  count = sample(c(NA, 0:10), 260, TRUE),
  percent = sample(c(NA, 0:10), 260, TRUE)/100
)
herds <- unique(data$herd)
herds1 <- sample(herds, 1)

# some colours
zzgreen <- "#69BE28"
zzblue <- "#009AA6"

ui <- fluidPage(

  cat("run ui function\n"),

  theme = shinythemes::shinytheme("spacelab"), # kinda similar to DairyNZ and plotly
  align="center",

  # https://www.w3schools.com/css/default.asp
  fluidRow(
    column(3,
           strong("Select Herd:", style="font-size: 14px;"),
           br(""),
           textInput("herd", label="Enter Herd Code:", value=herds1)
    ),
    column(9,
           align="left",
           strong("Herd Tests:", style="font-size: 14px;"),
           plotlyOutput("count_plot", height="auto"),
           strong("DNA Verified:", style="font-size: 14px;"),
           plotlyOutput("perc_plot", height="auto")
    )
  ),

  fluidRow(
    align="right",
    em(version)
  )

) # fluidPage

server <- function(input, output, session){

  cat("run server function\n")

  my <- reactiveValues(
    herd = herds1,
    frame = 0,
    data = filter(data, herd==herds1),
    speed = 500,
    plist = list()
  ) # reactiveValues

  observeEvent(input$herd, {
    req(input$herd %in% herds)
    my$herd <- input$herd
    my$frame <- my$frame + 1
    cat("new herd", input$herd, "new frame", my$frame, "calc plist\n")
    # filter data
    my$data <- data %>%
      filter(herd==my$herd)
    print(my$data)
    # get existing list
    pl <- my$plist
    # herd test count data
    pl[[1]] <- list(x=my$data$year,
                    y=my$data$count,
                    frame=my$frame,
                    name = "Herd Test Count",
                    showlegend=TRUE,
                    color=I(zzblue),
                    type="scatter",
                    mode="lines+markers")
    # percent DNA verified data
    pl[[2]] <- list(x=my$data$year,
                    y=my$data$percent*100,
                    frame=my$frame,
                    name = "Percent Verified",
                    showlegend=TRUE,
                    color=I(zzgreen),
                    type="scatter",
                    mode="lines+markers")
    # https://plot.ly/r/multiple-axes/
    # herd test count axis
    pl[[3]] <- list(
      title = list(text=my$herd),
      xaxis=list(title=list(text="<b>Year</b>"),
                 tick0=min(my$data$year),
                 dtick=1,
                 range=range(my$data$year),
                 zeroline=FALSE,
                 type="linear"),
      yaxis=list(title=list(text="<b>Herd Test Count</b>"),
                 zeroline=TRUE,
                 range=zrange(my$data$count),
                 type="linear"))
    cat("range", zrange(my$data$count), "\n")
    # percent DNA verified axis
    pl[[4]] <- list(
      xaxis=list(title=list(text="<b>Year</b>"),
                 tick0=min(my$data$year),
                 dtick=1,
                 range=range(my$data$year),
                 zeroline=FALSE,
                 type="linear"),
      yaxis=list(title=list(text="<b>Percent Verified</b>"),
                 zeroline=TRUE,
                 range=zrange(my$data$percent*100),
                 type="linear"))
    cat("range", zrange(my$data$percent*100), "\n")
    # animation options
    pl[[5]] <- list(frame=my$speed,
                    transition=my$speed,
                    redraw=FALSE,
                    mode="next")
    pl[[6]] <- list(frame=0,
                    transition=0,
                    redraw=FALSE,
                    mode="next")
    my$plist <- pl
  })

  output$count_plot <- renderPlotly({
    cat("initial count_plot\n")
    isolate({
      # https://stackoverflow.com/questions/39019212/suppress-plotly-warnings-in-shiny-app
      store_warn <- getOption("warn"); options(warn=-1)
      pl <- my$plist
      p <- plot_ly()
      p <- do.call(add_trace, prepend(pl[[1]], list(p)))
      p <- do.call(layout, prepend(pl[[3]], list(p)))
      p <- do.call(animation_opts, prepend(pl[[5]], list(p)))
      # restore warnings, delayed so plot is completed
      shinyjs::delay(100, options(warn=store_warn))
      p
    })
  }) # renderPlotly

  count_plot_proxy <- plotlyProxy("count_plot", session=session)

  output$perc_plot <- renderPlotly({
    cat("initial perc_plot\n")
    isolate({
      # https://stackoverflow.com/questions/39019212/suppress-plotly-warnings-in-shiny-app
      store_warn <- getOption("warn"); options(warn=-1)
      pl <- my$plist
      p <- plot_ly()
      p <- do.call(add_trace, prepend(pl[[2]], list(p)))
      p <- do.call(layout, prepend(pl[[4]], list(p)))
      p <- do.call(animation_opts, prepend(pl[[5]], list(p)))
      # restore warnings, delayed so plot is completed
      shinyjs::delay(100, options(warn=store_warn))
      p
    })
  }) # renderPlotly

  perc_plot_proxy <- plotlyProxy("perc_plot", session=session)

  observeEvent(my$herd, {
    cat("new herd", my$herd, "update plots\n")
    pl <- my$plist
    # plotlyProxyInvoke(count_plot_proxy, "animate",
    #                   list(
    #                     name = as.character(my$frame),
    #                     layout = pl[[3]]
    #                   ),
    #                   pl[[5]]
    # )
    plotlyProxyInvoke(count_plot_proxy, "animate",
                      list(
                        name = as.character(my$frame),
                        data = pl[1],
                        traces = as.list(as.integer(0)),
                        layout = pl[[3]]
                      ),
                      pl[[5]]
    )
    # plotlyProxyInvoke(count_plot_proxy, "relayout",
    #                   update = pl[3])
    # plotlyProxyInvoke(perc_plot_proxy, "animate",
    #                   list(
    #                     name = as.character(my$frame),
    #                     layout = pl[[4]]
    #                   ),
    #                   pl[[5]]
    # )
    plotlyProxyInvoke(perc_plot_proxy, "animate",
                      list(
                        name = as.character(my$frame),
                        data = pl[2],
                        traces = as.list(as.integer(0)),
                        layout = pl[[4]]
                      ),
                      pl[[5]]
    )
    # plotlyProxyInvoke(count_plot_proxy, "relayout",
    #                   update = pl[3])
  }) # observeEvent

} # server

# run app
shinyApp(ui, server)

Thanks so much for your help, I am adding extra text here so that SO allows me to post this.

Simon Woodward
  • 1,946
  • 1
  • 16
  • 24

0 Answers0