Speed concerns, renderHighchart() vs renderUI()

Problem

I am working with the highcharter package. In a shiny application I am creating a few highcharts and laying them out with html and css. The number of charts is variable, so the layout is not fixed. Because of this I use renderUI() instead of renderHighchart().

Recently I looked into reducing the time to generate charts and html. In my exploration I discovered renderHighchart() appears to be much faster compared to renderUI(). Please see below for a simplified reprex. I found renderHighchart() ran about 2 times faster than renderUI().

I am wondering if anyone has created a clever solution for the highcharts faceting problem? I am aware of highcharter::hw_grid() and right now use something similar.

Has anyone worked around speed concerns with renderUI() and html widgets (e.g. highcharter chart) when you could not use the corresponding render widget function?

Thank you in advance!

Reprex

library(shiny)
library(highcharter)

options(shiny.launch.browser = TRUE)

samp <- sample(5000, 100, replace = TRUE)

ui <- fluidPage(
  fluidRow(
    column(
      width = 6,
      h5("Highcharter output"),
      highchartOutput("hc_chart")
    ),
    column(
      width = 6,
      h5("UI output"),
      uiOutput("ui_chart")
    )
  )
)

server <- function(input, output) {
  output$hc_chart <- renderHighchart({
    highchart() %>%
      hc_chart(type = "column") %>%
      hc_title(text = "renderHighchart()") %>%
      hc_xAxis(categories = seq_along(samp)) %>%
      hc_add_series(
        data = samp,
        name = "Downloads"
      )
  })

  output$ui_chart <- renderUI({
    div(
      highchart() %>%
        hc_chart(type = "column") %>%
        hc_title(text = "renderUI()") %>%
        hc_xAxis(categories = seq_along(samp)) %>%
        hc_add_series(
          data = samp,
          name = "Downloads"
        )
    )
  })
}

app <- shinyApp(ui = ui, server = server)

profvis::profvis(runApp(app), interval = 0.008)

# hit escape in the R console once the application launches,
# this will open the profile

My intuition is that renderUI() is slower because you're essentially creating a complete, new HTML widget for each highchart() in the renderUI().

renderHighchart() can load the highchart dependencies and setup the htmlwidget infrastructure one time and then use this infrastructure for every htmlwidget created through renderHighchart().

But inside renderUI() the HTML could be anything, so htmlwidgets has to resolve and check if it should load the highcharts dependencies for each element (or at least once per render).

Here's an only slightly janky solution that uses renderUI() to create the highchartOutput()s and a for loop to call renderHighchart(). Profiling this example shows that, other than the static renderUI() cost, the "dynamic" highcharts in the for loop are just as fast as the single renderHighchart().

library(shiny)
library(highcharter)

options(shiny.launch.browser = TRUE)

samp <- sample(5000, 100, replace = TRUE)

ui <- fluidPage(
  actionButton("update", "Update"),
  sliderInput("n_charts", "Number of Charts", min = 1, max = 5, value = 2),
  fluidRow(
    column(
      width = 6,
      h5("Highcharter output"),
      highchartOutput("hc_chart")
    ),
    column(
      width = 6,
      h5("Manually Dynamic"),
      uiOutput("hc_dynamic")
    )
  )
)

server <- function(input, output) {
  output$hc_chart <- renderHighchart({
    input$update
    highchart() %>%
      hc_chart(type = "column") %>%
      hc_title(text = "renderHighchart()") %>%
      hc_xAxis(categories = seq_along(samp)) %>%
      hc_add_series(
        data = samp,
        name = "Downloads"
      )
  })
  
  output$hc_dynamic <- renderUI(
    tagList(
      lapply(seq_len(input$n_charts), function(n) highchartOutput(paste0("chart_", n)))
    )
  )
  
  observeEvent(input$update, {
    for (n in seq_len(input$n_charts)) {
      output[[paste0("chart_", n)]] <- renderHighchart({
        highchart() %>%
          hc_chart(type = "column") %>%
          hc_title(text = "renderHighchart()") %>%
          hc_xAxis(categories = seq_along(samp)) %>%
          hc_add_series(
            data = samp,
            name = "Downloads"
          )
      })
    }
  }, ignoreInit = FALSE, ignoreNULL = FALSE)
}

app <- shinyApp(ui = ui, server = server)
1 Like

This looks good to me! The only change Iā€™d make is to avoid the for loop and use lapply instead due to this quirk: https://gist.github.com/bborgesr/e1ce7305f914f9ca762c69509dda632e

2 Likes

A post was split to a new topic: advice on optimizing my shiny app for speed

Thank you for the response, Garrick. Your intuition sounds right to me. After blending your example and the application's current approach there were clear improvements!

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.