How to display image on point hover of 3D plot in python?

Viewed 843

I'd like for an image to display at the point I'm hovering over in the 3D plot (shown below) or next it. sHow can I do this? Currently, I'm using plotly to create 3D plots like the one below. It has the hover feature but only for text. Is there some other python program that allows for images to be displayed instead of text?

Thanks. enter image description here

The following code is in response to Derek's comment

Traceback (most recent call last):
  File "C:\Users\User\anaconda3\lib\site-packages\ipykernel_launcher.py", line 16, in <module>
    app.launch_new_instance()
  File "C:\Users\User\anaconda3\lib\site-packages\traitlets\config\application.py", line 663, in launch_instance
    app.initialize(argv)
  File "<decorator-gen-125>", line 2, in initialize
  File "C:\Users\User\anaconda3\lib\site-packages\traitlets\config\application.py", line 87, in catch_config_error
    return method(app, *args, **kwargs)
  File "C:\Users\User\anaconda3\lib\site-packages\ipykernel\kernelapp.py", line 567, in initialize
    self.init_sockets()
  File "C:\Users\User\anaconda3\lib\site-packages\ipykernel\kernelapp.py", line 271, in init_sockets
    self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
  File "C:\Users\User\anaconda3\lib\site-packages\ipykernel\kernelapp.py", line 218, in _bind_socket
    return self._try_bind_socket(s, port)
  File "C:\Users\User\anaconda3\lib\site-packages\ipykernel\kernelapp.py", line 194, in _try_bind_socket
    s.bind("tcp://%s:%i" % (self.ip, port))
  File "zmq/backend/cython/socket.pyx", line 550, in zmq.backend.cython.socket.Socket.bind
  File "zmq/backend/cython/checkrc.pxd", line 26, in zmq.backend.cython.checkrc._check_rc
zmq.error.ZMQError: Address in use
1 Answers

According to this thread, displaying images on point hover isn't possible in plotly-python but might be built out in the future.

However, you can accomplish this in plotly-dash because this library allows you to modify the html using callbacks.

Following the example here, I created some sample data and a go.Scatter3d figure that matches your use case a bit more:

from dash import Dash, dcc, html, Input, Output, no_update
import plotly.graph_objects as go
import pandas as pd

## create sample random data
df = pd.DataFrame({
    'x': [1,2,3],
    'y': [2,3,4],
    'z': [3,4,5],
    'color': ['red','green','blue'],
    'img_url': [
        "https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/Stack_Overflow_logo.svg/2880px-Stack_Overflow_logo.svg.png",
        "https://upload.wikimedia.org/wikipedia/commons/3/37/Plotly-logo-01-square.png",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ed/Pandas_logo.svg/2880px-Pandas_logo.svg.png"
    ]
})

fig = go.Figure(data=[
    go.Scatter3d(
        x=df['x'], 
        y=df['y'], 
        z=df['z'],
        mode='markers',
        marker=dict(color=df['color'])
    )
])

# turn off native plotly.js hover effects - make sure to use
# hoverinfo="none" rather than "skip" which also halts events.
fig.update_traces(hoverinfo="none", hovertemplate=None)
fig.update_layout(
    scene = dict(
        xaxis = dict(range=[-1,8],),
                     yaxis = dict(range=[-1,8],),
                     zaxis = dict(range=[-1,8],),
    ),
)
app = Dash(__name__)

app.layout = html.Div([
    dcc.Graph(id="graph-basic-2", figure=fig, clear_on_unhover=True),
    dcc.Tooltip(id="graph-tooltip"),
])


@app.callback(
    Output("graph-tooltip", "show"),
    Output("graph-tooltip", "bbox"),
    Output("graph-tooltip", "children"),
    Input("graph-basic-2", "hoverData"),
)
def display_hover(hoverData):
    if hoverData is None:
        return False, no_update, no_update

    # demo only shows the first point, but other points may also be available
    pt = hoverData["points"][0]
    bbox = pt["bbox"]
    num = pt["pointNumber"]

    df_row = df.iloc[num]
    img_src = df_row['img_url']

    children = [
        html.Div([
            html.Img(src=img_src, style={"width": "100%"}),
        ], style={'width': '100px', 'white-space': 'normal'})
    ]

    return True, bbox, children

if __name__ == "__main__":
    app.run_server(debug=True)

EDIT: I am also including an answer that allows you to run a dash app from a jupyter notebook based on this sample notebook:

from jupyter_dash import JupyterDash
from dash import Dash, dcc, html, Input, Output, no_update
import plotly.graph_objects as go
import pandas as pd

## create sample random data
df = pd.DataFrame({
    'x': [1,2,3],
    'y': [2,3,4],
    'z': [3,4,5],
    'color': ['red','green','blue'],
    'img_url': [
        "https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/Stack_Overflow_logo.svg/2880px-Stack_Overflow_logo.svg.png",
        "https://upload.wikimedia.org/wikipedia/commons/3/37/Plotly-logo-01-square.png",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ed/Pandas_logo.svg/2880px-Pandas_logo.svg.png"
    ]
})

fig = go.Figure(data=[
    go.Scatter3d(
        x=df['x'], 
        y=df['y'], 
        z=df['z'],
        mode='markers',
        marker=dict(color=df['color'])
    )
])

# turn off native plotly.js hover effects - make sure to use
# hoverinfo="none" rather than "skip" which also halts events.
fig.update_traces(hoverinfo="none", hovertemplate=None)
fig.update_layout(
    scene = dict(
        xaxis = dict(range=[-1,8],),
                     yaxis = dict(range=[-1,8],),
                     zaxis = dict(range=[-1,8],),
    ),
)

app = JupyterDash(__name__)

server = app.server

app.layout = html.Div([
    dcc.Graph(id="graph-basic-2", figure=fig, clear_on_unhover=True),
    dcc.Tooltip(id="graph-tooltip"),
])


@app.callback(
    Output("graph-tooltip", "show"),
    Output("graph-tooltip", "bbox"),
    Output("graph-tooltip", "children"),
    Input("graph-basic-2", "hoverData"),
)
def display_hover(hoverData):
    if hoverData is None:
        return False, no_update, no_update

    # demo only shows the first point, but other points may also be available
    pt = hoverData["points"][0]
    bbox = pt["bbox"]
    num = pt["pointNumber"]

    df_row = df.iloc[num]
    img_src = df_row['img_url']

    children = [
        html.Div([
            html.Img(src=img_src, style={"width": "100%"}),
        ], style={'width': '100px', 'white-space': 'normal'})
    ]

    return True, bbox, children

app.run_server(mode="inline")

enter image description here

Related