Show Sidebar Hide Sidebar

3D Plots in Python

A tutorial on how to make beautiful 3d plots with plotly and Python or IPython.

Section 8: 3D Plots

Welcome to Plotly's Python API User Guide.

Links to the other sections are on the User Guide's homepage
The Github repository is available here

Quickstart (a 3d scatter plot of 3 points):

>>> import plotly.plotly as py
>>> from plotly.graph_objs import *
>>> # auto sign-in with credentials or use py.sign_in()
>>> trace1 = Scatter3d(
        x=[1,2,3],
        y=[3,4,5],
        z=[1,3,4]
    )
>>> data = Data([trace1])
>>> py.plot(data)

Check which version is installed on your machine and please upgrade if needed.

# (*) Import plotly package
import plotly

# Check plolty version (if not latest, please upgrade)
plotly.__version__
'1.6.6'

See the User Guide's homepage for more info on installation and upgrading.


In this section, we introduce Plotly's 3D plot engine. 3D plots are a new feature to Plotly and this section (in its currently form) only scratches the surface on its possibilities.

We first import a few modules and sign in to Plotly using our credential file:

# (*) To communicate with Plotly's server, sign in with credentials file
import plotly.plotly as py

# (*) Useful Python/Plotly tools
import plotly.tools as tls

# (*) Graph objects to piece together plots
from plotly.graph_objs import *

import numpy as np  # (*) numpy for math functions and arrays

If you are not familiar with credentials files, refer to the User Guide's homepage .

8.1 A simple surface plot

Let's try to make a Plotly version of this plot taken from Roberto Colistete's web page .
Big thanks for the inspiration!

The function to be plotted is:

$$ f(x,y) = A \cos(\pi x y) e^{-(x^2+y^2)/2} $$

where $A$ is some number corresponding to the amplitude of the surface. So,

# (*) Import the math functions needed in this cell
from numpy import pi, cos, exp

# Define the function to be plotted
def fxy(x, y):
    A = 1  # choose a maximum amplitude 
    return A*(cos(pi*x*y))**2 * exp(-(x**2+y**2)/2.)
# Choose length of square domain, make row and column vectors
L = 4
x = y = np.arange(-L/2., L/2., 0.1)  # use a mesh spacing of 0.1
yt = y[:, np.newaxis]  # (!) make column vector

# Get surface coordinates!
z = fxy(x, yt)

We will generate the surface using (you guessed it) the Surface graph object. As always, a good way to start using a new Plotly graph object is to call help:

help(Surface)  # call help()!
Help on class Surface in module plotly.graph_objs.graph_objs:

