7

I have made a Shiny app using different plotting solutions to render graphs from ggplot2 on Shiny (my favorite being plotly).
I like the fact that the user can interact with the graph: with plotly the user can zoom on the graph or click on points (on a scatterplot for exemple) and access their values.

I would like to link each point on my scatterplot to a URL (without displaying it) and to allow the user to click on a dot, which would trigger the activation of the hyperlink opening a new webpage.

If I could use plotly to do that that would be amazing but I am open to any other kind of technologies (ggvis,dygraph, etc.).

jeandut
  • 2,471
  • 4
  • 29
  • 56
  • I found these lines of code : `library(ggvis) iris %>% ggvis(~Sepal.Width, ~Petal.Length, shape=~Species) %>% layer_points() %>% add_tooltip(html=function(df) paste0('', df$Species, ' (wikipedia)'), on="click")` – jeandut Aug 17 '15 at 21:24
  • which seem to produce the result I mentionned but does anyone know more on the topic ? (possibly a plotly implementation) – jeandut Aug 17 '15 at 21:26

1 Answers1

13

Shiny apps use plotly's postMessage API or plotly.js, both of which expose click, hover, and zoom events. These events aren't yet exposed as callbacks to the underlying shiny server, but they are accessible with custom javascript that you can serve yourself in shiny.

Here's an example with click events:

Adding click events to graphs in Shiny

ui.R

library(shiny)
library(plotly)

shinyUI(fluidPage(
  mainPanel(
    plotlyOutput("trendPlot"),
    tags$head(tags$script(src="clickhandler.js"))
  )
))

server.R

library(shiny)
library(plotly)

x = c(1, 2, 3)
y = c(4, 2, 4)
links = c("https://plot.ly/r/", 
          "https://plot.ly/r/shiny-tutorial", 
          "https://plot.ly/r/click-events")

df = data.frame(x, y, links)

shinyServer(function(input, output) {

  output$trendPlot <- renderPlotly({
    # Create a ggplot
    g = ggplot(data=df, aes(x = x, y = y)) + geom_point()
    # Serialize as Plotly's graph universal format
    p = plotly_build(g)
    # Add a new key, links, that JS will access on click events
    p$data[[1]]$links = links
    p

    # Alternatively, use Plotly's native syntax. More here: https://plot.ly/r
    # plot_ly(df, x=x,y=y,links=links)
  })
})

www/clickhandler.js

$(document).ready(function(){
// boiler plate postMessage plotly code (https://github.com/plotly/postMessage-API)
var plot = document.getElementById('trendPlot').contentWindow;

pinger = setInterval(function(){
    plot.postMessage({task: 'ping'}, 'https://plot.ly')
}, 100);

var clickResponse = function(e) {
     plot = document.getElementById('trendPlot').contentWindow;
    var message = e.data;
     console.log( 'New message from chart', message );
    if(message.pong) {
        // tell the embedded plot that you want to listen to click events
        clearInterval(pinger);
        plot.postMessage({
              task: 'listen', events: ['click']}, 'https://plot.ly');
          plot.postMessage({
            task: 'relayout',
            'update': {hovermode: 'closest'},
        },
        'https://plot.ly');
    }
    else if(message.type === 'click') {
        var curveNumber = message['points'][0]['curveNumber'],
            pointNumber = message['points'][0]['pointNumber'];

        var link;
        var traces = message.points[0].data;
        if(traces !== null && typeof traces === 'object') {
            link = traces.links[pointNumber];
        } else {
            link = traces[curveNumber].links[pointNumber];
        }

        console.log(link);

        var win = window.open(link, '_blank');
        win.focus();
    }
};

window.addEventListener("message", clickResponse, false);

});

Here are some more resources that might be helpful:

Chris P
  • 2,888
  • 21
  • 22
  • When I try to implement this, `document.getElementById('trendPlot')` has no `contentWindow` attribute. (`Uncaught TypeError: Cannot read property 'postMessage' of undefined`). Any idea why this might happen? – Hillary Sanders Jan 06 '16 at 18:26
  • @HillarySanders The given code builds the plot inside a `
    ...
    ` Element. And those do not have a `contentWindow` property. As it seems, these on click events are only available if your plot is wrapped in an `iframe`. But so far i don't see any possibility to let Shiny create one of those...
    – K. Rohde Feb 02 '16 at 11:55
  • @ChrisP : great answer! can you please take a look at my question if you have some time? https://stackoverflow.com/questions/66699881/interactive-plots-in-r thank you! – stats_noob Mar 21 '21 at 17:12