matplotlib 3D: equivalent to Matlab patch()?

Dear list,

is there an equivalent function to matlabs patch() function in matplotlib?
  http://dali.feld.cvut.cz/ucebna/matlab/techdoc/ref/patch.html
That means a function which does not require its X,Y,Z arguments to
come from a prior call to meshgrid? That is at least what I believe to
be the requirement for Axes3DSubplot.plot_surface.

In matlab you can pass n x k matrices as X,Y,Z argument, which then
means that there are n Polygons (each with k vertices) and they each
get shaded. The problem with the meshgrid structure is, that an
unstructured mesh does not have this tensor structure, so if I have
1000 quadrilaterals, then I would have to call
`Axes3DSubplot.plot_surface` for each of them separately. At least
with `gtkagg` this even seems to fail with z-buffer problem. Btw.
which backend to you recommend for 3D plots?

-Holger

···

--
Holger Brandsmeier, SAM, ETH Zürich
http://www.sam.math.ethz.ch/people/bholger

There are some ways to do what you want. For some simple examples:

http://matplotlib.sourceforge.net/examples/mplot3d/polys3d_demo.html

http://matplotlib.sourceforge.net/examples/mplot3d/pathpatch3d_demo.html

These examples show how you can take a normal 2D matplotlib patch and convert it into a 3D object.

Unfortunately, I do not have any example on hand on how to work with the 3D versions of the objects directly, but that is possible. You can find the classes in the ‘mpl_toolkits.mplot3d.art3d’ module. Do keep in mind that mplot3d is currently only meant for simple 3D plots, as it can not properly lay out a 3D environment (intersecting polygons, for example will look Escher-like). As for backends, the only thing that mplot3d requires is that it uses an Agg-based backend so that it can perform arbitrary rotation of text elements (for axes labels).

I hope this helps a bit.
Ben Root

···

On Thu, Sep 29, 2011 at 4:09 AM, Holger Brandsmeier <holger.brandsmeier@…3803…> wrote:

Dear list,

is there an equivalent function to matlabs patch() function in matplotlib?

http://dali.feld.cvut.cz/ucebna/matlab/techdoc/ref/patch.html

That means a function which does not require its X,Y,Z arguments to

come from a prior call to meshgrid? That is at least what I believe to

be the requirement for Axes3DSubplot.plot_surface.

In matlab you can pass n x k matrices as X,Y,Z argument, which then

means that there are n Polygons (each with k vertices) and they each

get shaded. The problem with the meshgrid structure is, that an

unstructured mesh does not have this tensor structure, so if I have

1000 quadrilaterals, then I would have to call

Axes3DSubplot.plot_surface for each of them separately. At least

with gtkagg this even seems to fail with z-buffer problem. Btw.

which backend to you recommend for 3D plots?

-Holger

Hi Ben,

in your example

http://matplotlib.sourceforge.net/examples/mplot3d/polys3d_demo.html

the essential line is:
  ax.add_collection3d(poly, zs=zs, zdir='y')
according to everything I see this can only draw something that is
made out of several axes parallel parts. I don't think how with such a
command you could end up with some picture like:
  http://matplotlib.sourceforge.net/plot_directive/mpl_examples/mplot3d/surface3d_demo.hires.png
What I want to plot should look like that picture (with is part of
your gallery), just that I don't have a meshgrid data underlying.

Your second example seems to have the same limitation
  http://matplotlib.sourceforge.net/examples/mplot3d/pathpatch3d_demo.html
just it works with the command
  art3d.pathpatch_2d_to_3d(p, z=0, zdir="x")

The only way that I currently see is that I call
  ax.plot_surface()
for each quadrilateral of my mesh.

I'm actually only looking for a function which can shade a set of
polygons that make up a picture like
  http://matplotlib.sourceforge.net/plot_directive/mpl_examples/mplot3d/surface3d_demo.hires.png
I might be able to limit the polygons to be quadrilaterals only. The
mesh will be nice in the sense that polygons do not intersect.
Otherwise the list of polygons should be arbitrary.

-Holger

···

On Thu, Sep 29, 2011 at 17:33, Benjamin Root <ben.root@...1304...> wrote:

