Plot surface with binary colormap - python

I would like to make a 3d plot of a surface parametrised by a function, and I would like the surface to be of one color (say white) where it is above some value a, and of another color (say black) where it is below a.
Here is the code to generate and plot the surface (the way the surface is generated is not important, it could be a much simpler function):
from __future__ import division
import numpy as np
import time,random
random.seed(-2)
def build_spden(N,M, alpha):
#computes the spectral density in momentum space
sp_den = np.zeros((N,M))
for k1 in prange(-N//2, N//2):
for k2 in prange(-M//2, M//2):
sp_den[k1,k2] = np.abs(2*(np.cos(2*np.pi*k1/N)+np.cos(2*np.pi*k2/M)-2))
sp_den[0,0]=1
return 1/sp_den**(alpha/2)
def gaussian_field(N,M,alpha):
'''Builds a correlated gaussian field on a surface NxM'''
spectral_density = build_spden(N,M, alpha)
# FFT of gaussian noise:
noise_real = np.random.normal(0, 1, size = (N,M))
noise_fourier = np.fft.fft2(noise_real)
# Add correlations by Fourier Filtering Method:
convolution = noise_fourier*np.sqrt(spectral_density)
# Take IFFT and exclude residual complex part
correlated_noise = np.fft.ifft2(convolution).real
# Return normalized field
return correlated_noise * (np.sqrt(N*M)/np.sqrt(np.sum(spectral_density)) )
#PLOT
N = 2**5
alpha = .75
a = -.1985
surf = gaussian_field(N,N,alpha)
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.outer(np.arange(0, N), np.ones(N))
y = x.copy().T # transpose
z = surf
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z,alpha=.4) #plot the surface
z2 = a*np.ones((N,N))
ax.plot_surface(x, y, z2, alpha=0.9) #plot a plane z = a.
plt.show()
The output is:
I would therefore like the surface to be white above the plane and black below.
Many thanks !

You can define a custom color map and pass to plot_surface:
from matplotlib.colors import ListedColormap, BoundaryNorm
cmap = ListedColormap(['r', 'b'])
norm = BoundaryNorm([z.min(), a, z.max()], cmap.N)
ax.plot_surface(x, y, z, cmap=cmap, norm=norm, alpha=.4) #plot the surface
z2 = a*np.ones((N,N))
ax.plot_surface(x, y, z2, colalpha=0.9) #plot a plane z = a.
plt.show()
Output:

Related

Is there a matplotlib method that allows to plot a vector field on a surface so that the surface is opaque to the vectors?

I wrote a matplotlib program that plots a 2d surface embedded in three dimensional space using plot_trisurf() and then plots a vector field defined on the surface using quiver(). I'd like the surface to be opaque to the vector field but instead the program plots both the vectors that are in front of the surface and those that are behind the surface with respect to the camera, despite the surface's alpha value being 1.0.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.tri as mtri
fig = plt.figure(figsize=plt.figaspect(1.0) * 1.7)
# Make a mesh in the space of parameterisation variables u and v
u = np.linspace(0, 2.0 * np.pi, endpoint=True, num=30) # u: theta
v = np.linspace(0, 2.0 * np.pi, endpoint=True, num=60) # v: phi
u, v = np.meshgrid(u, v)
u, v = u.flatten(), v.flatten()
x, y, z = F(u, v)
# Triangulate parameter space to determine the triangles
tri = mtri.Triangulation(u, v)
# Plot the surface. The triangles in parameter space determine which x, y, z
# points are connected by an edge.
ax = fig.add_subplot(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=plt.cm.magma, alpha = 1.0)
xl = ax.get_xlim()
yl = ax.get_ylim()
ax.set_zlim(xl[0], xl[1])
plt.show()
Here's where the vector field gets plotted:
alpha = 1.0
lenght = 0.25
ax.quiver(xf, yf, zf, ox, oy, oz, color='red', alpha=alpha, length=lenght, normalize=True)
Here's an example that shows how the full vector field gets plotted.
I've also tried adding zorder parameters in the plotting functions but with no success: ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=plt.cm.magma, alpha = 1.0, zorder = 2)
Set antialiased=False in plot_trisurf

Make 3D triangle lines more visible in pyplot

I'd like to make a triangle plot in matplotlib with a mostly-transparent surface. I'm running the example code at https://matplotlib.org/mpl_examples/mplot3d/trisurf3d_demo.py:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
n_radii = 8
n_angles = 36
# Make radii and angles spaces (radius r=0 omitted to eliminate duplication).
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius.
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords.
# (0, 0) is manually added at this stage, so there will be no duplicate
# points in the (x, y) plane.
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Compute z to make the pringle surface.
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, linewidth=0.2, antialiased=True)
plt.show()
I can set
ax.plot_trisurf(x, y, z, linewidth=0.2, alpha = 0.2, antialiased=True)
to set the opacity to 0.2, but then the lines disappear. Furthermore, when I change the linewidth, even without the alpha, I see no change in the thickness of the lines between the points. How can I have a triangle plot where the faces are mostly transparent and the lines are clearly visible?

Matplotlib plot contourf on 3d surface