class Surface(PlotlyTrace)
 |  A dictionary-like object for representing a 3D surface trace in plotly.
 |
 |  Quick method reference:
 |
 |      Surface.update(changes)
 |      Surface.strip_style()
 |      Surface.get_data()
 |      Surface.to_graph_objs()
 |      Surface.validate()
 |      Surface.to_string()
 |      Surface.force_clean()
 |
 |  Valid keys:
 |
 |      z [required=True] (value=list of lists or 2d numpy array of numbers)
 |      (streamable):
 |          Sets the surface coordinates. Say the dimensions of the list of
 |          lists or 2d numpy array linked to 'z' has n rows and m columns then
 |          the resulting contour will have n coordinates along the y-axis and m
 |          coordinates along the x-axis. Therefore, the i-th row/ j-th column
 |          cell in the list of lists or 2d numpy array linked to 'z' is mapped
 |          to the i-th partition of the y-axis and the j-th partition of the
 |          x-axis
 |
 |      x [required=False] (value=list or 1d numpy array of numbers, strings,
 |      datetimes or list of lists or 2d numpy array of numbers) (streamable):
 |          Sets the horizontal coordinates referring to the columns of the list
 |          of lists or 2d numpy array linked to 'z'. If the 'z' is a list or 1d
 |          numpy array of strings, then the x-labels are spaced evenly. If the
 |          dimensions of the list of lists or 2d numpy array linked to 'z' are
 |          (n x m), the length of the 'x' array should equal m.
 |
 |      y [required=False] (value=list or 1d numpy array of numbers, strings,
 |      datetimes or list of lists or 2d numpy array of numbers) (streamable):
 |          Sets the vertical coordinates referring to the rows of the list of
 |          lists or 2d numpy array linked to 'z'. If strings, the y-labels are
 |          spaced evenly. If the dimensions of the list of lists or 2d numpy
 |          array linked to 'z' are (n x m), the length of the 'y' array should
 |          equal n.
 |
 |      name [required=False] (value=a string):
 |          The label associated with this trace. This name will appear in the
 |          column header in the online spreadsheet.
 |
 |      colorscale [required=False] (value=list or 1d numpy array of value-color
 |      pairs | 'Greys' | 'Greens' | 'Bluered' | 'Hot' | 'Picnic' | 'Portland' |
 |      'Jet' | 'RdBu' | 'Blackbody' | 'Earth' | 'Electric' | 'YIOrRd' |
 |      'YIGnBu'):
 |          Sets and/or defines the color scale for this trace. The string
 |          values are pre-defined color scales. For custom color scales, define
 |          a list or 1d numpy array of value-color pairs where, the first
 |          element of the pair corresponds to a normalized value of z from 0-1,
 |          i.e. (z-zmin)/ (zmax-zmin), and the second element of pair
 |          corresponds to a color. Use with 'zauto', 'zmin' and 'zmax to fine-
 |          tune the map from 'z' to rendered colors.
 |
 |          Examples:
 |              'Greys' | [[0, 'rgb(0,0,0)'], [0.5, 'rgb(65, 182, 196)'], [1,
 |              'rgb(255,255,255)']]
 |
 |      scene [required=False] (value='scene1' | 'scene2' | 'scene3' | etc.):
 |          This key determines the scene on which this trace will be plotted
 |          in.
 |
 |      stream [required=False] (value=Stream object | dictionary-like object):
 |          Links a dictionary-like object that initializes this trace as a
 |          writable-stream, for use with the streaming API.
 |
 |          For more, run `help(plotly.graph_objs.Stream)`
 |
 |      visible [required=False] (value=a boolean: True | False):
 |          Toggles whether or not this object will be visible on the rendered
 |          figure.
 |
 |      type [required=False] (value='surface'):
 |          Plotly identifier for this data's trace type.
 |
 |  Method resolution order:
 |      Surface
 |      PlotlyTrace
 |      PlotlyDict
 |      __builtin__.dict
 |      __builtin__.object
 |
 |  Methods inherited from PlotlyTrace:
 |
 |  __init__(self, *args, **kwargs)
 |
 |  to_string(self, level=0, indent=4, eol='\n', pretty=True, max_chars=80)
 |      Returns a formatted string showing graph_obj constructors.
 |
 |      Example:
 |
 |          print(obj.to_string())
 |
 |      Keyword arguments:
 |      level (default = 0) -- set number of indentations to start with
 |      indent (default = 4) -- set indentation amount
 |      eol (default = '\n') -- set end of line character(s)
 |      pretty (default = True) -- curtail long list output with a '...'
 |      max_chars (default = 80) -- set max characters per line
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from PlotlyDict:
 |
 |  __setitem__(self, key, value)
 |
 |  force_clean(self, caller=True)
 |      Attempts to convert to graph_objs and call force_clean() on values.
 |
 |      Calling force_clean() on a PlotlyDict will ensure that the object is
 |      valid and may be sent to plotly. This process will also remove any
 |      entries that end up with a length == 0.
 |
 |      Careful! This will delete any invalid entries *silently*.
 |
 |  get_data(self)
 |      Returns the JSON for the plot with non-data elements stripped.
 |
 |  get_ordered(self, caller=True)
 |
 |  strip_style(self)
 |      Strip style from the current representation.
 |
 |      All PlotlyDicts and PlotlyLists are guaranteed to survive the
 |      stripping process, though they made be left empty. This is allowable.
 |
 |      Keys that will be stripped in this process are tagged with
 |      `'type': 'style'` in graph_objs_meta.json.
 |
 |      This process first attempts to convert nested collections from dicts
 |      or lists to subclasses of PlotlyList/PlotlyDict. This process forces
 |      a validation, which may throw exceptions.
 |
 |      Then, each of these objects call `strip_style` on themselves and so
 |      on, recursively until the entire structure has been validated and
 |      stripped.
 |
 |  to_graph_objs(self, caller=True)
 |      Walk obj, convert dicts and lists to plotly graph objs.
 |
 |      For each key in the object, if it corresponds to a special key that
 |      should be associated with a graph object, the ordinary dict or list
 |      will be reinitialized as a special PlotlyDict or PlotlyList of the
 |      appropriate `kind`.
 |
 |  update(self, dict1=None, **dict2)
 |      Update current dict with dict1 and then dict2.
 |
 |      This recursively updates the structure of the original dictionary-like
 |      object with the new entries in the second and third objects. This
 |      allows users to update with large, nested structures.
 |
 |      Note, because the dict2 packs up all the keyword arguments, you can
 |      specify the changes as a list of keyword agruments.
 |
 |      Examples:
 |      # update with dict
 |      obj = Layout(title='my title', xaxis=XAxis(range=[0,1], domain=[0,1]))
 |      update_dict = dict(title='new title', xaxis=dict(domain=[0,.8]))
 |      obj.update(update_dict)
 |      obj
 |      {'title': 'new title', 'xaxis': {'range': [0,1], 'domain': [0,.8]}}
 |
 |      # update with list of keyword arguments
 |      obj = Layout(title='my title', xaxis=XAxis(range=[0,1], domain=[0,1]))
 |      obj.update(title='new title', xaxis=dict(domain=[0,.8]))
 |      obj
 |      {'title': 'new title', 'xaxis': {'range': [0,1], 'domain': [0,.8]}}
 |
 |      This 'fully' supports duck-typing in that the call signature is
 |      identical, however this differs slightly from the normal update
 |      method provided by Python's dictionaries.
 |
 |  validate(self, caller=True)
 |      Recursively check the validity of the keys in a PlotlyDict.
 |
 |      The valid keys constitute the entries in each object
 |      dictionary in graph_objs_meta.json
 |
 |      The validation process first requires that all nested collections be
 |      converted to the appropriate subclass of PlotlyDict/PlotlyList. Then,
 |      each of these objects call `validate` and so on, recursively,
 |      until the entire object has been validated.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from PlotlyDict:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from __builtin__.dict:
 |
 |  __cmp__(...)
 |      x.__cmp__(y) <==> cmp(x,y)
 |
 |  __contains__(...)
 |      D.__contains__(k) -> True if D has a key k, else False
 |
 |  __delitem__(...)
 |      x.__delitem__(y) <==> del x[y]
 |
 |  __eq__(...)
 |      x.__eq__(y) <==> x==y
 |
 |  __ge__(...)
 |      x.__ge__(y) <==> x>=y
 |
 |  __getattribute__(...)
 |      x.__getattribute__('name') <==> x.name
 |
 |  __getitem__(...)
 |      x.__getitem__(y) <==> x[y]
 |
 |  __gt__(...)
 |      x.__gt__(y) <==> x>y
 |
 |  __iter__(...)
 |      x.__iter__() <==> iter(x)
 |
 |  __le__(...)
 |      x.__le__(y) <==> x<=y
 |
 |  __len__(...)
 |      x.__len__() <==> len(x)
 |
 |  __lt__(...)
 |      x.__lt__(y) <==> x<y
 |
 |  __ne__(...)
 |      x.__ne__(y) <==> x!=y
 |
 |  __repr__(...)
 |      x.__repr__() <==> repr(x)
 |
 |  __sizeof__(...)
 |      D.__sizeof__() -> size of D in memory, in bytes
 |
 |  clear(...)
 |      D.clear() -> None.  Remove all items from D.
 |
 |  copy(...)
 |      D.copy() -> a shallow copy of D
 |
 |  fromkeys(...)
 |      dict.fromkeys(S[,v]) -> New dict with keys from S and values equal to v.
 |      v defaults to None.
 |
 |  get(...)
 |      D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.
 |
 |  has_key(...)
 |      D.has_key(k) -> True if D has a key k, else False
 |
 |  items(...)
 |      D.items() -> list of D's (key, value) pairs, as 2-tuples
 |
 |  iteritems(...)
 |      D.iteritems() -> an iterator over the (key, value) items of D
 |
 |  iterkeys(...)
 |      D.iterkeys() -> an iterator over the keys of D
 |
 |  itervalues(...)
 |      D.itervalues() -> an iterator over the values of D
 |
 |  keys(...)
 |      D.keys() -> list of D's keys
 |
 |  pop(...)
 |      D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
 |      If key is not found, d is returned if given, otherwise KeyError is raised
 |
 |  popitem(...)
 |      D.popitem() -> (k, v), remove and return some (key, value) pair as a
 |      2-tuple; but raise KeyError if D is empty.
 |
 |  setdefault(...)
 |      D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
 |
 |  values(...)
 |      D.values() -> list of D's values
 |
 |  viewitems(...)
 |      D.viewitems() -> a set-like object providing a view on D's items
 |
 |  viewkeys(...)
 |      D.viewkeys() -> a set-like object providing a view on D's keys
 |
 |  viewvalues(...)
 |      D.viewvalues() -> an object providing a view on D's values
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes inherited from __builtin__.dict:
 |
 |  __hash__ = None
 |
 |  __new__ = <built-in method __new__ of type object>
 |      T.__new__(S, ...) -> a new object with type S, a subtype of T


