trisurf plots with independent color data

I often have Electromagnetic surface current data which I use MATLAB's trisurf function to plot. Since the surfaces are 3-dimensional I need a trisurf plotting tool which lets me specify the color of each triangle/vertex. MATLAB's trisurf function allows me to do that by passing it an array of colors (http://www.mathworks.com/help/matlab/ref/trisurf.html) along with the arrays of X, Y, and Z coordinates of vertices.

Matplotlib's plot_trisurf from mplot3d only seems to allow me to specify one constant color for the entire trisurf plot or to color the triangles according to the z-coordinates. Are there any plans to add this functionality to mplot3d's plot_trisurf? Does anyone here know how difficult it would be?

Byron Boulton

Hi Byron,

This is a bit of a workaround, but you can specify facecolors explicitly by creating a triangulation of your surface explicitly and creating a Poly3DCollection with these facecolors. I’m attaching an example below which is a modified version of the plot_trisurf demo [1] in the matplotlib documentation. It showcases both random colors and a smooth gradient (the latter in the line that’s commented out).

I would have thought that it should be possible to pass an argument like “facecolors” to plot_trisurf directly, since the documentation [2] states that “other arguments are passed on to Poly3DCollection”. However, I couldn’t get this to work quickly. Maybe someone else knows how?

Best regards,

Max

[1] http://matplotlib.org/examples/mplot3d/trisurf3d_demo.html

[2] http://matplotlib.org/mpl_toolkits/mplot3d/api.html

from mpl_toolkits.mplot3d import Axes3D

from matplotlib import cm

import matplotlib.pyplot as plt

import numpy as np

from matplotlib.tri import Triangulation

from mpl_toolkits.mplot3d.art3d import Poly3DCollection

n_angles = 36

n_radii = 8

An array of radii

Does not include radius r=0, this is to eliminate duplicate points

radii = np.linspace(0.125, 1.0, n_radii)

An array of angles

angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)

Repeat all angles for each radius

angles = np.repeat(angles[…,np.newaxis], n_radii, axis=1)

Convert polar (radii, angles) coords to cartesian (x, y) coords

(0, 0) is added here. There are no duplicate points in the (x, y) plane

x = np.append(0, (radii*np.cos(angles)).flatten())

y = np.append(0, (radii*np.sin(angles)).flatten())

Pringle surface

z = np.sin(-x*y)

tri = Triangulation(x, y) # NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!

triangle_vertices = np.array([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.triangles])

midpoints = np.average(triangle_vertices, axis=1)

def find_color_for_point(pt):

x, y, z = pt

col = [(y+1)/2, (1-y)/2, 0]

return col

#facecolors = [find_color_for_point(pt) for pt in midpoints] # smooth gradient

facecolors = [np.random.random(3) for pt in midpoints] # random colors

coll = Poly3DCollection(triangle_vertices, facecolors=facecolors, edgecolors=‘black’)

fig = plt.figure()

ax = fig.gca(projection=‘3d’)

ax.add_collection(coll)

ax.set_xlim(-1, 1)

ax.set_ylim(-1, 1)

ax.set_zlim(-1, 1)

ax.elev = 50

plt.show()

Thanks. This looks like it might work for me. I got your example to work, but I still need to figure out how to apply it to my problem. In particular there is the note about your Triangulation line which says that we assume there is a nice projection of the surface into the x/y-plane. Is this just a requirement of the Triangulation function or of the Poly3Dcollection? My surfaces are typically enclosed bodies, but since they’re meshed for the Electromagnetic Simulation I already know the connectivity of the vertices. Also, in your example it appears that you define one color per triangle but the trisurf files I have consist of one color per vertex. Will I need to come up with my own function for defining facecolors based on the three color values associated with each of my triangles?

Byron Boulton

···

From: Maximilian Albert [mailto:maximilian.albert@…149…]
Sent: Wednesday, January 21, 2015 11:03 AM
To: Byron K. Boulton
Cc: matplotlib-devel@lists.sourceforge.net
Subject: Re: [matplotlib-devel] trisurf plots with independent color data

Hi Byron,

This is a bit of a workaround, but you can specify facecolors explicitly by creating a triangulation of your surface explicitly and creating a Poly3DCollection with these facecolors. I’m attaching an example below which is a modified version of the plot_trisurf demo [1] in the matplotlib documentation. It showcases both random colors and a smooth gradient (the latter in the line that’s commented out).

