3D points on a surface PYTHON - python

I am trying to plot some points on a surface. The result I want to get is (the plot at the right):
I tried the following code (x_k contains the coordinates of the points I want to plot):
f = lambda x : x[0]**2+x[1]**3+3*x[0]*x[1]
x_axis = [x_k[i][0] for i in range(0, len(x_k))]
y_axis = [x_k[i][1] for i in range(0, len(x_k))]
z_axis = [f(x_k[i]) for i in range(0, len(x_k))]
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
X, Y = np.meshgrid(x, y)
Z = f(np.array([X, Y]))
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z,alpha=0.8,cmap='viridis',edgecolor='white',linewidth=0.3)
ax.scatter(x_axis, y_axis, z_axis, zdir='z',marker='.', s=10, c='black', depthshade=False)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_aspect("equal")
plt.tight_layout()
plt.show()
And I get this:
See that points are transparent because of the surface...
I want them to be black, so as to see them better. How could I do that? Thank you!

Related

3d matplotlib zaxis font

using this code :
data=np.genfromtxt('jpdfomegal2_90.dat')
x_len= len(np.unique(data[:, 0]))
y_len= len(np.unique(data[:, 1]))
#reshape X, Y, and Z into 2D arrays
X = data[:, 0].reshape(x_len, y_len)
Y = data[:, 1].reshape(x_len, y_len)
Z = data[:, 2].reshape(x_len, y_len)
Zmin = np.where(Z > 0, Z, np.inf).min()
Zmax = Z.max()
Z[Z==0] = 0.9 * Zmin
Zlog = np.log10(Z)
fig = plt.figure(figsize=(12,8))
ax = fig.add_subplot(projection='3d')
rc('font',family='palatino')
rc('font',size=14)
ax.set_xlim3d(0,15)
ax.set_zlim3d(np.floor(np.log10(Zmin))-2, np.ceil(np.log10(10))) #,font:'palatino')
ax.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
ax.zaxis.set_major_locator(mticker.MaxNLocator(integer=True))
ax.contour(X, Y, np.log10(Z), 4, lw=0.1, colors="k", linestyles="--", offset=np.floor(np.log10(Zmin))-2)
ax.plot_surface(X, Y, np.log10(Z), cmap="binary", lw=0.1,alpha=0.5)
ax.plot_wireframe(X, Y, np.log10(Z),linewidth=0.5,color='k')
ax.contour(X, Y, np.log10(Z), 4, lw=0.1, colors="k", linestyles="solid")
ax.view_init(elev=17, azim=-60)
for spine in ax.spines.values():
spine.set_visible(False)
plt.tight_layout()
plt.savefig('jpdf_lambda2_90.pdf', bbox_inches='tight')
plt.show()
I obtain a palatino font in x,y axis numerations but not in Z like. in the piucture :
how can obtain the same font (everywhere) ?
and how can I define the z-axis to obtain sub-thicks like in the classical logarithmic scale? thanks

How to stop line plots from exceeding x and y limits when plotting in 3D Plot using Matplotlib

I am using Matplot to plot a 3D surface of a function with 4 lines on the XY plane representing the region of integration. However even though I am setting the x and y limits the line still extends beyond the limit but I am not sure why. How do I set limits such that the line stops at the end of the defined x and y limit? (see image of current and ideal plot)
# Plot Surface with Region R
# Create plot area
fig = plt.figure(figsize=(30,20))
ax = fig.add_subplot(2, 1, 1, projection = '3d')
# On first subplot, plot 3D surface of f1 = x**2*y**4 with Region R
x = np.linspace(0.001, 5, 100)
y = np.linspace(0.001, 10, 100)
X,Y = np.meshgrid(x, y)
Z = X**2*Y**4
# Plot function
surf = ax.plot_surface(X,Y,Z, rstride= 2, cstride=2, cmap = cm.coolwarm, linewidth = 0, alpha =0.7)
# Set limits
ax.set_xlim(0.001, 5)
ax.set_ylim(0.001, 10)
# y = 6x
ax.plot(x,6*x, color = "red")
# y = 4/x
ax.plot(x, 4/x, c = "blue", clip_on = true)
# y = 10/x
ax.plot(x, 10/x, c = "green")
# y = x
ax.plot(x, x, c = "orange")
# Set axis labels
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
This is as far as I can get
This is the part of the line extending beyond the y axis I want to get rid of, highlighted in black

draw functions in 3D data

