Highlighting scatter3d selection via callback & mesh3d blocking click event

I have created a 3D scatter plot of my data. This data can be divided into labeled data and non labeled data but all belonging to one instance.

The goal was to show a 3d model of this data with a difference in color for labeled and non labeled points. I also tried to add a mesh3d to get a better feel of the model. The code can be found below

Questions

  1. I would like to highlight a selected point in my scatter3d plot. Make it bigger / change color- whatever is visible. How can this be done via callback?
  2. How can I avoid my mesh3d of blocking clickable points (labeled and non-labeled)?

Code

#3d model cluster
labeled = dict(
mode = “markers”,
name = “Labeled”,
type = “scatter3d”,
opacity = 0.5,
x = df_labeled[‘x’].values,
y = df_labeled[‘y’].values,
z = df_labeled[‘z’].values,
ids = df_labeled[‘id’].values,
text = df_labeled[‘id’].values,
hoverinfo = ‘text’,
marker = dict( size=2, color=“red” )
)

nonlabeled = dict(
mode = “markers”,
name = “Non Labeled”,
type = “scatter3d”,
opacity = 0.3,
x = df_nonlabeled[‘x’].values,
y = df_nonlabeled[‘y’].values,
z = df_nonlabeled[‘z’].values,
ids = df_nonlabeled[‘id’].values,
text = df_nonlabeled[‘id’].values,
hoverinfo = ‘text’,
marker = dict( size=4, color=“blue” )
)

clusters = dict(
alphahull = 6,
name = “mesh”,
color = ‘red’,
opacity = 0.2,
type = “mesh3d”,
x = x,
y = y,
z = z,
mode = ‘markers’,
hoverinfo = ‘skip’,
showlegend = True
)

layout = dict(
title = ‘3D Point Clustering Model’,
scene = dict(
xaxis = dict( zeroline=False ),
yaxis = dict( zeroline=False ),
zaxis = dict( zeroline=False ),
),
layout = {‘clickmode’: ‘event+select’}
)

#fig= dict( data=[labeled, nonlabeled, clusters], layout=layout ) # including mesh3d - this blocks clickable points
fig = dict( data=[labeled, nonlabeled], layout=layout )
model3d = dcc.Graph(id=“myScatter3d”,figure=fig,style={‘height’: ‘650px’, ‘width’:‘800px’})

Hi @aochtman, for the update of the scatter3d via a callback I adapted below the example of https://dash.plot.ly/interactive-graphing (I kept the div because it’s quite useful to see the information in clickData, hoverData etc).

import json
from textwrap import dedent as d

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
import numpy as np

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

styles = {
    'pre': {
        'border': 'thin lightgrey solid',
        'overflowX': 'scroll'
    }
}


fig = go.Figure(go.Scatter3d(x=np.arange(10),
                             y=np.arange(10),
                             z=np.arange(10),
                             marker_size=8 * np.ones(10),
                             mode='markers'))

app.layout = html.Div([
    dcc.Graph(
        id='basic-interactions',
        figure=fig
    ),

    html.Div(className='row', children=[
        html.Div([
            dcc.Markdown(d("""
                **Hover Data**

                Mouse over values in the graph.
            """)),
            html.Pre(id='hover-data', style=styles['pre'])
        ], className='three columns'),

        html.Div([
            dcc.Markdown(d("""
                **Click Data**

                Click on points in the graph.
            """)),
            html.Pre(id='click-data', style=styles['pre']),
        ], className='three columns'),

        html.Div([
            dcc.Markdown(d("""
                **Zoom and Relayout Data**

                Click and drag on the graph to zoom or click on the zoom
                buttons in the graph's menu bar.
                Clicking on legend items will also fire
                this event.
            """)),
            html.Pre(id='relayout-data', style=styles['pre']),
        ], className='three columns')
    ])
])


@app.callback(
    Output('hover-data', 'children'),
    [Input('basic-interactions', 'hoverData')])
def display_hover_data(hoverData):
    return json.dumps(hoverData, indent=2)


@app.callback(
    [Output('click-data', 'children'),
     Output('basic-interactions', 'figure')],
    [Input('basic-interactions', 'clickData')],
    [State('basic-interactions', 'relayoutData')])
def display_click_data(clickData, relayoutData):
    if not clickData:
        return dash.no_update, dash.no_update
    point = clickData["points"][0]
    # Do something only for a specific trace
    if point["curveNumber"] > 0:
        return dash.no_update, dash.no_update
    else: 
        fig = go.Figure(go.Scatter3d(
            x=np.arange(10),
            y=np.arange(10),
            z=np.arange(10),
            mode='markers'))
        sizes = 8 * np.ones(10)
        sizes[point["pointNumber"]] = 15
        colors = ['blue',]*10
        colors[point["pointNumber"]] = 'red'
        fig.update_traces(marker_size=sizes, marker_color=colors)
    # Make sure the view/angle stays the same when updating the figure
    if relayoutData and "scene.camera" in relayoutData:
        fig.update_layout(scene_camera=relayoutData["scene.camera"])
    return json.dumps(clickData, indent=2), fig


@app.callback(
    Output('relayout-data', 'children'),
    [Input('basic-interactions', 'relayoutData')])
def display_relayout_data(relayoutData):
    return json.dumps(relayoutData, indent=2)


if __name__ == '__main__':
    app.run_server(debug=True, port=8089)

In this example I made sure the callback for clickData does something only for one single trace so it would exclude the mesh3d but unfortunately I don’t know any way of disabling interaction with one trace.