matplotlib 3D surface - gaps / holes in surface - python

A grid is interpolated with scipy's griddata() and contains values and NaNs. However, when the grid is plotted with mplot3d's plot_surface() there are gaps in the surface (see upper plot). The lower the view height, the more of such gaps are rendered. When the grid is plotted in 2D with imshow() there are no gaps (see lower plot).
Comparison plot_surface and imshow
Here is a close-up:
I couldn't find an answer to this problem and I am wondering whether this is a known issue.
This small example reproduces the problem: (data.txt)
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
Z = np.loadtxt('data.txt')
X = np.linspace(0,3,301)
Y = np.linspace(0,3,301)
X = np.array([X,]*(301)).transpose()
Y = np.array([Y,]*(301))
fig = plt.figure(figsize=(15,15), dpi=100)
ax = fig.add_subplot(2, 1, 1, projection='3d')
plt.hold(True)
ax.view_init(40,300)
surf = ax.plot_surface(X, Y, Z, cmap='jet', rstride=1, cstride=1, antialiased=False, shade=False, alpha=1.0, linewidth=0, vmin=0.25, vmax=0.35);
ax.invert_yaxis()
ax.dist = 11
ax = fig.add_subplot(2, 1, 2)
plt.hold(True)
plt.imshow(Z, cmap='jet', vmin=0.25, vmax=0.35, origin='lower');
plt.show()

Related

Matplotlib smoothing 3D surface data

I have an issue with smoothing out the mesh representation of my 3D surface with matplotlib. Below, please see my example. I am having a hard time figuring out how to make the plot look nicer/smoother if possible. Thank you for your time in advance!
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.colors import LightSource
import numpy as np
X = [1,1,1,1,1,1,50,50,50,50,50,50]
Y = [3,5,7,8,9,10,3,5,7,8,9,10]
Z = [5.23,3.11,17.54,0.93,40.11,10.15,1.47,14.32,5.46,55.93,40.8,10.2]
x = np.reshape(X, (2, 6))
y = np.reshape(Y, (2, 6))
z = np.reshape(Z, (2, 6))
X, Y = np.meshgrid(x, y)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z)
ax.set_xlabel('Persistence Length')
ax.set_ylabel('Complexity')
ax.set_zlabel('Relative number of configurational states')
surf = ax.plot_surface(x, y, z, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
To obtain smooth line/surface you can set antialiased=True on the surface plot. Note that you were plotting two identical surface: in the following example I have eliminated the first.
To obtain a smoother mesh, you probably want to interpolate between your data points. One way to do that is to use griddata from the scipy.interpolate module.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from scipy.interpolate import griddata
X = [1,1,1,1,1,1,50,50,50,50,50,50]
Y = [3,5,7,8,9,10,3,5,7,8,9,10]
Z = [5.23,3.11,17.54,0.93,40.11,10.15,1.47,14.32,5.46,55.93,40.8,10.2]
points = np.array([X, Y]).T
# create a grid of coordinates between the minimum and
# maximum of your X and Y. 50j indicates 50 discretization
# points between the minimum and maximum.
X_grid, Y_grid = np.mgrid[1:50:50j, 3:10:50j]
# interpolate your values on the grid defined above
Z_grid = griddata(points, Z, (X_grid, Y_grid), method='cubic')
fig = plt.figure(constrained_layout=True)
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('Persistence Length')
ax.set_ylabel('Complexity')
ax.set_zlabel('Relative number of configurational states')
surf = ax.plot_surface(X_grid, Y_grid, Z_grid, cmap=cm.coolwarm,
linewidth=0, antialiased=True)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
Here is an example of antialiased=False on the left, vs antialiased=True on the right:

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.

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