rr-highcharter

How to use base64 in highcharteR in R?


Let's say we have the following matrix:

m <- matrix(1:12, 3, 4, dimnames=list(LETTERS[1:3], LETTERS[1:4]))

and we plot that using highcharteR:

library(highcharter)
hchart(head(m, -1), "bubble", hcaes(),
       showInLegend = FALSE)

we could than replace the labels of the x-axis by images, e.g. for A:

hchart(head(m, -1), "bubble", hcaes(),
       showInLegend = FALSE) %>% 
  hc_xAxis(type = 'category',
           labels = list(
             formatter = JS(
               "function(){
                  if(this.value == 'A'){
                    return '<img src=\"https://www.highcharts.com/samples/graphics/sun.png\"></img>';
                  } else {
                    return this.value;
                  }
               }"
             ),
             useHTML = TRUE
           ))

That will give us:a picture of the highcharter output

But how do we get the same result with base64 instead of url? Is there any way to embedd the image within the html highcharteR produces?

I tryed converting the image to base64 and embedding it into the chart:

image_path <- "https://www.highcharts.com/samples/graphics/sun.png"
image_data <- base64enc::dataURI(file=image_path, mime="image/png")


hchart(head(m, -1), "bubble", hcaes(),
       showInLegend = FALSE) %>% 
  hc_xAxis(type = 'category',
           labels = list(
             formatter = JS(
               sprintf(
                 "function(){
                    if(this.value == 'A'){
                      return '<img src=\"%s\"></img>';
                    } else {
                      return this.value;
                    }
                 }",
                 image_data
               )
             ),
             useHTML = TRUE
           ))
  
  

But that gives me no image. The output is blank and html (in the output) at this position is just: <img>


Solution

  • The problem is that highcharts.js is verifying the html produced by such functions as labels.formatter. In particular, the src attribute has to start with one of the values contained in Highcharts.AST.allowedReferences, see that in the source code.

    A simple solution would be to set the src separately, immediately after the current javascript sequence of instruction is completed, using setTimeout. That means replacing

    return '<img src=\"%s\"></img>';
    

    with

    setTimeout(function(){document.getElementById('img_data1').src = '%s'}, 0);
    return '<img id=\"img_data1\"></img>';
    

    A more canonical solution would be to alter Highcharts.AST.allowedReferences by adding "data:" as an allowed prefix. While that can be done in the formatter itself:

    function(){
        if(this.value == 'A'){
            if(Highcharts.AST.allowedReferences.indexOf('data:') === -1){
                Highcharts.AST.allowedReferences.push('data:')
            }
            return '<img src=\"%s\"></img>';
        } else {
            return this.value;
        }
    }
    

    it would be more efficient to do the change to allowedReferences only once, for instance by prepending that javascript statement to the htmlwidget produced by highcharter (or by other initialization hooks specific to your application):

    library(highcharter);
    library(htmlwidgets);
    library(htmltools);
    
    m <- matrix(1:12, 3, 4, dimnames=list(LETTERS[1:3], LETTERS[1:4]))
    image_path <- "https://www.highcharts.com/samples/graphics/sun.png"
    image_data <- base64enc::dataURI(file=image_path, mime="image/png")
    
    initCode <- HTML("<script>Highcharts.AST.allowedReferences.push('data:')</script>")
    
    hchart(head(m, -1), "bubble", hcaes(),
           showInLegend = FALSE) %>% 
      prependContent(initCode) %>%     
      hc_xAxis(type = 'category',
               labels = list(
                 formatter = JS(
                   sprintf(
                     "function(){
                        if(this.value == 'A'){
                          return '<img src=\"%s\"></img>';
                        } else {
                          return this.value;
                        }
                     }",
                     image_data
                   )
                 ),
                 useHTML = TRUE
               ))