Plotting a line and surface together with correct occlusion - python

I'm looking to plot a 2D probability distribution with one of its marginals in a single plot using Python and matplotlib. I'm almost there, but the line in the plot is always drawn in front of the surface, instead of being occluded properly. How do I fix this?
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
fig = plt.figure()
ax = fig.gca(projection='3d')
delta = 0.05
f = 0.5
X, Y = np.meshgrid(np.arange(-3.0, 3.0, delta),
np.arange(-3.0, 3.0, delta))
xy = np.hstack((X.flatten()[:, None], Y.flatten()[:, None]))
p1 = stats.multivariate_normal.pdf(xy, mean=[1, -1], cov=(np.eye(2) * 0.28 * f))
p2 = stats.multivariate_normal.pdf(xy, mean=[-1, 1], cov=(np.eye(2) * 0.5 * f))
p = 0.3 * p1 + 0.7 * p2
Z = p.reshape(len(X), len(X))
plt.plot(X[0, :], np.zeros(len(X)) + 3, np.sum(Z, 0) * 0.05) # , color='red')
ax.plot_surface(X, Y, Z, alpha=1.0, cmap='jet', linewidth=0.1, rstride=2, cstride=2)
ax.set_xlabel('Object colour')
ax.set_ylabel('Illumination colour')
ax.set_zlabel('Probability density')
ax.set_zlim(min(cont_offset, np.min(Z)), max(np.max(Z), cont_offset))
plt.show()

The built-in contour function at least gets the z-order right; if you don't want the full thing, you could cheat with a calculated Z. To start, replacing your call to plt.plot with this:
from matplotlib import cm
cset = ax.contour(X, Y, Z, zdir='y', offset=3, cmap='binary')
cset = ax.contour(X, Y, Z, zdir='x', offset=-3, cmap='Blues')
Faking up Z's for the contours, one way:
from matplotlib import cm
Zys = np.zeros_like(Z)
Zys[60,:] = Z.max(0)
cset = ax.contour(X, Y, Zys, zdir='y', offset=3, cmap='binary')
Zys = np.zeros_like(Z)
Zys[:,60] = Z.max(1)
cset = ax.contour(X, Y, Zys, zdir='x', offset=-3, cmap='Blues')
More ambitiously, somewhere in the contour code they're calculating the z-order...

Related

How to draw sphere with arrow pointing from sphere in matplotlib

I try to draw a sphere to represent a qubit using matplotlib
import numpy as np
import matplotlib.pyplot as plt
theta = [0, np.pi]
phi = [0, 2* np.pi]
N=100
theta_array = np.linspace(theta[0], theta[1], N)
phi_array = np.linspace(phi[0], phi[1], N)
theta_grid, phi_grid = np.meshgrid(theta_array,phi_array)
x = np.sin(theta_grid) * np.cos(phi_grid)
y = np.sin(theta_grid) * np.sin(phi_grid)
z = np.cos(theta_grid)
fig = plt.figure(figsize=(6,6))
ax = fig.gca(projection='3d')
ax.plot_surface(x, y, z, rstride=1, cstride=1, shade=False,linewidth=0)
plt.show()
I want to add tube arrows on the sphere with directions parallel with xyz axis, like this:
I am not an expert in matplotlib, so it's seem pretty tough to me. Can anyone help me? thanks in advance!
You can use the quiver function to to what you want.
See code below:
import numpy as np
import matplotlib.pyplot as plt
theta = [0, np.pi]
phi = [0, 2* np.pi]
N=100
theta_array = np.linspace(theta[0], theta[1], N)
phi_array = np.linspace(phi[0], phi[1], N)
theta_grid, phi_grid = np.meshgrid(theta_array,phi_array)
x = np.sin(theta_grid) * np.cos(phi_grid)
y = np.sin(theta_grid) * np.sin(phi_grid)
z = np.cos(theta_grid)
fig = plt.figure(figsize=(6,6))
ax = fig.gca(projection='3d')
ax.view_init(azim=60)
ax.plot_surface(x, y, z, rstride=1, cstride=1, shade=False,linewidth=0)
#Set up arrows
ax.quiver(1,0,0,1,0,0,color = 'k', alpha = .8, lw = 3) #x arrow
ax.text(2.4,0,0,'Sx',fontsize=20)
ax.quiver(0,1,0,0,1,0,color = 'k', alpha = .8, lw = 3)#y arrow
ax.text(0,2.4,0,'Sy',fontsize=20)
ax.quiver(0,0,1,0,0,1,color = 'k', alpha = .8, lw = 3)#z arrow
ax.text(-0.3,0,1.8,'Sz',fontsize=20)
plt.show()
And the output gives:

