Having a volume implicitly defined by
x*y*z <= 1
for
-5 <= x <= 5
-5 <= y <= 5
-5 <= z <= 5
how would I go about plotting its outer surface using available Python modules, preferably mayavi?
I am aware of the function mlab.mesh, but I don't understand its input. It requires three 2D arrays, that I don't understand how to create having the above information.
EDIT:
Maybe my problem lies with an unsufficient understanding of the meshgrid()-function or the mgrid-class of numpy. I see that I have to use them in some way, but I do not completely grasp their purpose or what such a grid represents.
EDIT:
I arrived at this:
import numpy as np
from mayavi import mlab
x, y, z = np.ogrid[-5:5:200j, -5:5:200j, -5:5:200j]
s = x*y*z
src = mlab.pipeline.scalar_field(s)
mlab.pipeline.iso_surface(src, contours=[1., ],)
mlab.show()
This results in an isosurface (for x*y*z=1) of a volume though, which is not quite what I was looking for. What I am looking for is basically a method to draw an arbitrary surface, like a "polygon in 3d" if there is such a thing.
I created the following code, which plots a surface (works with mayavi, too). I would need to modify this code to my particular problem, but to do that I need to understand why and how a 3d surface is defined by three 2d-arrays? What do these arrays (x, y and z) represent?
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import axes3d, Axes3D
phi, theta = np.mgrid[0:np.pi:11j, 0:2*np.pi:11j]
x = np.sin(phi) * np.cos(theta)
y = np.sin(phi) * np.sin(theta)
z = np.cos(phi)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(x,y,z)
fig.show()
The outer surface, implicitly defined by
x*y*z = 1,
cannot be defined explicitly globally. To see this, consider x and y given, then:
z = 1/(x*y),
which is not defined for x = 0 or y = 0. Therefore, you can only define your surface locally for domains that do not include the singularity, e.g. for the domain
0 < x <= 5
0 < y <= 5
z is indeed defined (a hyperbolic surface). Similarly, you need to plot the surfaces for the other domains, until you have patched together
-5 <= x <= 5
-5 <= y <= 5
Note that your surface is not defined for x = 0 and y = 0, i.e. the axis of your coordinate system, so you cannot patch your surfaces together to get a globally defined surface.
Using numpy and matplotlib, you can plot one of these surfaces as follows (adopted from http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#surface-plots):
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0.25, 5, 0.25)
Y = np.arange(0.25, 5, 0.25)
X, Y = np.meshgrid(X, Y)
Z = 1/(X*Y)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(0, 10)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
I'm not familiar with mayavi, but I would assume that creating the meshes with numpy would work the same.
The test case in the Mayavi docs where the function test_mesh() is defined is capable of producing a sphere. This is done by replacing
r = sin(m0*phi)**m1 + cos(m2*phi)**m3 + sin(m4*theta)**m5 + cos(m6*theta)**m7
with r = 1.0 say.
However, your problem is you need to understand that the equations you are writing define a volume when you want to draw a sphere. You need to reformulate them to give a parametric equation of a sphere. This is essentially what is done in the above example, but it may be worth your while to try it yourself. As a hint consider the equation of a circle and extend it.
Related
I'm trying to graph a 3d mesh surface with matplotlib and constrain the limits of the graph. The X and Y axes are correctly constrained, but there is overflow in the Z-Axis.
What am I missing? Here's my code:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(10,10))
x = np.linspace(-6,6,100)
y = np.linspace(-6,6,100)
X,Y = np.meshgrid(x,y)
def f(x,y):
return x**2 + 3*y
Z = f(X,Y)
ax = plt.axes(projection = '3d')
ax.plot_surface(X,Y,Z,cmap='viridis')
ax.title.set_text("z=x**2+3y")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_zlim3d(zmin=-3,zmax=5)
ax.set_xlim3d(xmin=-6,xmax=6)
ax.set_ylim3d(ymin=-6,ymax=6)
plt.show()
The graph:
Edit:
When I add clipping/min/max to the Z values, the graph is a little better, but it sets z values outside the bounds to the bounds themselves. Both of the following suggestions do this. Perhaps it's because I'm on a mac?
z_tmp = np.maximum(np.minimum(5,Z),-3)
z_temp = np.clip(Z, -3, 5, None)
Your data is outside the axis boundaries. Try rotate the view and you will notice.
z = x**2 + 3*y
If you want to only show a defined area of the data you could add a max() min() limitation on the Z data to exclude the data outside your wanted limitations.
Z = f(X,Y)
z_tmp = np.maximum(np.minimum(5,Z),-3)
ax = plt.axes(projection = '3d')
ax.plot_surface(X,Y,z_tmp,cmap='viridis')
I'm not sure the matplotlib behaves as it should in your default case.
So, I've got three arrays of data, X, Y, and Z, each 225 numbers long. What I would like to do is to plot all three values at the same time on a surface plot. When I try to use
ax.plot_surface(X,Y,Z)
it tells me that Z needs to be a 2D array. I've looked it up and I've seen that it's possible to plot Z if it was a function of X and Y, but I need the first Z point to be associated with the first X and Y point etc. Is this possible in Python?
If your arrays are all 1-D, then I think what you want is
ax.plot_trisurf(X,Y,Z, triangles=tri.triangles, cmap=plt.cm.Spectral)
See more info at https://matplotlib.org/examples/mplot3d/trisurf3d_demo2.html
So, I've got three arrays of data, X, Y, and Z, each 225 numbers long. What I would like to do is to plot all three values at the same time on a surface plot.
So, from what i understood you want to plot a 3d surface plot.
But it seems you are only providing 3 1xn arrays. (in this case n == 255)
When plotting a surface plot, what you are doing in practice is getting each and every possible combination of a base (XY plane) and telling how high is a point Z on that given XY coordinates, hence Z is depicted as a function Z(i,j)
but I need the first Z point to be associated with the first X and Y point etc. Is this possible in Python?
Yes, but if you associate each Z point to the first X,Y and so on, you would only have the Z values for X==Y, which would be incomplete information for your surfaceplot!
A good (great) example of surface plot comes from matplotlib official docs
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
# Make data.
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
which results in:
What the code is actually doing:
Defining the input vectors X and Y (both range and interval)
Making a meshgrid out of those vectors (if unclear as to what a meshgrid is, print the output!)
Defining a function over the X,Y domain
Applying it to get Z
If you check, X,Y and Z are 2 dimensional arrays!
Hope it helps!
Okay, apologies for this question but I'm pulling my hair out here.
I have a data structure loaded in python in the form:
[(1,0,#),(1,1,#),(1,2,#),(1,3,#),(2,0,#),(2,1,#) ... (26,3,#)]
with # being a different number each time that I wish to represent on the z-axis. You can see that x and y are always integers.
Plotting a scatter graph is simple:
x,y,z = zip(*data)
fig = plt.figure()
ax = fig.gca(projection = '3d')
surface = ax.scatter(x, y, z)
plt.show()
But when it comes to surfaces, I can see two methods:
1) Call ax.plot_trisurf(), which should work with 1D arrays similar to ax.scatter() and apparently works here, but for me gives me an error:
"AttributeError: Axes3D subplot object has not attribute 'plot_trisurf'"
This error also appears if I use the example source code at:
http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#tri-surface-plots, suggesting it's something wrong with my installation - my Matplotlib version is 1.1.1rc,. This error does not appear if, for example, ax.plot_surface() is called, nor ax.scatter().
2) Use meshgrid() or griddata() in combination with ax.plot_surface() - in either case, after two days' of pouring over the documentation and examples, I still don't understand how to correctly use these in my case, particularly when it comes to generating the values for Z.
Any help would be much appreciated.
To address your first question (1) I believe you need to import Axes3D from the mplot3d library, even if you're not directly calling it. Maybe try adding
from mpl_toolkits.mplot3d import Axes3D
before your main code (this line triggered a memory while reading the tutorial).
As for (2), X, Y and Z need to be matrix (2d array) type objects. This can get confusing, but you may consider an example:
# two arrays - one for each axis
x = np.arange(-5, 5, 0.25)
y = np.arange(-5, 5, 0.25)
# create a mesh / matrix like object from the arrays
X, Y = np.meshgrid(x, y)
# create Z values - also in a mesh like shape
Z = np.sin(np.sqrt(X**2 + Y**2))
# plot!
surface = ax.plot_surface(X, Y, Z)
Here is an example of how could you extract your z-values from data
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
data = [(j,i,i**2 + j) for j in range(1,27) for i in range(4)]
print data
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0, 4, 1)
Y = np.arange(1, 27, 1)
X, Y = np.meshgrid(X, Y)
print X.shape
print Y.shape
Z = np.array([z for _,_,z in data]).reshape(26,4)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=True)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
I want to create some plots of the farfield of electromagnetic scattering processes.
To do this, I calculated values θ, φ and r. The coordinates θ and φ create a regular grid on the unitsphere so I can use plot_Surface (found here) with conversion to cartesian coordinates.
My problem is now, that I need a way to color the surface with respect to the radius r and not height z, which seems to be the default.
Is there a way, to change this dependency?
I don't know how you're getting on, so maybe you've solved it. But, based on the link from Paul's comment, you could do something like this. We pass the color values we want using the facecolor argument of plot_surface.
(I've modified the surface3d demo from the matplotlib docs)
EDIT: As Stefan noted in his comment, my answer can be simplified to:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
xlen = len(X)
Y = np.arange(-5, 5, 0.25)
ylen = len(Y)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
maxR = np.amax(R)
Z = np.sin(R)
# Note that the R values must still be normalized.
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=cm.jet(R/maxR),
linewidth=0)
plt.show()
And (the end of) my needlessly complicated original version, using the same code as above though omitting the matplotlib.cm import,
# We will store (R, G, B, alpha)
colorshape = R.shape + (4,)
colors = np.empty( colorshape )
for y in range(ylen):
for x in range(xlen):
# Normalize the radial value.
# 'jet' could be any of the built-in colormaps (or your own).
colors[x, y] = plt.cm.jet(R[x, y] / maxR )
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=colors,
linewidth=0)
plt.show()
Teacher in class gave this formula
w = x**2 + y**2 - z**2
and showed its 3d graphic in class seen below. How do I plot this using Matplotlib (minus the intersecting plane)? I guess first a specific value for w needs to be selected, for example 10, otherwise 3d plotting would not be possible. Then should I convert to polar coordinates because of the z**2 in the formula? I tried this and failed. Any help would be appreciated. Also, does this shape have a name?
Got it. Found some good stuff here, and following the formulas presented, I have the Python code below.
http://msenux.redwoods.edu/Math4Textbook/Plotting/ParametricSurfaces.pdf
from __future__ import division
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=plt.figaspect(1)) # Square figure
ax = fig.add_subplot(111, projection='3d')
r=1;
u=np.linspace(-2,2,200);
v=np.linspace(0,2*np.pi,60);
[u,v]=np.meshgrid(u,v);
a = 1
b = 1
c = 1
x = a*np.cosh(u)*np.cos(v)
y = b*np.cosh(u)*np.sin(v)
z = c*np.sinh(u)
ax.plot_surface(x, y, z, rstride=4, cstride=4, color='b')
plt.show()