Plotting contour and wireframe plots with Matplotlib - python

I have to plot contour and wireframe plots for function . This is the code I have so far:
# Number of uniformly ditributed random numbers
n = 2000
def func_vec(x1s, x2s):
return x1s * x1s + 4 * x2s * x2s
np.random.seed()
x1s = np.random.uniform(-1, 1, n)
x2s = np.random.uniform(-1, 1, n)
ys = func_vec(x1s, x2s)
fig = plt.figure()
# Scatter
ax1 = fig.add_subplot(1, 2, 1)
ax1.scatter(x1s, x2s, color = 'g', s = 2, edgecolor = 'none')
ax1.set_ylim([-1,1])
ax1.set_xlim([-1,1])
# Contour
ax1.contour(x2s, x1s, ys[np.newaxis,:].repeat(n, axis = 0))
# 3D visualization
ax2 = fig.add_subplot(1, 2, 2, projection = '3d')
X = x1s
Y = x2s
Z = ys
ax2.plot_wireframe(X, Y, Z, rstride = 1, cstride = 1)
plt.show()
What I don't understand is how do contour() and plot_firewrame() actually work? Can somebody please be so kind and explain this to me (in the context of specified function)? Furthermore, how should I specify X, Y and Z?
This is how the plot looks now:
and this is how it should look like (scatter above works OK):

Here's the code that will produce the correct plots. Anyone struggling with this, should find the code pretty much self-explanatory:
# Number of uniformly ditributed random numbers
n = 2000
def func_vec(x1s, x2s):
return x1s * x1s + 4 * x2s * x2s
np.random.seed()
x1s = np.random.uniform(-1, 1, n)
x2s = np.random.uniform(-1, 1, n)
ys = func_vec(x1s, x2s)
fig = plt.figure(22)
# Scatter
ax1 = fig.add_subplot(1, 2, 1)
ax1.scatter(x1s, x2s, color = 'g', s = 2, edgecolor = 'none')
ax1.set_ylim([-1,1])
ax1.set_xlim([-1,1])
# Contour
xi = np.linspace(-1,1,20)
yi = np.linspace(-1,1,20)
zi = griddata((x2s, x1s), ys, (xi[None,:], yi[:,None]), method = 'cubic')
ax1.contour(xi, yi, zi, 6, linewidths = 1, colors = ('#0000ff', '#0099ff', '#009999', '#999900', '#ff9900', '#ff0000'))
# 3D visualization
ax2 = fig.add_subplot(1, 2, 2, projection = '3d')
X, Y = np.meshgrid(xi, yi)
ax2.plot_wireframe(X, Y, zi, rstride = 1, cstride = 1)
ax2.view_init(28, -144)
plt.show()

Related

How to Add another subplot to show Solid of Revolution toward x-axis?