Shade the surface and contour parts

I want to shade the surface and the contours of a specific function based on some constraint in the domain of the function. So far I have the following and I want to improve it.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import LinearLocator, FormatStrFormatter
plt.figure(figsize=(8, 6))
plt.axes(projection="3d")
xdata = np.linspace(-3, 3, 20000)
ydata = np.linspace(-3, 3, 20000)
X, Y = np.meshgrid(xdata, ydata)
Z1 = X ** 2 + Y ** 2
Z2 = Z1.copy()
Z3 = Z1.copy()
Z1[np.multiply(X, Y) > 3] = np.nan
Z2[np.multiply(X, Y) <= 3] = np.nan
Z3[np.multiply(X, Y) == 3] = np.nan
ax3d = plt.axes(projection='3d')
ax3d.plot_surface(X, Y, Z1, cmap='Greys', antialiased=True, vmin=-np.nanmin(Z1), vmax=np.nanmax(Z1))
ax3d.plot_surface(X, Y, Z2, cmap='YlGnBu', antialiased=True, vmin=-np.nanmin(Z2), vmax=np.nanmax(Z2))
ax3d.contourf(X, Y, Z1, zdir='z', offset=0, cmap='Greys')
ax3d.contourf(X, Y, Z2, zdir='z', offset=0, cmap='Greys')
ax3d.set_title('Surface Plot in Matplotlib')
ax3d.set_xlabel('X')
ax3d.set_ylabel('Y')
ax3d.set_zlabel('Z')
plt.show()
Could you please someone help to solve the following problems:
The surface is over-imposed by the contour surface.
There are some gaps in the surface.
The contours of the two constraints are not continuous.
Is it possible to plot a line in the border of the two surfaces and contours?
Any help is highly appreciated.
The code below makes the following changes:
creating a custom colormap combining the two existing colormaps
using a TwoSlopeNorm to have the separation at z=3
setting antialiased=False (otherwise matplotlib creates a plot of antialiased lines instead of polygons)
xdata and ydata with 300 steps
using rstride=1, cstride=1 so every x and every y will be considered; this makes the surface smoother (but takes more time)
calling plt.axes(...) only once to prevent a dummy subplot
calling contourf before plot_surface; due to the painter's algorithm, matplotlib only minimally supports 3D overlaps
import matplotlib.pyplot as plt
from matplotlib.colors import TwoSlopeNorm, ListedColormap
import numpy as np
xdata = np.linspace(-3, 3, 300)
ydata = np.linspace(-3, 3, 300)
X, Y = np.meshgrid(xdata, ydata)
Z1 = X ** 2 + Y ** 2
cmap1 = plt.get_cmap('Greys')
cmap2 = plt.get_cmap('YlGnBu')
cmap = ListedColormap(np.r_[cmap1(np.linspace(0, 1, 128)), cmap2(np.linspace(0, 1, 128))])
norm = TwoSlopeNorm(vmin=np.nanmin(Z1), vmax=np.nanmax(Z1), vcenter=3)
plt.figure(figsize=(8, 6))
ax3d = plt.axes(projection='3d')
ax3d.contourf(X, Y, Z1, zdir='z', offset=0, cmap=cmap, norm=norm)
ax3d.plot_surface(X, Y, Z1, cmap=cmap, antialiased=False, norm=norm, rstride=1, cstride=1)
ax3d.set_title('Surface Plot in Matplotlib')
ax3d.set_xlabel('X')
ax3d.set_ylabel('Y')
ax3d.set_zlabel('Z')
plt.show()
xdata = np.linspace(-3, 3, 1000)
ydata = np.linspace(-3, 3, 1000)
X, Y = np.meshgrid(xdata, ydata)
Z1 = X ** 2 + Y ** 2
Z2 = Z1.copy()
Z3 = Z1.copy()
Z2[np.multiply(X, Y) <= 3] = np.nan
Z3[np.multiply(X, Y) == 3] = np.nan
plt.figure(figsize=(8, 6))
ax3d = plt.axes(projection='3d')
ax3d.contourf(X, Y, Z1, zdir='z', offset=0, cmap='Greys')
ax3d.contourf(X, Y, Z2, zdir='z', offset=0, cmap='YlGnBu')
ax3d.plot_surface(X, Y, Z1, cmap='Greys', antialiased=True, vmin=-np.nanmin(Z1), vmax=np.nanmax(Z1), alpha=.5)
ax3d.plot_surface(X, Y, Z2, cmap='YlGnBu', antialiased=True, vmin=-np.nanmin(Z2), vmax=np.nanmax(Z2), alpha=.5)
ax3d.set_title('Surface Plot in Matplotlib')
ax3d.set_xlabel('X')
ax3d.set_ylabel('Y')
ax3d.set_zlabel('Z')
plt.show()