In a lot of ways, Surface works offers the same functionaliy as the Heatmap and Contour offers. So consider,

trace1 = Surface(
    z=z,  # link the fxy 2d numpy array
    x=x,  # link 1d numpy array of x coords
    y=y   # link 1d numpy array of y coords
)

# Package the trace dictionary into a data object
data = Data([trace1])
Axes in 3D plots

Axes in 3D Plotly plots work in little differently than in 2D: axes are bound to a Scene .

help(Scene)  # call help()!
Help on class Scene in module plotly.graph_objs.graph_objs:

class Scene(PlotlyDict)
 |  A dictionary-like object for representing a 3D scene in plotly.
 |
 |  Parent key:
 |
 |      scene
 |
 |  Quick method reference:
 |
 |      Scene.update(changes)
 |      Scene.strip_style()
 |      Scene.get_data()
 |      Scene.to_graph_objs()
 |      Scene.validate()
 |      Scene.to_string()
 |      Scene.force_clean()
 |
 |  Valid keys:
 |
 |      xaxis [required=False] (value=XAxis object | dictionary-like object):
 |          Links a dictionary-like object describing an x-axis of a particular
 |          3D scene.
 |
 |          For more, run `help(plotly.graph_objs.XAxis)`
 |
 |      yaxis [required=False] (value=YAxis object | dictionary-like object):
 |          Links a dictionary-like object describing an y-axis of a particular
 |          3D scene.
 |
 |          For more, run `help(plotly.graph_objs.YAxis)`
 |
 |      zaxis [required=False] (value=ZAxis object | dictionary-like object):
 |          Links a dictionary-like object describing an z-axis of a particular
 |          3D scene.
 |
 |          For more, run `help(plotly.graph_objs.ZAxis)`
 |
 |      cameraposition [required=False] (value=camera position list or 1d numpy
 |      array):
 |          Sets the camera position with respect to the scene. The first entry
 |          (a list or 1d numpy array of length 4) sets the angular position of
 |          the camera. The second entry (a list or 1d numpy array of length 3)
 |          sets the (x,y,z) translation of the camera. The third entry (a
 |          scalar) sets zoom of the camera.
 |
 |          Examples:
 |              [[0.2, 0.5, 0.1, 0.2], [0.1, 0, -0.1], 3]
 |
 |      domain [required=False] (value=domain dictionary):
 |          Sets the x-y domain of this scene on the plotting surface.
 |
 |          Examples:
 |              {'x': [0, 0.4], 'y': [0.6, 1]} | dict(x=[0, 0.4], y=[0.6, 1])
 |
 |      bgcolor [required=False] (value=a string describing color):
 |          Sets the background (bg) color of this scene (i.e. of the plotting
 |          surface and the margins).
 |
 |          Examples:
 |              'green' | 'rgb(0, 255, 0)' | 'rgba(0, 255, 0, 0.3)' |
 |              'hsl(120,100%,50%)' | 'hsla(120,100%,50%,0.3)' | '#434F1D'
 |
 |  Method resolution order:
 |      Scene
 |      PlotlyDict
 |      __builtin__.dict
 |      __builtin__.object
 |
 |  Methods inherited from PlotlyDict:
 |
 |  __init__(self, *args, **kwargs)
 |
 |  __setitem__(self, key, value)
 |
 |  force_clean(self, caller=True)
 |      Attempts to convert to graph_objs and call force_clean() on values.
 |
 |      Calling force_clean() on a PlotlyDict will ensure that the object is
 |      valid and may be sent to plotly. This process will also remove any
 |      entries that end up with a length == 0.
 |
 |      Careful! This will delete any invalid entries *silently*.
 |
 |  get_data(self)
 |      Returns the JSON for the plot with non-data elements stripped.
 |
 |  get_ordered(self, caller=True)
 |
 |  strip_style(self)
 |      Strip style from the current representation.
 |
 |      All PlotlyDicts and PlotlyLists are guaranteed to survive the
 |      stripping process, though they made be left empty. This is allowable.
 |
 |      Keys that will be stripped in this process are tagged with
 |      `'type': 'style'` in graph_objs_meta.json.
 |
 |      This process first attempts to convert nested collections from dicts
 |      or lists to subclasses of PlotlyList/PlotlyDict. This process forces
 |      a validation, which may throw exceptions.
 |
 |      Then, each of these objects call `strip_style` on themselves and so
 |      on, recursively until the entire structure has been validated and
 |      stripped.
 |
 |  to_graph_objs(self, caller=True)
 |      Walk obj, convert dicts and lists to plotly graph objs.
 |
 |      For each key in the object, if it corresponds to a special key that
 |      should be associated with a graph object, the ordinary dict or list
 |      will be reinitialized as a special PlotlyDict or PlotlyList of the
 |      appropriate `kind`.
 |
 |  to_string(self, level=0, indent=4, eol='\n', pretty=True, max_chars=80)
 |      Returns a formatted string showing graph_obj constructors.
 |
 |      Example:
 |
 |          print(obj.to_string())
 |
 |      Keyword arguments:
 |      level (default = 0) -- set number of indentations to start with
 |      indent (default = 4) -- set indentation amount
 |      eol (default = '\n') -- set end of line character(s)
 |      pretty (default = True) -- curtail long list output with a '...'
 |      max_chars (default = 80) -- set max characters per line
 |
 |  update(self, dict1=None, **dict2)
 |      Update current dict with dict1 and then dict2.
 |
 |      This recursively updates the structure of the original dictionary-like
 |      object with the new entries in the second and third objects. This
 |      allows users to update with large, nested structures.
 |
 |      Note, because the dict2 packs up all the keyword arguments, you can
 |      specify the changes as a list of keyword agruments.
 |
 |      Examples:
 |      # update with dict
 |      obj = Layout(title='my title', xaxis=XAxis(range=[0,1], domain=[0,1]))
 |      update_dict = dict(title='new title', xaxis=dict(domain=[0,.8]))
 |      obj.update(update_dict)
 |      obj
 |      {'title': 'new title', 'xaxis': {'range': [0,1], 'domain': [0,.8]}}
 |
 |      # update with list of keyword arguments
 |      obj = Layout(title='my title', xaxis=XAxis(range=[0,1], domain=[0,1]))
 |      obj.update(title='new title', xaxis=dict(domain=[0,.8]))
 |      obj
 |      {'title': 'new title', 'xaxis': {'range': [0,1], 'domain': [0,.8]}}
 |
 |      This 'fully' supports duck-typing in that the call signature is
 |      identical, however this differs slightly from the normal update
 |      method provided by Python's dictionaries.
 |
 |  validate(self, caller=True)
 |      Recursively check the validity of the keys in a PlotlyDict.
 |
 |      The valid keys constitute the entries in each object
 |      dictionary in graph_objs_meta.json
 |
 |      The validation process first requires that all nested collections be
 |      converted to the appropriate subclass of PlotlyDict/PlotlyList. Then,
 |      each of these objects call `validate` and so on, recursively,
 |      until the entire object has been validated.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from PlotlyDict:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from __builtin__.dict:
 |
 |  __cmp__(...)
 |      x.__cmp__(y) <==> cmp(x,y)
 |
 |  __contains__(...)
 |      D.__contains__(k) -> True if D has a key k, else False
 |
 |  __delitem__(...)
 |      x.__delitem__(y) <==> del x[y]
 |
 |  __eq__(...)
 |      x.__eq__(y) <==> x==y
 |
 |  __ge__(...)
 |      x.__ge__(y) <==> x>=y
 |
 |  __getattribute__(...)
 |      x.__getattribute__('name') <==> x.name
 |
 |  __getitem__(...)
 |      x.__getitem__(y) <==> x[y]
 |
 |  __gt__(...)
 |      x.__gt__(y) <==> x>y
 |
 |  __iter__(...)
 |      x.__iter__() <==> iter(x)
 |
 |  __le__(...)
 |      x.__le__(y) <==> x<=y
 |
 |  __len__(...)
 |      x.__len__() <==> len(x)
 |
 |  __lt__(...)
 |      x.__lt__(y) <==> x<y
 |
 |  __ne__(...)
 |      x.__ne__(y) <==> x!=y
 |
 |  __repr__(...)
 |      x.__repr__() <==> repr(x)
 |
 |  __sizeof__(...)
 |      D.__sizeof__() -> size of D in memory, in bytes
 |
 |  clear(...)
 |      D.clear() -> None.  Remove all items from D.
 |
 |  copy(...)
 |      D.copy() -> a shallow copy of D
 |
 |  fromkeys(...)
 |      dict.fromkeys(S[,v]) -> New dict with keys from S and values equal to v.
 |      v defaults to None.
 |
 |  get(...)
 |      D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.
 |
 |  has_key(...)
 |      D.has_key(k) -> True if D has a key k, else False
 |
 |  items(...)
 |      D.items() -> list of D's (key, value) pairs, as 2-tuples
 |
 |  iteritems(...)
 |      D.iteritems() -> an iterator over the (key, value) items of D
 |
 |  iterkeys(...)
 |      D.iterkeys() -> an iterator over the keys of D
 |
 |  itervalues(...)
 |      D.itervalues() -> an iterator over the values of D
 |
 |  keys(...)
 |      D.keys() -> list of D's keys
 |
 |  pop(...)
 |      D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
 |      If key is not found, d is returned if given, otherwise KeyError is raised
 |
 |  popitem(...)
 |      D.popitem() -> (k, v), remove and return some (key, value) pair as a
 |      2-tuple; but raise KeyError if D is empty.
 |
 |  setdefault(...)
 |      D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
 |
 |  values(...)
 |      D.values() -> list of D's values
 |
 |  viewitems(...)
 |      D.viewitems() -> a set-like object providing a view on D's items
 |
 |  viewkeys(...)
 |      D.viewkeys() -> a set-like object providing a view on D's keys
 |
 |  viewvalues(...)
 |      D.viewvalues() -> an object providing a view on D's values
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes inherited from __builtin__.dict:
 |
 |  __hash__ = None
 |
 |  __new__ = <built-in method __new__ of type object>
 |      T.__new__(S, ...) -> a new object with type S, a subtype of T


