I have programmed plt.quiver(x,y,u,v,color), where there are arrows that start at x,y and the direction is determined by u,v. My question is how can I know exactly where the arrow ends?
In general, the arrows are of length length as described in the Quiver documentation and are auto-calculated by the matplotlib. I don't know which kwarg may help to return the length.
Another approach could be to define the exact position by scaling the plot with the help of scale=1, units='xy'.
import numpy as np
import matplotlib.pyplot as plt
# define arrow
x = np.linspace(0,1,11)
y = np.linspace(1,0,11)
u = v = np.zeros((11,11))
u[5,5] = 0.3
v[5,5] = 0.3
plt.quiver(x, y, u, v, scale=1, units='xy')
plt.axis('equal')
plt.xlim(0,1)
plt.ylim(0,1)
plt.show()
Color arrows that end at a specific point
Applying the principles above could result in:
import numpy as np
import matplotlib.pyplot as plt
import random
n = 11
cx = 0.7 #x-position of specific end point
cy = 0.5 #y-position of specific end point
# define random arrows
x = np.linspace(0,1,n)
y = np.linspace(0,1,n)
u = np.zeros((n,n))
v = np.zeros((n,n))
# color everything black
colors = [(0, 0, 0)]*n*n
# make sure at least some points end at the same point
u[5][5] = 0.2
u[5][8] = -0.1
v[2][7] = 0.3
# search for specific point
for i in range(len(x)):
for j in range(len(y)):
endPosX = x[i] + u[j][i]
endPosY = y[j] + v[j][i]
if np.isclose(endPosX, cx) and np.isclose(endPosY, cy):
#found specific point -> color it red
colors[j*n+i] = (1,0,0)
# plot data
plt.quiver(x, y, u, v, color=colors, scale=1, units='xy')
plt.axis('equal')
plt.show()
Related
This question already has answers here:
Plot a point on a line closest to a point
(1 answer)
Python: point on a line closest to third point
(3 answers)
Python: Closest Point to a line
(1 answer)
Closed 3 months ago.
This post was edited and submitted for review 3 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have the problem of finding the point which is closest to a line from an array of x- and y-data.
The line is semi-infinite originating from the origin at (0,0) and running into the direction of a given angle.
The x,y data of the points are given in relation to the origin.
How do I find the closest point (and its distance) to the line in line direction (not opposite)?
This is an example of the data I have:
import numpy as np
import matplotlib.pyplot as plt
def main():
depth = np.random.random((100))*20+50
angle = np.linspace(0, 2*np.pi, 100)
x,y = depth2xy(depth, angle)
line = np.random.random_sample()*2*np.pi
# fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
plt.scatter(x, y)
plt.plot([0,100*np.cos(line)], [0, 100*np.sin(line)], markersize=10, color = "r")
plt.show()
def depth2xy(depth, angle):
x, y = np.zeros(len(depth)), np.zeros(len(depth))
for i in range(len(depth)):
x[i] = depth[i]*np.cos(angle[i])
y[i] = depth[i]*np.sin(angle[i])
return x,y
if __name__ == "__main__": main()
I could try a brute force approach, iterating over different distances along the line to find the ultimate smallest distance.
But as time efficiency is critical my case and the algorithm would not perform as well as I think it could, I would rather try an analytical approach.
I also thought about scipy.spatial.distance, but I am not sure how this would work for a line.
Your assigned line passes through the origin, its parametric equation is
x = u cos(a)
y = u sin(a)
and you can see the parameter u is simply the (oriented) distance beteween the origin and a point on the assigned line.
Now, consider a point of coordinates X and Y, a line perpendicular to the assigned one has the parametric equation
x = X - v sin(a)
y = Y + v cos(a)
and again, the parameter v is simply the (oriented) distance between (X, Y) and a point on a line passing per (X, Y) and perpendicular to the assigned one.
The intersection is given by the equation
X = u cos(a) + v sin(a)
Y = u sin(a) - v cos(a)
you can check by inspection that the solution of the system is
u = X cos(a) + Y sin(a)
v = X sin(a) - Y cos(a)
The distance of the point (X, Y) from the assigned line is hence
d = | X sin(a) - Y cos(a) |
A Python Implementation
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(20221126)
X = 2*np.random.random(32)-1
Y = 2*np.random.random(32)-1
fig, ax = plt.subplots()
ax.set_xlim((-1.2, 1.2))
ax.set_ylim((-1.2, 1.2))
ax.grid(1)
ax.set_aspect(1)
ax.scatter(X, Y, s=80, ec='k', color='y')
a = 2*np.random.random()*np.pi
s, c = np.sin(a), np.cos(a)
plt.plot((0, c), (0, s), color='k')
plt.plot((-s, s), (c, -c), color='r')
# strike out "bad" points
bad = X*c+Y*s<0
plt.scatter(X[bad], Y[bad], marker='x', color='k')
# consider only good (i.e., not bad) points
Xg, Yg = X[~bad], Y[~bad]
# compute all distances (but for good points only)
d = np.abs(Xg*s-Yg*c)
# find the nearest point and hilight it
imin = np.argmin(d)
plt.scatter(Xg[imin], Yg[imin], ec='k', color='r')
plt.show()
An OVERDONE Example
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(20221126)
X = 2*np.random.random(32)-1
Y = 2*np.random.random(32)-1
fig, axs = plt.subplots(2, 4, figsize=(10,5), layout='constrained')
for ax, a in zip(axs.flat,
(2.8, 1.8, 1.4, 0.2,
3.4, 4.5, 4.9, 6.0)):
ax.set_xlim((-1.2, 1.2))
ax.set_xticks((-1, -0.5, 0, 0.5, 1.0))
ax.set_ylim((-1.2, 1.2))
ax.grid(1)
ax.set_aspect(1)
ax.set_title('$\\alpha \\approx %d^o$'%round(np.rad2deg(a)))
ax.scatter(X, Y, s=80, ec='k', color='yellow')
s, c = np.sin(a), np.cos(a)
ax.arrow(0, 0, 1.2*c, 1.2*s, fc='k',
length_includes_head=True,
head_width=0.08, head_length=0.1)
# divide the drawing surface in two semiplanes
if abs(c)>abs(s):
if c>0:
ax.plot((1.2*s, -1.2*s), (-1.2, 1.2))
else:
ax.plot((-1.2*s, 1.2*s), (-1.2, 1.2))
elif abs(s)>=abs(c):
if s>0:
ax.plot((-1.2, 1.2), (1.2*c, -1.2*c))
else:
ax.plot((-1.2, 1.2), (-1.2*c, 1.2*c))
# strike out "bad" points
bad = X*c+Y*s<0
ax.scatter(X[bad], Y[bad], marker='x', color='k')
# consider only good (i.e., not bad) points
Xg, Yg = X[~bad], Y[~bad]
# compute all distances (but for good points only)
d = np.abs(Xg*s-Yg*c)
# find the nearest point and hilight it
imin = np.argmin(d)
ax.scatter(Xg[imin], Yg[imin], s=80, ec='k', color='yellow')
ax.scatter(Xg[imin], Yg[imin], s= 10, color='k', alpha=1.0)
plt.show()
Let P be a point from your know data set. Let Q be the projection of this point on the line. You can use an analytic approach to determine the exact location of Q:
OQ is the segment from the origin to the Q point. It is aligned to the line.
PQ is the distance of the point P to the line.
from geometry, the dot product between QP and OQ is zero (the two segments are orthogonal to each other). From this equation we can compute the point Q.
After that, you simply compute all distances and find the shortest one.
I'm going to use SymPy for the analytical part, Numpy for the numerical part and Matplotlib for plotting:
from sympy import *
import numpy as np
import matplotlib.pyplot as plt
xq, xp, yq, yp, m = symbols("x_Q, x_P, y_Q, y_P, m")
A = Matrix([xq - xp, yq - yp])
B = Matrix([xq, yq])
# this equations contains two unkowns: xq, yq
eq = A.dot(B)
# but we know the line equation: yq = m * xq, so we substitute it into
# eq and solve for xq
xq_expr = solve(eq.subs(yq, m * xq), xq)[1]
print(xq_expr)
# (m*y_P + x_P)/(m**2 + 1)
# generate data
mv = -0.5
xp_vals = np.random.uniform(2, 10, 30)
yp_vals = np.random.uniform(2, 10, 30)
# convert the symbolic expression to a numerical function
f = lambdify([m, xp, yp], xq_expr)
# compute the projections on the line
xq_vals = f(mv, xp_vals, yp_vals)
yq_vals = mv * xq_vals
# compute the distance
d = np.sqrt((xp_vals - xq_vals)**2 + (yp_vals - yq_vals)**2)
# find the index of the shortest distance
idx = d.argmin()
fig, ax = plt.subplots()
xline = np.linspace(0, 10)
yline = mv * xline
ax.plot(xline, yline, "k:", label="line")
ax.scatter(xq_vals, yq_vals, label="Q", marker=".")
ax.scatter(xp_vals, yp_vals, label="P", marker="*")
ax.plot([xp_vals[idx], xq_vals[idx]], [yp_vals[idx], yq_vals[idx]], "r", label="min distance")
ax.set_aspect("equal")
ax.legend()
plt.show()
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
I'm trying to use quiver and I am struggling to understand how quiver is interpreting the input arrays U, V in its documentation https://matplotlib.org/3.5.0/api/_as_gen/matplotlib.pyplot.quiver.html
With the following code I expect the bottom-right arrow (element [2,0]) to point to the left. However, it's the top-left as in the attached figure.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0.3,.9,3)
y = x
nb_points = x.size
X,Y = np.meshgrid(x,y)
U = np.ones((nb_points, nb_points))
V = np.ones((nb_points, nb_points))
U[2,0] = -3
M = (np.hypot(U, V))
fig,ax = plt.subplots(1,1, figsize=(4,4))
ax.quiver(X, Y, U, V, M, units='width', pivot='middle')
ax.set_xlim(.2,1)
ax.set_ylim(.2,1)
Can anyone explain what's going on?
I completely misinterpreted the meaning of U,V's indeces
U[2,0] is supposed to be interpreted as the direction of X[2,0],Y[2,0] = (.3,.9), which is the top-left point
For my report, I'm creating a special color plot in jupyter notebook. There are two parameters, x and y.
import numpy as np
x = np.arange(-1,1,0.1)
y = np.arange(1,11,1)
with which I compute a third quantity. Here is an example to demonstrate the concept:
values = []
for i in range(len(y)) :
z = y[i] * x**3
# in my case the value z represents phases of oscillators
# so I will transform the computed values to the intervall [0,2pi)
values.append(z)
values = np.array(values) % 2*np.pi
I'm plotting y vs x. For each y = 1,2,3,4... there will be a horizontal line with total length two. For example: The coordinate (0.5,8) stands for a single point on line 8 at position x = 0.5 and z(0.5,8) is its associated value.
Now I want to represent each point on all ten lines with a unique color that is determined by z(x,y). Since z(x,y) takes only values in [0,2pi) I need a color scheme that starts at zero (for example z=0 corresponds to blue). For increasing z the color continuously changes and in the end at 2pi it takes the same color again (so at z ~ 2pi it becomes blue again).
Does someone know how this can be done in python?
The kind of structure for x, y and z you need, is easier using a meshgrid. Also, to have a lot of x-values between -1 and 1, np.linspace(-1,1,N) divides the range in N even intervals.
Using meshgrid, z can be calculated in one line using numpy's vectorization. This runs much faster.
To set a repeating color, a cyclic colormap such as hsv can be used. There the last color is the same as the starting color.
import numpy as np
from matplotlib import pyplot as plt
x, y = np.meshgrid(np.linspace(-1,1,100), np.arange(1,11,1))
z = (y * x**3) % 2*np.pi
plt.scatter(x, y, c=z, s=6, cmap='hsv')
plt.yticks(range(1,11))
plt.show()
Alternatively, a symmetric colormap could be built taken the colors from and existing map and combining them with the same colors in reverse order.
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.colors as mcolors
colors_orig = plt.cm.viridis_r(np.linspace(0, 1, 128))
# combine the colors with the reversed array and build a new colormap
colors = np.vstack((colors_orig, colors_orig[::-1]))
symcmap = mcolors.LinearSegmentedColormap.from_list('symcmap', colors)
x, y = np.meshgrid(np.linspace(-1,1,100), np.arange(1,11,1))
z = (y * x**3) % 2*np.pi
plt.scatter(x, y, c=z, s=6, cmap=symcmap)
plt.yticks(range(1,11))
plt.show()
Multicolored lines are somewhat more complicated than just scatter plots. The docs have an example using LineCollections. Here is the adapted code. Note that the line segments are colored using their start point, so make sure there are enough x values. Also, the x and y limits aren't set automatically any more.
The code also adds a colorbar to illustrate how the colors map to the z values. Some interesting code from Jake VanderPlas shows how to create ticks for multiples of π.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
# code from Jake VanderPlas
def format_func(value, tick_number):
# find number of multiples of pi/2
N = int(np.round(2 * value / np.pi))
if N == 0:
return "0"
elif N == 1:
return r"$\pi/2$"
elif N == 2:
return r"$\pi$"
elif N % 2 > 0:
return r"${0}\pi/2$".format(N)
else:
return r"${0}\pi$".format(N // 2)
x = np.linspace(-1, 1, 500)
y_max = 10
# Create a continuous norm to map from data points to colors
norm = plt.Normalize(0, 2 * np.pi)
for y in range(1, y_max + 1):
z = (y * x ** 3) % 2 * np.pi
y_array = y * np.ones_like(x)
points = np.array([x, y_array]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, cmap='hsv', norm=norm)
lc.set_array(z) # Set the values used for colormapping
lc.set_linewidth(2)
line = plt.gca().add_collection(lc)
# plt.scatter(x, y_array, c=z, s=10, norm=norm, cmap='hsv')
cbar = plt.colorbar(line) # , ticks=[k*np.pi for k in np.arange(0, 2.001, 0.25)])
cbar.locator = plt.MultipleLocator(np.pi / 2)
cbar.minor_locator = plt.MultipleLocator(np.pi / 4)
cbar.formatter = plt.FuncFormatter(format_func)
cbar.ax.minorticks_on()
cbar.update_ticks()
plt.yticks(range(1, y_max + 1)) # one tick for every y
plt.xlim(x.min(), x.max()) # the LineCollection doesn't force the limits
plt.ylim(0.5, y_max + 0.5)
plt.show()
Problem:
I have an array terrain of shape (N+2,N+2,4). In the last dimension, we have either 1's or -1's, -1 if the arrow in that direction is pointing TO the point in question and 1 if the arrow points FROM the point in that direction. The order is north, south, east, west. So, 1,-1,1,1 would correspond to:
The code I'm trying to work with is the following. But that doesn't work since they are all plotted FROM the point. I couldn't find a way to easily plot from or to the point in the desired direction based on the value of the position in terrain[i,j] that corresponds to the direction in question.
import numpy as np
import matplotlib.pyplot as plt
N=3
terrain = np.zeros((N+2,N+2,4))
for i in range(1, N+1):
for j in range(1, N+1):
terrain[i,j] = np.random.choice([-1,1],4)
for i in range(N+2):
for j in range(N+2):
iterr.append([i,j])
iterr = np.array(iterr)
fig = plt.figure()
ax = fig.add_subplot(111)
plt.quiver(iterr[:,0], iterr[:,1], terrain[:,:,0].reshape(((N+2)**2,1)), np.zeros(((N+2)**2,1)), color='k')
plt.quiver(iterr[:,0], iterr[:,1], terrain[:,:,1].reshape(((N+2)**2,1)), np.zeros(((N+2)**2,1)), color='yellow')
plt.quiver(iterr[:,0], iterr[:,1], np.zeros(((N+2)**2,1)), terrain[:,:,2].reshape(((N+2)**2,1)), color='blue')
plt.quiver(iterr[:,0], iterr[:,1], np.zeros(((N+2)**2,1)), terrain[:,:,3].reshape(((N+2)**2,1)), color='red')
Probably plt.quiver(X, Y, U, V, C, **kw) is not the easiest method to use in your case. The quiver method will always plot an arrow vector (u,v) from (x,y).
It is probably easier to iterate over the data and use plt.arrow(x, y, dx, dy, **kwargs) to draw the arrows.
Using your data structures and plt.arrow I wrote this, where I skipped all the 0 values in terrain:
import numpy as np
import matplotlib.pyplot as plt
N=3
terrain = np.zeros((N+2,N+2,4))
for i in range(1, N+1):
for j in range(1, N+1):
terrain[i,j] = np.random.choice([-1,1],4)
iterr = []
for i in range(N+2):
for j in range(N+2):
iterr.append([i,j])
iterr = np.array(iterr)
#-------------------------------------------------
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
X = iterr[:,0]
Y = iterr[:,1]
cardinal_vectors = [(0,1), (0,-1), (1,0), (-1,0)] # north, south, east, west
cardinal_colors = ['black', 'green', 'blue', 'red']
# plot all points
plt.plot(X,Y,'k.')
for x, y, arrow_directions in zip(X, Y, terrain.reshape(((N+2)**2,4))):
for direction, vector, color in zip(arrow_directions,
cardinal_vectors,
cardinal_colors):
arrow_size = 0.5
head_size = 0.2
arrow_args = {'length_includes_head' : True,
'head_width' : head_size,
'head_length' : head_size,
'width' : 0.02,
'fc' : color,
'ec' : color}
dx = vector[0] * arrow_size
dy = vector[1] * arrow_size
if direction == 1:
# arrow FROM (x,y)
plt.arrow(x, y, dx, dy, **arrow_args)
if direction == -1:
# arrow TO (x,y)
plt.arrow(x + dx, y + dy, -dx, -dy, **arrow_args)
plt.show()