Search code examples
rhighchartsshinyrcharts

Add highcharts plotband after render in R shiny/rcharts


I'm trying to replicate this (jsfiddle) highcharts functionality in an app running rCharts and shiny. I want a user event (changing a value in a text box) to add a plotband to an already rendered plot (or ideally remove previous/add a new one). I can make it work by redrawing the plot with plotband in the xAxis options, but that can be really slow for some of the plots I'm drawing.

I'm not sure if the functionality is there in the r packages, but is there a way to drop into javascript to execute the function?

Here's a minimal working example:

server.R

library(shiny)
library(plyr)
library(rCharts)
shinyServer(function(input, output,session){
  output$h1chart <- renderChart({
    h1 <- rCharts::Highcharts$new()
    h1$chart(type = "spline")
    h1$series(data = c(1, 3, 2, 4, 5, 4, 6, 2, 3, 5, NA), dashStyle = "longdash")
    h1$series(data = c(NA, 4, 1, 3, 4, 2, 9, 1, 2, 3, 4), dashStyle = "shortdot")
    h1$legend(symbolWidth = 80)
    h1$xAxis(title = list(text = "Test Plot"),startOnTick=TRUE,min=1,max=9,endOnTick=TRUE,
          plotBands = list(list(from=1.5,to=3.5,color='rgba(68, 170, 213, 0.1)',label=list(text="1",style=list(color="#6D869F"),verticalAlign="bottom"))))
    h1$set(dom="h1chart")
    return(h1)
  })
  output$selectedOut <- renderUI({
    numericInput("selected", "", value=2.5)
  })
  output$windowOut <- renderUI({    
    sliderInput(inputId="window",label="Window size around selected point:",min=1,max=5,value=2)
  })
})#end server

ui.R:

library(shiny)
library(plyr)
library(rCharts)
shinyUI(pageWithSidebar(
  headerPanel(""),
  sidebarPanel(
    wellPanel(
      h6("Change here should update plotband:"),
      uiOutput("selectedOut"),
      uiOutput("windowOut")
    ),
    tags$head(tags$style(type="text/css", ".jslider { max-width: 245px; }"),tags$style(type='text/css', ".well { max-width: 250px; }"),
          tags$style(type='text/css', ".row-fluid .span4 {width: 20%}\n.row-fluid .span8 {width: 75%}"))
  ),
  mainPanel(showOutput("h1chart","highcharts"))  
))

I can capture changes to the numeric box/slider with this function:

addPB <- reactive({
  center <- as.numeric(input$selected[[1]])
  winHigh <- center+input$window[1]
  winLow <- center-input$window[1] #eventually I would use winLow/winHigh to change the plotband range
  list(winLow=winLow,winHigh=winHigh)
})
observe(print(addPB()$winLow))

And I've tried building a javascript function to run them, but I don't if this is how to access the highchart object with javascript or how to get the code to execute from shiny.

#!function(){$('#container').highcharts().xAxis[0].addPlotBand({from: 2,to: 4, color: 'rgba(68, 170, 213, 0.1)', label: 'asdf'}); } !#

Solution

  • You can do this by using message passing in Shiny. Here is how it works. Whenever you change the data in the slider or the numericInput, a message is sent to the server using session$sendCustomMessage along with a json payload, which in this case is band.

    On the server side, the message is received by Shiny.addCustomMessageHandler. We access the existing band, remove it, and then create a new band using the options received by the handler.

    For a more detailed overview of message passing in Shiny, I would recommend reading this excellent blog post by Mark Heckmann

    Add the following code to server.R and ui.R

    # addition to server.R
    observe({
        center <- as.numeric(input$selected[[1]])
        winHigh <- center + input$window[1]
        winLow <- center - input$window[1]
        #eventually I would use winLow/winHigh to change the plotband range
        band = list(from = winLow, to = winHigh, color = "rgba(68, 170, 213, 0.1)")
        print(band)
        session$sendCustomMessage(type = "customMsg", band)
    })
    
    # addition to ui.R
    mainPanel(
      showOutput("h1chart","highcharts"),
      tags$script('Shiny.addCustomMessageHandler("customMsg", function(bandOpts){
         chartXAxis = $("#h1chart").highcharts().xAxis[0]
         chartXAxis.removePlotBand()
         chartXAxis.addPlotBand(bandOpts)
       })')
    )