plotting a 3d-vector field with colors in dependence of the z component of the vectors - python-3.x

This is my first question and I hope I can describe my issue properly.
I tried to write down a minimal example. My goal is to get a nice plot of a vector field in the xy plane (so just one layer, but a 3d view) where the colors of my arrows should be completely red (blue) if they are pointing completely in the positive (negative) z-direction and gray if they are located in the xy plane. (Slightly red resp. red if they have some positive resp. negative z component etc - so I thought about a 'coolwarm' colormap. But I do not really know how to tho this. I tried to solve my problem with this question and the answers Adding colors to a 3d quiver plot in matplotlib and with the way I am adding my color bars to pcolormesh-plots where it is working fine.
, but I didn't really manage to do it properly, as you can see here:
plot obtained from my code
I do not really understand some of the code they used there and it would be nice if someone could help me with that :)
I do not understand what this part does q.set_array(np.linspace(-1,1,3)) and why I need q.set_edgecolor(c) and q.set_facecolor(c).
Besides I am
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import MaxNLocator
# Make the grid
x, y, z = np.meshgrid(np.arange(-0.8, 1, 0.2),
np.arange(-0.8, 1, 0.2),
np.arange(0.0, 0.6, 0.5))
# Make the direction data for the arrows
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
w = 0.2 + np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) * np.sin(np.pi * z)
#define colorbar like I usually do it for 2d density plot etc where it works
cmap = 'coolwarm'
cm = plt.get_cmap(cmap)
plot_min = -1.
plot_max = 1.
levels = MaxNLocator(nbins=100).tick_values(plot_min, plot_max)
norm = BoundaryNorm(levels, ncolors=cm.N, clip=True)
# Color by z-component of vectors (u,v,w) angle
c = w
# Flatten and normalize
c = (c.ravel() - c.min()) / c.ptp()
# Repeat for each body line and two head lines
c = np.concatenate((c, np.repeat(c, 2)))
# Colormap
c = getattr(plt.cm, cmap)(c)
fig = plt.figure(figsize=(10,7))
ax = fig.gca(projection='3d')
q = ax.quiver(x, y, z, u, v, w, colors=c, cmap = cmap, length=0.1, normalize=norm)
q.set_array(np.linspace(-1,1,3))
cbar = fig.colorbar(q, ticks=[-1, 0, 1], fraction=0.015)
cbar.ax.set_yticklabels(['-1', '0', '1'])
cbar.ax.tick_params(labelsize=15)
q.set_edgecolor(c)
q.set_facecolor(c)
#ax.set_zlim(-0.4, 0.4)
ax.view_init(azim=90, elev=20)
ax.grid(False)
plt.axis('off')
plt.show()
if this would work, it would be super cool!
Is there a way to make the arrows look nicer? It would be perfect if the arrows could look like the ones in Mathematica-plots like this:
example from Mathematica
Thank you a lot in advance!

"Tube" Arrows in Python
I found this awesome post. It was exactly the way I want my arrows to look like in the end :)

Related

Find out if point is part of curve (spline, splipy)

I have some coordinates of a 3D point curve through which I lay a spline like so:
from splipy import curve_factory
pts = [...] #3D coordinate points
curve = curve_factory.curve(pts)
I know that I can get a point in 3D along the curve by evaluating it after a certain length:
point_on_curve = curve.evaluate(t)
print(point_on_curve) #outputs coordinates: (x y z)
Is it however somehow possible to do it the other way round? Is there a function/method that can tell me if a certain point is part of the curve? Or if its almost part of the curve? Something like:
curve.func(point) #output: True
or
curve.func(point) #output: distance to curve 0.0001 --> also part of curve
Thanks!
I've found this script by ventusff that performs an optimization to find the value of the parameter that you call t (in the script is u) which gives the point on the spline closest to the external point.
I report below the code with some changes to make it clearer for you. I've defined a tolerance equal to 0.001.
The selection of the optimization solver and of its parameter values requires a little bit of study. I do not have enough time now for doing that, but you can try to experiment a little bit.
In this case SciPy is used for spline generation and evaluation, but you can easily replace it with splipy. The optimization is the interesting part performed using SciPy.
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import splprep, splev
from scipy.spatial.distance import euclidean
from scipy.optimize import fmin_bfgs
points_count = 40
phi = np.linspace(0, 2. * np.pi, points_count)
k = np.linspace(0, 2, points_count)
r = 0.5 + np.cos(phi)
x, y, z = r * np.cos(phi), r * np.sin(phi), k
tck, u = splprep([x, y, z], s=1)
points = splev(u, tck)
idx = np.random.randint(low=0, high=40)
noise = np.random.normal(scale=0.01)
external_point = np.array([points[0][idx], points[1][idx], points[2][idx]]) + noise
def distance_to_point(u_):
s = splev(u_, tck)
return euclidean(external_point, [s[0][0], s[1][0], s[2][0]])
closest_u = fmin_bfgs(distance_to_point, x0=np.array([0.0]), gtol=1e-8)
closest_point = splev(closest_u, tck)
tol = 1e-3
if euclidean(external_point, [closest_point[0][0], closest_point[1][0], closest_point[2][0]]) < tol:
print("The point is very close to the spline.")
ax = plt.figure().add_subplot(projection='3d')
ax.plot(points[0], points[1], points[2], "r-", label="Spline")
ax.plot(external_point[0], external_point[1], external_point[2], "bo", label="External Point")
ax.plot(closest_point[0], closest_point[1], closest_point[2], "go", label="Closest Point")
plt.legend()
plt.show()
The script draws the plot below:
and prints the following output:
Current function value: 0.000941
Iterations: 5
Function evaluations: 75
Gradient evaluations: 32
The point is very close to the spline.

How to do color code the 3D scatter plot according to time of occurrence in an animated graph?

I have developed a code to create an animated scatter graph.
About the dataset, I have the X,Y,Z coordinate of each point and each event point are assigned a value (M) and each happened at a specific time (t).
I have the size of each point to be proportional to their value (i.e., M), now I want to add the color to each point so that it also shows the time of occurrence. I know I have to use .set_color(c) but c value expects a tuple value. I tried to normalize the values of the time to map the color from this post. However, there is something that I miss because the code is not working to color the points with related time. I would appreciate it if someone could share their experiences?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
from IPython.display import HTML # Animation on jupyter lab
from matplotlib.animation import PillowWriter # For GIF animation
#####Data Generation####
# Space Coordinate
X = np.random.random((100,)) * 255 * 2 - 255
Y = np.random.random((100,)) * 255 * 2 - 255
Z = np.random.random((100,)) * 255 * 2 - 255
# Magnitude of each point
# M = np.random.random((100,))*-1+0.5
M = np.random.randint(1,70, size=100)
# Time
t = np.sort(np.random.random((100,))*10)
#ID each point should be color coded. Moreover, each point belongs to a cluster `ID`
ID = np.sort(np.round([np.random.random((100,))*5]))
x = []
y = []
z = []
m = []
def update_lines(i):
# for i in range (df_IS["EASTING [m]"].size):
dx = X[i]
dy = Y[i]
dz = Z[i]
dm = M[i]
# text.set_text("{:d}: [{:.0f}] Mw[{:.2f}]".format(ID[i], t[i],ID[i])) # for debugging
x.append(dx)
y.append(dy)
z.append(dz)
m.append(dm)
graph._offsets3d = (x, y, z)
graph.set_sizes(m)
return graph,
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(111, projection="3d")
graph = ax.scatter(X, Y, Z, s=M, color='orange') # s argument here
text = fig.text(0, 1, "TEXT", va='top') # for debugging
ax.set_xlim3d(X.min(), X.max())
ax.set_ylim3d(Y.min(), Y.max())
ax.set_zlim3d(Z.min(), Z.max())
# Creating the Animation object
ani = animation.FuncAnimation(fig, update_lines, frames=100, interval=500, blit=False, repeat=False)
# plt.show()
ani.save('test3Dscatter.gif', writer='pillow')
plt.close()
HTML(ani.to_html5_video())
You need to change "Color" to "cmap" so that you are able to call set of colors, see below:
graph = ax.scatter(X, Y, Z, s=M, cmap='jet') #jet is similar to rainbow

Order of object in 3d plot - spiral

I plotted a spiral and a line that should go through the spiral. I am not able to set that the line is behind the front part of the spiral and in front of the back part of the spiral. I tried to use zorder but the line is either whole in front of the spiral or whole behind the spiral. Thank you
Code:
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve')
ax.plot([-1,-1], # x
[2,2], # y
[-2, 2], c='red')
plt.show()
For instance, here. The red line is in front of the spiral. If I set zorder it could be behind the spiral. How to set the line goes properly throught the spiral?
Note that matplotlib isn't fully 3D. In order to get enough speed for complex plots, 3D is simulated drawing everything back to front, with each element drawn in its entirety on a specific depth. If you need full 3D, packages such as mayavi are worth investigating.
In order to get the red line inside the spiral, using matplotlib, the following approach can be used:
draw the spiral
draw the red line
draw the spiral again, but only the part that would be in front of the line
Note that such an approach only works if you don't rotate the view too much and you don't use transparency.
Now, to draw only a part of a curve, the standard way uses numpy's masked arrays. But these don't seem to be respected by the 3D plot. The alternative is to set unwanted points to NaN.
To better demonstrates the approach, the code below draws the red line much wider and uses green for the part of the spiral in front of the line. For the real thing, the spiral and the partial spiral would use the same colors.
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve') # the full spiral
ax.plot([-1,-1], # x
[2,2], # y
[-2, 2], c='red', lw=10)
ym = np.copy(y)
ym[y > 0] = np.NaN
ax.plot(x, ym, z, color='lime') # partial spiral
plt.show()

Polar plot in Matplotlib by mapping into Cartesian coordinate

I have a variable (P) which is a function of angle (theta):
In this equation the K is a constant, theta_p is equal to zero and I is the modified Bessel function of the first kind (order 0) which is defined as:
Now, I want to plot the P versus theta for different values of constant K. First I calculated the parameter I and then plug it into the first equation to calculate P for different angles theta. I mapped it into a Cartesian coordinate by putting :
x = P*cos(theta)
y = P*sin(theta)
Here is my python implementation using matplotlib and scipy when the constant k=2.0:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import quad
def integrand(x, a, k):
return a*np.exp(k*np.cos(x))
theta = (np.arange(0, 362, 2))
theta_p = 0.0
X = []
Y = []
for i in range(len(theta)):
a = (1 / np.pi)
k = 2.0
Bessel = quad(integrand, 0, np.pi, args=(a, k))
I = list(Bessel)[0]
P = (1 / (np.pi * I)) * np.exp(k * np.cos(2 * (theta[i]*np.pi/180. - theta_p)))
x = P*np.cos(theta[i]*np.pi/180.)
y = P*np.sin(theta[i]*np.pi/180.)
X.append(x)
Y.append(y)
plt.plot(X,Y, linestyle='-', linewidth=3, color='red')
axes = plt.gca()
plt.show()
I should get a set of graphs like the below figure for different K values:
(Note that the distributions were plotted on a circle of unit 1 to ease visualization)
However it seems like the graphs produced by the above code are not similar to the above figure.
Any idea what is the issue with the above implementation?
Thanks in advance for your help.
Here is how it looks like (for k=2):
The reference for these formulas are the equation 5 and 6 that you could find here
You had a mistake in your formula.
Your formula gives the delta of your function above a unit circle. So in your function to get the plot you want, simply add 1 to it.
Here is what you want, with some tidied up python. ...note you can do the whole calculation of the 'P' values as a numpy vector line, you don't need to loop over the indicies. ...also you can just do a polar plot directly in matplotlib - you don't need to transform it into cartesian.
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import quad
theta = np.arange(0, 2*np.pi+0.1, 2*np.pi/100)
def integrand(x, a, k):
return a*np.exp(k*np.cos(x))
for k in np.arange(0, 5, 0.5):
a = (1 / np.pi)
Bessel = quad(integrand, 0, np.pi, args=(a, k))
I = Bessel[0]
P = 1 + (1/(np.pi * I)) * np.exp(k * np.cos(2 * theta))
plt.polar(theta, P)
plt.show()

Plotting all of a trigonometric function (x^2 + y^2 == 1) with matplotlib and python

As an exercise in learning Matplotlib and improving my math/coding I decided to try and plot a trigonometric function (x squared plus y squared equals one).
Trigonometric functions are also called "circular" functions but I am only producing half the circle.
#Attempt to plot equation x^2 + y^2 == 1
import numpy as np
import matplotlib.pyplot as plt
import math
x = np.linspace(-1, 1, 21) #generate np.array of X values -1 to 1 in 0.1 increments
x_sq = [i**2 for i in x]
y = [math.sqrt(1-(math.pow(i, 2))) for i in x] #calculate y for each value in x
y_sq = [i**2 for i in y]
#Print for debugging / sanity check
for i,j in zip(x_sq, y_sq):
print('x: {:1.4f} y: {:1.4f} x^2: {:1.4f} y^2: {:1.4f} x^2 + Y^2 = {:1.4f}'.format(math.sqrt(i), math.sqrt(j), i, j, i+j))
#Format how the chart displays
plt.figure(figsize=(6, 4))
plt.axhline(y=0, color='y')
plt.axvline(x=0, color='y')
plt.grid()
plt.plot(x, y, 'rx')
plt.show()
I want to plot the full circle. My code only produces the positive y values and I want to plot the full circle.
Here is how the full plot should look. I used Wolfram Alpha to generate it.
Ideally I don't want solutions where the lifting is done for me such as using matplotlib.pyplot.contour. As a learning exercise, I want to "see the working" so to speak. Namely I ideally want to generate all the values and plot them "manually".
The only method I can think of is to re-arrange the equation and generate a set of negative y values with calculated x values then plot them separately. I am sure there is a better way to achieve the outcome and I am sure one of the gurus on Stack Overflow will know what those options are.
Any help will be gratefully received. :-)
The equation x**2 + y**2 = 1 describes a circle with radius 1 around the origin.
But suppose you wouldn't know this already, you can still try to write this equation in polar coordinates,
x = r*cos(phi)
y = r*sin(phi)
(r*cos(phi))**2 + (r*sin(phi))**2 == 1
r**2*(cos(phi)**2 + sin(phi)**2) == 1
Due to the trigonometric identity cos(phi)**2 + sin(phi)**2 == 1 this reduces to
r**2 == 1
and since r should be real,
r == 1
(for any phi).
Plugging this into python:
import numpy as np
import matplotlib.pyplot as plt
phi = np.linspace(0, 2*np.pi, 200)
r = 1
x = r*np.cos(phi)
y = r*np.sin(phi)
plt.plot(x,y)
plt.axis("equal")
plt.show()
This happens because the square root returns only the positive value, so you need to take those values and turn them into negative values.
You can do something like this:
import numpy as np
import matplotlib.pyplot as plt
r = 1 # radius
x = np.linspace(-r, r, 1000)
y = np.sqrt(r-x**2)
plt.figure(figsize=(5,5), dpi=100) # figsize=(n,n), n needs to be equal so the image doesn't flatten out
plt.grid(linestyle='-', linewidth=2)
plt.plot(x, y, color='g')
plt.plot(x, -y, color='r')
plt.legend(['Positive y', 'Negative y'], loc='lower right')
plt.axhline(y=0, color='b')
plt.axvline(x=0, color='b')
plt.show()
And that should return this:
PLOT

Resources