Saturday, April 30, 2016

Place a chart in plotly popup

Leave a Comment

I'm using plotly for R, although I'm open to using the Python version, as well. When I hover over a datapoint, is there a way to make the popup contain another chart? Ideally the chart would be created from the data, although I can use a static image as a fallback.

I'm unsure where to start on this, and apologize in advance for not having an MWE.

3 Answers

Answers 1

Solution 1: Stick to R

Thanks to @MLavoie. The following example use pure R to create two plot, the "mainplot" and the "hover" which reacts to the hover event of the first one.

library(shiny) library(plotly)  ui <- fluidPage(   plotlyOutput("mainplot"),   plotlyOutput("hover") )  server <- function(input, output) {   output$mainplot <- renderPlotly({     # https://plot.ly/r/     d <- diamonds[sample(nrow(diamonds), 1000), ]     plot_ly(d, x = carat, y = price, text = paste("Clarity: ", clarity), mode = "markers", color = carat, size = carat, source="main")   })    output$hover <- renderPlotly({     eventdat <- event_data('plotly_hover', source="main") # get event data from source main     if(is.null(eventdat) == T) return(NULL)        # If NULL dont do anything     point <- as.numeric(eventdat[['pointNumber']]) # Index of the data point being charted      # draw plot according to the point number on hover     plot_ly(  x = c(1,2,3), y = c(point, point*2, point*3), mode = "scatter")   }) } shinyApp(ui, server) 

This example use the shiny binds for plotly. For every hover event, a POST request is sent to the server, then the server will update the popup-chart. It's very inefficient thus may not work well on slow connections.

The above code is just for demo, and not yet tested. See a working and much more complicated example here (with source).

Solution 2: Javascript

Yes, you can do it using the plotly Javascript API.

Short answer

  1. Create your graph using R or Python or any other supported language.
  2. Insert the graph into a new HTML page and add a callback function as shown in the example below. If you have good knowledge about DOM, you can also add the JS to the original HTML instead of creating a new one.
  3. Draw the popup graph inside the callback function which accepts parameters containing the data of the datapoint on-hover.

Details

As @MLavoie mentioned, a good example is shown in plotly.hover-events

Let's dig into the code. In the JS file, there is a simple callback function attached to Plot:

Plot.onHover = function(message) { var artist = message.points[0].x.toLowerCase().replace(/ /g, '-');  var imgSrc = blankImg; if(artistToUrl[artist] !== undefined) imgSrc = artistToUrl[artist];  Plot.hoverImg.src = imgSrc; }; 

Above, artistToUrl is a huge object filled with base64 string which I will not paste here to overflow the post. But you can see it under the JS tab of the example page. It has such structure:

var artistToUrl = { 'bob-dylan': 'data:image/jpeg;base64,/...',...} 

Working example:

For demonstration, I prepare a simple example here (click to try):

<!DOCTYPE html> <html> <head>    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> </head> <body> <iframe id="plot" style="width: 900px; height: 600px;" src="https://plot.ly/~jackp/10816.embed" seamless></iframe> <div id="myDiv"></div> <script> (function main() { var Plot = { id: 'plot', domain: 'https://plot.ly' }; Plot.onHover = function(message) {     var y = message.points[0].y; /*** y value of the data point(bar) under hover ***/     var line1 = {       x: [0.25,0.5,1],           /*** dummy x array in popup-chart ***/       y: [1/y, 2, y],            /*** dummy y array in popup-chart ***/       mode: 'lines+markers'     };     var layout = {       title:'Popup graph on hover',       height: 400,       width: 480     };     Plotly.newPlot('myDiv', [  line1 ], layout); // this finally draws your popup-chart }; Plot.init = function init() {     var pinger = setInterval(function() {         Plot.post({task: 'ping'});     }, 500);      function messageListener(e) {         var message = e.data;         if(message.pong) {             console.log('Initial pong, frame is ready to receive');             clearInterval(pinger);             Plot.post({                 'task': 'listen',                 'events': ['hover']             });         }         else if(message.type === 'hover') {             Plot.onHover(message);         }     }     window.removeEventListener('message', messageListener);     window.addEventListener('message', messageListener); }; Plot.post = function post(o) {     document.getElementById(Plot.id).contentWindow.postMessage(o, Plot.domain); };  Plot.init(); })(); </script> </body> </html> 

This is modified from the poltly.hover-events example for python. Instead of poping up an image, I change the onhover callback to plot a curve based on the y value of the each bar.

The main chart is generated by python and inserted here as iframe. You can make your own by any language including R. In this page we add a <div id="myDiv"></div> and use the plotly.js to draw the popup-chart whithin it.

Answers 2

If you want to stick with R you could use Shiny to get almost the result you want. When you hover each point an image will be render under the main plot. For the example below, I used the first three rows of the mtcars datasets. To run the code, you only need 3 logos/images corresponding to the name of the first three rows (under mtcars$name, Mazda RX4, Mazda RX4 Wag, Datsun 710 in this example).

    library(shiny)     library(plotly)      datatest <- diamonds %>% count(cut)     datatest$ImageNumber <- c(0, 1, 2, 3, 4)     datatest$name <- c("Image0", "Image1", "Image2", "Image3", "Image4")       ui <- fluidPage(   plotlyOutput("plot"),  # verbatimTextOutput("hover2"),   #imageOutput("hover"),   plotlyOutput("hover3")  )  server <- function(input, output, session) {   output$plot <- renderPlotly({   plot_ly(datatest, x = cut, y = n, type = "bar", marker = list(color = toRGB("black")))   })    selected_image <- reactive({   eventdat <- event_data('plotly_hover', source = 'A')   ImagePick <- as.numeric(eventdat[['pointNumber']])    sub <- datatest[datatest$ImageNumber %in% ImagePick, ]   return(sub)       })   # output$hover2 <- renderPrint({   #d <- event_data("plotly_hover")   #if (is.null(d)) "Hover events appear here (unhover to clear)" else d   #})   # output$hover <- renderImage({  # datag <- selected_image()   #filename <- normalizePath(file.path('/Users/drisk/Desktop/temp',         #                      paste(datag$name, '.png', sep='')))    # Return a list containing the filename and alt text  # list(src = filename,  # alt = paste("Image number", datag$name))  # }, deleteFile = FALSE)       output$hover3 <- renderPlotly({ datag <- selected_image()      # draw plot according to the point number on hover     plot_ly(data=datag,  x = ImageNumber, y = n, mode = "scatter")   })  } shinyApp(ui, server) 

enter image description here

Answers 3

Seems the answers posted aren't working for you @Adam_G. I have been exploring similar libraries for my own work and determined that Plot.ly is not always the right path when you want advanced features. Have you seen bokeh? It is basically designed for this type of task and much easier to implement (also a D3.js library like Plot.ly). Here is a copy of an example they posted where you can move a slider to change a graph of data (similar to the example posted by @gdlmx for Plot.ly but you can use it without hosting it on a website). I added the flexx package so you can use this writing pure Python (no JavaScript - it can translate Python functions to JavaScript https://github.com/zoofIO/flexx-notebooks/blob/master/flexx_tutorial_pyscript.ipynb):

from bokeh.io import vform from bokeh.models import CustomJS, ColumnDataSource, Slider from bokeh.plotting import figure, output_file, show import flexx   output_file("callback.html")  x = [x*0.005 for x in range(0, 200)] y = x  source = ColumnDataSource(data=dict(x=x, y=y))  plot = figure(plot_width=400, plot_height=400) plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)  def callback(source=source):     data = source.get('data')     f = cb_obj.get('value') #this is the bokeh callback object     x, y = data['x'], data['y']     for i in range(len(x)):         y[i] = y[i] = x[i]**f     source.trigger('change')  slider = Slider(start=0.1, end=4, value=1, step=.1, title="power", callback=CustomJS.from_py_func(callback))   layout = vform(slider, plot)  show(layout)         

See here for the actual example in action: http://bokeh.pydata.org/en/0.10.0/docs/user_guide/interaction.html#customjs-for-widgets

To integrate with hover events see here ( from bokeh.models import HoverTool): http://bokeh.pydata.org/en/0.10.0/docs/user_guide/interaction.html#customjs-for-hover

Hover example:

from bokeh.plotting import figure, output_file, show, ColumnDataSource from bokeh.models import HoverTool  output_file("toolbar.html")  source = ColumnDataSource(         data=dict(             x=[1, 2, 3, 4, 5],             y=[2, 5, 8, 2, 7],             desc=['A', 'b', 'C', 'd', 'E'],         )     )  hover = HoverTool(         tooltips=[             ("index", "$index"),             ("(x,y)", "($x, $y)"),             ("desc", "@desc"),         ]     )  p = figure(plot_width=400, plot_height=400, tools=[hover], title="Mouse over the dots")  p.circle('x', 'y', size=20, source=source)  show(p) 

Looking at the 1st code you could put whatever formula you want under the def callback function. This is passed as CustomJS which bokeh supports, but flexx allows you to write it all in Python. The alternative is to put whatever you want customized on the toolbar using HTML (although again, this example is placing images in dictionaries instead of new plots from the underlying data): http://bokeh.pydata.org/en/0.10.0/docs/user_guide/tools.html#custom-tooltip

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment