I'm trying to use the streamplot function to plot a velocity field but for some reason it is failing. Here is an original SO post about the function with an example: how to plot streamlines , when i know u and v components of velocity(numpy 2d arrays), using a plotting program in python?. The example works fine for me; however, I tried to modify the values to simplify the function and imitate initial conditions and now it no longer works.
Here's my "simplified" code:
import matplotlib.pyplot as plt
import numpy as np
from streamplot import streamplot
x = np.linspace(0, 1, 10)
y = np.linspace(0, 2, 10)
u = np.zeros((len(x), len(y)))
v = np.zeros((len(x), len(y)))
u[:,len(y)-1]=1
speed = np.sqrt(u*u + v*v)
plt.figure()
plt.subplot(121)
streamplot(x, y, u, v,density=1, INTEGRATOR='RK4', color='b')
plt.subplot(122)
streamplot(x, y, u, v, density=(1,1), INTEGRATOR='RK4', color=u,
linewidth=5*speed/speed.max())
plt.show()
Any recommendations or help is appreciated.
I think the problem is that the density of your (x,y) grid (you've switched x and y in your initialization of u and v, by the way) is less than the density of the streamplot grid. When you set density=1 or (1,1) (they should be equivalent) then "the domain is divided into a 25x25 grid". I think that means that there is some smoothing going on if your data is nonzero in a slim enough region compared to the density of either the streamplot or your x-y grid. I couldn't get it to work by increasing those densities (density or the linspace spacing). but if you make two columns nonzero at the edge, it seems to work fine.
Seems like the streamplot function is not very robust for these cases and perhaps you should submit a bug.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 1, 10)
y = np.linspace(0, 2, 10)
u = np.zeros((y.size, x.size))
v = np.zeros((y.size, x.size))
u[:,-2:] = 1
speed = np.sqrt(u*u + v*v)
plt.figure()
plt.subplot(121)
plt.streamplot(x, y, u, v,density=1, color='b')
plt.subplot(122)
plt.streamplot(x, y, u, v, density=(1,1), color=u, linewidth=5*speed/speed.max())
plt.show()
Related
I have extracted nodal fields from my geodynamic simulation, but I'm stuck on visualizing it. Here I take rock density rho [kg/m3] as an example. The data are represented on the nodal points of a 3D rectangular grid, with a resolution of 10 * 3 * 10 km.
The lengths of my coordinates are X * Y * Z = 213 * 69 * 133 nodes, where X and Z are horizontal and Y is the vertical coordinate. The dimensions of rho are 69 * 213 * 133 (so y, x, z)
Ideally, I would like to draw some sort of box in (x,z,y) space and assign a color to it according to the value of rho. However, for now I'm fine with creating a color-coded scatter plot at the nodes. I've looked in many places, e.g. the matplotlib.mpl_toolkits.Axes3D documentation, and here, and almost all of them say something like
img = ax.scatter(x, z, y, c=rho, cmap=plt.hot())
Or versions using ax.plot_surface, or ax.plot_trisurf, and those explicitly require X, Y, Z to be of the same length.
ax.scatter also only seems to work when x, y, z and rho have the same length, which seems unnecessary to me... However I try, I get errors like (on calling the scatter command):
File "/usr/lib/python3/dist-packages/mpl_toolkits/mplot3d/axes3d.py", line 2354, in scatter
xs, ys, zs = np.broadcast_arrays(
File "<__array_function__ internals>", line 5, in broadcast_arrays
File "/usr/lib/python3/dist-packages/numpy/lib/stride_tricks.py", line 264, in broadcast_arrays
shape = _broadcast_shape(*args)
File "/usr/lib/python3/dist-packages/numpy/lib/stride_tricks.py", line 191, in _broadcast_shape
b = np.broadcast(*args[:32])
ValueError: shape mismatch: objects cannot be broadcast to a single shape
I looked up what that means, and supposedly it means that the dimensions don't match. But I checked in a debugger, and the dimensions are correct.
I tried flattening rho (same result) and I tried looping over each node and plotting each point separately (sample down below)
rho, x, y, z = read_data('/path/to/my/data')
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
for i in range(len(x)):
for j in range(len(z)):
for k in range(len(y)):
ax.scatter(x[i], z[j], y[k], c=ronew[k, i, j], cmap='hot')
cbar = plt.colorbar(orientation='horizontal')
cbar.set_label('Density [kg/m3]')
plt.xlabel("X [km]")
plt.ylabel("Z [km]")
plt.zlabel("Depth [km]")
plt.show()
I imagine I'm not the only one who needs to make these "4D" figures. Therefore, any help would be very much appreciated! To make helping easier, here's a sample data that produces the above copied error:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import pyplot as plt
x = np.arange(0, 10)
y = np.arange(0, 6)
z = np.arange(0, 20)
rho = np.random.randint(1000, 3500, (10, 6, 20))
fig = plt.figure()
print(x.shape, y.shape, z.shape, rho.shape)
ax = fig.add_subplot(projection='3d')
ax.scatter(x, y, z, c=rho, cmap='hot')
plt.show()
Okay, so thanks to #JohanC 's comment I found out that the 4D scatter is not possible with matplotlib version 3.1.4 (the reason is beyond me). After upgrading matplotlib to 3.4.3 I had no problems after calling xx, yy, zz = np.meshgrid(x, y, z) and plotting like
ax.scatter(xx, zz, -yy, c=rho, cmap='hot')
I produced the following figure from the test code:
And this one from the real data file
I want to get 2d and 3d plots as shown below.
The equation of the curve is given.
How can we do so in python?
I know there may be duplicates but at the time of posting
I could not fine any useful posts.
My initial attempt is like this:
# Imports
import numpy as np
import matplotlib.pyplot as plt
# to plot the surface rho = b*cosh(z/b) with rho^2 = r^2 + b^2
z = np.arange(-3, 3, 0.01)
rho = np.cosh(z) # take constant b = 1
plt.plot(rho,z)
plt.show()
Some related links are following:
Rotate around z-axis only in plotly
The 3d-plot should look like this:
Ok so I think you are really asking to revolve a 2d curve around an axis to create a surface. I come from a CAD background so that is how i explain things.
and I am not the greatest at math so forgive any clunky terminology. Unfortunately you have to do the rest of the math to get all the points for the mesh.
Heres your code:
#import for 3d
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
change arange to linspace which captures the endpoint otherwise arange will be missing the 3.0 at the end of the array:
z = np.linspace(-3, 3, 600)
rho = np.cosh(z) # take constant b = 1
since rho is your radius at every z height we need to calculate x,y points around that radius. and before that we have to figure out at what positions on that radius to get x,y co-ordinates:
#steps around circle from 0 to 2*pi(360degrees)
#reshape at the end is to be able to use np.dot properly
revolve_steps = np.linspace(0, np.pi*2, 600).reshape(1,600)
the Trig way of getting points around a circle is:
x = r*cos(theta)
y = r*sin(theta)
for you r is your rho, and theta is revolve_steps
by using np.dot to do matrix multiplication you get a 2d array back where the rows of x's and y's will correspond to the z's
theta = revolve_steps
#convert rho to a column vector
rho_column = rho.reshape(600,1)
x = rho_column.dot(np.cos(theta))
y = rho_column.dot(np.sin(theta))
# expand z into a 2d array that matches dimensions of x and y arrays..
# i used np.meshgrid
zs, rs = np.meshgrid(z, rho)
#plotting
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
fig.tight_layout(pad = 0.0)
#transpose zs or you get a helix not a revolve.
# you could add rstride = int or cstride = int kwargs to control the mesh density
ax.plot_surface(x, y, zs.T, color = 'white', shade = False)
#view orientation
ax.elev = 30 #30 degrees for a typical isometric view
ax.azim = 30
#turn off the axes to closely mimic picture in original question
ax.set_axis_off()
plt.show()
#ps 600x600x600 pts takes a bit of time to render
I am not sure if it's been fixed in latest version of matplotlib but the setting the aspect ratio of 3d plots with:
ax.set_aspect('equal')
has not worked very well. you can find solutions at this stack overflow question
Only rotate the axis, in this case x
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
np.seterr(divide='ignore', invalid='ignore')
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-3, 3, 60)
rho = np.cosh(x)
v = np.linspace(0, 2*np.pi, 60)
X, V = np.meshgrid(x, v)
Y = np.cosh(X) * np.cos(V)
Z = np.cosh(X) * np.sin(V)
ax.set_xlabel('eje X')
ax.set_ylabel('eje Y')
ax.set_zlabel('eje Z')
ax.plot_surface(X, Y, Z, cmap='YlGnBu_r')
plt.plot(x, rho, 'or') #Muestra la curva que se va a rotar
plt.show()
The result:
I was inspired by this answer by #James to see how griddata and map_coordinates might be used. In the examples below I'm showing 2D data, but my interest is in 3D. I noticed that griddata only provides splines for 1D and 2D, and is limited to linear interpolation for 3D and higher (probably for very good reasons). However, map_coordinates seems to be fine with 3D using higher order (smoother than piece-wise linear) interpolation.
My primary question: if I have random, unstructured data (where I can not use map_coordinates) in 3D, is there some way to get smoother than piece-wise linear interpolation within the NumPy SciPy universe, or at least nearby?
My secondary question: is spline for 3D not available in griddata because it is difficult or tedious to implement, or is there a fundamental difficulty?
The images and horrible python below show my current understanding of how griddata and map_coordinates can or can't be used. Interpolation is done along the thick black line.
STRUCTURED DATA:
UNSTRUCTURED DATA:
Horrible python:
import numpy as np
import matplotlib.pyplot as plt
def g(x, y):
return np.exp(-((x-1.0)**2 + (y-1.0)**2))
def findit(x, X): # or could use some 1D interpolation
fraction = (x - X[0]) / (X[-1]-X[0])
return fraction * float(X.shape[0]-1)
nth, nr = 12, 11
theta_min, theta_max = 0.2, 1.3
r_min, r_max = 0.7, 2.0
theta = np.linspace(theta_min, theta_max, nth)
r = np.linspace(r_min, r_max, nr)
R, TH = np.meshgrid(r, theta)
Xp, Yp = R*np.cos(TH), R*np.sin(TH)
array = g(Xp, Yp)
x, y = np.linspace(0.0, 2.0, 200), np.linspace(0.0, 2.0, 200)
X, Y = np.meshgrid(x, y)
blob = g(X, Y)
xtest = np.linspace(0.25, 1.75, 40)
ytest = np.zeros_like(xtest) + 0.75
rtest = np.sqrt(xtest**2 + ytest**2)
thetatest = np.arctan2(xtest, ytest)
ir = findit(rtest, r)
it = findit(thetatest, theta)
plt.figure()
plt.subplot(2,1,1)
plt.scatter(100.0*Xp.flatten(), 100.0*Yp.flatten())
plt.plot(100.0*xtest, 100.0*ytest, '-k', linewidth=3)
plt.hold
plt.imshow(blob, origin='lower', cmap='gray')
plt.text(5, 5, "don't use jet!", color='white')
exact = g(xtest, ytest)
import scipy.ndimage.interpolation as spndint
ndint0 = spndint.map_coordinates(array, [it, ir], order=0)
ndint1 = spndint.map_coordinates(array, [it, ir], order=1)
ndint2 = spndint.map_coordinates(array, [it, ir], order=2)
import scipy.interpolate as spint
points = np.vstack((Xp.flatten(), Yp.flatten())).T # could use np.array(zip(...))
grid_x = xtest
grid_y = np.array([0.75])
g0 = spint.griddata(points, array.flatten(), (grid_x, grid_y), method='nearest')
g1 = spint.griddata(points, array.flatten(), (grid_x, grid_y), method='linear')
g2 = spint.griddata(points, array.flatten(), (grid_x, grid_y), method='cubic')
plt.subplot(4,2,5)
plt.plot(exact, 'or')
#plt.plot(ndint0)
plt.plot(ndint1)
plt.plot(ndint2)
plt.title("map_coordinates")
plt.subplot(4,2,6)
plt.plot(exact, 'or')
#plt.plot(g0)
plt.plot(g1)
plt.plot(g2)
plt.title("griddata")
plt.subplot(4,2,7)
#plt.plot(ndint0 - exact)
plt.plot(ndint1 - exact)
plt.plot(ndint2 - exact)
plt.title("error map_coordinates")
plt.subplot(4,2,8)
#plt.plot(g0 - exact)
plt.plot(g1 - exact)
plt.plot(g2 - exact)
plt.title("error griddata")
plt.show()
seed_points_rand = 2.0 * np.random.random((400, 2))
rr = np.sqrt((seed_points_rand**2).sum(axis=-1))
thth = np.arctan2(seed_points_rand[...,1], seed_points_rand[...,0])
isinside = (rr>r_min) * (rr<r_max) * (thth>theta_min) * (thth<theta_max)
points_rand = seed_points_rand[isinside]
Xprand, Yprand = points_rand.T # unpack
array_rand = g(Xprand, Yprand)
grid_x = xtest
grid_y = np.array([0.75])
plt.figure()
plt.subplot(2,1,1)
plt.scatter(100.0*Xprand.flatten(), 100.0*Yprand.flatten())
plt.plot(100.0*xtest, 100.0*ytest, '-k', linewidth=3)
plt.hold
plt.imshow(blob, origin='lower', cmap='gray')
plt.text(5, 5, "don't use jet!", color='white')
g0rand = spint.griddata(points_rand, array_rand.flatten(), (grid_x, grid_y), method='nearest')
g1rand = spint.griddata(points_rand, array_rand.flatten(), (grid_x, grid_y), method='linear')
g2rand = spint.griddata(points_rand, array_rand.flatten(), (grid_x, grid_y), method='cubic')
plt.subplot(4,2,6)
plt.plot(exact, 'or')
#plt.plot(g0rand)
plt.plot(g1rand)
plt.plot(g2rand)
plt.title("griddata")
plt.subplot(4,2,8)
#plt.plot(g0rand - exact)
plt.plot(g1rand - exact)
plt.plot(g2rand - exact)
plt.title("error griddata")
plt.show()
Good question! (and nice plots!)
For unstructured data, you'll want to switch back to functions meant for unstructured data. griddata is one option, but uses triangulation with linear interpolation in between. This leads to "hard" edges at triangle boundaries.
Splines are radial basis functions. In scipy terms, you want scipy.interpolate.Rbf. I'd recommend using function="linear" or function="thin_plate" over cubic splines, but cubic is available as well. (Cubic splines will exacerbate problems with "overshooting" compared to linear or thin-plate splines.)
One caveat is that this particular implementation of radial basis functions will always use all points in your dataset. This is the most accurate and smooth approach, but it scales poorly as the number of input observation points increases. There are several ways around this, but things will get more complex. I'll leave that for another question.
At any rate, here's a simplified example. We'll generate random data and then interpolate it at points that are on a regular grid. (Note that the input is not on a regular grid, and the interpolated points don't need to be either.)
import numpy as np
import scipy.interpolate
import matplotlib.pyplot as plt
np.random.seed(1977)
x, y, z = np.random.random((3, 10))
interp = scipy.interpolate.Rbf(x, y, z, function='thin_plate')
yi, xi = np.mgrid[0:1:100j, 0:1:100j]
zi = interp(xi, yi)
plt.plot(x, y, 'ko')
plt.imshow(zi, extent=[0, 1, 1, 0], cmap='gist_earth')
plt.colorbar()
plt.show()
Choice of spline type
I chose "thin_plate" as the type of spline. Our input observations points range from 0 to 1 (they're created by np.random.random). Notice that our interpolated values go slightly above 1 and well below zero. This is "overshooting".
Linear splines will completely avoid overshooting, but you'll wind up with "bullseye" patterns (nowhere near as severe as with IDW methods, though). For example, here's the exact same data interpolated with a linear radial basis function. Notice that our interpolated values never go above 1 or below 0:
Higher order splines will make trends in the data more continuous but will overshoot more. The default "multiquadric" is fairly similar to a thin-plate spline, but will make things a bit more continuous and overshoot a bit worse:
However, as you go to even higher order splines such as "cubic" (third order):
and "quintic" (fifth order)
You can really wind up with unreasonable results as soon as you move even slightly beyond your input data.
At any rate, here's a simple example to compare different radial basis functions on random data:
import numpy as np
import scipy.interpolate
import matplotlib.pyplot as plt
np.random.seed(1977)
x, y, z = np.random.random((3, 10))
yi, xi = np.mgrid[0:1:100j, 0:1:100j]
interp_types = ['multiquadric', 'inverse', 'gaussian', 'linear', 'cubic',
'quintic', 'thin_plate']
for kind in interp_types:
interp = scipy.interpolate.Rbf(x, y, z, function=kind)
zi = interp(xi, yi)
fig, ax = plt.subplots()
ax.plot(x, y, 'ko')
im = ax.imshow(zi, extent=[0, 1, 1, 0], cmap='gist_earth')
fig.colorbar(im)
ax.set(title=kind)
fig.savefig(kind + '.png', dpi=80)
plt.show()
I have ran into a problem relating to the drawing of the Ellipsoid.
The ellipsoid that I am drawing to draw is the following:
x**2/16 + y**2/16 + z**2/16 = 1.
So I saw a lot of references relating to calculating and plotting of an Ellipse void and in multiple questions a cartesian to spherical or vice versa calculation was mentioned.
Ran into a website that had a calculator for it, but I had no idea on how to successfully perform this calculation. Also I am not sure as to what the linspaces should be set to. Have seen the ones that I have there as defaults, but as I got no previous experience with these libraries, I really don't know what to expect from it.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=plt.figaspect(1)) # Square figure
ax = fig.add_subplot(111, projection='3d')
multip = (1, 1, 1)
# Radii corresponding to the coefficients:
rx, ry, rz = 1/np.sqrt(multip)
# Spherical Angles
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
# Cartesian coordinates
#Lots of uncertainty.
#x =
#y =
#z =
# Plot:
ax.plot_surface(x, y, z, rstride=4, cstride=4, color='b')
# Axis modifications
max_radius = max(rx, ry, rz)
for axis in 'xyz':
getattr(ax, 'set_{}lim'.format(axis))((-max_radius, max_radius))
plt.show()
Your ellipsoid is not just an ellipsoid, it's a sphere.
Notice that if you use the substitution formulas written below for x, y and z, you'll get an identity. It is in general easier to plot such a surface of revolution in a different coordinate system (spherical in this case), rather than attempting to solve an implicit equation (which in most plotting programs ends up jagged, unless you take some countermeasures).
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
phi = np.linspace(0,2*np.pi, 256).reshape(256, 1) # the angle of the projection in the xy-plane
theta = np.linspace(0, np.pi, 256).reshape(-1, 256) # the angle from the polar axis, ie the polar angle
radius = 4
# Transformation formulae for a spherical coordinate system.
x = radius*np.sin(theta)*np.cos(phi)
y = radius*np.sin(theta)*np.sin(phi)
z = radius*np.cos(theta)
fig = plt.figure(figsize=plt.figaspect(1)) # Square figure
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, color='b')
I want to create some plots of the farfield of electromagnetic scattering processes.
To do this, I calculated values θ, φ and r. The coordinates θ and φ create a regular grid on the unitsphere so I can use plot_Surface (found here) with conversion to cartesian coordinates.
My problem is now, that I need a way to color the surface with respect to the radius r and not height z, which seems to be the default.
Is there a way, to change this dependency?
I don't know how you're getting on, so maybe you've solved it. But, based on the link from Paul's comment, you could do something like this. We pass the color values we want using the facecolor argument of plot_surface.
(I've modified the surface3d demo from the matplotlib docs)
EDIT: As Stefan noted in his comment, my answer can be simplified to:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
xlen = len(X)
Y = np.arange(-5, 5, 0.25)
ylen = len(Y)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
maxR = np.amax(R)
Z = np.sin(R)
# Note that the R values must still be normalized.
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=cm.jet(R/maxR),
linewidth=0)
plt.show()
And (the end of) my needlessly complicated original version, using the same code as above though omitting the matplotlib.cm import,
# We will store (R, G, B, alpha)
colorshape = R.shape + (4,)
colors = np.empty( colorshape )
for y in range(ylen):
for x in range(xlen):
# Normalize the radial value.
# 'jet' could be any of the built-in colormaps (or your own).
colors[x, y] = plt.cm.jet(R[x, y] / maxR )
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=colors,
linewidth=0)
plt.show()