Using R plotly to show an image when hovering over a location on a map

After successfully implementing the technique of displaying an image on hover in a regular R plotly chart, I encountered issues when trying to apply the same method to a plotly map. Specifically, the approach broke down and failed to display the image. Can anyone assist in modifying the code to make it 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);
  });
}
'

The above code successfully 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

However, the following code fails to display the image on a plotly map...

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

Answer №1

The reason why it's not functioning as expected is due to the structural changes in Plotly when dealing with maps. While the concept of x and y axes still exists, Plotly does not connect axes to maps.

I have presented two solutions for you. The first one is essentially an adjustment of the original onRender. The second solution involves a bit more modification.

In the initial setup, if you zoom into the plot/map, the image shifts out of viewable space. However, with the second option, the appearance will remain consistent with the first plot when there is no zooming. But when you do zoom in, the image will stay visible regardless of the zoom level.

The first adaptation of the object js

js <- '
function(el) {
  var tooltip = d3.select("#" + el.id + " .svg-container")  /* identifying the container */
    .append("div")
    .attr("class", "my-custom-tooltip");

  el.on("plotly_hover", function(d) {                     /* adding content on hover */ 
      pt = d.points[0];
      img = "<img src=\\\"" + pt.customdata + "\\\" width=100>";  /* displaying image on hover */ 
                                        /* determining the map position on the webpage */ 
      bx = document.querySelector("div.mapboxgl-map").getBoundingClientRect();
      tooltip.html(img)
        .style("position", "absolute")
        .style("left", bx.left + "px") /* setting left and top to align with map position*/
        .style("top", bx.top + "px");
      tooltip.transition()         
        .duration(300)
        .style("opacity", 1);
  })
  el.on("plotly_unhover", function(d) {  /* collapsing image when tooltip collapses */
      tooltip.transition()
      .duration(500)
      .style("opacity", 0);
  });
}
'

https://i.sstatic.net/cUgT4.png

The second adaptation of the object js, ensuring the image remains visible while zooming/panning, etc.

js2 <- '
function(el) {
  var tooltip = d3.select("#" + el.id + " .svg-container")  /* identifying the container */
    .append("div")
    .attr("class", "my-custom-tooltip");

  el.on("plotly_hover", function(d) {                     /* adding content on hover */ 
      pt = d.points[0];
      console.log(pt);
      img = "<img src=\\\"" +  pt.customdata + "\\\" width=100>";  /* displaying image on hover */ 
                                        /* determining the map position on the webpage */ 
      bx = document.querySelector("div.mapboxgl-map").getBoundingClientRect();
      var h = window.innerHeight;       /* getting viewer's screen size */
      var w = window.innerWidth;
      tooltip.html(img)             /* calculating % left/right position relative to the screen */
        .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) {  /* collapsing image when tooltip collapses */
      tooltip.transition()
      .duration(500)
      .style("opacity", 0);
  });
}
'

https://i.sstatic.net/7v4Us.png

If you have any questions, feel free to ask.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Cannot see the created item in Rails application when using RSpec, Capybara, Selenium, and JavaScript

Currently, I am in the process of developing a web store. The key functionality is already implemented where all products are displayed on one screen along with the list of ordered items. Whenever a product is selected for ordering, it should instantly app ...

Exploring data trends using R for visualization

I've imported a CSV file which includes the following data: 10,130,A,100,1000 10,130,B,200,-200 10,130,C,300,1200 20,140,A,120,1050 20,140,B,220,-300 20,140,C,320,1250 30,120,A,145,1130 30,120,B,255,1000 30,120,C,355,1110 ... For every 10 increment ...

Toggle the visibility of an element using a checkbox in JavaScript

In my scenario, there are 8 checkboxes where only one is checked by default. If you click on the unchecked checkboxes twice, the table linked to them will hide. Ideally, I want the unchecked checkboxes to be hidden by default and the checked one to be sh ...

Having trouble receiving a JSON array after making an Ajax request

I have read through previous posts on this issue, but I still can't seem to figure it out. Hopefully, someone here can help me solve this problem. My challenge lies in retrieving an array, which I have encoded in json, from a PHP script and passing i ...

