Search code examples
rplotlyinteractive

Showing images on hover in plotly R


I am trying to create an interactive PCA plot where I want to be able to hover over each point and display an image for that point. It has been done before, however, I am unable to replicate the example.

I was wondering whether anyone has been able to replicate this example successfully using R 4.3.1?

I am currently getting this error: [WARNING] Deprecated: --self-contained. use --embed-resources --standalone.

Example link: https://plotly-r.com/supplying-custom-data

x <- 1:3 
y <- 1:3
logos <- c("r-logo", "penguin", "rstudio")
# base64 encoded string of each image
uris <- purrr::map_chr(
  logos, ~ base64enc::dataURI(file = sprintf("images/%s.png", .x)) # i created a folder called images with 3 random photos
)

d3 <- htmltools::htmlDependency(
  "d3", "7.3",
  src = c(href = "https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/"),
  script = "d3.min.js"
)

# hoverinfo = "none" will hide the plotly.js tooltip, but the 
# plotly_hover event will still fire
plot_ly(hoverinfo = "none") %>%
  add_text(x = x, y = y, customdata = uris, text = logos) %>%
  htmlwidgets::onRender(readLines("tooltip-image.js"))

tooltip-image.js file

function(el) {
  var tooltip = d3.select('#' + el.id + ' .svg-container')
    .append("div")
    .attr("class", "my-custom-tooltip");

  el.on('plotly_hover', function(d) {
    var pt = d.points[0];
    // Choose a location (on the data scale) to place the image
    // Here I'm picking the top-left corner of the graph
    var x = pt.xaxis.range[10];
    var y = pt.yaxis.range[6];
    // Transform the data scale to the pixel scale
    var xPixel = pt.xaxis.l2p(x) + pt.xaxis._offset;
    var yPixel = pt.yaxis.l2p(y) + pt.yaxis._offset;
    // Insert the base64 encoded image
    var img = "<img src='" +  pt.customdata + "' width=100>";
    tooltip.html(img)
      .style("position", "absolute")
      .style("left", xPixel + "px")
      .style("top", yPixel + "px");
    // Fade in the image
    tooltip.transition()
      .duration(300)
      .style("opacity", 1);
  });

  el.on('plotly_unhover', function(d) {
    // Fade out the image
    tooltip.transition()
      .duration(500)
      .style("opacity", 0);
  });
}
\n

Solution

  • Plotly.d3 is undefined, one has to include d3 as a HTML dependency:

    library(plotly)
    
    d3 <- htmltools::htmlDependency(
      "d3", "7.3",
      src = c(href = "https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/"),
      script = "d3.min.js"
    )
    
    js <- '
    function(el) {
      var tooltip = d3.select("#" + el.id + " .svg-container")
        .append("div")
        .attr("class", "my-custom-tooltip");
    
      el.on("plotly_hover", function(d) {
        var pt = d.points[0];
        var x = pt.xaxis.range[0];
        var y = pt.yaxis.range[1];
        var xPixel = pt.xaxis.l2p(x) + pt.xaxis._offset;
        var yPixel = pt.yaxis.l2p(y) + pt.yaxis._offset;
        var img = "<img src=\\\"" +  pt.customdata + "\\\" width=100>";
        tooltip.html(img)
          .style("position", "absolute")
          .style("left", xPixel + "px")
          .style("top", yPixel + "px");
        tooltip.transition()
          .duration(300)
          .style("opacity", 1);
      });
    
      el.on("plotly_unhover", function(d) {
        tooltip.transition()
          .duration(500)
          .style("opacity", 0);
      });
    }
    '
    
    x <- 1:3 
    y <- 1:3
    logos <- c("img1", "img2", "img3")
    # base64 encoded string of each image
    uris <- purrr::map_chr(
      logos, ~ base64enc::dataURI(file = sprintf("images/%s.png", .x))
    )
    # hoverinfo = "none" will hide the plotly.js tooltip, but the 
    # plotly_hover event will still fire
    fig <- plot_ly(hoverinfo = "none") %>%
      add_text(x = x, y = y, customdata = uris, text = logos) %>%
      htmlwidgets::onRender(js)
    
    fig$dependencies <- c(fig$dependencies, list(d3))
    
    fig
    

    enter image description here