Plotting 3D, Irregularly Triangulated Surfaces - An Example

Hi folks,

I have a data set that is in the form of an irregular 2D grid with
associated values, one for each node. I would like to plot this as a
raised surface, with colours that indicate the z-value. Somehow I
didn't find just quite the example I was looking for. After digging
around in the matplotlib and mpl_toolkits.mplot3d I was able to solve
the problem. To save the next person the trouble, here is my
annotated example. Comments and improvements are most welcome. I'd
be happy to have a version of this included as an official example, if
it passes muster.

Please note that randomly tesselating like this does generate dodgy
surfaces occasionally, but that's the nature of any Delaunay
triangulation in a case like this one.

Best regards
-- Simon

#!/usr/bin/python

···

#
# Demonstration of how to plot a triangulated surface.
#
# We randomly tesselate the (x,y) plane and compute two quadratic functions
# over those points. The plot displays the two surfaces, each coloured by the
# average z-location of the triangle.
#
import random as rn
import numpy as np

# Matplot lib and its associated toolkits
import matplotlib.delaunay as dl
import mpl_toolkits.mplot3d.art3d as ar3
import mpl_toolkits.mplot3d.axes3d as ax3
import matplotlib.pyplot as plt

# Generate 200 random points between -2.0 and 2.0.
x = np.empty( [ 204 ] )
y = np.empty( [ 204 ] )

x[0:200] = np.random.uniform( -2.0, 2.0, [200] )
y[0:200] = np.random.uniform( -2.0, 2.0, [200] )

# Put corners on the range for interest
x[200:204] = [ -2.0,-2.0, 2.0, 2.0 ]
y[200:204] = [ -2.0,-2.0, 2.0, 2.0 ]

# Create a triangulation of our region. We will re-use this for both curves.
circumcenters, edges, tri_points, tri_neighbors = dl.delaunay(x, y)

# Compute the first function of (x,y)
z = 2.0 - 1.0 * ( x[:]**2 + y[:]**2 ) - 0.5*y[:]

# Construct the triangles for the surface.
verts = ( [ np.array( [ [ x[ t[0] ] , y[ t[0] ] , z[ t[0] ] ]
, [ x[ t[1] ] , y[ t[1] ] , z[ t[1] ] ]
, [ x[ t[2] ] , y[ t[2] ] , z[ t[2] ] ] ] )
for t in tri_points
]
)

# To get a coloured plot, we need to assign a value to each face that dictates
# the colour. In this case we'll just use the average z co-ordinate of the
# three triangle vertices. One of these values is required for each face
# (triangle).
z_color = np.array( [ ( np.sum( v_p[:,2] ) / 3.0 ) for v_p in verts ] )

# Choiced for colour maps are :
# autumn bone cool copper flag gray hot hsv jet pink prism spring summer
# winter spectral
cmhot = plt.cm.get_cmap("hot")

# Our triangles are now turned into a collection of polygons using the vertex
# array. We assign the colour map here, which will figure out its required
# ranges all by itself.
triCol = ar3.Poly3DCollection( verts, cmap=cmhot )

# Set the value array associated with the polygons.
triCol.set_array ( z_color )

# Let's repeat the process for a second function.
z2 = 2.0 + 1.0 * ( x[:]**2 + y[:]**2 ) - 0.5*y[:]

# Construct the vertices, this time re-using the triangulation but using a new
# z co-ordinate.
verts2 = ( [ np.array( [ [ x[ t[0] ] , y[ t[0] ] , z2[ t[0] ] ]
, [ x[ t[1] ] , y[ t[1] ] , z2[ t[1] ] ]
, [ x[ t[2] ] , y[ t[2] ] , z2[ t[2] ] ] ] )
for t in tri_points
]
)

# We require a new array of values that will tell our colour map what to do.
z2_color = np.array( [ ( np.sum( v_p[:,2] ) / 3.0 ) for v_p in verts ] )

# Let's choose a different colour map this time.
cmjet = plt.cm.get_cmap("jet")

# We need a new set of 3D polygons, since this is a new surface.
triCol2 = ar3.Poly3DCollection( verts2, cmap=cmjet )
# Let's set the edge colour to black and make the triangle edges into thicker,
# dashed lines. Then we assign the array of values that will be used to colour
# the surface.
triCol2.set_edgecolor('k')
triCol2.set_linewidth( 2.0 )
triCol2.set_linestyle( 'dashed' )
triCol2.set_array( z2_color )

# Create the plotting figure and the 3D axes.
fig = plt.figure()
ax = ax3.Axes3D(fig)

# Add our two collections of 3D polygons directly. The collections have all of
# the point and color information. We don't need the add_collection3d method,
# since that method actually converts 2D polygons to 3D polygons. We already
# have 3D polygons.
ax.add_collection( triCol )
ax.add_collection( triCol2 )

# Add a label, for interest
ax.text3D( 0.0, 0.0, 2.1, "Peak/Trough" )

# If we don't bound the axes correctly the display will be off.
ax.set_xlim3d(-2, 2)
ax.set_ylim3d(-2, 2)
ax.set_zlim3d( np.min(z), np.max(z2) )

# We could also print to a file here.
plt.show()

--
1129 Ibbetson Lane
Mississauga, Ontario#!/usr/bin/python
L5C 1K9 Canada

Thanks for this. The tripcolor function does what you want in 2D, but it hasn’t yet been extended to work with 3D axes. It was on my ‘to do’ list, and you’ve motivated me to start looking at it. When it’s done, your example code can be much simpler as the triangulation and colormap manipulation will all be done for you.

Ian

···

On 17 September 2010 16:26, Simon S. Clift <ssclift@…287…> wrote:

I have a data set that is in the form of an irregular 2D grid with

associated values, one for each node. I would like to plot this as a

raised surface, with colours that indicate the z-value. Somehow I

didn’t find just quite the example I was looking for. After digging

around in the matplotlib and mpl_toolkits.mplot3d I was able to solve

the problem. To save the next person the trouble, here is my

annotated example. Comments and improvements are most welcome. I’d

be happy to have a version of this included as an official example, if

it passes muster.