I am trying to use the colormap feature of a 3d-surface plot in matplotlib to color the surface based on values from another array instead of the z-values.
The surface plot is created and displayed as follows:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def gauss(x, y, w_0):
r = np.sqrt(x**2 + y**2)
return np.exp(-2*r**2 / w_0**2)
x = np.linspace(-100, 100, 100)
y = np.linspace(-100, 100, 100)
X, Y = np.meshgrid(x, y)
Z = gauss(X, Y, 50)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_surface(X, Y, Z, cmap='jet')
Now instead of coloring based on elevation of the 3d-surface, I am looking to supply the color data for the surface in form of another array, here as an example a random one:
color_data = np.random.uniform(0, 1, size=(Z.shape))
However, I did not find a solution to colorize the 3d-surface based on those values. Ideally, it would look like a contourf plot in 3d, just on the 3d surface.
You can use matplotlib.colors.from_levels_and_colors to obtain a colormap and normalization, then apply those to the values to be colormapped.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors
x = np.linspace(-100, 100, 101)
y = np.linspace(-100, 100, 101)
X, Y = np.meshgrid(x, y)
Z = np.exp(-2*np.sqrt(X**2 + Y**2)**2 / 50**2)
c = X+50*np.cos(Y/20) # values to be colormapped
N = 11 # Number of level (edges)
levels = np.linspace(-150,150,N)
colors = plt.cm.get_cmap("RdYlGn", N-1)(np.arange(N-1))
cmap, norm = matplotlib.colors.from_levels_and_colors(levels, colors)
color_vals = cmap(norm(c))
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_surface(X, Y, Z, facecolors=color_vals, rstride=1, cstride=1)
plt.show()

Matplotlib - contour and quiver plot in projected polar coordinates

I need to plot contour and quiver plots of scalar and vector fields defined on an uneven grid in (r,theta) coordinates.
As a minimal example of the problem I have, consider the contour plot of a Stream function for a magnetic dipole, contours of such a function are streamlines of the corresponeding vector field (in this case, the magnetic field).
The code below takes an uneven grid in (r,theta) coordinates, maps it to the cartesian plane and plots a contour plot of the stream function.
import numpy as np
import matplotlib.pyplot as plt
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
psi = np.zeros((N_r, N_theta))
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig, ax = plt.subplots()
# ax.plot(x,y,'b.') # plot grid points
ax.set_aspect('equal')
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
plt.show()
For some reason though, the plot I get doesn't look right:
If I interchange x and y in the contour function call, I get the desired result:
Same thing happens when I try to make a quiver plot of a vector field defined on the same grid and mapped to the x-y plane, except that interchanging x and y in the function call no longer works.
It seems like I made a stupid mistake somewhere but I can't figure out what it is.
If psi = m * np.sin(theta_matrix)**2 / r_matrix
then psi increases as theta goes from 0 to pi/2 and psi decreases as r increases.
So a contour line for psi should increase in r as theta increases. That results
in a curve that goes counterclockwise as it radiates out from the center. This is
consistent with the first plot you posted, and the result returned by the first version of your code with
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
An alternative way to confirm the plausibility of the result is to look at a surface plot of psi:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.set_aspect('equal')
ax.plot_surface(x, y, psi, rstride=8, cstride=8, alpha=0.3)
ax.contour(x, y, psi, colors='black',levels=contour_levels)
plt.show()

Draw seamless distribution of tweets

I've collected tweets from twitter now I'm trying to draw the distribution of tweets geographically. To do that, I divide the entire square area into small square and count number of tweets in each square. Finally, I use matplotlib to draw the following figure:
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, alpha=0.3, cmap='Accent')
The problem is that the elevation map is not smooth. I'd like a way to draw smooth curve from the data. One example for that in 2D is when we have a histogram of image, we can draw smooth curve over the distribution as follows:
So my question is that is there a way to draw a smooth surface from the discrete data?
Expanding on my answer, here's what you can get with resampling and smoothing (gaussian_filter())/spline interpolation (RectBivariateSpline). Note that it would be nice of you to provide a template code that plots your graph, but since you haven't, I had to improvise.
import numpy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def plot(name, method):
numpy.random.seed(123)
x = numpy.linspace(0, 50, 51)
X, Y = numpy.meshgrid(x, x)
Z = numpy.zeros((x.size, x.size))
for n in range(50):
i = numpy.random.randint(0, x.size)
j = numpy.random.randint(0, x.size)
Z[i, j] = numpy.abs(numpy.random.normal()) * 1000
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
if method == 0:
# regular plot
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, alpha=0.3, cmap='Accent')
else:
# create a finer grid
resample_coeff = 2
Z2 = numpy.repeat(Z, resample_coeff, 0).repeat(resample_coeff, 1)
x2 = numpy.linspace(x[0], x[-1], x.size * resample_coeff)
X2, Y2 = numpy.meshgrid(x2, x2)
if method == 1:
# smoothing
from scipy.ndimage.filters import gaussian_filter
Z2 = gaussian_filter(Z2, 1)
elif method == 2:
# interpolation
from scipy.interpolate import RectBivariateSpline
spline = RectBivariateSpline(
x, x, Z, bbox=[x[0], x[-1], x[0], x[-1]])
Z2 = spline.ev(X2, Y2)
ax.plot_surface(X2, Y2, Z2, rstride=1, cstride=1, alpha=0.3, cmap='Accent')
fig.savefig(name)
if __name__ == '__main__':
plot('t0.png', 0)
plot('t1.png', 1)
plot('t2.png', 2)
Initial graph:
Smoothing:
Interpolation (notice the negative regions; that's polynomial interpolation for you):

Categories

Resources