Surface_plot: Add legend to facecolors - python

Let's say I'm plotting something with two different surface colors, as follows in colors red and blue. I want to add two items to the legend, with a custom string and the color for each color in my set.
How would I do that?
# generate the plot
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
from numpy import random
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
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)
col1, col2 = cm.jet(np.array([0.1, 0.9]))
my_choice = random.choice([0, 1], size=X.shape)
my_color = my_choice[..., None] * col1[None, None, :] + (1 - my_choice)[..., None] * col2[None, None, :]
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors = my_color,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
# customstrings for legend:
myLegendLabels = {0: 'very red', 1: 'very blue'}

You can do that with "proxy artists":
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
from numpy import random
# generate the plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
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)
col1, col2 = cm.jet(np.array([0.1, 0.9]))
my_choice = random.choice([0, 1], size=X.shape)
my_color = my_choice[..., None] * col1[None, None, :] + (1 - my_choice)[..., None] * col2[None, None, :]
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors = my_color,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
# Add legend with proxy artists
col1_patch = mpatches.Patch(color=col1, label='very blue')
col2_patch = mpatches.Patch(color=col2, label='very red')
plt.legend(handles=[col1_patch, col2_patch])
Result:

Related

Plotting 3D scatter plot on top of map or image [duplicate]

