mplot3d narrow spline surface missing from output - python

I have setup mplot3d to provide a 3D surface plot per the example.
When I plot my data I am seeing that the surface is missing from a ridge running through the surface (see image). I noticed that surface filling appears to follow the stride but the grid-lines make viewing difficult at lower step sizes.
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm, pyplot
import numpy
Z = data[-300::]
X,Y = numpy.mgrid[:len(Z), :len(Z[0])]
fig = pyplot.figure(figsize=(20, 10), dpi=800)
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X,
Y,
Z,
rstride=len(Z)/5,
cstride=len(Z[0])/10,
alpha=.6,
linewidths=(.5,),
antialiased=True,
cmap=cm.coolwarm,
vmin=124,
vmax=186
)
cset = ax.contourf(X, Y, Z, zdir='z', offset=130, cmap=cm.coolwarm, vmin=124, vmax=186)
ax.set_xlim(len(Z) * 1.2, 0)
ax.set_ylim(0, len(Z[0]) * 1.2)
ax.elev = 25
ax.azim = 20
cb = fig.colorbar(surf, shrink=0.5, aspect=5)
Is there a way to fill the missing surface?

The only way i have found to accomplish this is by setting the stride to one and linewidth to 0. The downside to this is that I appear to lose the grid overlay.
surf = ax.plot_surface(X,
Y,
Z,
shade=True,
rstride=1, cstride=1, linewidth=0,
linewidths=(.5,),
antialiased=True,
)

Related

Relocate colorbar

Is it possible to put the color diagram (which is now on the right side of the original figure) on the top of the figure?
My code:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D, get_test_data
from matplotlib import cm
import numpy as np
# set up a figure twice as wide as it is tall
fig = plt.figure(figsize=plt.figaspect(0.5))
#===============
# First subplot
#===============
# set up the axes for the first plot
ax = fig.add_subplot(1, 2, 1, projection='3d')
# plot a 3D surface like in the example mplot3d/surface3d_demo
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.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
fig.colorbar(surf, shrink=0.5, aspect=10)
fig.savefig('64bit.png')
You have to add additional axes (add_axes) to put your colorbar at the desired position:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D, get_test_data
from matplotlib import cm
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
# set up a figure twice as wide as it is tall
fig = plt.figure(figsize=plt.figaspect(0.5))
#===============
# First subplot
#===============
# set up the axes for the first plot
ax = fig.add_subplot(1, 2, 1, projection='3d')
# plot a 3D surface like in the example mplot3d/surface3d_demo
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.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
# position of colorbar
# where arg is [left, bottom, width, height]
cax = fig.add_axes([0.15, .87, 0.35, 0.03])
fig.colorbar(surf, orientation='horizontal', cax=cax)
plt.show()
Yes it is, there are multiple answers here in the site showing you how to move the colorbar around like this one: positioning the colorbar
In your case, you want to combine that with the orientation argument. As far as I know, there is no easy way of just placing the colorbar to the top of your figure automatically, you will have to place it manually. Here is my code that replaces your fig.colorbar(surf, shrink=0.5, aspect=10):
cbax = fig.add_axes([0.1, 0.89, 0.5, 0.05])
fig.colorbar(surf, orientation="horizontal", cax=cbax)
The numbers in the list describe some characteristics of the colorbar which are [left, bottom, width, height] as mentioned in the other answer that I have attached.
These numbers came out nicely for your plot, feel free to change them to your liking.
In order to get the colorbar on top of the plot you need to create some axes, designated to host the colorbar.
This can either be done manually by placing a new axes at some given position in figure coordinates,
cax = fig.add_axes([0.2,0.8,0.3,.05])
fig.colorbar(surf, cax=cax, orientation="horizontal")
or, by using a subplot grid (gridspec), which is shown in the following:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
import matplotlib.gridspec as gridspec
import numpy as np
x = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(x,x)
Z = np.sin(np.sqrt(X**2 + Y**2))
gs = gridspec.GridSpec(2, 2, height_ratios=[0.05,1])
fig = plt.figure()
ax = fig.add_subplot(gs[1,0], projection='3d')
cax = fig.add_subplot(gs[0,0])
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap="coolwarm",
linewidth=0, antialiased=False, vmin=-1, vmax=1)
fig.colorbar(surf, cax=cax, orientation="horizontal", ticks=[-1,0,1])
plt.show()
For a method which avoids having to manually create new axes and instead allows us to keep the colorbar linked to an existing plot axis, we can use the location keyword (method adapted initially from here).
The location argument is meant to be used on colorbars which reference multiple axes in a list (and will throw an error if colorbar is given only one axis), but if you simply put your one axis in a list, it will allow you to use the argument. You can use the following code as an example:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
axp = ax.imshow(np.random.randint(0, 100, (100, 100)))
cb = plt.colorbar(axp,ax=[ax],location='top')
plt.show()
which yields this plot. From here, you can edit the colorbar using the typical methods (pad, shrink, etc.) to further tune the appearance of your plot.
Fair warning, I haven't seen this method used many other places and it could be less robust than going through the extra steps of creating a new axis for your colorbar.

