Projection of 3D Surface in Python/v3

How to project 3D Surface plots in 2D with Plotly.


Note: this page is part of the documentation for version 3 of Plotly.py, which is not the most recent version.
See our Version 4 Migration Guide for information about how to upgrade.

New to Plotly?¶

Plotly's Python library is free and open source! Get started by downloading the client and reading the primer.
You can set up Plotly to work in online or offline mode, or in jupyter notebooks.
We also have a quick-reference cheatsheet (new!) to help you get started!

Projections of a 3d surface onto planes¶

This example will demonstrate how to create heatmaps of projections of a 3d surface onto planes perpendicular to the z, x, respectively y-direction. Usually surfaces in the 3d space are colored with a colormap associated to the normalized range of the z coordinates of points on that surface. Recently, Plotly devised a method to color a surface according to a custom color function.

Namely, if $x, y, z$ are numpy arrays of shape (m, n), defined by a discretization (via a meshgrid) of a surface z=f(x,y) or in parametric form, $x=x(u,v), y=y(u,v), z=z(u,v)$, then a custom function, Color(x,y,z), returns a numpy array C, of the same shape as z, and the surface is colored by a colormap, according to the values in C.

This method allows to project a surface onto planes perpendicular to the z, x or y-direction in the 3d space and interpret the projection as a planar surface colored according to the z, x or y value at each point of the coresponding plane.

First, define the surface and its discretization:

In [1]:
import plotly.plotly as py
import plotly.graph_objs as go

import numpy as np

xx=np.linspace(-3.5, 3.5, 100)
yy=np.linspace(-3.5, 3.5, 100)
x,y=np.meshgrid(xx, yy)
z=np.exp(-(x-1)**2-y**2)-10*(x**3+y**4-x/5)*np.exp(-(x**2+y**2))

Color according to normalized z-values¶

In [2]:
colorscale=[[0.0, 'rgb(20,29,67)'],
           [0.1, 'rgb(28,76,96)'],
           [0.2, 'rgb(16,125,121)'],
           [0.3, 'rgb(92,166,133)'],
           [0.4, 'rgb(182,202,175)'],
           [0.5, 'rgb(253,245,243)'],
           [0.6, 'rgb(230,183,162)'],
           [0.7, 'rgb(211,118,105)'],
           [0.8, 'rgb(174,63,95)'],
           [0.9, 'rgb(116,25,93)'],
           [1.0, 'rgb(51,13,53)']]

Add hover text for the surface:¶

In [3]:
textz = [['x: '+'{:0.5f}'.format(x[i][j])+'<br>y: '+'{:0.5f}'.format(y[i][j])+
        '<br>z: '+'{:0.5f}'.format(z[i][j]) for j in range(z.shape[1])] for i in range(z.shape[0])]

trace1= go.Surface(
    x=tuple(x),
    y=tuple(y),
    z=tuple(z),
    colorscale=colorscale,
    text=textz,
    hoverinfo='text',
)

Set Plot Layout:¶

In [4]:
axis = dict(
showbackground=True,
backgroundcolor="rgb(230, 230,230)",
showgrid=False,
zeroline=False,
showline=False)

ztickvals=list(range(-6,4))
layout = go.Layout(title="Projections of a surface onto coordinate planes" ,
                autosize=False,
                width=700,
                height=600,
                scene=dict(xaxis=dict(axis, range=[-3.5, 3.5]),
                            yaxis=dict(axis, range=[-3.5, 3.5]),
                            zaxis=dict(axis , tickvals=ztickvals),
                            aspectratio=dict(x=1,
                                             y=1,
                                             z=0.95)
                           )
                )

Discretization of each Plane¶

The surface projections will be plotted in the planes of equations Z=np.min(z)-2, X=np.min(xx), respectively Y=np.min(yy).

In [5]:
z_offset=(np.min(z)-2)*np.ones(z.shape)#
x_offset=np.min(xx)*np.ones(z.shape)
y_offset=np.min(yy)*np.ones(z.shape)

Define the color functions and the color numpy arrays, C_z, C_x, C_y, corresponding to each plane:
Define the 3-tuples of coordinates to be displayed at hovering the mouse over the projections. The first two coordinates give the position in the projection plane, whereas the third one is used for assigning the color, just in the same way the coordinate z is used for the z-direction projection.

In [7]:
proj_z=lambda x, y, z: z#projection in the z-direction
colorsurfz=proj_z(x,y,z)
proj_x=lambda x, y, z: x
colorsurfx=proj_z(x,y,z)
proj_y=lambda x, y, z: y
colorsurfy=proj_z(x,y,z)

textx=[['y: '+'{:0.5f}'.format(y[i][j])+'<br>z: '+'{:0.5f}'.format(z[i][j])+
        '<br>x: '+'{:0.5f}'.format(x[i][j]) for j in range(z.shape[1])]  for i in range(z.shape[0])]
texty=[['x: '+'{:0.5f}'.format(x[i][j])+'<br>z: '+'{:0.5f}'.format(z[i][j]) +
        '<br>y: '+'{:0.5f}'.format(y[i][j]) for j in range(z.shape[1])] for i in range(z.shape[0])]

tracex = go.Surface(z=list(z),
                x=list(x_offset),
                y=list(y),
                colorscale=colorscale,
                showlegend=False,
                showscale=False,
                surfacecolor=colorsurfx,
                text=textx,
                hoverinfo='text'
               )
tracey = go.Surface(z=list(z),
                x=list(x),
                y=list(y_offset),
                colorscale=colorscale,
                showlegend=False,
                showscale=False,
                surfacecolor=colorsurfy,
                text=texty,
                hoverinfo='text'
               )
tracez = go.Surface(z=list(z_offset),
                x=list(x),
                y=list(y),
                colorscale=colorscale,
                showlegend=False,
                showscale=False,
                surfacecolor=colorsurfx,
                text=textz,
                hoverinfo='text'
               )

data=[trace1, tracex, tracey, tracez]
fig = go.Figure(data=data, layout=layout)
py.iplot(fig)
Out[7]: