Search code examples
javascriptrd3.jsplotlytooltip

Embedding tweets in D3 tooltip


I'm trying to build a custom D3 tooltip that displays a tweet widget using the copy/paste code from publish.twitter.com.

I'm setting up plots using plotly in R and would like a tweet widget to be displayed when clicking on the respective data point (using this example tweet here). I have managed to use the approach described here to display image tooltips and have tried to adapt it to show tweet widgets. However, while the text of the tweet, the twitter handle and date of the tweet are displayed on clicking, the tweet widget is not (Tweet text is displayed, widget isn't.. The 'widgets.js' script source is provided in the copy/paste text from twitter, but seems to fail, leaving only the tweet text etc. to be displayed.

Here is a minimal example in R:

# Load packages

library(plotly)
library(htmlwidgets)
library(htmltools)
library(magrittr)


# Define text and position
x <- 1 
y <- 1

label <- c("Click here for tweet")

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

# Define plot, provide .js file with custom tweet tooltip & add D3 dependency
p <- plot_ly() %>%
  add_text(x = x, y = y, text = label) %>%
  htmlwidgets::onRender(readLines("tweet_tooltip.js")) 

p$dependencies <- c(p$dependencies, list(d3))

# Render plot
p

Where 'tweet_tooltip.js' contains the following function:


// tweet_tooltip.js
// Adapted from https://stackoverflow.com/questions/71601216/r-plotly-display-image-on-hover

function(el) {
    
    // Define tooltip
    var tooltip = d3.select('#' + el.id + ' .svg-container')
      .append("div")
      .attr("class", "my-custom-tooltip");
      
    el.on('plotly_click', function(d) {
      
      var pt = d.points[0];
        
      // Choose a location (on the data scale) to place the image: upper left corner
      var x = pt.xaxis.range[0];
      var y = pt.yaxis.range[1];
      
      
      // 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;
      
      // Define tweet variable with twitter blockquote/script
      var tweet = '<blockquote class="twitter-tweet"><p lang="en" dir="ltr">How much is it?</p>&mdash; Elon Musk (@elonmusk) <a href="https://twitter.com/elonmusk/status/943902052542849024?ref_src=twsrc%5Etfw">December 21, 2017</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>'
      
      // Add to tooltip & define position
      tooltip.html(tweet)
       .style("position", "absolute")
       .style("left", xPixel + "px")
       .style("top", yPixel + "px");
      
      tooltip

    });
}



Solution

  • This still seems clunky to me (so there is likely a better answer out there). You're not going to be able to add an iframe within the Plotly SVG, but you can make it look like you did. Additionally, if you aren't careful, because it's a widget, as is Plotly, you'll overwrite the plot.

    You will not see the Twitter Widget in the preview pane in RStudio. You have to send the plot in your browser. If you're not sure how to do that, let me know.

    enter image description here

    If you use my code, you don't need D3. However, despite the differences, the only thing that truly changed is where the Twitter widget element is placed. I added the tooltip element after the all Plotly content, but still within the htmlwidget that houses Plotly.

    In this code, I didn't make the js code a separate file, it's in my R script. Since it's in an R script you'll see things like \' instead of ' so that R knew that these were nested quotations. With a separate JS file, you would not do that. For example, in the creation of tooltip in my code, you'll see var tooltip = $("<div class=\'my-custom... In a separate JS script, it would be written as var tooltip = $("<div class='my-custom...

    Here's the code I used.

    plot_ly() %>%
      add_text(x = x, y = y, text = label) %>%
      htmlwidgets::onRender(
        'function(el) {
          // Define tooltip
          var tooltip = $("<div class=\'my-custom-tooltip\'></div>").insertAfter("#js-plotly-tester");
          
          el.on("plotly_click", function(d) {
            var pt = d.points[0];
            
            // Choose a location (on the data scale) to place the image: upper left corner
            var x = pt.xaxis.range[0];
            var y = pt.yaxis.range[1];
            
            // 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;
            
            // Define tweet variable with twitter blockquote/script
            var tweet = "<blockquote class=\'twitter-tweet\'><p lang=\'en\' dir=\'ltr\'>How much is it?</p>&mdash; Elon Musk (@elonmusk) <a href=\'https://twitter.com/elonmusk/status/943902052542849024?ref_src=twsrc%5Etfw\'>December 21, 2017</a></blockquote> <script async src=\'https://platform.twitter.com/widgets.js\' charset=\'utf-8\'></script>";
        
            // Add to tooltip & define position
            tooltip.html(tweet)
              .css({position: "absolute", left: xPixel + "px", top: yPixel + "px"});
            
            tooltip
          });
      }')
    

    There is a slight delay between when you trigger the Twitter Widget and when it shows up (less than a second).