3D Scatter plot with surface plot

I am trying to plot 3D scatter plot over the surface plot in the same figure. Using the below code lines, only surface plot is shown and the scatter plot is missing.

from matplotlib import cm
import plotly
import plotly.graph_objs as go

layout = go.Layout(
    margin=dict(l=80, r=80, t=100, b=80),title='Chasing global Minima')
    
fig = go.Figure(data=[go.Surface(x = xcoordinates, y = ycoordinates,z=Z)],layout=layout)

fig.update_traces(contours_z=dict(show=True, usecolormap=True,
                                  highlightcolor="limegreen", project_z=True))

fig.add_scatter3d(x=[xcoordinates], y=[ycoordinates],z = Z, mode='markers')

# fig.add_trace(go.Scatter3d(x=[xcoordinates], y=[ycoordinates],z = Z, mode='markers',
#     marker=dict(
#         size=12,
#         color=xcoordinates,                # set color to an array/list of desired values
#         colorscale='Viridis',   # choose a colorscale
#         opacity=0.8)))

plotly.offline.iplot(fig)

@saran A surface is defined by a meshgrid:

x = np.linspace(-1, 1, 70)
X, Y = np.meshgrid(x, x)
Z = X*Y**2-X**2 + Y**3

in this case the scatter3d trace must be defined by X.flatten(), Y.flatten(), Z.flatten(), and the marker_size=2 or 3. With a size of 12, the 3d points are indistinguishable. They cover the surface completely.

layout = go.Layout(width = 700, height =700,
                             title_text='Chasing global Minima')
fig = go.Figure(data=[go.Surface(x = X, y = Y, z=Z, colorscale = 'Blues')], layout=layout)

fig.update_traces(contours_z=dict(show=True, usecolormap=True,
                                  highlightcolor="limegreen", project_z=True))

fig.add_scatter3d(x=X.flatten(), y=Y.flatten(), z = Z. flatten(), mode='markers', 
                  marker=dict(size=2, color=X.flatten(),               
                              colorscale='Reds'))

2 Likes

Thanks! Thatโ€™s really helpful!

Is there a way to update the scatter3D component via a callback using add_scatter3d?

The surface data I have is static, but the scatter3D data varies depending on what column you choose so I want to update it, but add_scatter3d doesnโ€™t work with a callback โ€œAttributeError: โ€˜dictโ€™ object has no attribute โ€˜add_scatter3dโ€™โ€.

Got it to work :smiley:

I can plot and overlay a scatter3d and surface plot on top of one another using two different data sets.

I can also update each data set with new data for both plot types individually, and clear both individually.

Example in case anyone else needs to know how to update and clear 3D plots in a similar method.

Screenshot from 2021-07-06 17-00-58

import dash
from dash.dependencies import Output, Input, State
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
from dash.exceptions import PreventUpdate
import numpy as np
import pandas as pd

# Read data from a csv
z_surf = 50*pd.read_csv(
    'https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv')
# Create second surface data set
z_surf_neg = -1*z_surf

# Create scatter3D points
x = np.linspace(0, 20, 70)
X, Y = np.meshgrid(x, x)
z_scat = X*Y**2-X**2 + Y**3
# Create second scatter3D data set
z_scat_neg = -1*z_scat

layout = go.Layout(width = 700, height =700,
    title_text='Test example')

fig = go.Figure()

# Predefined empty surface and scatter3D plots
fig.add_surface(name='surf_p', z=[])
fig.add_scatter3d(name='scat_p', x=[], y=[], z=[], mode='markers',
    marker=dict(size=1, color=X.flatten(),colorscale='Reds'))

# Predefine surface and scatter3D plots with data
# fig.add_surface(name='surf_p', z=z_surf)
# fig.add_scatter3d(name='scat_p', x=X.flatten(), y=Y.flatten(), z=z_scat.flatten(), mode='markers',
#     marker=dict(size=1, color=X.flatten(),colorscale='Reds'))

app = dash.Dash(__name__)
app.layout = html.Div(
    [
        # Plot figure
        dcc.Graph(id='graph', figure=fig),
        # Buttons to update graph
        html.Button(id='surf-button', n_clicks=0, children='Surf Pos'),
        html.Button(id='scat3D-button', n_clicks=0, children='Scatter3D Pos'),
        html.Button(id='surf-button-neg', n_clicks=0, children='Surf Neg'),
        html.Button(id='scat3D-button-neg', n_clicks=0, children='Scatter3D Neg'),
        html.Button(id='surf-button-clear', n_clicks=0, children='Surf Clear'),
        html.Button(id='scat3D-button-clear', n_clicks=0, children='Scatter3D Clear')
    ]
)

@app.callback(Output('graph', 'figure'),
    [Input('surf-button', 'n_clicks'),
    Input('scat3D-button', 'n_clicks'),
    Input('surf-button-neg', 'n_clicks'),
    Input('scat3D-button-neg', 'n_clicks'),
    Input('surf-button-clear', 'n_clicks'),
    Input('scat3D-button-clear', 'n_clicks')],
    [State('graph', 'figure')])
def data_headers(surfPos, scat3DPos, surfNeg, scat3DNeg, surfClear, scat3DClear, figure):
    ctx = dash.callback_context
    if not ctx.triggered:
        print("Not triggered")
        raise PreventUpdate()
    else:
        button_id = ctx.triggered[0]['prop_id'].split('.')[0]
        # How to update figure plot data:
        # figure['data'][0] references the surface plot
        # Update figure['data'][0]['z'] to change surface data
        # figure['data'][1] references the scatter3D plot
        # Update figure['data'][1]['x']
        # Update figure['data'][1]['y']
        # Update figure['data'][1]['z']
        if button_id == "surf-button":
            print("Surf positive updated.")
            figure['data'][0]['z'] = z_surf.to_numpy()
        elif button_id == "scat3D-button":
            print("scatter3D positive updated.")
            figure['data'][1]['x'] = X.flatten()
            figure['data'][1]['y'] = Y.flatten()
            figure['data'][1]['z'] = z_scat.flatten()
        elif button_id == "surf-button-neg":
            print("Surf negative updated.")
            figure['data'][0]['z'] = z_surf_neg.to_numpy()
        elif button_id == "scat3D-button-neg":
            print("Scatter3D negative updated.")
            figure['data'][1]['x'] = X.flatten()
            figure['data'][1]['y'] = Y.flatten()
            figure['data'][1]['z'] = z_scat_neg.flatten()
        elif button_id == "surf-button-clear":
            print("Surf clear.")
            z_clear = []
            figure['data'][0]['z'] = z_clear
        elif button_id == "scat3D-button-clear":
            print("Scatter3D clear.")
            Scatter3DClear = []
            figure['data'][1]['x'] = Scatter3DClear
            figure['data'][1]['y'] = Scatter3DClear
            figure['data'][1]['z'] = Scatter3DClear
        return figure

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

Now all I need to do is work out how to handle multiple scatter3D plots? The scatter3D data will be dynamic. So I need a way of generating more than 1 scatter3D plot dynamically?