I'm following these 2 great examples for how to display an image when hovering over a point in R plotly: 1 and 2. I'm able to make them work just fine, until I make the plot into a map. Then the approach breaks. Could anyone help modify it to work with a plotly map?
library(dplyr)
library(plotly)
df <- data.frame(x = c(-81.026, -81.025, -81.028), y = c(44.13, 44.14, 44.15),
url = c("https://upload.wikimedia.org/wikipedia/commons/6/6f/Beethoven.jpg",
"https://upload.wikimedia.org/wikipedia/commons/4/47/Croce-Mozart-Detail.jpg",
"https://upload.wikimedia.org/wikipedia/commons/6/6a/Johann_Sebastian_Bach.jpg"))
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);
});
}
'
This works and displays an image on hover
p <-plot_ly(df, x = ~x, y = ~y, type = "scatter", mode = "markers", hoverinfo = ~x, customdata = ~url) %>%
htmlwidgets::onRender(js)
p$dependencies <- c(p$dependencies, list(d3))
p
This does not...
p <- plot_ly(df, lon = ~x, lat = ~y, type = "scattermapbox", mode = "markers", hoverinfo = ~x, customdata = ~url) %>%
layout(mapbox = list(style = "white-bg", sourcetype = 'raster', zoom = 4,
center = list(lon = -81 ,lat= 44),
layers = list(list(below = 'traces', sourcetype = "raster",
source = list("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"))))) %>%
htmlwidgets::onRender(js)
p$dependencies <- c(p$dependencies, list(d3))
p
The reason it doesn't work is the structure of Plotly changes significantly with maps. While the idea of x and y axes is obviously still present, Plotly does not associate axes with maps.
I've provided 2 solutions for you. The first is basically an adaptation of the original onRender
. The second modifies it a bit more.
In the original format, when you zoom into the plot/map, the image moves out of viewable space. With the second option, it will look the same as the first plot if you don't zoom. If you zoom in, you'll see that the image remains visible regardless of the zoom position.
The first adaptation of the object js
js <- '
function(el) {
var tooltip = d3.select("#" + el.id + " .svg-container") /* identify container */
.append("div")
.attr("class", "my-custom-tooltip");
el.on("plotly_hover", function(d) { /* add content on hover */
pt = d.points[0];
img = "<img src=\\\"" + pt.customdata + "\\\" width=100>"; /* image in hover */
/* the position of the map on the webpage */
bx = document.querySelector("div.mapboxgl-map").getBoundingClientRect();
tooltip.html(img)
.style("position", "absolute")
.style("left", bx.left + "px") /* assign left and top to match map position*/
.style("top", bx.top + "px");
tooltip.transition()
.duration(300)
.style("opacity", 1);
})
el.on("plotly_unhover", function(d) { /* collapse image when tooltip collapses */
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
}
'
The second adaptation of the object js
, so the image is viewable when zooming/panning, etc.
js2 <- '
function(el) {
var tooltip = d3.select("#" + el.id + " .svg-container") /* identify container */
.append("div")
.attr("class", "my-custom-tooltip");
el.on("plotly_hover", function(d) { /* add content on hover */
pt = d.points[0];
console.log(pt);
img = "<img src=\\\"" + pt.customdata + "\\\" width=100>"; /* image in hover */
/* the position of the map on the webpage */
bx = document.querySelector("div.mapboxgl-map").getBoundingClientRect();
var h = window.innerHeight; /* get viewers screen size */
var w = window.innerWidth;
tooltip.html(img) /* calculate % left/right map to screen position */
.style("position", "absolute")
.style("left", (bx.left/w * 100) + "vw")
.style("top", (bx.top/h * 100) + "vh");
tooltip.transition()
.duration(300)
.style("opacity", 1);
})
el.on("plotly_unhover", function(d) { /* collapse image when tooltip collapses */
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
}
'
Let me know if you have any questions.