Search code examples
highchartszoominglegendr-highcharter

R Highcharter: Clever way to separate legends or multiple charts with synchronized zoom/tooltip?


My question relates to the case where I'm using highstock to plot multiple stacked y axes, such as in the last example here: http://jkunst.com/highcharter/highstock.html.

I would like to have separate legends for each of the y axes, but doing some digging it appears as though highcharts can have only one legend (Highcharts multiple legends).

Then I thought maybe I could use annotations to mimic the desired legends. But drawing the colored line segments would be messy as they would show up as series (although I guess I could just use colored text to indicate which series is which).

Is there a clever way to get around the one-legend limitation when using multiple y axes in highcharts?

If not, then if I absolutely needed to have separate legends, I would seemingly have to use multiple stacked charts. In which case, I would need to synchronize the zoom and the tooltip across those multiple charts. There's lots of info on how to do this via javascript (which I have limited experience with), but there also seems to be recurring problems getting both the zoom and the tooltip to synchronize.

I was wondering if there are any examples on how to synchronize the zoom and tooltip across multiple charts in R highcharter (I've searched high and low and haven't been able to find any)?

I'd be extremely grateful for any insight the community here might have.

library(highcharter)
library(gplots)

dates   <- seq(as.Date('2015-04-24'),as.Date('2020-04-24'),1)
nDates  <- NROW(dates)
x11     <- xts(100 * cumprod(1 + 0.0010 * (1 + 0.0002 * rnorm(nDates))), dates)
x12     <- xts(100 * cumprod(1 + 0.0012 * (1 + 0.0001 * rnorm(nDates))), dates)
x2      <- xts(100 * runif(nDates), dates)
x31     <- xts(-1 + 2 * runif(nDates), dates)
x32     <- xts( 1 - 2 * runif(nDates), dates)

glc     <- col2hex('gray65') #gridLineColor)

