{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### New to Plotly?\n",
"Plotly's Python library is free and open source! [Get started](https://plotly.com/python/getting-started/) by downloading the client and [reading the primer](https://plotly.com/python/getting-started/).\n",
" You can set up Plotly to work in [online](https://plotly.com/python/getting-started/#initialization-for-online-plotting) or [offline](https://plotly.com/python/getting-started/#initialization-for-offline-plotting) mode, or in [jupyter notebooks](https://plotly.com/python/getting-started/#start-plotting-online).\n",
" We also have a quick-reference [cheatsheet](https://images.plot.ly/plotly-documentation/images/python_cheat_sheet.pdf) (new!) to help you get started!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Defining and plotting triangulated surfaces\n",
"#### with Plotly `Mesh3d` "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A triangulation of a compact surface is a finite collection of triangles that cover the surface in such a way that every point on the surface is in a triangle, and the intersection of any two triangles is either void, a common edge or a common vertex. A triangulated surface is called tri-surface.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The triangulation of a surface defined as the graph of a continuous function, $z=f(x,y), (x,y)\\in D\\subset\\mathbb{R}^2$ or in a parametric form:\n",
"$$x=x(u,v), y=y(u,v), z=z(u,v), (u,v)\\in U\\subset\\mathbb{R}^2,$$\n",
"is the image through $f$,respectively through the parameterization, of the Delaunay triangulation or an user defined triangulation of the planar domain $D$, respectively $U$.\n",
"\n",
"The Delaunay triangulation of a planar region is defined and illustrated in a Python Plotly tutorial posted [here](https://plotly.com/python/alpha-shapes/).\n",
"\n",
"If the planar region $D$ ($U$) is rectangular, then one defines a meshgrid on it, and the points\n",
"of the grid are the input points for the `scipy.spatial.Delaunay` function that defines the planar triangulation of $D$, respectively $U$.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Triangulation of the Moebius band ###"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The Moebius band is parameterized by:\n",
"\n",
"$$\\begin{align*}\n",
"x(u,v)&=(1+0.5 v\\cos(u/2))\\cos(u)\\\\\n",
"y(u,v)&=(1+0.5 v\\cos(u/2))\\sin(u)\\quad\\quad u\\in[0,2\\pi],\\: v\\in[-1,1]\\\\\n",
"z(u,v)&=0.5 v\\sin(u/2) \n",
"\\end{align*}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define a meshgrid on the rectangle $U=[0,2\\pi]\\times[-1,1]$:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import plotly.plotly as py\n",
"import plotly.graph_objs as go\n",
"\n",
"import numpy as np\n",
"import matplotlib.cm as cm\n",
"from scipy.spatial import Delaunay\n",
"\n",
"u=np.linspace(0,2*np.pi, 24)\n",
"v=np.linspace(-1,1, 8)\n",
"u,v=np.meshgrid(u,v)\n",
"u=u.flatten()\n",
"v=v.flatten()\n",
"\n",
"#evaluate the parameterization at the flattened u and v\n",
"tp=1+0.5*v*np.cos(u/2.)\n",
"x=tp*np.cos(u)\n",
"y=tp*np.sin(u)\n",
"z=0.5*v*np.sin(u/2.)\n",
"\n",
"#define 2D points, as input data for the Delaunay triangulation of U\n",
"points2D=np.vstack([u,v]).T\n",
"tri = Delaunay(points2D)#triangulate the rectangle U"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`tri.simplices` is a `np.array` of integers, of shape (`ntri`,3), where `ntri` is the number of triangles generated by `scipy.spatial.Delaunay`.\n",
"Each row in this array contains three indices, i, j, k, such that points2D[i,:], points2D[j,:], points2D[k,:] are vertices of a triangle in the Delaunay triangularization of the rectangle $U$.\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(322, 3) \n",
"[73 96 72]\n"
]
}
],
"source": [
"print tri.simplices.shape, '\\n', tri.simplices[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The images of the `points2D` through the surface parameterization are 3D points. The same simplices define the triangles on the surface."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Setting a combination of keys in `Mesh3d` leads to generating and plotting of a tri-surface, in the same way as `plot_trisurf` in matplotlib or `trisurf` in Matlab does.\n",
"\n",
"We note that `Mesh3d` with different combination of keys can generate [alpha-shapes](https://plotly.com/python/alpha-shapes/)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In order to plot a tri-surface, we choose a colormap, and associate to each triangle on the surface, the color in colormap, corresponding to the normalized mean value of z-coordinates of the triangle vertices."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define a function that maps a mean z-value to a matplotlib color, converted to a Plotly color:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"def map_z2color(zval, colormap, vmin, vmax):\n",
" #map the normalized value zval to a corresponding color in the colormap\n",
" \n",
" if vmin>vmax:\n",
" raise ValueError('incorrect relation between vmin and vmax')\n",
" t=(zval-vmin)/float((vmax-vmin))#normalize val\n",
" R, G, B, alpha=colormap(t)\n",
" return 'rgb('+'{:d}'.format(int(R*255+0.5))+','+'{:d}'.format(int(G*255+0.5))+\\\n",
" ','+'{:d}'.format(int(B*255+0.5))+')' \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To plot the triangles on a surface, we set in Plotly `Mesh3d` the lists of x, y, respectively z- coordinates of the vertices, and the lists of indices, i, j, k, for x, y, z coordinates of all vertices:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"def tri_indices(simplices):\n",
" #simplices is a numpy array defining the simplices of the triangularization\n",
" #returns the lists of indices i, j, k\n",
" \n",
" return ([triplet[c] for triplet in simplices] for c in range(3))\n",
"\n",
"def plotly_trisurf(x, y, z, simplices, colormap=cm.RdBu, plot_edges=None):\n",
" #x, y, z are lists of coordinates of the triangle vertices \n",
" #simplices are the simplices that define the triangularization;\n",
" #simplices is a numpy array of shape (no_triangles, 3)\n",
" #insert here the type check for input data\n",
" \n",
" points3D=np.vstack((x,y,z)).T\n",
" tri_vertices=map(lambda index: points3D[index], simplices)# vertices of the surface triangles \n",
" zmean=[np.mean(tri[:,2]) for tri in tri_vertices ]# mean values of z-coordinates of \n",
" #triangle vertices\n",
" min_zmean=np.min(zmean)\n",
" max_zmean=np.max(zmean) \n",
" facecolor=[map_z2color(zz, colormap, min_zmean, max_zmean) for zz in zmean] \n",
" I,J,K=tri_indices(simplices)\n",
" \n",
" triangles=go.Mesh3d(x=x,\n",
" y=y,\n",
" z=z,\n",
" facecolor=facecolor, \n",
" i=I,\n",
" j=J,\n",
" k=K,\n",
" name=''\n",
" )\n",
" \n",
" if plot_edges is None:# the triangle sides are not plotted \n",
" return [triangles]\n",
" else:\n",
" #define the lists Xe, Ye, Ze, of x, y, resp z coordinates of edge end points for each triangle\n",
" #None separates data corresponding to two consecutive triangles\n",
" lists_coord=[[[T[k%3][c] for k in range(4)]+[ None] for T in tri_vertices] for c in range(3)]\n",
" Xe, Ye, Ze=[reduce(lambda x,y: x+y, lists_coord[k]) for k in range(3)]\n",
" \n",
" #define the lines to be plotted\n",
" lines=go.Scatter3d(x=Xe,\n",
" y=Ye,\n",
" z=Ze,\n",
" mode='lines',\n",
" line=dict(color= 'rgb(50,50,50)', width=1.5)\n",
" )\n",
" return [triangles, lines]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Call this function for data associated to Moebius band:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"data1=plotly_trisurf(x,y,z, tri.simplices, colormap=cm.RdBu, plot_edges=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Set the layout of the plot:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"axis = dict(\n",
"showbackground=True, \n",
"backgroundcolor=\"rgb(230, 230,230)\",\n",
"gridcolor=\"rgb(255, 255, 255)\", \n",
"zerolinecolor=\"rgb(255, 255, 255)\", \n",
" )\n",
"\n",
"layout = go.Layout(\n",
" title='Moebius band triangulation',\n",
" width=800,\n",
" height=800,\n",
" scene=dict( \n",
" xaxis=dict(axis),\n",
" yaxis=dict(axis), \n",
" zaxis=dict(axis), \n",
" aspectratio=dict(\n",
" x=1,\n",
" y=1,\n",
" z=0.5\n",
" ),\n",
" )\n",
" )\n",
"\n",
"fig1 = go.Figure(data=data1, layout=layout)\n",
"\n",
"py.iplot(fig1, filename='Moebius-band-trisurf')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Triangularization of the surface $z=\\sin(-xy)$, defined over a disk ###"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We consider polar coordinates on the disk, $D(0, 1)$, centered at origin and of radius 1, and define\n",
"a meshgrid on the set of points $(r, \\theta)$, with $r\\in[0,1]$ and $\\theta\\in[0,2\\pi]$:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"n=12 # number of radii\n",
"h=1.0/(n-1)\n",
"r = np.linspace(h, 1.0, n)\n",
"theta= np.linspace(0, 2*np.pi, 36)\n",
"\n",
"r,theta=np.meshgrid(r,theta)\n",
"r=r.flatten()\n",
"theta=theta.flatten()\n",
"\n",
"#Convert polar coordinates to cartesian coordinates (x,y)\n",
"x=r*np.cos(theta)\n",
"y=r*np.sin(theta)\n",
"x=np.append(x, 0)# a trick to include the center of the disk in the set of points. It was avoided\n",
" # initially when we defined r=np.linspace(h, 1.0, n)\n",
"y=np.append(y,0)\n",
"z = np.sin(-x*y) \n",
"\n",
"points2D=np.vstack([x,y]).T\n",
"tri=Delaunay(points2D)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Plot the surface with a modified layout:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data2=plotly_trisurf(x,y,z, tri.simplices, colormap=cm.cubehelix, plot_edges=None)\n",
"fig2 = go.Figure(data=data2, layout=layout)\n",
"fig2['layout'].update(dict(title='Triangulated surface',\n",
" scene=dict(camera=dict(eye=dict(x=1.75, \n",
" y=-0.7, \n",
" z= 0.75)\n",
" )\n",
" )))\n",
"\n",
"py.iplot(fig2, filename='trisurf-cubehx')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This example is also given as a demo for matplotlib [`plot_trisurf`](http://matplotlib.org/examples/mplot3d/trisurf3d_demo.html)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Plotting tri-surfaces from data stored in ply-files ###"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A PLY (Polygon File Format or Stanford Triangle Format) format is a format for storing graphical objects\n",
"that are represented by a triangulation of an object, resulted usually from scanning that object. A Ply file contains the coordinates of vertices, the codes for faces (triangles) and other elements, as well as the color for faces or the normal direction to faces.\n",
"\n",
"In the following we show how we can read a ply file via the Python package, `plyfile`. This package can be installed with `pip`.\n",
"\n",
"We choose a ply file from a list provided [here](http://people.sc.fsu.edu/~jburkardt/data/ply/ply.html)."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Collecting plyfile\n",
" Using cached https://files.pythonhosted.org/packages/51/20/cd2d20e6936c31587738f05ec7f96fceeed8e6eb3732f541f760267e8cdb/plyfile-0.5.tar.gz\n",
"Requirement already satisfied: numpy>=1.8 in c:\\python36\\lib\\site-packages (from plyfile) (1.14.1)\n",
"Installing collected packages: plyfile\n",
" Running setup.py install for plyfile: started\n",
" Running setup.py install for plyfile: finished with status 'done'\n",
"Successfully installed plyfile-0.5\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"You are using pip version 10.0.0, however version 10.0.1 is available.\n",
"You should consider upgrading via the 'python -m pip install --upgrade pip' command.\n"
]
}
],
"source": [
"!pip install plyfile\n",
"from plyfile import PlyData, PlyElement\n",
"\n",
"import urllib2\n",
"req = urllib2.Request('http://people.sc.fsu.edu/~jburkardt/data/ply/chopper.ply') \n",
"opener = urllib2.build_opener()\n",
"f = opener.open(req)\n",
"plydata = PlyData.read(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Read the file header:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"element vertex 1066\r\n",
"property float x\r\n",
"property float y\r\n",
"property float z\n",
"element face 2094\r\n",
"property list uchar int vertex_indices\n"
]
}
],
"source": [
"for element in plydata.elements:\n",
" print element\n",
" \n",
"nr_points=plydata.elements[0].count\n",
"nr_faces=plydata.elements[1].count"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Read the vertex coordinates:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([0, 1, 2])"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"points=np.array([plydata['vertex'][k] for k in range(nr_points)])\n",
"points[0]\n",
"\n",
"x,y,z=zip(*points)\n",
"\n",
"faces=[plydata['face'][k][0] for k in range(nr_faces)]\n",
"faces[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can get data for a Plotly plot of the graphical object read from the ply file:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data3=plotly_trisurf(x,y,z, faces, colormap=cm.RdBu, plot_edges=None)\n",
"\n",
"title=\"Trisurf from a PLY file \"+\\\n",
" \"Data Source: [1]\"\n",
"\n",
"noaxis=dict(showbackground=False,\n",
" showline=False, \n",
" zeroline=False,\n",
" showgrid=False,\n",
" showticklabels=False,\n",
" title='' \n",
" )\n",
"\n",
"fig3 = go.Figure(data=data3, layout=layout)\n",
"fig3['layout'].update(dict(title=title,\n",
" width=1000,\n",
" height=1000,\n",
" scene=dict(xaxis=noaxis,\n",
" yaxis=noaxis, \n",
" zaxis=noaxis, \n",
" aspectratio=dict(x=1, y=1, z=0.4),\n",
" camera=dict(eye=dict(x=1.25, y=1.25, z= 1.25) \n",
" )\n",
" )\n",
" ))\n",
" \n",
"py.iplot(fig3, filename='Chopper-Ply-cls')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This a version of the same object plotted along with triangle edges:"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import HTML\n",
"HTML('')\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Reference\n",
"See https://plotly.com/python/reference/ for more information and chart attribute options!"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Collecting git+https://github.com/plotly/publisher.git\n",
" Cloning https://github.com/plotly/publisher.git to c:\\users\\brand\\appdata\\local\\temp\\pip-req-build-wtgphx07\n",
"Installing collected packages: publisher\n",
" Found existing installation: publisher 0.11\n",
" Uninstalling publisher-0.11:\n",
" Successfully uninstalled publisher-0.11\n",
" Running setup.py install for publisher: started\n",
" Running setup.py install for publisher: finished with status 'done'\n",
"Successfully installed publisher-0.11\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"You are using pip version 10.0.0, however version 10.0.1 is available.\n",
"You should consider upgrading via the 'python -m pip install --upgrade pip' command.\n",
"C:\\Python27\\lib\\site-packages\\IPython\\nbconvert.py:13: ShimWarning:\n",
"\n",
"The `IPython.nbconvert` package has been deprecated since IPython 4.0. You should import from nbconvert instead.\n",
"\n",
"C:\\Python27\\lib\\site-packages\\publisher\\publisher.py:53: UserWarning:\n",
"\n",
"Did you \"Save\" this notebook before running this command? Remember to save, always save.\n",
"\n"
]
}
],
"source": [
"from IPython.display import display, HTML\n",
"\n",
"display(HTML(''))\n",
"display(HTML(''))\n",
"\n",
"! pip install git+https://github.com/plotly/publisher.git --upgrade\n",
"\n",
"import publisher\n",
"publisher.publish(\n",
" 'triangulation.ipynb', 'python/surface-triangulation/', 'Surface Triangulation',\n",
" 'How to make Tri-Surf plots in Python with Plotly.',\n",
" title = 'Python Surface Triangulation | plotly',\n",
" name = 'Surface Triangulation',\n",
" has_thumbnail='true', thumbnail='thumbnail/trisurf.jpg',\n",
" language='python',\n",
" display_as='3d_charts', order=11,\n",
" ipynb= '~notebook_demo/71')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.14"
}
},
"nbformat": 4,
"nbformat_minor": 1
}