This topic has been touched here, but no indications were given as to how to create a 3D plot and insert an image in the (x,y) plane, at a specified z height.
So to come up with a simple and reproducible case, let's say that I create a 3D plot like this with mplot3d:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
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)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.winter,
linewidth=0, antialiased=True)
ax.set_zlim(-1.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
plt.show()
Visually we have:
At the level z=min(z)-1, where -1 is a visual offset to avoid overlapping, I want to insert an image representing the elements for which the curve shows a certain value. How to do it?
In this example I don't care about a perfect matching between the element and its value, so please feel free to upload any image you like. Also, is there a way of letting that image rotate, in case one is not happy with the matching?
EDIT
This is a visual example of something similar made for a 3D histogram. The grey shapes at the level z=0 are the elements for which the bars show a certain z value. Source.
Use plot_surface to draw image via facecolors argument.
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
from matplotlib._png import read_png
from matplotlib.cbook import get_sample_data
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, .25)
Y = np.arange(-5, 5, .25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.winter,
linewidth=0, antialiased=True)
ax.set_zlim(-2.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
fn = get_sample_data("./lena.png", asfileobj=False)
arr = read_png(fn)
# 10 is equal length of x and y axises of your surface
stepX, stepY = 10. / arr.shape[0], 10. / arr.shape[1]
X1 = np.arange(-5, 5, stepX)
Y1 = np.arange(-5, 5, stepY)
X1, Y1 = np.meshgrid(X1, Y1)
# stride args allows to determine image quality
# stride = 1 work slow
ax.plot_surface(X1, Y1, -2.01, rstride=1, cstride=1, facecolors=arr)
plt.show()
If you need to add values use PathPatch:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
from matplotlib.text import TextPath
from matplotlib.transforms import Affine2D
from matplotlib.patches import PathPatch
def text3d(ax, xyz, s, zdir="z", size=None, angle=0, usetex=False, **kwargs):
x, y, z = xyz
if zdir == "y":
xy1, z1 = (x, z), y
elif zdir == "y":
xy1, z1 = (y, z), x
else:
xy1, z1 = (x, y), z
text_path = TextPath((0, 0), s, size=size, usetex=usetex)
trans = Affine2D().rotate(angle).translate(xy1[0], xy1[1])
p1 = PathPatch(trans.transform_path(text_path), **kwargs)
ax.add_patch(p1)
art3d.pathpatch_2d_to_3d(p1, z=z1, zdir=zdir)
# main
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, .25)
Y = np.arange(-5, 5, .25)
Xg, Yg = np.meshgrid(X, Y)
R = np.sqrt(Xg**2 + Yg**2)
Z = np.sin(R)
surf = ax.plot_surface(Xg, Yg, Z, rstride=1, cstride=1, cmap=cm.winter,
linewidth=0, antialiased=True)
ax.set_zlim(-2.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
# add pathces with values
for i,x in enumerate(X[::4]):
for j,y in enumerate(Y[::4]):
text3d(ax, (x, y, -2.01), "{0:.1f}".format(Z[i][j]), zdir="z", size=.5, ec="none", fc="k")
plt.show()

Python - Plotting two 3D graphs with a contour map

I am trying to plot a figure in Python with two 3D graphs (same function, different angles) and a 2D contour map of the same function and I'm not sure why but the two first figures are okay and the contour map is weird, it appears at the bottom of the two first figures and the sizing is all weird (see the picture attached). Is there a way to place the map at the right of the 2 other figures and to resize it to make it more like a square?
Thank you for your help.
Here's my code :
import numpy as np
import matplotlib.pylab as plt
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
x = np.arange(-5, 5, 0.01)
y = np.arange(-5, 5, 0.01)
X, Y = np.meshgrid(x, y)
Z = 5 + (10 * X**2 + 20 * Y**2) * np.exp((-X**2)-(Y**2)) + 3 *np.sin(X) - np.sin(Y)
fig = plt.figure(figsize=(15,5))
ax1 = plt.subplot(131, projection='3d')
surf1 = ax1.plot_surface(X, Y, Z, cmap=cm.coolwarm)
ax2 = plt.subplot(132, projection='3d')
surf2 = ax2.plot_surface(X, Y, Z, cmap=cm.coolwarm)
for angle in range(0,360):
ax2.view_init(20, angle)
plt.pause(.001)
ax3 = plt.subplot(133)
surf3 = ax3.contour(X, Y, Z, colors='black', linestyles='dashed')
plt.clabel(surf3, fmt = '%.0f', inline=True, fontsize=8)
ax1.set_xlabel('X')
ax2.set_xlabel('X')
ax3.set_xlabel('X')
ax1.set_ylabel('Y')
ax2.set_ylabel('Y')
ax3.set_ylabel('Y')
ax1.set_zlabel('Z')
ax2.set_zlabel('Z')
plt.show()
Got it:
import numpy as np
import matplotlib.pylab as plt
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
x = np.arange(-5, 5, 0.01)
y = np.arange(-5, 5, 0.01)
X, Y = np.meshgrid(x, y)
Z = 5 + (10 * X**2 + 20 * Y**2) * np.exp((-X**2)-(Y**2)) + 3 *np.sin(X) - np.sin(Y)
fig = plt.figure(figsize=(15,5))
ax1 = fig.add_subplot(1, 3, 1, projection='3d')
surf1 = ax1.plot_surface(X, Y, Z, cmap=cm.coolwarm)
ax3 = fig.add_subplot(1, 3, 2)
surf3 = ax3.contour(X, Y, Z, colors='black', linestyles='dashed')
plt.clabel(surf3, fmt = '%.0f', inline=True, fontsize=8)
ax2 = fig.add_subplot(1, 3, 3, projection='3d')
surf2 = ax2.plot_surface(X, Y, Z, cmap=cm.coolwarm)
for angle in range(0,360):
ax2.view_init(20, angle)
plt.pause(.001)
ax1.set_xlabel('X')
ax2.set_xlabel('X')
ax3.set_xlabel('X')
ax1.set_ylabel('Y')
ax2.set_ylabel('Y')
ax3.set_ylabel('Y')
ax1.set_zlabel('Z')
ax2.set_zlabel('Z')
plt.show()

Python plt.axis('Equal') xlim

I have a simple 3D surface plot in which I want the axes to be equal in all directions.
I have the following piece of code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
X = np.array([-100, 0, 100])
Y = np.array([ 0, 10, 20])
X_grid, Y_grid = np.meshgrid(X,Y)
Z_grid = np.matrix('0 10 4;'
'1 11 3;'
'0 10 5')
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X_grid, Y_grid, Z_grid, rstride=1, cstride=1, cmap=cm.coolwarm, linewidth=1, antialiased=True)
plt.axis('Equal')
which yields this plot:
I then have to manually zoom out to get proper axis limits.
I have tried plt.xlim(-100,100), but it doesn't seem to respond?
Also, the plt.axis('Equal') doesn't seem to apply to the z-axis?
The plot should look like this:
You can easily adapt the strategies from the link in the comment so the operations just affect the X-Y plane:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
X = np.array([-100, 0, 100])
Y = np.array([ 0, 10, 20])
X_grid, Y_grid = np.meshgrid(X,Y)
Z_grid = np.matrix('0 10 4;'
'1 11 3;'
'0 10 5')
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X_grid, Y_grid, Z_grid, rstride=1, cstride=1, cmap=cm.coolwarm, linewidth=1, antialiased=True)
max_range = np.array([X_grid.max()-X_grid.min(), Y_grid.max()-Y_grid.min()]).max() / 2.0
mid_x = (X_grid.max()+X_grid.min()) * 0.5
mid_y = (Y_grid.max()+Y_grid.min()) * 0.5
ax.set_xlim(mid_x - max_range, mid_x + max_range)
ax.set_ylim(mid_y - max_range, mid_y + max_range)
plt.show()
Output:

Add background image to 3d plot