hc <- highchart(type = 'stock') %>% 
        hc_chart(marginLeft = 75, marginRight = 75) %>% 
        hc_title(text = 'Example', align = 'center', verticalAlign = 'top', style = list(fontWeight = 'bold', fontSize = '20px')) %>%
        hc_rangeSelector(selected = 7) %>%
        hc_navigator(series = list(color = hex_to_rgba('black',1))) %>%
        hc_xAxis(type = 'datetime') %>% 
        hc_yAxis_multiples( 
                            #yAxis = 0, plot x11 and x12 here
                            list(top = "0%",  height = '60%', opposite = FALSE, type = 'logarithmic', showLastLabel = TRUE, allowDecimals = FALSE, 
                                     labels = list(align = 'right', x = -10, format = '{value}%', distance = 0), gridLineDashStyle = 'Dot', gridLineColor = glc, startOnTick = FALSE, endOnTick = TRUE),

                            #yAxis = 1 to duplicate axis labels on opposite side    
                            list(top = "0%",  height = '60%', opposite = TRUE, type = 'logarithmic', showLastLabel = TRUE, allowDecimals = FALSE, linkedTo = 0, opposite = TRUE, 
                                labels = list(align = 'right', x =  50, format = '{value}%', distance = 0), gridLineDashStyle = 'Dot', gridLineColor = glc, startOnTick = FALSE, endOnTick = TRUE),

                            #yAxis = 2, just to separate the charts
                            list(top = '60%', height = '5%'),

                            #yAxis = 3, plot x2 here
                            list(top = '65%', height = '15%', opposite = FALSE, tickPositions = c(0, 33, 67, 100), gridLineDashStyle = 'Dot', gridLineColor = glc, showLastLabel = TRUE,
                                    labels = list(align = 'right', x= 35, format = '{value}%', distance = 0)),

                            #yAxis = 4, to duplicate the axis labels on the opposite side
                            list(top = '65%', height = '15%', linkedTo = 3, opposite = TRUE, tickPositions = c(0, 33, 67, 100), gridLineDashStyle = 'Dot', gridLineColor = glc, showLastLabel = TRUE,
                                        labels = list(align = 'right', x= 50, format = '{value}%', distance = 0)),

                            #yAxis = 5, to separate the charts
                            list(top = '80%', height = '5%'),

                            #yAxis = 6, plot x31, x32, x33 here
                            list(top = '85%', height = '15%', opposite = FALSE, tickPositions = c(-2, -1, 0, 1, 2), gridLineDashStyle = 'Dot', gridLineColor = glc, showLastLabel = TRUE,
                                    labels = list(align = 'right', x = 30, distance = 0, format = '{value:.1f}'), plotLines = list(list(color = "black", width = 2, value = 0))),

                            #yAxis = 7, to duplicate the axis labels on the opposite side
                            list(top = '85%', height = '15%', linkedTo = 6, opposite = TRUE, tickPositions = c(-2, -1, 0, 1, 2), gridLineDashStyle = 'Dot', gridLineColor = glc, showLastLabel = TRUE,
                                        labels = list(align = 'right', x = 50, distance = 0, format = '{value:.1f}'))

                          ) %>%


            #Chart 1
            hc_add_series(x11, yAxis = 0, color = 'navy',   name = 'Series 11', tooltip = list(valueDecimals = 1, valueSuffix = '%')) %>%  
            hc_add_series(x12, yAxis = 0, color = 'green',  name = 'Series 12', tooltip = list(valueDecimals = 1, valueSuffix = '%')) %>% 

            #Chart 2
            hc_add_series(x2, yAxis = 3, color = 'black', name = 'Series 2', tooltip = list(valueDecimals = 0, valueSuffix = '%')) %>%

            #Chart 3
            hc_add_series(x31,  yAxis = 6, color = 'blue',  name = 'Series 31', tooltip = list(valueDecimals = 2)) %>%
            hc_add_series(x32,  yAxis = 6, color = 'green', name = 'Series 32', tooltip = list(valueDecimals = 2)) %>%

            #Chart 'titles'
            hc_annotations(list(labels = list(  list(point = list(x = 0, y = 20),                       text = 'Chart 1',   backgroundColor = 'white', borderColor = 'white', color = 'black', style = list(fontWeight = 'bold')), 
                                                list(point = list(yAxis = 3, x = index(x)[1], y = 90),  text = 'Chart 2',   backgroundColor = 'white', borderColor = 'white', color = 'black', style = list(fontWeight = 'bold')), 
                                                list(point = list(yAxis = 7, x = index(x)[1], y = 0.9), text = 'Chart 3',   backgroundColor = 'white', borderColor = 'white', color = 'black', style = list(fontWeight = 'bold')))))

print(hc)

In the example above, ideally, one would be able to put separate legends on Chart 1 and Chart 3 where there are multiple series.