I would have thought that it should be possible to pass an argument like “facecolors” to plot_trisurf directly, since the documentation [2] states that “other arguments are passed on to Poly3DCollection”. However, I couldn’t get this to work quickly. Maybe someone else knows how?

Best regards,

Max

[1] http://matplotlib.org/examples/mplot3d/trisurf3d_demo.html

[2] http://matplotlib.org/mpl_toolkits/mplot3d/api.html

from mpl_toolkits.mplot3d import Axes3D

from matplotlib import cm

import matplotlib.pyplot as plt

import numpy as np

from matplotlib.tri import Triangulation

from mpl_toolkits.mplot3d.art3d import Poly3DCollection

n_angles = 36

n_radii = 8

An array of radii

Does not include radius r=0, this is to eliminate duplicate points

radii = np.linspace(0.125, 1.0, n_radii)

An array of angles

angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)

Repeat all angles for each radius

angles = np.repeat(angles[…,np.newaxis], n_radii, axis=1)

Convert polar (radii, angles) coords to cartesian (x, y) coords

(0, 0) is added here. There are no duplicate points in the (x, y) plane

x = np.append(0, (radii*np.cos(angles)).flatten())

y = np.append(0, (radii*np.sin(angles)).flatten())

Pringle surface

z = np.sin(-x*y)

tri = Triangulation(x, y) # NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!

triangle_vertices = np.array([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.triangles])

midpoints = np.average(triangle_vertices, axis=1)

def find_color_for_point(pt):

x, y, z = pt

col = [(y+1)/2, (1-y)/2, 0]

return col

#facecolors = [find_color_for_point(pt) for pt in midpoints] # smooth gradient

facecolors = [np.random.random(3) for pt in midpoints] # random colors

coll = Poly3DCollection(triangle_vertices, facecolors=facecolors, edgecolors=‘black’)

fig = plt.figure()

ax = fig.gca(projection=‘3d’)

ax.add_collection(coll)

ax.set_xlim(-1, 1)

ax.set_ylim(-1, 1)

ax.set_zlim(-1, 1)

ax.elev = 50

plt.show()

Hi,

You can also try to pass a color array of size ntri (number of triangles) to the set_array method of the collection returned by plot_trisurf.

See for instance: http://stackoverflow.com/questions/24218543/colouring-the-surface-of-a-sphere-with-a-set-of-scalar-values-in-matplotlib/24229480#24229480


···

2015-01-21 17:02 GMT+01:00 Maximilian Albert <maximilian.albert@…149…>:

Hi Byron,

This is a bit of a workaround, but you can specify facecolors explicitly by creating a triangulation of your surface explicitly and creating a Poly3DCollection with these facecolors. I’m attaching an example below which is a modified version of the plot_trisurf demo [1] in the matplotlib documentation. It showcases both random colors and a smooth gradient (the latter in the line that’s commented out).

I would have thought that it should be possible to pass an argument like “facecolors” to plot_trisurf directly, since the documentation [2] states that “other arguments are passed on to Poly3DCollection”. However, I couldn’t get this to work quickly. Maybe someone else knows how?

Best regards,

Max

[1] http://matplotlib.org/examples/mplot3d/trisurf3d_demo.html

[2] http://matplotlib.org/mpl_toolkits/mplot3d/api.html

from mpl_toolkits.mplot3d import Axes3D

from matplotlib import cm

import matplotlib.pyplot as plt

import numpy as np

from matplotlib.tri import Triangulation

from mpl_toolkits.mplot3d.art3d import Poly3DCollection

n_angles = 36

n_radii = 8

An array of radii

Does not include radius r=0, this is to eliminate duplicate points

radii = np.linspace(0.125, 1.0, n_radii)

An array of angles

angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)

Repeat all angles for each radius

angles = np.repeat(angles[…,np.newaxis], n_radii, axis=1)

Convert polar (radii, angles) coords to cartesian (x, y) coords

(0, 0) is added here. There are no duplicate points in the (x, y) plane

x = np.append(0, (radii*np.cos(angles)).flatten())

y = np.append(0, (radii*np.sin(angles)).flatten())

Pringle surface

z = np.sin(-x*y)

tri = Triangulation(x, y) # NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!

triangle_vertices = np.array([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.triangles])

midpoints = np.average(triangle_vertices, axis=1)

def find_color_for_point(pt):

