I am trying to add legend to a surface plot but unable to do so. Here is the code.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import random
def fun(x, y):
return 0.063*x**2 + 0.0628*x*y - 0.15015876*x + 96.1659*y**2 - 74.05284306*y + 14.319143466051
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-1.0, 1.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array([fun(x,y) for x,y in zip(np.ravel(X), np.ravel(Y))])
Z = zs.reshape(X.shape)
ax.plot_surface(X, Y, Z)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.plot(color='red',label='Lyapunov function on XY plane',linewidth=4) # Adding legend
plt.show()
Kindly help. Thanks in advance.
It is not trivial to make a legend in a 3D axis. You can use the following hack:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib as mpl
import random
def fun(x, y):
return 0.063*x**2 + 0.0628*x*y - 0.15015876*x + 96.1659*y**2 - 74.05284306*y + 14.319143466051
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-1.0, 1.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array([fun(x,y) for x,y in zip(np.ravel(X), np.ravel(Y))])
Z = zs.reshape(X.shape)
ax.plot_surface(X, Y, Z)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
fake2Dline = mpl.lines.Line2D([0],[0], linestyle="none", c='b', marker = 'o')
ax.legend([fake2Dline], ['Lyapunov function on XY plane'], numpoints = 1)
plt.show()
I would say a title is more appropriate than a legend in this case.
According to this question, the issue is ongoing, and there is a relatively simple workaround. You can manually set the two missing attributes that would allow legend to automatically create the patch for you:
surf = ax.plot_surface(X, Y, Z, label='Lyapunov function on XY plane')
surf._edgecolors2d = surf._edgecolor3d
surf._facecolors2d = surf._facecolor3d
ax.legend()
The attribute names on the right hand side of the assignment are surf._edgecolors3d and surf.facecolors3d for matplotlib < v3.3.3.
Related
I have computed a lot (~5000) of 3d points (x,y,z) in a quite complicated way so I have no function such that z = f(x,y). I can plot the 3d surface using
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
X = surface_points[:,0]
Y = surface_points[:,1]
Z = surface_points[:,2]
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
surf = ax.plot_trisurf(X, Y, Z, cmap=cm.coolwarm, vmin=np.nanmin(Z), vmax=np.nanmax(Z))
I would like to plot this also in 2d, with a colorbar indicating the z-value. I know there is a simple solution using ax.contour if my z is a matrix, but here I only have a vector.
Attaching the plot_trisurf result when rotated to xy-plane. This is what I what like to achieve without having to rotate a 3d plot. In this, my variable surface_points is an np.array with size 5024 x 3.
I had the same problems in one of my codes, I solved it this way:
import numpy as np
from scipy.interpolate import griddata
import matplotlib.pylab as plt
from matplotlib import cm
N = 10000
surface_points = np.random.rand(N,3)
X = surface_points[:,0]
Y = surface_points[:,1]
Z = surface_points[:,2]
nx = 10*int(np.sqrt(N))
xg = np.linspace(X.min(), X.max(), nx)
yg = np.linspace(Y.min(), Y.max(), nx)
xgrid, ygrid = np.meshgrid(xg, yg)
ctr_f = griddata((X, Y), Z, (xgrid, ygrid), method='linear')
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.contourf(xgrid, ygrid, ctr_f, cmap=cm.coolwarm)
plt.show()
You could use a scatter plot to display a projection of your z color onto the x-y axis.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
N = 10000
surface_points = np.random.rand(N,3)
X = surface_points[:,0]
Y = surface_points[:,1]
Z = surface_points[:,2]
# fig = plt.figure()
# ax = fig.add_subplot(projection='3d')
# surf = ax.plot_trisurf(X, Y, Z, cmap=cm.coolwarm, vmin=np.nanmin(Z), vmax=np.nanmax(Z))
fig = plt.figure()
cmap = cm.get_cmap('coolwarm')
color = cmap(Z)[..., :3]
plt.scatter(X,Y,c=color)
plt.show()
Since you seem to have a 3D shape that is hollow, you could split the projection into two like if you cur the shape in two pieces.
fig = plt.figure()
plt.subplot(121)
plt.scatter(X[Z<0.5],Y[Z<0.5],c=color[Z<0.5])
plt.title('down part')
plt.subplot(122)
plt.scatter(X[Z>=0.5],Y[Z>=0.5],c=color[Z>+0.5])
plt.title('top part')
plt.show()
I would like to create a 3D plot with the z axis in log scale. This is an example code.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
def fun(x, y):
return x**2 + y
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-3.0, 3.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array([fun(x,y) for x,y in zip(np.ravel(X), np.ravel(Y))])
Z = zs.reshape(X.shape)
ax.plot_surface(X, Y, Z)
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.show()
I tried ax.set_zscale('log') and ax.zaxis.set_scale('log') but that is not working. It should be possible though. If I use ax.plot_surface(X, Y, np.log(Z)) the z ticks are off. I could not find an answer to this seemingly simple problem. I hope you can help.
I have defined a function I(a,b) = integral f(a,b,t) dt and want to plot it to see how it depend on the variables a and b. I first wrote a program that graphed y = I(k,x) and it worked just fine, but i wanted to see how it depends on both variables so i tried to write a program that graphs it in 3D.
The program worked for simpler functions like trigonometric and polynomials, but when i try to graph I(x,y) it just gives me the error "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()"
This is the code, I originally wrote my own program to approximate the integral but then used scipy
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
import scipy.integrate as integrate
def integral(x,y):
return integrate.quad(lambda t: np.sqrt((x**2 + y**2 - 2*x*y*np.cos(np.pi*t*(np.sqrt(1/x**3) - np.sqrt(1/y**3))))/(x**3*y**3)), 0, np.sqrt(x**3*y**3))
X = np.arange(0.1,5,0.1)
Y = np.arange(0.1,5,0.1)
X,Y = np.meshgrid(X, Y)
Z = integral(X,Y)
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.plot_wireframe(X, Y, Z, color='green')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
cmap='winter', edgecolor='none')
ax.set_title('copper');
plt.show()
'''
scipy.integrate.quad returns a tuple. You only want the first value of that. Also you need to vectorize the function.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
import scipy.integrate as integrate
def integral(x,y):
return integrate.quad(lambda t: np.sqrt((x**2 + y**2 - 2*x*y*np.cos(np.pi*t*(np.sqrt(1/x**3) - np.sqrt(1/y**3))))/(x**3*y**3)), 0, np.sqrt(x**3*y**3))[0]
X = np.arange(0.1,5,0.1)
Y = np.arange(0.1,5,0.1)
X,Y = np.meshgrid(X, Y)
Z = np.vectorize(integral)(X,Y)
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.plot_wireframe(X, Y, Z, color='green')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
cmap='winter', norm=plt.Normalize(np.nanmin(Z), np.nanmax(Z)), edgecolor='none')
plt.show()
I am trying to plot a 1D line along with a 2D surface in matplotlib with Axes3D:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-1., 1.1, 0.1)
y = x.copy()
X, Y = np.meshgrid(x, y)
Z = np.abs(X) + np.abs(Y)
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(np.zeros_like(y), y, 1, color='k')
ax.plot(x, np.zeros_like(x), 1, color='k')
surf = ax.plot_surface(X, Y, Z, color='w')
plt.show(block=False)
but the 2D plot somehow hides the lines:
If I comment the surf = plot_surface(...) code line, the 1D lines show correctly:
How can I have the lines showing correctly along with the surface?
Axes3D.plot_surface() apparently accepts a transparency (alpha) argument, which actually gets forwarded to a base class, Poly3DCollection.
And of course the line plot() calls accept a linewidth argument.
So if you render the line plots with thicker lines and you render the surface with some transparency, you should be able to find a combination of settings which let you see both the lines and the surface in a balanced way.
https://matplotlib.org/tutorials/toolkits/mplot3d.html#mpl_toolkits.mplot3d.Axes3D.plot_surface
https://matplotlib.org/api/_as_gen/mpl_toolkits.mplot3d.art3d.Poly3DCollection.html#mpl_toolkits.mplot3d.art3d.Poly3DCollection
You can also achieve this by using the zorder in the plot_surface and plot commands to make the lines sit on top of the surface. E.g.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-1., 1.1, 0.1)
y = x.copy()
X, Y = np.meshgrid(x, y)
Z = np.abs(X) + np.abs(Y)
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, color='w', zorder=1)
ax.plot(np.zeros_like(y), y, 1, color='k', zorder=10)
ax.plot(x, np.zeros_like(x), 1, color='k', zorder=11)
plt.show(block=False)
I'm using a newer version of matplotlib and the argument that sets the linewidth was removed. They seem to have changed it so I set it in Collections object, but I can't find a way of doing this.
I tried their example with a different linewidth:
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=10, antialiased=False)
# Customize the z axis.
ax.set_zlim(-1.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
# Add a color bar which maps values to colors.
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
But as the figure shows, it doesn't add lines to my surface.
What is the new method for setting linewidths?
Thanks!
The linewidth can of course only take effect if there is actually a line to be shown. So one would need to specify the color of the lines to show in order to see them.
surf = ax.plot_surface(X, Y, Z, cmap="RdYlGn", linewidth=2, edgecolor="limegreen")