How to order ax.scatter and ax.quiver in Python 3D plot?

I want to draw a spin systems. I have the picture below. However, I want the quivers to be INSIDE the scatter circles and the "Main arrow" to be a bright one. Thus, how can I order three plots?
Thank You in advance!
Here is the code:
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
############################## A sphere #################################
r = 4
pi = np.pi
cos = np.cos
sin = np.sin
phi, theta = np.mgrid[0.0:pi:100j, 0.0:2.0*pi:100j]
x = r*sin(phi)*cos(theta)
y = r*sin(phi)*sin(theta)
z = r*cos(phi)
#Set colours and render
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, rstride=1, cstride=1, color='red', alpha=0.6, linewidth=0)
############################ One arrow ########################
x1 = 0
y1 = 0
z1 = 0
dx1 = 0
dy1 = 0
dz1 = 2
ax.quiver(x1, y1, z1, dx1, dy1, dz1, pivot='middle', color="blue", linewidths=5.0,length=1.4)
ax.scatter(0, 0, 0,color="blue", s=800)
############################ Array of arrows ############################################
ax.set_xlim([-4,4])
ax.set_ylim([-4,4])
ax.set_zlim([-4,4])
ax.set_aspect("equal")
plt.tight_layout()
N = 5 # number of points - 5x5x5
scale = 0.4 # scale for the increment of an arrow
ax = fig.gca(projection='3d')
xx = np.linspace(-3.5, 3.5, N)
yy = np.linspace(-3.5, 3.5, N)
zz = np.linspace(-3.5, 3.5, N)
dxx = scale*np.random.rand(N)
dyy = scale*np.random.rand(N)
dzz = scale*np.random.rand(N)
x, y, z = np.meshgrid(yy, zz, xx)
dx, dy, dz = np.meshgrid(dxx, dyy, dzz)
ax.scatter(x, y, z,color="orange", s=200)
ax.quiver(x, y, z, dx, dy, dz, pivot='middle', length=1.6, color="blue")
plt.show()

I am plotting a 3d plot and i want the colours to be less 'distinct'