Solution

  • The simplest solution is to set legend.layout to proximate, see the example:

    library(highcharter)
    library(gplots)
    library(xts)
    
    dates   <- seq(as.Date('2015-04-24'),as.Date('2020-04-24'),1)
    nDates  <- NROW(dates)
    x11     <- xts(100 * cumprod(1 + 0.0010 * (1 + 0.0002 * rnorm(nDates))), dates)
    x12     <- xts(100 * cumprod(1 + 0.0012 * (1 + 0.0001 * rnorm(nDates))), dates)
    x2      <- xts(100 * runif(nDates), dates)
    x31     <- xts(-1 + 2 * runif(nDates), dates)
    x32     <- xts( 1 - 2 * runif(nDates), dates)
    
    glc     <- col2hex('gray65') #gridLineColor)
    
    hc <- highchart(type = 'stock') %>% 
      hc_chart(marginLeft = 150, marginRight = 75) %>% 
      hc_title(text = 'Example', align = 'center', verticalAlign = 'top', style = list(fontWeight = 'bold', fontSize = '20px')) %>%
      hc_rangeSelector(selected = 7) %>%
      hc_navigator(series = list(color = hex_to_rgba('black',1)), top = 580) %>%
      hc_xAxis(type = 'datetime') %>% 
      hc_legend(enabled = TRUE, layout = 'proximate', align = 'left', floating = FALSE) %>%
      hc_yAxis_multiples( 
        #yAxis = 0, plot x11 and x12 here
        list(top = "0%",  height = '60%', opposite = FALSE, type = 'logarithmic', showLastLabel = TRUE, allowDecimals = FALSE, 
             labels = list(align = 'right', x = -10, format = '{value}%', distance = 0), gridLineDashStyle = 'Dot', gridLineColor = glc, startOnTick = FALSE, endOnTick = TRUE),
    
        #yAxis = 1 to duplicate axis labels on opposite side    
        list(top = "0%",  height = '60%', opposite = TRUE, type = 'logarithmic', showLastLabel = TRUE, allowDecimals = FALSE, linkedTo = 0, opposite = TRUE, 
             labels = list(align = 'right', x =  50, format = '{value}%', distance = 0), gridLineDashStyle = 'Dot', gridLineColor = glc, startOnTick = FALSE, endOnTick = TRUE),
    
        #yAxis = 2, just to separate the charts
        list(top = '60%', height = '5%'),
    
        #yAxis = 3, plot x2 here
        list(top = '65%', height = '15%', opposite = FALSE, tickPositions = c(0, 33, 67, 100), gridLineDashStyle = 'Dot', gridLineColor = glc, showLastLabel = TRUE,
             labels = list(align = 'right', x= 35, format = '{value}%', distance = 0)),
    
        #yAxis = 4, to duplicate the axis labels on the opposite side
        list(top = '65%', height = '15%', linkedTo = 3, opposite = TRUE, tickPositions = c(0, 33, 67, 100), gridLineDashStyle = 'Dot', gridLineColor = glc, showLastLabel = TRUE,
             labels = list(align = 'right', x= 50, format = '{value}%', distance = 0)),
    
        #yAxis = 5, to separate the charts
        list(top = '80%', height = '5%'),
    
        #yAxis = 6, plot x31, x32, x33 here
        list(top = '85%', height = '15%', opposite = FALSE, tickPositions = c(-2, -1, 0, 1, 2), gridLineDashStyle = 'Dot', gridLineColor = glc, showLastLabel = TRUE,
             labels = list(align = 'right', x = 30, distance = 0, format = '{value:.1f}'), plotLines = list(list(color = "black", width = 2, value = 0))),
    
        #yAxis = 7, to duplicate the axis labels on the opposite side
        list(top = '85%', height = '15%', linkedTo = 6, opposite = TRUE, tickPositions = c(-2, -1, 0, 1, 2), gridLineDashStyle = 'Dot', gridLineColor = glc, showLastLabel = TRUE,
             labels = list(align = 'right', x = 50, distance = 0, format = '{value:.1f}'))
    
      ) %>%
    
    
      #Chart 1
      hc_add_series(x11, yAxis = 0, color = 'navy',   name = 'Series 11', tooltip = list(valueDecimals = 1, valueSuffix = '%')) %>%  
      hc_add_series(x12, yAxis = 0, color = 'green',  name = 'Series 12', tooltip = list(valueDecimals = 1, valueSuffix = '%')) %>% 
    
      #Chart 2
      hc_add_series(x2, yAxis = 3, color = 'black', name = 'Series 2', tooltip = list(valueDecimals = 0, valueSuffix = '%')) %>%
    
      #Chart 3
      hc_add_series(x31,  yAxis = 6, color = 'blue',  name = 'Series 31', tooltip = list(valueDecimals = 2)) %>%
      hc_add_series(x32,  yAxis = 6, color = 'green', name = 'Series 32', tooltip = list(valueDecimals = 2))
    
    
    print(hc)
    

    Unfortunately, there is a bug with this layout in Highstock. The navigator is placed incorrectly - I reported this bug here: https://github.com/highcharts/highcharts/issues/13392

    As a workaround, I suggest moving the navigator using navigator.top property, but it is not a perfect solution. We could change the core code and write the logic that would place the navigator correctly and automatically, but it requires some custom JavaScript coding.

    If this solution doesn't meet your requirements, you could always render your legend items using Highcharts SVG Renderer tool, but it also requires some custom coding. You can find many examples of custom legend on official Highcharts Forum, StackOverflow, or you can see this article I wrote about Renderer usage: https://support.highcharts.com/support/solutions/articles/44001706971-how-to-use-highcharts-svg-renderer