Removing floor from 3D surface plot

I am trying to "remove the floor" from a 3D surface plot. For example, in this matplotlib demo code:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.3)
cset = ax.contour(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(-40, 40)
ax.set_ylabel('Y')
ax.set_ylim(-40, 40)
ax.set_zlabel('Z')
ax.set_zlim(-100, 100)
I am trying to just get the top half of the 3d surface, without the blue floor and the bottom hump. I'd like them transparent.
I've tried setting vmin appropriately, and even using a masked array but I still get the "floor" of color in my plots.
Note: My real situation is plotting a KDE generated on some data, on a grid of points and I dont want the entire bottom of my plot to be the same blue color.
The idea can be to set the unwanted part to a transparent color, using a normalization of the colormap.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.colors
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
norm = matplotlib.colors.Normalize(0,100)
cmap = cm.coolwarm
cmap.set_under((0,0,0,0), alpha=0.0)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, norm=norm, cmap=cmap)
ax.set_xlabel('X')
ax.set_xlim(-40, 40)
ax.set_ylabel('Y')
ax.set_ylim(-40, 40)
ax.set_zlabel('Z')
ax.set_zlim(-100, 100)
plt.show()

Surface and 3d contour in matplotlib

I would like to plot a surface with a colormap, wireframe and contours using matplotlib. Something like this:
Notice that I am not asking about the contours that lie in the plane parallel to xy but the ones that are 3D and white in the image.
If I go the naïve way and plot all these things I cannot see the contours (see code and image below).
import numpy as np
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
X, Y = np.mgrid[-1:1:30j, -1:1:30j]
Z = np.sin(np.pi*X)*np.sin(np.pi*Y)
ax.plot_surface(X, Y, Z, cmap="autumn_r", lw=0.5, rstride=1, cstride=1)
ax.contour(X, Y, Z, 10, lw=3, cmap="autumn_r", linestyles="solid", offset=-1)
ax.contour(X, Y, Z, 10, lw=3, colors="k", linestyles="solid")
plt.show()
If a add transparency to the surface facets then I can see the contours, but it looks really cluttered (see code and image below)
import numpy as np
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
X, Y = np.mgrid[-1:1:30j, -1:1:30j]
Z = np.sin(np.pi*X)*np.sin(np.pi*Y)
ax.plot_surface(X, Y, Z, cmap="autumn_r", lw=0.5, rstride=1, cstride=1, alpha=0.5)
ax.contour(X, Y, Z, 10, lw=3, cmap="autumn_r", linestyles="solid", offset=-1)
ax.contour(X, Y, Z, 10, lw=3, colors="k", linestyles="solid")
plt.show()
Question: Is there a way to obtain this result in matplotlib? The shading is not necessary, though.
Apparently it is a bug, if you try this
import numpy as np
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
X, Y = np.mgrid[-1:1:30j, -1:1:30j]
Z = np.sin(np.pi*X)*np.sin(np.pi*Y)
ax.plot_surface(X, Y, Z, cmap="autumn_r", lw=0, rstride=1, cstride=1)
ax.contour(X, Y, Z+1, 10, lw=3, colors="k", linestyles="solid")
plt.show()
And rotate around, you will see the contour lines disappearing when they shouldn't
I think you want to set the offset to the contour :
ax.contour(X, Y, Z, 10, offset=-1, lw=3, colors="k", linestyles="solid", alpha=0.5)
See this example for more:
http://matplotlib.org/examples/mplot3d/contour3d_demo3.html
And the docs here:
http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#contour-plots
offset: If specified plot a projection of the contour lines on this position in plane normal to zdir
Note, zdir = 'z' by default, but you can project in the x or y direction be setting the zdir accordingly.