I have this code modified from the topic here:
How to produce a revolution of a 2D plot with matplotlib in Python
The plot contains a subplot in the XY plane and another subplot of the solid of revolution toward the y-axis.
I want to add another subplot that is the solid of revolution toward the x-axis + how to add a legend to each subplot (above them), so there will be 3 subplots.
This is my MWE:
# Compare the plot at xy axis with the solid of revolution
# For function x=(y-2)^(1/3)
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
n = 100
fig = plt.figure(figsize=(12,6))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122,projection='3d')
y = np.linspace(np.pi/8, np.pi*40/5, n)
x = (y-2)**(1/3) # x = np.sin(y)
t = np.linspace(0, np.pi*2, n)
xn = np.outer(x, np.cos(t))
yn = np.outer(x, np.sin(t))
zn = np.zeros_like(xn)
for i in range(len(x)):
zn[i:i+1,:] = np.full_like(zn[0,:], y[i])
ax1.plot(x, y)
ax2.plot_surface(xn, yn, zn)
plt.show()
Option 1:
Simply reverse x and y to switch the axes of the function.
x = np.linspace(np.pi/8, np.pi*40/5, n)
y = (x-2)**(1/3)
Option 2:
It is a little complicated. You can also accomplish this by finding the inverse of the original function.
The inverse of f(x) = y = x^3 + 2 is f^{-1}(y) = (y - 2)^(1/3).
I modified the code you provided.
import matplotlib.pyplot as plt
import numpy as np
n = 100
fig = plt.figure(figsize=(14, 7))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222, projection='3d')
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224, projection='3d')
y = np.linspace(np.pi / 8, np.pi * 40 / 5, n)
x = (y - 2) ** (1 / 3)
t = np.linspace(0, np.pi * 2, n)
xn = np.outer(x, np.cos(t))
yn = np.outer(x, np.sin(t))
zn = np.zeros_like(xn)
for i in range(len(x)):
zn[i:i + 1, :] = np.full_like(zn[0, :], y[i])
ax1.plot(x, y)
ax1.set_title("$f(x)$")
ax2.plot_surface(xn, yn, zn)
ax2.set_title("$f(x)$: Revolution around $y$")
# find the inverse of the function
x_inverse = y
y_inverse = np.power(x_inverse - 2, 1 / 3)
xn_inverse = np.outer(x_inverse, np.cos(t))
yn_inverse = np.outer(x_inverse, np.sin(t))
zn_inverse = np.zeros_like(xn_inverse)
for i in range(len(x_inverse)):
zn_inverse[i:i + 1, :] = np.full_like(zn_inverse[0, :], y_inverse[i])
ax3.plot(x_inverse, y_inverse)
ax3.set_title("Inverse of $f(x)$")
ax4.plot_surface(xn_inverse, yn_inverse, zn_inverse)
ax4.set_title("$f(x)$: Revolution around $x$")
plt.tight_layout()
plt.show()

How to properly set labels in contourf subplots?

I am trying to get rid of these purple points on the picture below. Here is my code:
p_values = [0., 0.05, 0.25, 0.5, 1, 1.5, 2, 5, 10, np.inf]
xx, yy = np.meshgrid(np.linspace(-3, 3, num = 101),
np.linspace(-3, 3, num = 101))
fig, axes = plt.subplots(ncols = (len(p_values) + 1) // 2,
nrows = 2, figsize = (16, 7))
for p, ax in zip(p_values, axes.flat):
### BEGIN Solution (do not delete this comment)
z = np.linalg.norm([xx, yy], ord = p, axis = 0)
ax.contourf(yy, xx, z, 25, cmap = 'coolwarm')
ax.contour(yy, xx, z, [1], colors = 'fuchsia', linewidths = 3)
ax.set_title(f'p = {p}')
ax.legend([f'$x: |x|_{{{p}}} = 1$']);
### END Solution (do not delete this comment)
plt.show()
Which parameters should be specified in ax.legend() in order to plot the graph clear.
You could create the legend using an explicit handle. In this case the fuchsia colored line is stored as the last element of ax.collections. Creating the legend with only labels, when there were no "handles with labels" set, could be the cause of the weird purple dots.
import matplotlib.pyplot as plt
import numpy as np
p_values = [0., 0.05, 0.25, 0.5, 1, 1.5, 2, 5, 10, np.inf]
xx, yy = np.meshgrid(np.linspace(-3, 3, num=101),
np.linspace(-3, 3, num=101))
fig, axes = plt.subplots(ncols=(len(p_values) + 1) // 2,
nrows=2, figsize=(16, 7))
cmap = plt.get_cmap('magma').copy()
cmap.set_extremes(over='green', under='black', bad='turquoise')
for p, ax in zip(p_values, axes.flat):
### BEGIN Solution (do not delete this comment)
z = np.linalg.norm([xx, yy], ord=p, axis=0)
cnt = ax.contourf(yy, xx, z, 25, cmap='coolwarm')
ax.contour(yy, xx, z, [1], colors='fuchsia', linewidths=3)
ax.set_title(f'p = {p}')
ax.legend(handles=[ax.collections[-1]], labels=[f'$x: |x|_{{{p}}} = 1$'])
plt.colorbar(cnt, ax=ax)
### END Solution (do not delete this comment)
plt.tight_layout()
plt.show()

Way to contour outer edge of selected grid region in Python