My code can be seen here:
import numpy as np
from matplotlib import cm
import mpl_toolkits.mplot3d.axes3d as axes3d
from matplotlib.ticker import LinearLocator, FormatStrFormatter
xlist = [+30,+20,+10,0,-10,-20,-30]
ylist = [0.0008,0.0009, 0.001, 0.0012, 0.0013]
total_costs=[[2084.8771849999903, 17314.19051000003, 26026.73173, 65340.709810000015, 108130.0746, 143560.64033000002, 188387.24033], [2129.155209999997, 17314.301310000024, 26026.996729999984, 65341.17821, 108130.792, 143561.44293000002, 188388.11793], [6637.1766100000095, 17314.412110000034, 26027.26173, 65341.646609999996, 108131.5094, 143562.24553000001, 188388.99553], [6623.21941000002, 17314.63371000004, 26027.791729999997, 65342.58341000001, 108132.9442, 150322.81264000002, 191661.16901], [6637.240810000003, 17314.744510000033, 26028.05673000001, 65343.05181000002, 110971.15911000001, 146393.01711000002, 191661.93621]]
Z = np.array(total_costs)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
X, Y = np.meshgrid(xlist, ylist)
ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False,alpha=0.5,
`rstride=1,cstride=1, label='skata')`
ax.set_xlabel('System-1 imbalance')
ax.set_ylabel('Penalization factor [€/MWh]')
ax.set_zlabel('Total balancing costs [€]')
#ax.set_legend('upper left', fontsize=15)
#ax.tick_params(axis='both', labelsize=15)
plt.show()
When i run this i get a figure like this:
What i would like is to get a figure like this:
I guess it has something to do with my result being a list within a list with discrete values. Anyone got an idea?
Thank you in advance
I guess you want a more gradual tone change on the graph - the way I know how to do it is to "simply" increase the number of points being plotted using interpolation:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d
import scipy.interpolate as interp
xlist = np.array([+30, +20, +10, 0, -10, -20, -30])
ylist = np.array([0.0008, 0.0009, 0.001, 0.0012, 0.0013])
total_costs = [[2084.8771849999903, 17314.19051000003, 26026.73173,
65340.709810000015, 108130.0746, 143560.64033000002,
188387.24033],
[2129.155209999997, 17314.301310000024, 26026.996729999984,
65341.17821, 108130.792, 143561.44293000002, 188388.11793],
[6637.1766100000095, 17314.412110000034, 26027.26173,
65341.646609999996, 108131.5094, 143562.24553000001,
188388.99553],
[6623.21941000002, 17314.63371000004, 26027.791729999997,
65342.58341000001, 108132.9442, 150322.81264000002,
191661.16901],
[6637.240810000003, 17314.744510000033, 26028.05673000001,
65343.05181000002, 110971.15911000001, 146393.01711000002,
191661.93621]]
X, Y = np.meshgrid(xlist, ylist)
Z = np.asarray(total_costs)
Zfunc = interp.interp2d(X, Y, Z, kind='cubic', copy=False)
n_points = 100 # change this to change the "resolution"
xnew = np.linspace(start=min(xlist), stop=max(xlist), num=n_points)
ynew = np.linspace(start=min(ylist), stop=max(ylist), num=n_points)
Xnew, Ynew = np.meshgrid(xnew, ynew)
Znew = Zfunc(xnew, ynew)
fig = plt.figure(figsize=(11, 8))
ax = plt.axes([0.05, 0.05, 0.9, 0.9], projection='3d')
surface = ax.plot_surface(Xnew, Ynew, Znew, rstride=1, cstride=1,
cmap='coolwarm', linewidth=0.25)
fig.colorbar(surface, shrink=0.75, aspect=9)
plt.show()
Linear interpolation:
Cubic interpolation:
The faces of the surface plot are colorized according to the Z value.
To get mixed or random colors on the faces you can supply a color array with the facecolors argument instead of a colormap.
colors=np.random.rand(X.shape[0]-1,X.shape[1]-1, 3)
ax.plot_surface(X, Y, Z, facecolors=colors,
linewidth=0, antialiased=False,alpha=0.5,
rstride=1,cstride=1, label='skata')
produces
In order to make the colors appear more close to each other, the solution would be not to use the complete range of the colormap. E.g. you could set vmin=0.5*Z.min(), vmax=2*Z.max(), in your call to plot_surface in order to map the colors to a range much larger than the one shown in the image, such that the actual values only cover part of the colormap.
ax.plot_surface(X, Y, Z, cmap=cm.coolwarm, vmin=0.5*Z.min(), vmax=2*Z.max(),
linewidth=0, antialiased=False,alpha=0.5,
rstride=1,cstride=1, label='skata')
Is that what you mean?
def stackQuestion():
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d
xlist = np.array([+30,+20,+10,0,-10,-20,-30])
ylist = np.array([0.0008,0.0009, 0.001, 0.0012, 0.0013])
total_costs=[[2084.8771849999903, 17314.19051000003, 26026.73173, 65340.709810000015, 108130.0746, 143560.64033000002, 188387.24033],
[2129.155209999997, 17314.301310000024, 26026.996729999984, 65341.17821, 108130.792, 143561.44293000002, 188388.11793],
[6637.1766100000095, 17314.412110000034, 26027.26173, 65341.646609999996, 108131.5094, 143562.24553000001, 188388.99553],
[6623.21941000002, 17314.63371000004, 26027.791729999997, 65342.58341000001, 108132.9442, 150322.81264000002, 191661.16901],
[6637.240810000003, 17314.744510000033, 26028.05673000001, 65343.05181000002, 110971.15911000001, 146393.01711000002, 191661.93621]]
X, Y = np.meshgrid(xlist, ylist)
Z = np.array(total_costs)
fig = plt.figure(figsize = (11, 8))
ax = plt.axes([0.05, 0.05, 0.9, 0.9], projection = '3d')
surface = ax.plot_surface(X, Y, Z, rstride = 1, cstride = 1,\
cmap = 'coolwarm', linewidth = 0.25)
fig.colorbar(surface, shrink = 0.75, aspect = 9)
plt.show()

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