Displaying Image on Point Hover in Plotly

Displaying image on point hover in Plotly

Unfortunately, there is no easy way to display images on hover on plotly graphs at the moment.

If you are willing to learn some javascript, plotly's embed API allows you to customize hover (as well as click) interactivity.

Here is an example of a custom hover interaction showing images on top of a plotly graph. The javascript source code can be found here.

Plotly go: how to add an image to the hover feature?

  • you want a callback that does hover and click
    • on hover display image associated with point and full hover info
    • on click update list of clicked points and figure title
  • Assume the dataframe df has a column 'image' have created one that is a b64 encoded image
  • have inserted this into the figure by using customdata (hover_data parameter in px)
  • have added an additional div image
  • have changed callback to behave as it did before and also contents on new div. This uses b64 encoded image, extending with necessary "data:image/png;base64,"
  • need to take note of this https://dash.plotly.com/vtk/click-hover and https://dash.plotly.com/advanced-callbacks
import json
from textwrap import dedent as d
import pandas as pd
import plotly.graph_objects as go
import numpy as np
import dash
import plotly.express as px
from dash.dependencies import Input, Output
from jupyter_dash import JupyterDash
import warnings
import base64, io, requests
from PIL import Image
from pathlib import Path

warnings.simplefilter(action="ignore", category=FutureWarning)

# app info
app = JupyterDash(__name__)
styles = {"pre": {"border": "thin lightgrey solid", "overflowX": "scroll"}}

# data for whare images can be found
df_flag = pd.read_csv(
io.StringIO(
"""country,Alpha-2 code,Alpha-3 code,URL
Australia,AU,AUS,https://www.worldometers.info//img/flags/small/tn_as-flag.gif
New Zealand,NZ,NZL,https://www.worldometers.info//img/flags/small/tn_nz-flag.gif"""
)
)

# ensure that images exist on your file system...
f = Path.cwd().joinpath("flags")
if not f.exists():
f.mkdir()

# download some images and use easy to use filenames...
for r in df_flag.iterrows():
flag_file = f.joinpath(f'{r[1]["Alpha-3 code"]}.gif')
if not flag_file.exists():
r = requests.get(r[1]["URL"], stream=True, headers={"User-Agent": "XY"})
with open(flag_file, "wb") as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)

# encode
def b64image(country):
b = io.BytesIO()
im = Image.open(Path.cwd().joinpath("flags").joinpath(f"{country}.gif"))
im.save(b, format="PNG")
b64 = base64.b64encode(b.getvalue())
return b64.decode("utf-8")

df_flag["image"] = df_flag["Alpha-3 code"].apply(b64image)

# data
df = px.data.gapminder().query("continent=='Oceania'")
df = df.merge(df_flag, on="country") # include URL and b64 encoded image

# plotly figure. Include URL and image columns in customdata by using hover_data
fig = px.line(
df,
x="year",
y="lifeExp",
color="country",
title="No label selected",
hover_data={"URL": True, "image": False},
)
fig.update_traces(mode="markers+lines")

app.layout = dash.html.Div(
[
dash.dcc.Graph(
id="figure1",
figure=fig,
),
dash.html.Div(
className="row",
children=[
dash.html.Div(id="image"),
dash.html.Div(
[
dash.dcc.Markdown(d("""Hoverdata using figure references""")),
dash.html.Pre(id="hoverdata2", style=styles["pre"]),
],
className="three columns",
),
dash.html.Div(
[
dash.dcc.Markdown(
d(
"""

Full hoverdata
"""
)
),
dash.html.Pre(id="hoverdata1", style=styles["pre"]),
],
className="three columns",
),
],
),
]
)

# container for clicked points in callbacks
store = []

@app.callback(
Output("figure1", "figure"),
Output("hoverdata1", "children"),
Output("hoverdata2", "children"),
Output("image", "children"),
[Input("figure1", "clickData"), Input("figure1", "hoverData")],
)
def display_hover_data(clickData, hoverData):
# is it a click or hover event?
ctx = dash.callback_context

if ctx.triggered[0]["prop_id"] == "figure1.clickData":
traceref = clickData["points"][0]["curveNumber"]
pointref = clickData["points"][0]["pointNumber"]
store.append(
[
fig.data[traceref]["name"],
fig.data[traceref]["x"][pointref],
fig.data[traceref]["y"][pointref],
]
)
fig.update_layout(title="Last label was " + fig.data[traceref]["name"])

return fig, dash.no_update, str(store), dash.no_update
elif ctx.triggered[0]["prop_id"] == "figure1.hoverData":
# simpler case of just use a URL...
# dimg = dash.html.Img(src=hoverData["points"][0]["customdata"][0], style={"width": "30%"})
# question wanted image encoded in dataframe....
dimg = dash.html.Img(
src="data:image/png;base64," + hoverData["points"][0]["customdata"][1],
style={"width": "30%"},
)

return fig, json.dumps(hoverData, indent=2), dash.no_update, dimg
else:
return fig, "None selected", "None selected", "no image"

# app.run_server(mode='external', port = 7077, dev_tools_ui=True,
# dev_tools_hot_reload =True, threaded=True)
app.run_server(mode="inline")

R plotly: Display image on hover

The 2.0 release of plotly.js dropped d3 as a bundled dependency, so you'll need to bring that in separately now:

library(htmlwidgets)
library(magrittr)
library(plotly)

x <- 1:3
y <- 1:3

artists <- c("Bethoven", "Mozart", "Bach")

image_links <- 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"
)

# hoverinfo = "none" will hide the plotly.js tooltip, but the
# plotly_hover event will still fire
p <- plot_ly(hoverinfo = "none") %>%
add_text(x = x, y = y, customdata = image_links, text = artists) %>%
htmlwidgets::onRender(readLines("hover_tooltip.js"))

p$dependencies <- c(p$dependencies, list(d3))
p

And then you'll need you change Plotly.d3 to d3 in the JavaScript:

// hover_tooltip.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];
// Choose a location (on the data scale) to place the image
// Here I'm picking the top-left corner of the graph
var x = pt.xaxis.range[0];
var y = pt.yaxis.range[1];
// Transform the data scale to the pixel scale
var xPixel = pt.xaxis.l2p(x) + pt.xaxis._offset;
var yPixel = pt.yaxis.l2p(y) + pt.yaxis._offset;
// Insert the base64 encoded image
var img = "<img src='" + pt.customdata + "' width=100>";
tooltip.html(img)
.style("position", "absolute")
.style("left", xPixel + "px")
.style("top", yPixel + "px");
// Fade in the image
tooltip.transition()
.duration(300)
.style("opacity", 1);
});

el.on('plotly_unhover', function(d) {
// Fade out the image
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
}

Hover over images with plotly in R

Solved with this: https://plotly-r.com/embedding-images.html. It finally seems to be easier than expected to use locally stored images.

Hovering over the plot in plotly and having some images appear

You could use Plotly's hoverevents without the need for jQuery.
The event data is used to show an image based on the index of the trace (curveNumber in the code below).

var trace1 = {  x: [1, 2, 3, 4, 5],  y: [1, 6, 3, 6, 1],  mode: 'markers',  type: 'scatter',};
var trace2 = { x: [1.5, 2.5, 3.5, 4.5, 5.5], y: [4, 1, 7, 1, 4], mode: 'markers', type: 'scatter',};
var data = [trace1, trace2];


Related Topics



Leave a reply



Submit