A scene is the wrapping element for the x, y and z axes of a 3D plot. A scene can be rotated, translated and zoomed into (more on how to do so at the end of this subsection). The Scene object links an XAxis , a YAxis and a ZAxis objects. Moreover, users can programmatically set the camera position using the 'cameraposition' key.

Note also that Plotly allows users to make multi-scene 3d plots, in a similar way than for multi-axis plots. Examples of this feature will be added to this section shortly.

For now, consider the layout object:

# Dictionary of style options for all axes
axis = dict(
    showbackground=True, # (!) show axis background
    backgroundcolor="rgb(204, 204, 204)", # set background color to grey
    gridcolor="rgb(255, 255, 255)",       # set grid line color
    zerolinecolor="rgb(255, 255, 255)",   # set zero grid line color
)

# Make a layout object
layout = Layout(
    title='$f(x,y) = A \cos(\pi x y) e^{-(x^2+y^2)/2}$', # set plot title
    scene=Scene(  # (!) axes are part of a 'scene' in 3d plots
        xaxis=XAxis(axis), # set x-axis style
        yaxis=YAxis(axis), # set y-axis style
        zaxis=ZAxis(axis)  # set z-axis style
    )
)

The 'showbackgroundcolor' and 'backgroundcolor' axis keys are new to the axis objects. With them users can set the color of the axes' wall. They have only an effects in 3D plots.

