chemic90
chemic90

Reputation: 13

Filling area below function on 3d plot of 2d slices in Matplotlib

I would like to make a 3D plot with several 2D line plot "slices" and shade the area between the x-axis and the curve (i.e. under the curve). When trying to do this with polygons I am getting filling but the correct areas are not being filled. Any help would be most appreciated!

%matplotlib notebook

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(111, projection='3d')

colors = ['r','b','g','m']
phi = [0,np.pi/4,np.pi/3, np.pi/2]

for c, k in zip(colors, phi):
    
    eps2 = 0.001j
    eps = np.linspace(-3,3,10000)
    E = eps + eps2
  
    gR = ((1-(((np.cos(k)+np.sin(k)*1j)**2)/((E+np.sqrt(1-E**2)*1j)**4)))/(1+(((np.cos(k)+np.sin(k)*1j)**2)/((E+np.sqrt(1-E**2)*1j)**4))))*1j
    N = gR.imag
    utol = 2
    N[N>utol] = 2   

    ax.plot(eps, N, k,zdir='y', color=c)
    
    verts = [list(zip(eps,N))]
    poly = PolyCollection(verts, facecolors=c)
    poly.set_alpha(1)
    ax.add_collection3d(poly, zs=k,zdir='y')
        
ax.set_xlabel('Energy')
ax.set_ylabel('Phi')
ax.set_zlabel('DOS')

ax.set_yticks(phi)
ax.set_zlim(0,2)
ax.set_ylim(0,2)

plt.show()

Incorrect Plot for reference:

Incorrect Plot for reference

Upvotes: 1

Views: 740

Answers (1)

JohanC
JohanC

Reputation: 80409

You created a polygon by connecting the first and last vertex of your curves. As these vertices have y = 2 everything gets connected with the horizontal line at that y-value. To close the polygon at zero, repeat the first and the last x-value (np.pad(eps, 1, mode='edge')) and pad the y-values with a zero at both ends (np.pad(N, 1)).

If desired, ax.set_yticklabels(...) can show the y-ticks as a formula with pi.

Further, matplotlib seems to have a serious problem about deciding the relative depth of each polygon, showing them all mixed up. A workaround could be to rotate everything 180 degrees, e.g. by setting ax.view_init(elev=22, azim=130).

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(15, 15))
ax = fig.add_subplot(111, projection='3d')

colors = ['r', 'b', 'g', 'm']
phi = [0, np.pi / 4, np.pi / 3, np.pi / 2]

for c, k in zip(colors, phi):
    eps2 = 0.001j
    eps = np.linspace(-3, 3, 10000)
    E = eps + eps2

    gR = ((1 - (((np.cos(k) + np.sin(k) * 1j) ** 2) / ((E + np.sqrt(1 - E ** 2) * 1j) ** 4))) / (
            1 + (((np.cos(k) + np.sin(k) * 1j) ** 2) / ((E + np.sqrt(1 - E ** 2) * 1j) ** 4)))) * 1j
    N = gR.imag
    utol = 2
    N[N > utol] = 2

    ax.plot(eps, N, k, zdir='y', color=c)

    verts = [list(zip(np.pad(eps, 1, mode='edge'), np.pad(N, 1)))]
    poly = PolyCollection(verts, facecolors=c)
    poly.set_alpha(1)
    ax.add_collection3d(poly, zs=k, zdir='y')

ax.set_xlabel('Energy')
ax.set_ylabel('Phi')
ax.set_zlabel('DOS')

ax.set_yticks(phi)
ax.set_yticklabels(['$0$' if k == 0 else f'$\pi / {np.pi / k:.0f}$' for k in phi])
ax.set_zlim(0, 2)
ax.set_ylim(0, 2)
ax.view_init(elev=22, azim=130)
plt.show()

resulting plot

Upvotes: 1

Related Questions