Issue encountered while managing login error messages: http://localhost:3000/auth/login net::ERR_ABORTED 405 (Method Not Allowed)

I am working on the /app/auth/login/route.ts file. import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { cookies } from 'next/headers' import { NextResponse } from 'next/server' export async functi ...

"Executing Javascript event onmouseUp without needing a click and hold

I've implemented an event listener for onMouseUp. I'm trying to distinguish between a quick click (short time duration) and an onMouseUp event that occurs after holding the mouse for more than one second OR with onMouseDown while moving the mous ...

Display PHP output for a brief moment

Here is the URL: http://www.example.com/?req=welcome. In PHP, to retrieve the parameter req, you can use the following code: echo $_GET['req']; The message will be displayed in the body but it should disappear after one second. What is the be ...

What is preventing us from setting a child's width and height to match that of the parent element?

Parent element is a div with a width of 300px and a height of 40px, containing a child input. In the following code snippet: myinput = document.getElementById("e2"); myinput.style.cssText ='width:100%;height:100%;padding:0px;margin:0px;' div{ ...

RequireJS - Enabling the loading of multiple module instances

I am working on a custom RequireJS plugin that needs to create a new object instance every time it is called. For illustration purposes, consider the following: define("loader", { load: function(name, req, onload, config) { var instance = GlobalGet ...

Achieving functionality with dropdown menus in jQuery

I am facing an issue with a dropdown menu that works perfectly in jsFiddle during testing, but does not function as expected when I run it on my testing server. jsFiddle: http://jsfiddle.net/cyberjo50/39bu8/2/ HTML <!doctype html> <html> < ...

How can AngularJS focus on the last element using ng-repeat and md-focus?

, there is a code snippet that utilizes AngularJS ng-repeat directive to iterate through 'items' and 'md-autofocus' attribute to focus on the last element. The code is contained within an md-dialog with vertical scrolling functionality. ...

Please explain this ES6 syntax to me: Using a colon after a function call

While exploring the documentation for a flux store in React, I came across an example that caught my attention. import {ReduceStore} from 'flux/utils'; class CounterStore extends ReduceStore<number> { getInitialState(): number { ret ...

Trigger an email notification in Google Sheets whenever a designated cell is populated

I am currently exploring ways to enhance collaboration in project management, especially when dealing with numerous stakeholders (Although there are free tools available, integrating new procedures into a public organization's projects can be quite ch ...

Is the Okta SDK compatible with all identity providers?

I am looking to incorporate a wide range of Identity providers into my app, such as Auth0 SSO OIDC, Onelogin SSO OIDC, Google SSO OIDC, and others. Is it possible to use this solution to make that happen? https://github.com/okta/okta-auth-js ...

Ways to extract innerHTML content from a loaded element generated by using the .load() method

Check out the code snippet below: template.html : <div id="nav"> Welcome to Space </div> layout.html : <div id="content"> </div> $('#content').load("template.html #nav"); ale ...

Knockout Observable Array causing UI to freeze and not refresh properly

I recently started using knockout and am exploring the use of observable arrays to track changes from the UI. The initial data is loaded into the array and I'm attempting to dynamically add new objects to the array from another screen. Although I hav ...

Create a custom Angular directive that allows you to replace tags while inserting the template

I have created a custom directive that can take templates based on the attribute provided. Check out the Plnkr example JS var app = angular.module('app', []); app.directive('sample', function($compile){ var template1 = '<d ...

Sending data to SOAP API using AngularJS

I've been struggling to send data to a SOAP API but have hit a roadblock. I've attempted various methods but continue to encounter errors when making the API call. The API URL is - and it requires data in XML format like <?xml version="1.0" ...

How to create a loop in Selenium IDE for selecting specific values from a drop-down list?

Is there a way to use Selenium IDE and JavaScript to test a drop-down box with specific items, not all of them, and continuously loop through until the entire list is covered? Any tips or recommendations on how to accomplish this? ...

How do I handle the error "Uncaught TypeError: Cannot read property 'func' of undefined in React JS

Hey there, I've encountered an issue while setting up a date picker on my project. I tried using these resources: https://github.com/Eonasdan/bootstrap-datetimepicker Would appreciate any help! https://codesandbox.io/s/18941xp52l render() { ...