On Thu, Sep 29, 2011 at 4:09 AM, Holger Brandsmeier > <holger.brandsmeier@...3803...> wrote:

Dear list,

is there an equivalent function to matlabs patch() function in matplotlib?
http://dali.feld.cvut.cz/ucebna/matlab/techdoc/ref/patch.html
That means a function which does not require its X,Y,Z arguments to
come from a prior call to meshgrid? That is at least what I believe to
be the requirement for Axes3DSubplot.plot_surface.

In matlab you can pass n x k matrices as X,Y,Z argument, which then
means that there are n Polygons (each with k vertices) and they each
get shaded. The problem with the meshgrid structure is, that an
unstructured mesh does not have this tensor structure, so if I have
1000 quadrilaterals, then I would have to call
`Axes3DSubplot.plot_surface` for each of them separately. At least
with `gtkagg` this even seems to fail with z-buffer problem. Btw.
which backend to you recommend for 3D plots?

-Holger

There are some ways to do what you want. For some simple examples:

http://matplotlib.sourceforge.net/examples/mplot3d/polys3d_demo.html
http://matplotlib.sourceforge.net/examples/mplot3d/pathpatch3d_demo.html

These examples show how you can take a normal 2D matplotlib patch and
convert it into a 3D object.

Unfortunately, I do not have any example on hand on how to work with the 3D
versions of the objects directly, but that is possible. You can find the
classes in the 'mpl_toolkits.mplot3d.art3d' module. Do keep in mind that
mplot3d is currently only meant for simple 3D plots, as it can not properly
lay out a 3D environment (intersecting polygons, for example will look
Escher-like). As for backends, the only thing that mplot3d requires is that
it uses an Agg-based backend so that it can perform arbitrary rotation of
text elements (for axes labels).

I hope this helps a bit.
Ben Root

--
Holger Brandsmeier, SAM, ETH Zürich
http://www.sam.math.ethz.ch/people/bholger

Hi Ben,

in your example

http://matplotlib.sourceforge.net/examples/mplot3d/polys3d_demo.html

the essential line is:

ax.add_collection3d(poly, zs=zs, zdir=‘y’)

according to everything I see this can only draw something that is

made out of several axes parallel parts. I don’t think how with such a

command you could end up with some picture like:

http://matplotlib.sourceforge.net/plot_directive/mpl_examples/mplot3d/surface3d_demo.hires.png

That is correct. It is only for simple conversions.

What I want to plot should look like that picture (with is part of

your gallery), just that I don’t have a meshgrid data underlying.

That is why you have to work with the 3D objects from mpl_toolkits.mplot3d.art3d directly. There isn’t a lot of documentation, so your best bet is to look at the source code of the module.

Your second example seems to have the same limitation

http://matplotlib.sourceforge.net/examples/mplot3d/pathpatch3d_demo.html

just it works with the command

art3d.pathpatch_2d_to_3d(p, z=0, zdir=“x”)

The only way that I currently see is that I call

ax.plot_surface()

for each quadrilateral of my mesh.

That would be overkill. However, try checking out the source code for plot_surface() to see how it creates its Poly3DCollection object.

Ben Root

···

On Thu, Sep 29, 2011 at 4:31 PM, Holger Brandsmeier <holger.brandsmeier@…3803…> wrote:

That is why you have to work with the 3D objects from
mpl_toolkits.mplot3d.art3d directly. There isn't a lot of documentation, so
your best bet is to look at the source code of the module.

Indeed, I was able to extract the necessary pieces from the source
code. In fact I split the function `plot_surface` into two functions
`polyMeshFromMeshGrid` and `plot_surfaceMesh`. The first functions
takes the meshgrid arguments and produces a list of polygons that
should be drawn, the second functions finally displays them (the code
in plot_surface is already structured like this). With this approach I
can plot functions which are made up of several parts which each have
meshgrid structure. Also I can add raw polygons to the mesh.

Maybe this splitting makes sense in the online code as well?

-Holger