x, y, z = pt
col = [(y+1)/2, (1-y)/2, 0]
return col

#facecolors = [find_color_for_point(pt) for pt in midpoints] # smooth gradient

facecolors = [np.random.random(3) for pt in midpoints] # random colors

coll = Poly3DCollection(triangle_vertices, facecolors=facecolors, edgecolors=‘black’)

fig = plt.figure()

ax = fig.gca(projection=‘3d’)

ax.add_collection(coll)

ax.set_xlim(-1, 1)

ax.set_ylim(-1, 1)

ax.set_zlim(-1, 1)

ax.elev = 50

plt.show()


New Year. New Location. New Benefits. New Data Center in Ashburn, VA.

GigeNET is offering a free month of service with a new server in Ashburn.

Choose from 2 high performing configs, both with 100TB of bandwidth.

Higher redundancy.Lower latency.Increased capacity.Completely compliant.

http://p.sf.net/sfu/gigenet


Matplotlib-devel mailing list

Matplotlib-devel@lists.sourceforge.net

https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

I’ve worked out how to create a Poly3DCollection as noted by Maximilian Albert and how to use the set_array method on the collection created by plot_trisurf as noted by Geoffroy Billotey. I have two follow up questions:

  1.  Would it be possible to make it so that if the color parameter of plot_trisurf is an array of length n_triangles, that plot_trisurf would internally use set_array to color the triangles with the colors in the color parameter?
    
  2.  Is there a way to color the triangles with gradients? I have an intensity value at each vertex of the triangles so Matlab/octave colors each triangle with a gradient whereas right now I’m taking an average of the values at the vertices and assigning the resulting value to the triangle.
    

Byron Boulton

···

From: Geoffroy Billotey [mailto:geoffroy.billotey@…149…]
Sent: Wednesday, January 21, 2015 12:33 PM
To: matplotlib-devel@lists.sourceforge.net; Byron K. Boulton
Subject: Re: [matplotlib-devel] trisurf plots with independent color data

Hi,

You can also try to pass a color array of size ntri (number of triangles) to the set_array method of the collection returned by plot_trisurf.

See for instance:
http://stackoverflow.com/questions/24218543/colouring-the-surface-of-a-sphere-with-a-set-of-scalar-values-in-matplotlib/24229480#24229480


2015-01-21 17:02 GMT+01:00 Maximilian Albert <maximilian.albert@...149...>:

> Hi Byron,

> This is a bit of a workaround, but you can specify facecolors explicitly by creating a triangulation of your surface explicitly and creating a Poly3DCollection with these facecolors. I'm attaching an example below which is a modified version of the plot_trisurf demo [1] in the matplotlib documentation. It showcases both random colors and a smooth gradient (the latter in the line that's commented out).

> I would have thought that it should be possible to pass an argument like "facecolors" to plot_trisurf directly, since the documentation [2] states that "other arguments are passed on to Poly3DCollection". However, I couldn't get this to work quickly. Maybe someone else knows how?

> Best regards,

> Max