This topic has been touched here, but no indications were given as to how to create a 3D plot and insert an image in the (x,y) plane, at a specified z height.
So to come up with a simple and reproducible case, let's say that I create a 3D plot like this with mplot3d:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
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)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.winter,
linewidth=0, antialiased=True)
ax.set_zlim(-1.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
plt.show()
Visually we have:
At the level z=min(z)-1, where -1 is a visual offset to avoid overlapping, I want to insert an image representing the elements for which the curve shows a certain value. How to do it?
In this example I don't care about a perfect matching between the element and its value, so please feel free to upload any image you like. Also, is there a way of letting that image rotate, in case one is not happy with the matching?
EDIT
This is a visual example of something similar made for a 3D histogram. The grey shapes at the level z=0 are the elements for which the bars show a certain z value. Source.
Use plot_surface to draw image via facecolors argument.
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
from matplotlib._png import read_png
from matplotlib.cbook import get_sample_data
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, .25)
Y = np.arange(-5, 5, .25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.winter,
linewidth=0, antialiased=True)
ax.set_zlim(-2.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
fn = get_sample_data("./lena.png", asfileobj=False)
arr = read_png(fn)
# 10 is equal length of x and y axises of your surface
stepX, stepY = 10. / arr.shape[0], 10. / arr.shape[1]
X1 = np.arange(-5, 5, stepX)
Y1 = np.arange(-5, 5, stepY)
X1, Y1 = np.meshgrid(X1, Y1)
# stride args allows to determine image quality
# stride = 1 work slow
ax.plot_surface(X1, Y1, -2.01, rstride=1, cstride=1, facecolors=arr)
plt.show()
If you need to add values use PathPatch:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
from matplotlib.text import TextPath
from matplotlib.transforms import Affine2D
from matplotlib.patches import PathPatch
def text3d(ax, xyz, s, zdir="z", size=None, angle=0, usetex=False, **kwargs):
x, y, z = xyz
if zdir == "y":
xy1, z1 = (x, z), y
elif zdir == "y":
xy1, z1 = (y, z), x
else:
xy1, z1 = (x, y), z
text_path = TextPath((0, 0), s, size=size, usetex=usetex)
trans = Affine2D().rotate(angle).translate(xy1[0], xy1[1])
p1 = PathPatch(trans.transform_path(text_path), **kwargs)
ax.add_patch(p1)
art3d.pathpatch_2d_to_3d(p1, z=z1, zdir=zdir)
# main
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, .25)
Y = np.arange(-5, 5, .25)
Xg, Yg = np.meshgrid(X, Y)
R = np.sqrt(Xg**2 + Yg**2)
Z = np.sin(R)
surf = ax.plot_surface(Xg, Yg, Z, rstride=1, cstride=1, cmap=cm.winter,
linewidth=0, antialiased=True)
ax.set_zlim(-2.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
# add pathces with values
for i,x in enumerate(X[::4]):
for j,y in enumerate(Y[::4]):
text3d(ax, (x, y, -2.01), "{0:.1f}".format(Z[i][j]), zdir="z", size=.5, ec="none", fc="k")
plt.show()

matplotlib surface plot extends past axis limits

How do I make a nice paraboloid in Matplotlib that looks like
All I can get is this,
where the top is not "cut off". I've tried just dropping all values of the Z array outside of the radius of the parabola at the top, but that gives very jagged edges. Can someone help me?
Here is my code:
from matplotlib import *
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X = np.arange(-5, 5, 0.1)
Y = np.arange(-5, 5, 0.1)
X, Y = np.meshgrid(X, Y)
Z = (X**2 + Y**2)
ax.set_zlim(-10, 20)
ax.plot_surface(X, Y, Z, alpha=0.9, rstride=4, cstride=4, linewidth=0.5, cmap=cm.summer)
plt.show()
For future reference, I had a thought to parametrize the surface in cylindrical coordinates, and it looks exactly how I want it:
from matplotlib import *
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
r = T = np.arange(0, 2*pi, 0.01)
r, T = np.meshgrid(r, T)
#Parametrise it
X = r*np.cos(T)
Y = r*np.sin(T)
Z = r**2
ax.plot_surface(X, Y, Z, alpha=0.9, rstride=10, cstride=10, linewidth=0.5, cmap=cm.summer)
plt.show()
I guess it makes sense: when working with a cylindrical object, use cylindrical coordinates!
Manual data clipping
One approach I've seen that works is to manually clip the data; e.g. your example would be updated to
from matplotlib import *
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X = np.arange(-5, 5, 0.1)
Y = np.arange(-5, 5, 0.1)
X, Y = np.meshgrid(X, Y)
Z = (X**2 + Y**2)
ax.set_zlim(-10, 20)
for i in range(len(X)):
for j in range(len(Y)):
if (Z[j,i] < -10) or (Z[j,i] > 20):
Z[j,i] = NaN
ax.plot_surface(X, Y, Z, alpha=0.9, rstride=4, cstride=4, linewidth=0.5, cmap=cm.summer)
plt.show()
Note
This can be done concisely for this case using
Z[Z>20] = NaN
Resulting in

Categories

Resources