def polyMeshFromMeshGrid(self, X, Y, Z, *args, **kwargs):
    rows, cols = Z.shape
    tX, tY, tZ = np.transpose(X), np.transpose(Y), np.transpose(Z)
    rstride = kwargs.pop('rstride', 10)
    cstride = kwargs.pop('cstride', 10)

    if 'facecolors' in kwargs:
        fcolors = kwargs.pop('facecolors')
    else:
        color = np.array(colorConverter.to_rgba(kwargs.pop('color', 'b')))
        fcolors = None

    cmap = kwargs.get('cmap', None)
    norm = kwargs.pop('norm', None)
    vmin = kwargs.pop('vmin', None)
    vmax = kwargs.pop('vmax', None)
    shade = kwargs.pop('shade', cmap is None)
    lightsource = kwargs.pop('lightsource', None)

    # Shade the data
    if shade and cmap is not None and fcolors is not None:
        fcolors = self._shade_colors_lightsource(Z, cmap, lightsource)

    polys = kwargs.pop('polys', [])
    normals = kwargs.pop('normals', [])
    #colset contains the data for coloring: either average z or the facecolor
    colset = kwargs.pop('colset', [])

    for rs in np.arange(0, rows-1, rstride):
        for cs in np.arange(0, cols-1, cstride):
            ps = []
            corners = []
            for a, ta in [(X, tX), (Y, tY), (Z, tZ)]:
                ztop = a[rs][cs:min(cols, cs+cstride+1)]
                zleft = ta[min(cols-1, cs+cstride)][rs:min(rows, rs+rstride+1)]
                zbase = a[min(rows-1, rs+rstride)][cs:min(cols, cs+cstride+1):]
                zbase = zbase[::-1]
                zright = ta[cs][rs:min(rows, rs+rstride+1):]
                zright = zright[::-1]
                corners.append([ztop[0], ztop[-1], zbase[0], zbase[-1]])
                z = np.concatenate((ztop, zleft, zbase, zright))
                ps.append(z)

            # The construction leaves the array with duplicate points, which
            # are removed here.
            ps = zip(*ps)
            lastp = np.array([])
            ps2 = []
            avgzsum = 0.0
            for p in ps:
                if p != lastp:
                    ps2.append(p)
                    lastp = p
                    avgzsum += p[2]
            polys.append(ps2)

            if fcolors is not None:
                colset.append(fcolors[rs][cs])
            else:
                colset.append(avgzsum / len(ps2))

            # Only need vectors to shade if no cmap
            if cmap is None and shade:
                v1 = np.array(ps2[0]) - np.array(ps2[1])
                v2 = np.array(ps2[2]) - np.array(ps2[0])
                normals.append(np.cross(v1, v2))

    return polys, normals, colset

def plot_surfaceMesh(self, polys, normals, colset, *args, **kwargs):
    had_data = self.has_data()

    rows, cols = Z.shape
    rstride = kwargs.pop('rstride', 10)
    cstride = kwargs.pop('cstride', 10)

    if 'facecolors' in kwargs:
        fcolors = kwargs.pop('facecolors')
    else:
        color = np.array(colorConverter.to_rgba(kwargs.pop('color', 'b')))
        fcolors = None

    cmap = kwargs.get('cmap', None)
    norm = kwargs.pop('norm', None)
    vmin = kwargs.pop('vmin', None)
    vmax = kwargs.pop('vmax', None)
    linewidth = kwargs.get('linewidth', None)
    shade = kwargs.pop('shade', cmap is None)
    lightsource = kwargs.pop('lightsource', None)

    polyc = art3d.Poly3DCollection(polys, *args, **kwargs)

    if fcolors is not None:
        if shade:
            colset = self._shade_colors(colset, normals)
        polyc.set_facecolors(colset)
        polyc.set_edgecolors(colset)
    elif cmap:
        colset = np.array(colset)
        polyc.set_array(colset)
        if vmin is not None or vmax is not None:
            polyc.set_clim(vmin, vmax)
        if norm is not None:
            polyc.set_norm(norm)
    else:
        if shade:
            colset = self._shade_colors(color, normals)
        else:
            colset = color
        polyc.set_facecolors(colset)

    self.add_collection(polyc)

    x, y, z = [], [], [];
    for pol in polys:
      for p in pol:
        x.append(p[0])
        y.append(p[1])
        z.append(p[2])

    self.auto_scale_xyz(x, y, z, had_data)
    return polyc