Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Place a chart in plotly popup

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.

like image 217
Adam_G Avatar asked Apr 20 '16 21:04

Adam_G


3 Answers

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.

Export R data frame to JS enviornment

Shiny uses jsonlite to convert R objects to json and send them to the client. We can use the same mechanism to pack and send our data frame so that the JS callback can use the data to render the popup chart.

server.r

output$json <- reactive({
  paste('<script>data =', RJSONIO::toJSON(your_data_frame, byrow=T, colNames=T),'</script>')

ui.r

fluidPage(..., htmlOutput("json"), ...)

In the JS callback function, you can use data as any other JS objects.

More detail goes here and here.

like image 182
gdlmx Avatar answered Nov 07 '22 11:11

gdlmx


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

like image 30
MLavoie Avatar answered Nov 07 '22 13:11

MLavoie


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 (CustomJS.from_py_func(callback)) 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, linked to the slider below
    x, y = data['x'], data['y']
    for i in range(len(x)):
        y[i] = x[i]**f #the slider value passed to this function as f will alter chart as a function of x and y
    source.trigger('change') #as the slider moves, the chart will 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://docs.bokeh.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://docs.bokeh.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 - some playing around required. You can get the hover to alter a graph next to it (hform(leftchart, rightchart) or above / below it (vform(topchart, bottomchart)). This is passed as CustomJS which bokeh uses to allow extendability and flexx allows you to write it in Python.

The alternative is to put whatever you want customized on the hover tooltips using HTML (although this example is placing images in dictionaries instead of new plots from the underlying data): http://docs.bokeh.org/en/0.10.0/docs/user_guide/tools.html#custom-tooltip

like image 29
Matt Avatar answered Nov 07 '22 13:11

Matt