using the below code, I create three-dimensional data to plot in a pcolormesh plot.
n = 100 # size
_min, _max = -10, 10
# generate 2 2d grids for the x & y bounds
x, y = np.meshgrid(np.linspace(_min, _max, n), np.linspace(_min, _max, n))
# generate z values with random noise
z = np.array([np.zeros(n) for i in range(n)])
for i in range(len(z)):
z[i] = z[i] + 0.1 * np.random.randint(0,3, size=len(z[i]))
# plotting
fig, ax = plt.subplots()
c = ax.pcolormesh(x, y, z, cmap='RdBu', vmin=-1, vmax=1)
ax.set_title('pcolormesh')
plt.plot([5,5,-2.5], [5,-5,5], color='darkblue', marker='o', markersize=15, linewidth=0) # dots (outer)
plt.plot([5,5,-2.5], [5,-5,5], color='lightblue', marker='o', markersize=10, linewidth=0) # dots (inner)
plt.grid(b=True) # background grid
# set the limits of the plot to the limits of the data
ax.axis([_min, _max, _min, _max])
fig.colorbar(c, ax=ax)
plt.show()
This gives an image:
However, I would now like to alter z values of specific x/y combinations according to specific functions, e.g. a circle described by (x-5)^2 + (y+5)^2 = 1. I would like to alter the data(!) and then plot it.
The 'goal' would be data producing an image like this:
I can experiment with the functions, it's mostly about the logic of altering the z values according to a mathematical function of the form z = f(x, y) that I cannot figure out.
It would follow the (pseudo code logic):
if the x / y combination of a point is on the function f(x, y): add the value c to the initial z value.
Could someone point me to how I can implement this? I tried multiple times but cannot figure it out... :( Many thanks in advance!!!
NOTE: an earlier version was imprecise. It wrongly explained this as a plotting problem although it seems that the data manipulation is the issue. Apologies for that!
You only need to plot a function, the same way.
With these lines I plot a function on your plot.
# Create the independent points of your plot
x = np.arange(0., 5., 0.2)
# Generate your dependent variables
y = np.exp(x)
# Plot your variables
plt.plot(x, y)
You could then do it multiple time.
In your full example it looks like this:
import numpy as np
import matplotlib.pyplot as plt
n = 100 # size
_min, _max = -10, 10
# generate 2 2d grids for the x & y bounds
x, y = np.meshgrid(np.linspace(_min, _max, n), np.linspace(_min, _max, n))
# generate z values with random noise
z = np.array([np.zeros(n) for i in range(n)])
for i in range(len(z)):
z[i] = z[i] + 0.1 * np.random.randint(0, 3, size=len(z[i]))
# plotting
fig, ax = plt.subplots()
c = ax.pcolormesh(x, y, z, cmap='RdBu', vmin=-1, vmax=1)
ax.set_title('pcolormesh')
plt.plot([5, 5, -2.5], [5, -5, 5], color='darkblue', marker='o', markersize=15, linewidth=0) # dots (outer)
plt.plot([5, 5, -2.5], [5, -5, 5], color='lightblue', marker='o', markersize=10, linewidth=0) # dots (inner)
plt.grid(b=True) # background grid
# set the limits of the plot to the limits of the data
ax.axis([_min, _max, _min, _max])
fig.colorbar(c, ax=ax)
x = np.arange(0., 5., 0.2)
plt.plot(x, np.exp(x))
plt.show()
Of course you need to change the line y = np.exp(x) with whatever function you need.

Crop plot 1 according to the shape of plot 2

I'm trying to plot a heatmap with the plt.pcolormesh, and then plot the outline of my figure with the plt.scatter and I would like to crop the result according to the shape of the last plot which is the outline.
It doesn't seem that hard to do but still I haven't managed to do it or found anything. Here is my code and an image to illustrate, thank you for your help!
plt.rcParams["figure.figsize"] = (10.0, 10.0)
plt.axis("off")
plt.pcolormesh(x, y, intens)
plt.colorbar()
coord = np.genfromtxt("myfile.csv", delimiter = ' ')
x = [coord[i][0] for i in range(0, len(coord))]
y = [coord[i][1] for i in range(0, len(coord))]
plt.scatter(x, y, c = 'k', s = 1.)
Image here
You can just use plt.xlim() and plt.ylim() and give them the coordinates of the new figure. Here is a modified version of the code:
plt.rcParams['figure.figsize'] = (10.0, 10.0)
plt.axis('off')
plt.pcolormesh(x, y, intens)
plt.colorbar()
coord = np.genfromtxt('myfile.csv', delimiter=' ')
x = coord[:, 0]
y = coord[:, 1]
plt.scatter(x, y, c='k', s=1.)
plt.xlim([np.min(x), np.max(x)])
plt.ylim([np.min(y), np.max(y)])

3D plot with Matplotlib

I'm simply trying to plot a surface and its contour in 3D, exactly as in this example.
This is the code I'm using to do it:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm
import numpy
def plot_3d_contour(x_dim, y_dim, x_steps, y_steps, scalar_field, file_path):
fig = plt.figure()
x, y = numpy.mgrid[-x_dim/2:x_dim/2:x_steps*1j, -y_dim/2:y_dim/2:y_steps*1j]
v_min = numpy.min(scalar_field)
v_max = nupmy.max(scalar_field)
ax = fig.gca(projection='3d')
cset = ax.contourf(x, y, scalar_field, zdir='z', offset=v_min, cmap=cm.coolwarm)
cset = ax.contourf(x, y, scalar_field, zdir='x', offset=-x_dim/2-1, cmap=cm.coolwarm)
cset = ax.contourf(x, y, scalar_field, zdir='y', offset=y_dim/2+1, cmap=cm.coolwarm)
ax.plot_surface(x, y, scalar_field, rstride=10, cstride=10, alpha=0.3)
ax.set_xlabel('X')
ax.set_xlim(-x_dim/2-1, x_dim/2+1)
ax.set_ylabel('Y')
ax.set_ylim(-y_dim/2-1, y_dim/2+1)
ax.set_zlabel('Z')
ax.set_zlim(v_min, v_max)
plt.savefig(file_path + '.jpg')
plt.close()
scalar_field = numpy.loadtxt('../scalar_field', delimiter=",")
plot_3d_contour(12, 12, 100, 100, scalar_field, 'scalar_field3D')
However, I'm getting a weird behavior in which the a contour (zdir=y) is being over the surface. Besides, I'm getting a weird contour in z_dir=z (with a section missing):
I'm wondering what I'm missing. The scalar field can be found here.
I agree with Ajean. I believe the problem arises because each matplotlib's artist (i.e. PolygonCollection) is rendered separately. There is no way different faces from the same object to be rendered on different sides of another object in the scene.
Here is a useful piece of code :
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
file_path = "./3D_surface_and_contour.jpg"
p = 0.05
f = -0.01
def get_data(p):
x, y, z = axes3d.get_test_data(p)
z = f * z
return x, y, z
def plot_3d_contour(p, f):
nrows = 4
ncols = 5
x, y, z = get_data(p)
x_min, x_max = np.min(x), np.max(x)
y_min, y_max = np.min(y), np.max(y)
z_min, z_max = np.min(z), np.max(z)
fig = plt.figure(figsize=(15, 10))
for n in range(nrows * ncols):
i = n % ncols
j = n / ncols
k = n + 1
if j == 0:
azim = -60 + (i - 2) * 15
elev = 30
elif j == 1:
azim = -60
elev = 30 + (i - 2) * 5
elif j == 2:
azim = 60 + (i - 2) * 10
elev = 30
elif j == 3:
azim = 60
elev = 30 + (i - 2) * 5
ax = fig.add_subplot(nrows, ncols, k, projection='3d')
ax.set_title("azim=" + str(azim) + " elev=" + str(elev))
ax.tick_params(labelsize=8)
ax.view_init(azim=azim, elev=elev)
ax.plot_surface(x, y, z, rstride=10, cstride=10, alpha=0.3)
ax.contourf(x, y, z, zdir='z', offset=z_min, cmap=cm.coolwarm)
ax.contourf(x, y, z, zdir='x', offset=x_min, cmap=cm.coolwarm)
if j == 0 or j == 1:
ax.contourf(x, y, z, zdir='y', offset=y_max, cmap=cm.coolwarm)
elif j == 2 or j == 3:
ax.contourf(x, y, z, zdir='y', offset=y_min, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(x_min, x_max)
ax.set_ylabel('Y')
ax.set_ylim(y_min, y_max)
ax.set_zlabel('Z')
ax.set_zlim(z_min, z_max)
plt.savefig(file_path, dpi=80)
plt.close()
plot_3d_contour(p, f)
which gives the following image :
The first two rows are produced by a code similar to yours. You might notice that setting the elevation with view_init to a higher value solve the problem. But it is not satisfactory. I have also determined the influence of the range of the z-values (not shown here), the bug seems to appear only when this range is small (you can use the f parameter to test it) which explain why the example does not suffer from it.
The solution I propose is to replace :
ax.contourf(x, y, scalar_field, zdir='y', offset=y_dim/2+1, cmap=cm.coolwarm)
by :
ax.contourf(x, y, scalar_field, zdir='y', offset=-y_dim/2-1, cmap=cm.coolwarm)
in your code and add this additional line :
ax.view_init(azim=60, elev=30)
As shown in the last two rows of the previous image, this way you will be able to avoid the whims of matplotlib.

Categories

Resources