> [1] [http://matplotlib.org/examples/mplot3d/trisurf3d_demo.html](http://cp.mcafee.com/d/k-Kr6x0SyMCyYUy-qenztPqdQPhOCepshhdETjd7aoVBNOVJ6WpEVj7cK8FCQrFCzBcsOUeKr85i5mPVRw2y7NzbOszP-ndAfz6nAV7DYKrggjZQwM_R-puK-_RXBQSkSu7fcEKsJteOaqJTCel3PWApmU6CQPrVK_9IInojvvpjdTdw0RtCIvmpYy700sYf21_QNlo-ISkcD6l6UKIE2G7GRU76OfRttzkJN2hVZp-m1h4jVsSyMMCMr1vF6y0QJGXdo-IPV42W7CvfdbFEwD6l6UKCy1o-IW6y0NapcQgiwxWTMg-4Ph1qFZoCy13pWrsodCuBsX5L-)

> [2] [http://matplotlib.org/mpl_toolkits/mplot3d/api.html](http://cp.mcafee.com/d/1jWVIq6h8gdEI9ELe8LCzBUTsSztcQsFzCn4kjqdQPhOCepssKrhKCqekNPbyapJ6WpEVj7cK3HCO1kxlI-to0ExYoOYD8Y_BPp3UNBVehV_bCQ44_t8cfZvCnHLLZuVtdBdDxPPabDbnjIyCHtVzBgY-F6lK1FJAS-rLOrb5S4TTSkPtPo0dnpH7RCv8xM075lzNmtqDBpjFyGNZpIEpGQK1NIzZnnoRbsgAuvmvBwkh4-ndEIc9I6MnWhEwdbqKPmfHc-h0KxVDPPiWq89NBhKbFEwmfHexEwciCjd44E8uJY4fxcQgmGvm9EwgSuCT63oeMA92eD)

> from mpl_toolkits.mplot3d import Axes3D

> from matplotlib import cm

> import matplotlib.pyplot as plt

> import numpy as np

> from matplotlib.tri import Triangulation

> from mpl_toolkits.mplot3d.art3d import Poly3DCollection

> n_angles = 36

> n_radii = 8

> # An array of radii

> # Does not include radius r=0, this is to eliminate duplicate points

> radii = np.linspace(0.125, 1.0, n_radii)

> # An array of angles

> angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)

> # Repeat all angles for each radius

> angles = np.repeat(angles[...,np.newaxis], n_radii, axis=1)

> # Convert polar (radii, angles) coords to cartesian (x, y) coords

> # (0, 0) is added here. There are no duplicate points in the (x, y) plane

> x = np.append(0, (radii*np.cos(angles)).flatten())

> y = np.append(0, (radii*np.sin(angles)).flatten())

> # Pringle surface

> z = np.sin(-x*y)

> tri = Triangulation(x, y)  # NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!

> triangle_vertices = np.array([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.triangles])

> midpoints = np.average(triangle_vertices, axis=1)

> def find_color_for_point(pt):

>     x, y, z = pt

>     col = [(y+1)/2, (1-y)/2, 0]

>     return col

> #facecolors = [find_color_for_point(pt) for pt in midpoints]  # smooth gradient

> facecolors = [np.random.random(3) for pt in midpoints]  # random colors

> coll = Poly3DCollection(triangle_vertices, facecolors=facecolors, edgecolors='black')

> fig = plt.figure()

> ax = fig.gca(projection='3d')

> ax.add_collection(coll)

> ax.set_xlim(-1, 1)

> ax.set_ylim(-1, 1)

> ax.set_zlim(-1, 1)

> ax.elev = 50

> plt.show()

> 
> ------------------------------------------------------------------------------
> New Year. New Location. New Benefits. New Data Center in Ashburn, VA.
> GigeNET is offering a free month of service with a new server in Ashburn.
> Choose from 2 high performing configs, both with 100TB of bandwidth.
> Higher redundancy.Lower latency.Increased capacity.Completely compliant.
> [http://p.sf.net/sfu/gigenet](http://cp.mcafee.com/d/avndy0sd6Qm4QnD4nPhOYrKrhKCqekNPbya9J6WpEVj7cKendETjd7aoVBN5cSztcQsFzCn1RPp0GgGSveI0kg-cpujAuvOVIxYoOYD8Y_BPq22vKA67-LPbRTT-LsKCOCPMVVB5PBHFShjlKYNOEuvkzaT0QSMrvdTVdByX2rXXapKVI06JR7u1KktrMz8zUddAvGWX6Fry4zPWPYI2y8DOVJ5xxdwS2_id41FrlSqNZpDO85Qfc-uqnjh1ecGdNtd42NZpQd41ykOpEwB13RLwxY9Cy2RjWNd426PQSUMrFKoBfrL)
> _______________________________________________
> Matplotlib-devel mailing list
> Matplotlib-devel@lists.sourceforge.net
> [https://lists.sourceforge.net/lists/listinfo/matplotlib-devel](http://cp.mcafee.com/d/5fHCMUi6hEpdEI9ELe8LCzBUTsSztcQsFzCn4kjqdQPhOCepssKrhKCqekNPbyapJ6WpEVj7cK3HCO1kxlI-to0ExYoOYD8Y_BPp3UNBVehV_bCQ44_t8cfZvCnHLLZuVtdBdDxPPabDbnjIyCHtVzBgY-F6lK1FJYS-rLOrb5S4TTSkPtPpesRG9pxYGjFRaJNJXE07-1K7OFeC7OFek7qUHIRzWPfAgbEupYYSh-HHIqBK8iffHfOMa8yvbCQm64S3obZ8Qg6BJnpH7RCv8wngYPVVFtd44UOET5QQgb7RDgQg69j9Cy2k4fm-27MCq8blfH4Qg8rfjrz1I7gc)

</details>