I have the following code:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-np.pi/2, np.pi/2, 30)
y = np.linspace(-np.pi/2, np.pi/2, 30)
x,y = np.meshgrid(x,y)
z = np.sin(x**2+y**2)[:-1,:-1]
fig,ax = plt.subplots()
ax.pcolormesh(x,y,z)
Which gives this image:
Now lets say I want to highlight the edge certain grid boxes:
highlight = (z > 0.9)
I could use the contour function, but this would result in a "smoothed" contour. I just want to highlight the edge of a region, following the edge of the grid boxes.
The closest I've come is adding something like this:
highlight = np.ma.masked_less(highlight, 1)
ax.pcolormesh(x, y, highlight, facecolor = 'None', edgecolors = 'w')
Which gives this plot:
Which is close, but what I really want is for only the outer and inner edges of that "donut" to be highlighted.
So essentially I am looking for some hybrid of the contour and pcolormesh functions - something that follows the contour of some value, but follows grid bins in "steps" rather than connecting point-to-point. Does that make sense?
Side note: In the pcolormesh arguments, I have edgecolors = 'w', but the edges still come out to be blue. Whats going on there?
EDIT:
JohanC's initial answer using add_iso_line() works for the question as posed. However, the actual data I'm using is a very irregular x,y grid, which cannot be converted to 1D (as is required for add_iso_line().
I am using data which has been converted from polar coordinates (rho, phi) to cartesian (x,y). The 2D solution posed by JohanC does not appear to work for the following case:
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
def pol2cart(rho, phi):
x = rho * np.cos(phi)
y = rho * np.sin(phi)
return(x, y)
phi = np.linspace(0,2*np.pi,30)
rho = np.linspace(0,2,30)
pp, rr = np.meshgrid(phi,rho)
xx,yy = pol2cart(rr, pp)
z = np.sin(xx**2 + yy**2)
scale = 5
zz = ndimage.zoom(z, scale, order=0)
fig,ax = plt.subplots()
ax.pcolormesh(xx,yy,z[:-1, :-1])
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmin, xmax = xx.min(), xx.max()
ymin, ymax = yy.min(), yy.max()
ax.contour(np.linspace(xmin,xmax, zz.shape[1]) + (xmax-xmin)/z.shape[1]/2,
np.linspace(ymin,ymax, zz.shape[0]) + (ymax-ymin)/z.shape[0]/2,
np.where(zz < 0.9, 0, 1), levels=[0.5], colors='red')
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)
This post shows a way to draw such lines. As it is not straightforward to adapt to the current pcolormesh, the following code demonstrates a possible adaption.
Note that the 2d versions of x and y have been renamed, as the 1d versions are needed for the line segments.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(-np.pi / 2, np.pi / 2, 30)
y = np.linspace(-np.pi / 2, np.pi / 2, 30)
xx, yy = np.meshgrid(x, y)
z = np.sin(xx ** 2 + yy ** 2)[:-1, :-1]
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z)
def add_iso_line(ax, value, color):
v = np.diff(z > value, axis=1)
h = np.diff(z > value, axis=0)
l = np.argwhere(v.T)
vlines = np.array(list(zip(np.stack((x[l[:, 0] + 1], y[l[:, 1]])).T,
np.stack((x[l[:, 0] + 1], y[l[:, 1] + 1])).T)))
l = np.argwhere(h.T)
hlines = np.array(list(zip(np.stack((x[l[:, 0]], y[l[:, 1] + 1])).T,
np.stack((x[l[:, 0] + 1], y[l[:, 1] + 1])).T)))
lines = np.vstack((vlines, hlines))
ax.add_collection(LineCollection(lines, lw=1, colors=color))
add_iso_line(ax, 0.9, 'r')
plt.show()
Here is an adaption of the second answer, which can work with only 2d arrays:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from scipy import ndimage
x = np.linspace(-np.pi / 2, np.pi / 2, 30)
y = np.linspace(-np.pi / 2, np.pi / 2, 30)
x, y = np.meshgrid(x, y)
z = np.sin(x ** 2 + y ** 2)
scale = 5
zz = ndimage.zoom(z, scale, order=0)
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z[:-1, :-1] )
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmin, xmax = x.min(), x.max()
ymin, ymax = y.min(), y.max()
ax.contour(np.linspace(xmin,xmax, zz.shape[1]) + (xmax-xmin)/z.shape[1]/2,
np.linspace(ymin,ymax, zz.shape[0]) + (ymax-ymin)/z.shape[0]/2,
np.where(zz < 0.9, 0, 1), levels=[0.5], colors='red')
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)
plt.show()
I'll try to refactor add_iso_line method in order to make it more clear an open for optimisations. So, at first, there comes a must-do part:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(-np.pi/2, np.pi/2, 30)
y = np.linspace(-np.pi/2, np.pi/2, 30)
x, y = np.meshgrid(x,y)
z = np.sin(x**2+y**2)[:-1,:-1]
fig, ax = plt.subplots()
ax.pcolormesh(x,y,z)
xlim, ylim = ax.get_xlim(), ax.get_ylim()
highlight = (z > 0.9)
Now highlight is a binary array that looks like this:
After that we can extract indexes of True cells, look for False neighbourhoods and identify positions of 'red' lines. I'm not comfortable enough with doing it in a vectorised manner (like here in add_iso_line method) so just using simple loop:
lines = []
cells = zip(*np.where(highlight))
for x, y in cells:
if x == 0 or highlight[x - 1, y] == 0: lines.append(([x, y], [x, y + 1]))
if x == highlight.shape[0] or highlight[x + 1, y] == 0: lines.append(([x + 1, y], [x + 1, y + 1]))
if y == 0 or highlight[x, y - 1] == 0: lines.append(([x, y], [x + 1, y]))
if y == highlight.shape[1] or highlight[x, y + 1] == 0: lines.append(([x, y + 1], [x + 1, y + 1]))
And, finally, I resize and center coordinates of lines in order to fit with pcolormesh:
lines = (np.array(lines) / highlight.shape - [0.5, 0.5]) * [xlim[1] - xlim[0], ylim[1] - ylim[0]]
ax.add_collection(LineCollection(lines, colors='r'))
plt.show()
In conclusion, this is very similar to JohanC solution and, in general, slower. Fortunately, we can reduce amount of cells significantly, extracting contours only using python-opencv package:
import cv2
highlight = highlight.astype(np.uint8)
contours, hierarchy = cv2.findContours(highlight, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cells = np.vstack(contours).squeeze()
This is an illustration of cells being checked:

How to combine two matplotlib (python) colormaps from scatter plot

I am trying to combine two colourmap legends in one. Colour values are defined from third (z) data.
I am trying plot one legend colormap with two color scheme.
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
df = pd.read_excel('C:\\Users\user1\\PycharmProjects\\untitled\\Python_test.xlsx')
x = df['Vp_dry']
y = df['Vs_dry']
q = df['Vp_wet']
w = df['Vs_wet']
fig, ax = plt.subplots()
popt, pcov = curve_fit(lambda fx, a, b: a * fx ** -b, x, y)
x_linspace = np.linspace(min(x - 100), max(x + 100), 100)
power_y = popt[0]*x_linspace ** -popt[1]
ax1 = plt.scatter(x, y, c=df['Porosity'], cmap=plt.cm.Greys, vmin=2, vmax=df['Porosity'].max(), edgecolors="#B6BBBD")
plt.plot(x_linspace, power_y, color='grey', label='Dry')
popt, pcov = curve_fit(lambda fx, a, b: a * fx ** -b, q, w)
q_linspace = np.linspace(min(q - 100), max(q + 100), 100)
power_w = popt[0]*q_linspace ** -popt[1]
ax2 = plt.scatter(q, w, c=df['Porosity'], cmap=plt.cm.Blues, vmin=2, vmax=df['Porosity'].max(), edgecolors="#3D83C1")
plt.plot(q_linspace, power_w, label='Wet')
cbar = fig.colorbar(ax2)
cbar = fig.colorbar(ax1)
cbar.set_label("Porosity (%)")
plt.xlabel('Vp (m/s)')
plt.ylabel('Vs (m/s)')
plt.grid()
plt.legend()
plt.show()
Desired result:
You seem to need a colorbar with two color maps combined, one of them reversed, and have the ticks changed to percentage values.
An approach is to manually create a second subplot, use two images and make it look like a colorbar:
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import numpy as np
# first create some dummy data to plot
N = 100
x = np.random.uniform(0, 10, N)
y = np.random.normal(15, 2, N)
q = np.random.uniform(0, 10, N)
w = np.random.normal(10, 2, N)
df_porosity = np.random.uniform(0, 5, N)
fig, (ax, ax2) = plt.subplots(ncols=2, figsize=(6, 4), gridspec_kw={"width_ratios": [1, 0.08]})
plot1 = ax.scatter(x, y, c=df_porosity, cmap=plt.cm.Greys, vmin=2, vmax=df_porosity.max(), edgecolors="#B6BBBD")
plot2 = ax.scatter(q, w, c=df_porosity, cmap=plt.cm.Blues, vmin=2, vmax=df_porosity.max(), edgecolors="#3D83C1")
img_cbar = np.linspace(0, 1, 256).reshape(256, 1)
ax2.imshow(img_cbar, cmap=plt.cm.Blues, extent=[0, 1, 1, 0]) # aspect='auto')
ax2.imshow(img_cbar, cmap=plt.cm.Greys, extent=[0, 1, -1, 0])
ax2.set_ylim(-1, 1)
ax2.set_aspect(10)
ax2.set_ylabel("Porosity (%)")
ax2.yaxis.set_label_position("right")
ax2.set_xticks([])
ax2.yaxis.tick_right()
# optionally show the ticks as percentage, where 1.0 corresponds to 100 %
ax2.yaxis.set_major_formatter(mtick.PercentFormatter(1.0))
plt.tight_layout()
plt.show()

How to add colors to each individual face of a cylinder using matplotlib

I am trying to color each individual face of a cylinder, however I am not sure how to go about it, I have tried the following:
for i in range(10):
col.append([])
for i in range(10):
for j in range(20):
col[i].append(plt.cm.Blues(0.4))
ax.plot_surface(X, Y, Z,facecolors = col,edgecolor = "red")
I want each face to be assigned its own color, so I would think I would supply an array of colors for each of the faces in a 2d array.
But this gives an error:
in plot_surface
colset.append(fcolors[rs][cs])
IndexError: list index out of range
Here is the full code:
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
origin = np.array([0, 0, 0])
#axis and radius
p0 = np.array([1, 3, 2])
p1 = np.array([8, 5, 9])
R = 5
#vector in direction of axis
v = p1 - p0
#find magnitude of vector
mag = norm(v)
#unit vector in direction of axis
v = v / mag
#make some vector not in the same direction as v
not_v = np.array([1, 0, 0])
if (v == not_v).all():
not_v = np.array([0, 1, 0])
#make vector perpendicular to v
n1 = np.cross(v, not_v)
#normalize n1
n1 /= norm(n1)
#make unit vector perpendicular to v and n1
n2 = np.cross(v, n1)
#surface ranges over t from 0 to length of axis and 0 to 2*pi
t = np.linspace(0, mag, 200)
theta = np.linspace(0, 2 * np.pi, 100)
#use meshgrid to make 2d arrays
t, theta = np.meshgrid(t, theta)
#generate coordinates for surface
X, Y, Z = [p0[i] + v[i] * t + R * np.sin(theta) * n1[i] + R * np.cos(theta) * n2[i] for i in [0, 1, 2]]
col = []
for i in range(10):
col.append([])
for i in range(10):
for j in range(20):
col[i].append(plt.cm.Blues(0.4))
ax.plot_surface(X, Y, Z,facecolors = col,edgecolor = "red")
#plot axis
ax.plot(*zip(p0, p1), color = 'red')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_zlim(0, 10)
plt.axis('off')
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
plt.show()
Your Z array is of size 100x200, yet you are only specifying 10x20 colors. A quicker way to make col (with the right dimensions) might be something like:
col1 = plt.cm.Blues(np.linspace(0,1,200)) # linear gradient along the t-axis
col1 = np.repeat(col1[np.newaxis,:, :], 100, axis=0) # expand over the theta-axis
col2 = plt.cm.Blues(np.linspace(0,1,100)) # linear gradient along the theta-axis
col2 = np.repeat(col2[:, np.newaxis, :], 200, axis=1) # expand over the t-axis
ax=plt.subplot(121, projection='3d')
ax.plot_surface(X, Y, Z, facecolors=col1)
ax=plt.subplot(122, projection='3d')
ax.plot_surface(X, Y, Z, facecolors=col2)
Which produces:

Categories