Packaging layout and data into a figure object and a call to Plotly gets us our first 3D plot:

# Make a figure object
fig = Figure(data=data, layout=layout)

# (@) Send to Plotly and show in notebook
py.iplot(fig, filename='s8_surface')

A 3D interacting surface plot

Plotly 3D plots have three modes of interaction (togglable from the top right corner of the plot's frame):

  1. Rotation (second from left button)
  2. Zoom (third from left button)
  3. Pan (rightmost button)

You are invited to play around with each of them, Plotly 3D plots are alive like no other.

In case you are having a hard time familiarizing yourself with the rotation drag interactions, you can return to the original camera position by clicking on the home button which is the leftmost button on the top right corner of the plot's frame.

Moreover, this surface plot can be viewed in full screen at the following unique URL:

py.plot(fig, auto_open=False, filename='s8_surface')
u'https://plot.ly/~etpinard/464'

8.2 A helix curve in 3D

Our next plot will be a 3-dimensional helix curve.

A helix curve is described mathematically in cartesian coordinates by:

$$\begin{align} x(t) &= \cos(t) \,, \\ y(t) &= \sin(t) \,, \\ z(t) &= t \end{align}$$

where $t$ is some conitnuous variable. So consider,

# (*) Import the math functions needed in this cell
from numpy import cos, sin

# Define a function generating the helix coordinates
def helix(t):
    x = cos(t)
    y = sin(t)
    z = t
    return x, y, z

Next, get the coordinates of the helix:

from numpy import pi  # import pi for this cell

# Make a linear space from 0 to 4pi (i.e. 2 revolutions), get coords
t = np.linspace(0, 4*pi, 200)
x, y, z = helix(t)

For this figure, we will use the Scatted3d object which shares the same functionality as its 2D version. Its available keys are:

help(Scatter3d)
Help on class Scatter3d in module plotly.graph_objs.graph_objs:

class Scatter3d(PlotlyTrace)
 |  A dictionary-like object for representing a 3D scatter trace in plotly.
 |
 |  Quick method reference:
 |
 |      Scatter3d.update(changes)
 |      Scatter3d.strip_style()
 |      Scatter3d.get_data()
 |      Scatter3d.to_graph_objs()
 |      Scatter3d.validate()
 |      Scatter3d.to_string()
 |      Scatter3d.force_clean()
 |
 |  Valid keys:
 |
 |      x [required=True] (value=list or 1d numpy array of numbers, strings,
 |      datetimes or list of lists or 2d numpy array of numbers) (streamable):
 |          Sets the x coordinates of the points of this 3D scatter trace. If
 |          'x' is linked to a list or 1d numpy array of strings, then the x
 |          coordinates are integers, 0, 1, 2, 3, ..., labeled on the x-axis by
 |          the list or 1d numpy array of strings linked to 'x'.
 |
 |      y [required=True] (value=list or 1d numpy array of numbers, strings,
 |      datetimes or list of lists or 2d numpy array of numbers) (streamable):
 |          Sets the y coordinates of the points of this 3D scatter trace. If
 |          'y' is linked to a list or 1d numpy array of strings, then the y
 |          coordinates are integers, 0, 1, 2, 3, ..., labeled on the y-axis by
 |          the list or 1d numpy array of strings linked to 'y'.
 |
 |      z [required=True] (value=list or 1d numpy array of numbers, strings,
 |      datetimes or list of lists or 2d numpy array of numbers) (streamable):
 |          Sets the z coordinates of the points of this scatter trace. If 'z'
 |          is linked to a list or 1d numpy array of strings, then the z
 |          coordinates are integers, 0, 1, 2, 3, ..., labeled on the z-axis by
 |          the list or 1d numpy array of strings linked to 'z'.
 |
 |      mode [required=False] (value='lines' | 'markers' | 'text' |
 |      'lines+markers' | 'lines+text' | 'markers+text' | 'lines+markers+text'):
 |          Plotting mode for this 3D scatter trace. If the mode includes 'text'
 |          then the 'text' will appear at the (x,y) points, otherwise it will
 |          appear on hover.
 |
 |      name [required=False] (value=a string):
 |          The label associated with this trace. This name will appear in the
 |          column header in the online spreadsheet.
 |
 |      text [required=False] (value=list or 1d numpy array of strings)
 |      (streamable):
 |          The text elements associated with each (x,y,z) pair in this 3D
 |          scatter trace. If the scatter 'mode' does not include 'text' then
 |          elements linked to 'text' will appear on hover only. In contrast, if
 |          'text' is included in 'mode', the elements in 'text' will be
 |          rendered on the plot at the locations specified in part by their
 |          corresponding (x,y,z) coordinate pair and the 'textposition' key.
 |
 |      error_z [required=False] (value=ErrorZ object | dictionary-like object)
 |      (streamable):
 |          Links a dictionary-like object describing the z-axis error bars that
 |          can be drawn from the (x,y,z) coordinates of this 3D scatter trace.
 |
 |          For more, run `help(plotly.graph_objs.ErrorZ)`
 |
 |      error_y [required=False] (value=ErrorY object | dictionary-like object)
 |      (streamable):
 |          Links a dictionary-like object describing the y-axis error bars that
 |          can be drawn from the (x,y,z) coordinates of this 3D scatter trace.
 |
 |          For more, run `help(plotly.graph_objs.ErrorY)`
 |
 |      error_x [required=False] (value=ErrorX object | dictionary-like object)
 |      (streamable):
 |          Links a dictionary-like object describing the x-axis error bars that
 |          can be drawn from the (x,y,z) coordinates of this 3D scatter trace.
 |
 |          For more, run `help(plotly.graph_objs.ErrorX)`
 |
 |      marker [required=False] (value=Marker object | dictionary-like object)
 |      (streamable):
 |          Links a dictionary-like object containing marker style parameters
 |          for this 3D scatter trace. Has an effect only if 'mode' contains
 |          'markers'.
 |
 |          For more, run `help(plotly.graph_objs.Marker)`
 |
 |      line [required=False] (value=Line object | dictionary-like object)
 |      (streamable):
 |          Links a dictionary-like object containing line parameters for this
 |          3D scatter trace. Has an effect only if 'mode' contains 'lines'.
 |
 |          For more, run `help(plotly.graph_objs.Line)`
 |
 |      textposition [required=False] (value='top left' | 'top' (or 'top
 |      center')| 'top right' | 'left' (or 'middle left') | '' (or 'middle
 |      center') | 'right' (or 'middle right') | 'bottom left' | 'bottom' (or
 |      'bottom center') | 'bottom right'):
 |          Sets the position of the text elements in the 'text' key with
 |          respect to the data points. By default, the text elements are
 |          plotted directly at the (x,y,z) coordinates.
 |
 |      scene [required=False] (value='scene1' | 'scene2' | 'scene3' | etc.):
 |          This key determines the scene on which this trace will be plotted
 |          in.
 |
 |      stream [required=False] (value=Stream object | dictionary-like object):
 |          Links a dictionary-like object that initializes this trace as a
 |          writable-stream, for use with the streaming API.
 |
 |          For more, run `help(plotly.graph_objs.Stream)`
 |
 |      visible [required=False] (value=a boolean: True | False):
 |          Toggles whether or not this object will be visible on the rendered
 |          figure.
 |
 |      type [required=False] (value='scatter3d'):
 |          Plotly identifier for this data's trace type.
 |
 |  Method resolution order:
 |      Scatter3d
 |      PlotlyTrace
 |      PlotlyDict
 |      __builtin__.dict
 |      __builtin__.object
 |
 |  Methods inherited from PlotlyTrace:
 |
 |  __init__(self, *args, **kwargs)
 |
 |  to_string(self, level=0, indent=4, eol='\n', pretty=True, max_chars=80)
 |      Returns a formatted string showing graph_obj constructors.
 |
 |      Example:
 |
 |          print(obj.to_string())
 |
 |      Keyword arguments:
 |      level (default = 0) -- set number of indentations to start with
 |      indent (default = 4) -- set indentation amount
 |      eol (default = '\n') -- set end of line character(s)
 |      pretty (default = True) -- curtail long list output with a '...'
 |      max_chars (default = 80) -- set max characters per line
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from PlotlyDict:
 |
 |  __setitem__(self, key, value)
 |
 |  force_clean(self, caller=True)
 |      Attempts to convert to graph_objs and call force_clean() on values.
 |
 |      Calling force_clean() on a PlotlyDict will ensure that the object is
 |      valid and may be sent to plotly. This process will also remove any
 |      entries that end up with a length == 0.
 |
 |      Careful! This will delete any invalid entries *silently*.
 |
 |  get_data(self)
 |      Returns the JSON for the plot with non-data elements stripped.
 |
 |  get_ordered(self, caller=True)
 |
 |  strip_style(self)
 |      Strip style from the current representation.
 |
 |      All PlotlyDicts and PlotlyLists are guaranteed to survive the
 |      stripping process, though they made be left empty. This is allowable.
 |
 |      Keys that will be stripped in this process are tagged with
 |      `'type': 'style'` in graph_objs_meta.json.
 |
 |      This process first attempts to convert nested collections from dicts
 |      or lists to subclasses of PlotlyList/PlotlyDict. This process forces
 |      a validation, which may throw exceptions.
 |
 |      Then, each of these objects call `strip_style` on themselves and so
 |      on, recursively until the entire structure has been validated and
 |      stripped.
 |
 |  to_graph_objs(self, caller=True)
 |      Walk obj, convert dicts and lists to plotly graph objs.
 |
 |      For each key in the object, if it corresponds to a special key that
 |      should be associated with a graph object, the ordinary dict or list
 |      will be reinitialized as a special PlotlyDict or PlotlyList of the
 |      appropriate `kind`.
 |
 |  update(self, dict1=None, **dict2)
 |      Update current dict with dict1 and then dict2.
 |
 |      This recursively updates the structure of the original dictionary-like
 |      object with the new entries in the second and third objects. This
 |      allows users to update with large, nested structures.
 |
 |      Note, because the dict2 packs up all the keyword arguments, you can
 |      specify the changes as a list of keyword agruments.
 |
 |      Examples:
 |      # update with dict
 |      obj = Layout(title='my title', xaxis=XAxis(range=[0,1], domain=[0,1]))
 |      update_dict = dict(title='new title', xaxis=dict(domain=[0,.8]))
 |      obj.update(update_dict)
 |      obj
 |      {'title': 'new title', 'xaxis': {'range': [0,1], 'domain': [0,.8]}}
 |
 |      # update with list of keyword arguments
 |      obj = Layout(title='my title', xaxis=XAxis(range=[0,1], domain=[0,1]))
 |      obj.update(title='new title', xaxis=dict(domain=[0,.8]))
 |      obj
 |      {'title': 'new title', 'xaxis': {'range': [0,1], 'domain': [0,.8]}}
 |
 |      This 'fully' supports duck-typing in that the call signature is
 |      identical, however this differs slightly from the normal update
 |      method provided by Python's dictionaries.
 |
 |  validate(self, caller=True)
 |      Recursively check the validity of the keys in a PlotlyDict.
 |
 |      The valid keys constitute the entries in each object
 |      dictionary in graph_objs_meta.json
 |
 |      The validation process first requires that all nested collections be
 |      converted to the appropriate subclass of PlotlyDict/PlotlyList. Then,
 |      each of these objects call `validate` and so on, recursively,
 |      until the entire object has been validated.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from PlotlyDict:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from __builtin__.dict:
 |
 |  __cmp__(...)
 |      x.__cmp__(y) <==> cmp(x,y)
 |
 |  __contains__(...)
 |      D.__contains__(k) -> True if D has a key k, else False
 |
 |  __delitem__(...)
 |      x.__delitem__(y) <==> del x[y]
 |
 |  __eq__(...)
 |      x.__eq__(y) <==> x==y
 |
 |  __ge__(...)
 |      x.__ge__(y) <==> x>=y
 |
 |  __getattribute__(...)
 |      x.__getattribute__('name') <==> x.name
 |
 |  __getitem__(...)
 |      x.__getitem__(y) <==> x[y]
 |
 |  __gt__(...)
 |      x.__gt__(y) <==> x>y
 |
 |  __iter__(...)
 |      x.__iter__() <==> iter(x)
 |
 |  __le__(...)
 |      x.__le__(y) <==> x<=y
 |
 |  __len__(...)
 |      x.__len__() <==> len(x)
 |
 |  __lt__(...)
 |      x.__lt__(y) <==> x<y
 |
 |  __ne__(...)
 |      x.__ne__(y) <==> x!=y
 |
 |  __repr__(...)
 |      x.__repr__() <==> repr(x)
 |
 |  __sizeof__(...)
 |      D.__sizeof__() -> size of D in memory, in bytes
 |
 |  clear(...)
 |      D.clear() -> None.  Remove all items from D.
 |
 |  copy(...)
 |      D.copy() -> a shallow copy of D
 |
 |  fromkeys(...)
 |      dict.fromkeys(S[,v]) -> New dict with keys from S and values equal to v.
 |      v defaults to None.
 |
 |  get(...)
 |      D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.
 |
 |  has_key(...)
 |      D.has_key(k) -> True if D has a key k, else False
 |
 |  items(...)
 |      D.items() -> list of D's (key, value) pairs, as 2-tuples
 |
 |  iteritems(...)
 |      D.iteritems() -> an iterator over the (key, value) items of D
 |
 |  iterkeys(...)
 |      D.iterkeys() -> an iterator over the keys of D
 |
 |  itervalues(...)
 |      D.itervalues() -> an iterator over the values of D
 |
 |  keys(...)
 |      D.keys() -> list of D's keys
 |
 |  pop(...)
 |      D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
 |      If key is not found, d is returned if given, otherwise KeyError is raised
 |
 |  popitem(...)
 |      D.popitem() -> (k, v), remove and return some (key, value) pair as a
 |      2-tuple; but raise KeyError if D is empty.
 |
 |  setdefault(...)
 |      D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
 |
 |  values(...)
 |      D.values() -> list of D's values
 |
 |  viewitems(...)
 |      D.viewitems() -> a set-like object providing a view on D's items
 |
 |  viewkeys(...)
 |      D.viewkeys() -> a set-like object providing a view on D's keys
 |
 |  viewvalues(...)
 |      D.viewvalues() -> an object providing a view on D's values
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes inherited from __builtin__.dict:
 |
 |  __hash__ = None
 |
 |  __new__ = <built-in method __new__ of type object>
 |      T.__new__(S, ...) -> a new object with type S, a subtype of T


trace1 = Scatter3d(
    x=x,  # x coords
    y=y,  # y coords
    z=z,  # z coords
    mode='lines',      # (!) draw lines between coords (as in Scatter)
    line=Line(
        color='black', # black line segments
        width=3        # set line segment width
    )
)

# Package the trace dictionary into a data object
data = Data([trace1])

Add a title to a layout object:

# Make a layout object
layout = Layout(
    title='Fig 8.2: Helix curve'
)

And finally,

# Make a figure object
fig = Figure(data=data, layout=layout)

# (@) Send to Plotly and show in notebook
py.iplot(fig, filename='s8_helix')

The full screen verison is available at the following URL:

py.plot(fig, auto_open=False, filename='s8_helix')
u'https://plot.ly/~etpinard/468'

8.3 Lorenz Attrator streaming plot

Plotly's Streaming API is compatible with the 3D plot engine. As an example, we simulate a chaotic solution to the Lorenz system, also known as the Lorenz attractor or butterfly .

The Lorenz system is described mathematically as:

$$\begin{align} \frac{\text{d}x}{\text{d}t} &= \sigma (y-x) \,, \\ \frac{\text{d}y}{\text{d}t} &= x(\rho-z) - y\,, \\ \frac{\text{d}z}{\text{d}t} &= xy - \beta z \end{align}$$

where $(x,y,z)$ are the spatial coordinates, $t$ is time, $\sigma$, $\beta$ and $\rho$ are constants. With $\sigma = 10$, $\beta = 8/3$ and $\rho = 28$ the system exhibits chaotic behavior.

For our following graph, we integrate the Lorenz system using the scipy.integrate module. The following computations were adapted from this blog post from Jake Vanderplas. Big thanks!

So, first define a function returning the time dervative (i.e. the right-hand side) of the Lorenz system with the choatic parameters mentioned above:

# Time derivatives of the Lorenz system
def lorenz_deriv((x, y, z), t0, sigma=10., beta=8./3, rho=28.0):
    return [sigma * (y - x), x * (rho - z) - y, x * y - beta * z]

The Plotly Streaming API makes use of stream tokens (more info in section 7.0 ):

stream_ids = tls.get_credentials_file()['stream_ids']

Our graph will feature two traces: one line trace tracking the trajectory and one marker trace of one point leading the way (similar to the double pendulum stream of section 7.2 ).

Those two traces require two unique stream tokens packaged in Stream graph objects. So, we initialize two Scatter3d objects as such:

# Choose random starting points, uniformly distributed from -15 to 15
np.random.seed(1)
x0 = -15 + 30 * np.random.random(3)
# Line trace following the trajectory 
trace1 = Scatter3d(
    x=x0[0],
    y=x0[1],
    z=x0[2],
    mode='lines',
    stream=Stream(
        token=stream_ids[0],  # (!) link stream id
        maxpoints=3000        # (!) show a max. of 3000 pts on plot
    )
)

# Marker trace leading the way
trace2 = Scatter3d(
    x=x0[0],
    y=x0[1],
    z=x0[2],
    mode='markers',
    marker=Marker(
        color="#1f77b4",  # a darker blue
        size=12,
        symbol='circle'
    ),
    stream=Stream(token=stream_ids[1])  # (!) link other stream id
)

# Package the trace dictionary into a data object
data = Data([trace1, trace2])

Add a title and fix the range of each of the axes as well as the margins in the layout object:

# Make a layout object
layout = Layout(
    title='Lorenz Attractor',
    scene=Scene(
        xaxis=dict(
            autorange=False,
            range=[-25, 25]  # set axis range
        ),
        yaxis=dict(
            autorange=False,
            range=[-35, 35]
        ),
        zaxis=dict(
            autorange=False,
            range=[0, 55]
        )
    ),
    margin=Margin(
        l=0,
        r=0,
        t=80,  # remove margin except top margin
        b=0
    ),
    showlegend=False
)

Initialize the graph by making a first call to Plotly:

# Make a figure object
fig = Figure(data=data, layout=layout)

# (@) Send to Plotly and show in different browser tab
py.plot(fig, filename='s8_lorenz-system')
u'https://plot.ly/~etpinard/521'

Now, make two stream link objects and open their connections:

# (@) Make 1st instance of the stream link object, 
#     with same stream id as the 1st stream id object (in trace1)
s1 = py.Stream(stream_ids[0])

# (@) Make 2nd instance of the stream link object, 
#     with same stream id as the 2nd stream id object (in trace2)
s2 = py.Stream(stream_ids[1])

# (@) Open both streams
s1.open()
s2.open()

And finally, integrate and stream the data to Plotly:

# (*) Import module to integrate ODE and keep track of time
import scipy.integrate as integrate
import time

N = 10  # number of time integrate.odeint() integrations 
i = 1   # init. counter

# Delay start of stream by 5 sec (time to switch tabs)
time.sleep(5)

# Solve the system N times
while i < N:

    # Generate a time vector (1000 pts from 0 to 4)
    # and integrate from initial position (x0)
    t = np.linspace(0, 4, 1000)
    X_t = integrate.odeint(lorenz_deriv, x0, t)

    # Loop through each time step 
    for x_t in X_t:

        s_data1 = Scatter3d(
            x=x_t[0],    # (!) write scalars to append the line trace
            y=x_t[1],
            z=x_t[2]
        )

        s_data2 = Scatter3d(
            x=[x_t[0]],  # (!) write list to overwrite the leading marker pt
            y=[x_t[1]],
            z=[x_t[2]]
        )

        s1.write(s_data1)  # (@) stream data to Plotly!
        s2.write(s_data2)

        time.sleep(0.05)   # (!) halt for 50 ms, for smoother plotting

    x0 = X_t[-1, :]  # overwrite initial coordiniates
    i += 1           # add to counter

# (@) Close both streams when done plotting
s1.close()
s2.close()

In the above, the integration loop is finite, but there is no reason to not let the streams go on indefinitely. Simply replace the while-loop condition with:

>>> while True:

and enjoy.

# Embed never-ending simulation
tls.embed('streaming-demos', '66')

Got Questions or Feedback?

About Plotly

Notebook styling ideas

Big thanks to


Still need help?
Contact Us

For guaranteed 24 hour response turnarounds, upgrade to a Developer Support Plan.