3D polygons in Plots.jl

Is there an equivalent to Matlab’s fill3 in Plots.jl to create filled shapes in 3D?
https://www.mathworks.com/help/matlab/ref/fill3.html

I see Plots.Shape, but it looks like it only supports 2D.

Thanks!

I found it. Looks like setting seriestype=:surface will do it.

2 Likes

Summary

An arguably better way to plot 3D polygons, that works even when your polygon lies in a vertical plane. This should be of special interest to people who want to mimic Matlab’s fill3d() in Julia.

The solution presented is for raw PyPlots. Hopefully somebody can massage it into something that works with Plots.jl.

I’d also be interested to hear if anyone has any success plotting polygons with holes in them. Probably the Python libraries have the capability to do this, but I am not very familiar with them. That’s another thing that’s probably pretty hard with surf(). (Polygons with holes aren’t too hard in 2D - you can just draw the outline in one color then draw the holes in another. But in 3D, the “holes” are never quite in the same plane as the main polygon at the 10th decimal place, so you get weird effects.)

Details

Background
For folks that have been following other threads, I’ve been fighting with this a bit. I’ve had issues getting the PyPlot backend to work when used from a script - it works fine interactively, but from a script I get that “_display is not defined” when I call PyPlot.gui(), and I haven’t been able to resolve that and haven’t seen anything about it online.

In any event, I switched to using straight PyPlot, and started plotting 3D polygons using surf() in PyPlot, which is the raw-PyPlot equivalent to the suggestion by rcnlee from March 2018.

The problem I’m trying to solve
What I ran into is that the PyPlot “surf” approach works fairly well, but at its core it appears to be a package that says “give me a z for every x and y”, and if you have a polygon that lies entirely in a vertical plane (e.g. every x-y-z point has x=0), it complains.

Some research indicated that what you want is to use the “Poly3DCollection” from matplotlib.pyplots and mpl_toolkits.mplot3d. I have verified that this makes nice clean polygons, a la Matlab fill3d.

What I came up with
After long futzing, reading the PyPlot code, and so on, I’ve come up with this. I am far from an expert, so I’m sure I’ll say some things wrong, but here is a set of steps.

First. get mplot3d. I think this step is required but not sure. This is also necessary in order to access certain PyPlot functions, I believe.

Pkg.add("PyCall")
using Pycall
pyimport_conda(“mpl_toolkits.mplot3d”, “mpl_toolkits”)

Now, at the root of what we want to do is call into Python’s ax.add_collection3d() and add a Poly3DCollection to the plot. That will render a polygon, even a vertical one.

Here is a code example that draws two polygons, including a vertical one, and also shows how to use some of the other options available. It also shows how you can mix native PyPlot calls with pycalls that reach more deeply into the 3D polygon objects and axes.

#####################
# Demo of 3D polygons including a vertical surface
#

using PyPlot
using PyCall
fig = figure()
art3d = PyObject(PyPlot.art3D)

# I use semicolons instead of commas, becaues I think of these as column
# vectors.  It doesn't really matter, but I guess I'm a purist.
xc = [0;0;1;1]
yc = [0;1;1;0]
zc = [1;1;2;2]
verts = (collect(zip(xc,yc,zc)),)
p3c = PyObject(art3d.Poly3DCollection(verts))

# This is necessary in order to get to a 3D axis
ax = gca(projection="3d")
# PyObject, PyPlot allows us use obj[:function_name] to call the member function
# ax[:plot](x, y, z).  But add_collection3d is not one of the symbols it
# defines.  This is what it would do if it were defined.
pycall(ax.add_collection3d, PyAny, p3c)

# Here I simply list them as points rather than x-y-z arrays
# Then you don't have to zip() them, and this is arguably more natural
# Just including this variant in case it's useful to anyone
p1 = [0;0;1]
p2 = [0;1;1]
p3 = [0;1;2]
p4 = [0;0;2]
verts2 = ([tuple(p1...); tuple(p2...); tuple(p3...); tuple(p4...)],)

p3c2 = PyObject(art3d.Poly3DCollection(verts2, alpha=0.5))
face_color = [1, 0, 0]
pycall(p3c2.set_facecolor, PyAny, face_color)
pycall(ax.add_collection3d, PyAny, p3c2)
ax.view_init(50,30)
zlim(0,2)

And the result:

Hopefully this helps the next person.

Brad

1 Like

I was wondering if these days there is a way to this in Julia? (I would like to plot a cylinder along the z axis).

Makie is the best option for this.

2 Likes