Properly rendered 3d plot_surface in matplotlib

I am trying to render a surface, but I do not manage to get a pretty visualisation. The plot_surface function from matplotlib gives me the following figure:
produced by the code below. How do I get rid of this transparency and the wireframe that is still visible if you look carefully?
import numpy as np
import matplotlib.pyplot as pl
from mpl_toolkits.mplot3d import Axes3D
pl.ion()
nx = 512
ny = 512
Lx = 2.e6
Ly = 2.e6
x = np.linspace(0., Lx, nx)
y = np.linspace(0., Ly, ny)
xx, yy = np.meshgrid(x,y)
Ld = 6.e4
h = np.exp(-( (xx - 0.5*Lx)**2 + (yy - 0.5*Ly)**2) / Ld**2 )
pl.figure()
ax = pl.subplot(111, projection='3d')
ax.plot_surface(xx/1000., yy/1000., h, alpha=1., cstride=1, rstride=1, linewidth=0)
ax.set_zlim3d(-0.2, 1.)
It's only a workaround, but this works for most matplotlib routines like e.g. contourf (where I had the same problem before); calling the plot routine (in this case plot_surface) twice solves both problems:
The left figure is with calling plot_surface once, the right one calling it twice.
For a non-transparent surface, setting antialiased=False helps (left figure below), with transparency antialiased=True produces very thin lines at the polygon edges (I suspect because the polygons slightly overlap), but they are hardly visible (right figure below).
fig = pl.figure()
ax = pl.subplot(121, projection='3d')
surf = ax.plot_surface(xx/1000., yy/1000., h, alpha=1.0, cstride=1, rstride=1, linewidth=0, antialiased=False)
ax = pl.subplot(122, projection='3d')
surf = ax.plot_surface(xx/1000., yy/1000., h, alpha=0.3, cstride=1, rstride=1, linewidth=0, antialiased=True)

matplotlib : project 3d surface on 2d plot

Is there an equivalent to "Axes3DSubplot.plot_surface" in 2D ?
I am trying to plot the projection of a mesh on the XY-plane in matplotlib (so not in '3d' mode).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Data (wireframe sphere)
theta, phi = np.meshgrid( np.linspace(0, np.pi/2, 10), np.linspace(0, np.pi/2, 10) )
x = np.sin(theta) * np.cos(phi)
y = np.sin(theta) * np.sin(phi)
z = np.cos(theta)
fig = plt.figure()
# Subplot 3D
ax1 = fig.add_subplot(1, 2, 1, projection='3d', aspect='equal')
colors = matplotlib.cm.jet(np.hypot(x,y))
surface = ax1.plot_surface(x, y, z, rstride=1, cstride=1, facecolors = colors, alpha=0.5 )
projection = ax1.plot_surface(0, y, z, rstride=1, cstride=1, facecolors = colors )
projection.set_edgecolor('k')
# Subplot 2D
ax2 = fig.add_subplot(1, 2, 2, aspect='equal')
ax2.plot(y, z, 'k')
ax2.plot(y.T, z.T, 'k')
I am trying to produce a similar result than :
ax1.plot_surface(0, y, z, rstride=1, cstride=1, facecolors = colors )
But in the 2D subplot. I cannot find an equivalent for plot_surface in the doc of AxesSubplot. The only thing I managed to do is plot the wireframe (but not the facecolors) with :
ax2.plot(y, z, 'k')
ax2.plot(y.T, z.T, 'k')
I cannot upload an image, but basically, I want to put the "colors" in the second subplot.
Thanks,
EDIT:
#Tim
Yeah, I suppose, in this case, I managed to do it with :
ax2.contourf(y, z, np.hypot(x,y), levels=np.hypot(x,y)[0], cmap=matplotlib.cm.jet)
In a more generic case, you'll need the right level-function and a bit of tweaking with the levels and colormap, but it seems doable.
Another solution would be to use matplotlib.patches.Polygon to draw each projected face.
You can use contourf to produce coloured 2D contours:
# Subplot 2D
ax2 = fig.add_subplot(1, 2, 2, aspect='equal')
ax2.contourf(x, y, z, facecolors=colors)
Although this doesn't seem to be exactly what you need, it's a step in the right direction.

Categories

Resources