I am tasked with generating evenly (more or less) spaced points on concentric rings of an invisible circle. The function should take a list of radii, and number of points to plot for a given radius as arguments. For example for a radius of 0 it should plot 1 point at (0,0). For a circle of radius of 1, it should plot 10 points along the circumference of the circle, spaced out by an angle of 2pi/10. For a circle of radius 2, 20 points along the circumference, spaced out by an angle of 2pi/20.
The generator should take the following parameters:
n, r_max, m
and should generate rings of coordinate pairs at radii
r_i = i*r_max/n for i = 0,1,..,n.
Each ring should have n*i points uniformly distributed in θ where
n_i=1 for i=0; n_i = mi for i>0
When the function is called like this:
for r, t in genpolar.rtuniform(n=10, rmax=0.1, m=6):
plot(r * cos(t), r * sin(t), 'bo')
it should return a plot that looks like:
Here is what I've come up with so far:
def rtpairs(R, N):
R=[0.0,0.1,0.2]
N=[1,10,20]
r=[]
t=[]
for i in N:
theta=2*np.pi/i
t.append(theta)
for j in R:
j=j
r.append(j)
plt.plot(r*np.cos(t),r*np.sin(t), 'bo')
plt.show()
but I'm pretty sure there is a more efficient method using two for loops.
Many thanks
I figured it out. The code goes like this:
import numpy as np
import matplotlib.pyplot as plt
T = [1, 10, 20, 30, 40, 50, 60]
R = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
def rtpairs(r, n):
for i in range(len(r)):
for j in range(n[i]):
yield r[i], j*(2 * np.pi / n[i])
for r, t in rtpairs(R, T):
plt.plot(r * np.cos(t), r * np.sin(t), 'bo')
plt.show()
Here is one way to do this.
import numpy as np
import matplotlib.pyplot as plt
def circle_points(r, n):
circles = []
for r, n in zip(r, n):
t = np.linspace(0, 2*np.pi, n, endpoint=False)
x = r * np.cos(t)
y = r * np.sin(t)
circles.append(np.c_[x, y])
return circles
When you pass this function in the appropriate lists, one with the radius for each circle and the other with the desired number of points it returns a list of coordinate arrays, one for each circle.
r = [0, 0.1, 0.2]
n = [1, 10, 20]
circles = circle_points(r, n)
These can be plotted as follows.
fig, ax = plt.subplots()
for circle in circles:
ax.scatter(circle[:, 0], circle[:, 1])
ax.set_aspect('equal')
plt.show()
Here we see the result for more circles.
n = [1, 10, 20, 30, 40, 50, 60]
r = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
circles = circle_points(r, n)
fig, ax = plt.subplots()
for circle in circles:
ax.scatter(circle[:, 0], circle[:, 1])
ax.set_aspect('equal')
plt.show()
Here is a way you could achieve this.
def circles(c_list: List[int]):
g_d_list = [] # graph data list
for g in c_list:
# create length of circle list. In this instance
# i'm multiplying by 8 each time but could be any number.
lg = [g] * (8*g)
ang = 360/len(lg) # calculate the angle of each entry in circle list.
ang_list = []
for i in range(len(lg)+1):
ang_list.append(ang*i)
for i, c in enumerate(lg):
# calculate the x and y axis points or each circle. in this instance
# i'm expanding circles by multiples of ten but could be any number.
x_axis = 0 + (10*g) * math.cos(math.radians(ang_list[i+1]))
y_axis = 0 + (10*g) * math.sin(math.radians(ang_list[i+1]))
# tuple structure ((axis tuple), circle size, circle colour)
g_d_list.append(((x_axis, y_axis), 1, 'r'))
fig, ax = plt.subplots()
for c in range(len(g_d_list)):
circle = plt.Circle(g_d_list[c][0], radius=g_d_list[c][1], fc=g_d_list[c][2])
ax.add_patch(circle)
plt.axis('scaled')
plt.axis('off') # optional if you don't want to show axis
plt.show()
It produces a graph when provided with a list containing the number of circles you require. For example circles([1,2,3]) returns.
I don't know python but this formula should help.
int ringNumber = 0
int n = ringNumber-1
((n/n+1)*60)-60 = degrees between points(except for ring zero, which the point is the center
You do have to cycle through the entire circle for all radii, so your plot call is pretty much stuck with some M*N process.
The code details can be slightly improved. For starters, your R list already holds the values you want; there's no need to construct a new list with the same values. You can build the t list with a straightforward list comprehension.
Is this what you wanted?
N=[1,10,20]
t = [2*np.pi/i for i in N]
for R in N:
plt.plot(R*np.cos(t),R*np.sin(t), 'bo')
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()
So I have tried plotting points on a circle and did pretty good, but now I'm at a roadblock.
I have used an algorithm that generates a pair of random numbers which have a linear probability line, so the closer you get to the max value going from 0 it has the greater chance to choose that point
(in my case the max value is +-1 [the circle's diameter is 2]).
Now I want to use that algorithm to generate X amount of pairs which I can use to plot the points all at once, how do I do that?
(The random number algorithm comes from this Youtube video https://www.youtube.com/watch?v=4y_nmpv-9lI great video BTW)
my code:
import random
import matplotlib.pyplot as plt
def sum_dist():
theta = random.random() * 2 * math.pi
r = random.random() + random.random()
if r >= 1:
r = 2 - r
if r <= -1:
r = 2 + r
#The random number algorithm
print((r * math.cos(theta), r * math.sin(theta)))
#This is to check if the numbers generated and what are they
x = r * math.cos(theta)
y = r * math.sin(theta)
circle1 = plt.Circle((0, 0), 1, color='blue', fill = False)
fig, ax = plt.subplots()
ax.add_patch(circle1)
plt.plot([x], [y], 'ro')
#plotting a single point in the circle
plt.plot([0], [0], 'ro')
#This is to Identify the center of the circle
plt.axis([0, 0, 0, 0])
plt.show()
plt.show()
sum_dist()
I believe you want a scatter plot, like this:
import math
import random
import matplotlib.pyplot as plt
def sum_dist():
x = []
y = []
for _ in range(100):
theta = random.random() * 2 * math.pi
# r = random.random()
r = random.random() + random.random()
if r >= 1:
r = 2 - r
# This can't happen.
# if r <= -1:
# r = 2 + r
x.append( r * math.cos(theta) )
y.append( r * math.sin(theta) )
fig, ax = plt.subplots()
ax.set_aspect( 1 )
ax.add_artist( plt.Circle((0, 0), 1, ec='blue', fill = False) )
ax.axhline(y=0)
ax.axvline(x=0)
plt.axis( [-1,1,-1,1] )
plt.scatter(x, y, marker='o')
plt.plot([0], [0], 'ro')
plt.show()
sum_dist()
It's worth pointing out that numpy could do that all inline, without using a loop. It's also worth pointing out that adding two random numbers together does not make them more random. random.random() * 2 would be exactly the same.
All the manipulations of r don't make sense. You generate a random number from 0 to 2, then you mirror those between 1 and 2. Mirroring a uniform random sequence is pointless. It's still uniform.
I'm trying to create 10 points distributed on a circle, just like a watch but with 10 numbers instead of 12. and also be able to use the dots/points to plot lines between them.
This is the code I used to create a circle but can't figure out how to make them 10 points, and also how to use the points' coordinates in my code.
import matplotlib.pyplot as plt
import numpy as np
# T = testing
# myList = testing.digitcounter(18, 20)
def circle_points(r, n):
circles = []
for r, n in zip(r, n):
t = np.linspace(0, 2*np.pi, n)
x = r * np.cos(t)
y = r * np.sin(t)
circles.append(np.c_[x, y])
return circles
r = [0.15]
n = [15]
circles = circle_points(r, n)
fig, ax = plt.subplots()
for circle in circles:
ax.scatter(circle[:, 0], circle[:, 1])
ax.set_aspect('equal')
plt.show()
With t = np.linspace(0, 2*np.pi, 10, endpoint=False), you can create 10 angles equally distributed over a circle. The default endpoint=True would have the first and last angle coincide. To have the first point at the top, and going clockwise, interchange cos and sin in the formula.
To plot a continuous line between digits, you could use ax.plot(circle[digits, 0], circle[digits, 1]), with digits a numpy array of integers between 0 and 1. Note that this will contain a line of zero length when two subsequent digits would be equal.
import matplotlib.pyplot as plt
import numpy as np
def circle_points(r, n):
circles = []
for r, n in zip(r, n):
t = np.linspace(0, 2 * np.pi, n, endpoint=False)
x = r * np.sin(t)
y = r * np.cos(t)
circles.append(np.c_[x, y])
return circles
digits = np.random.randint(0, 10, 7) # 7 random digits between 0 and 9
circle10 = circle_points([0.15], [10])[0] # 10 points on a circle
circle10 = circle10[(np.arange(10) - 3) % 10, :] # with 3 at the top
fig, ax = plt.subplots()
ax.scatter(circle10[:, 0], circle10[:, 1], color='crimson')
ax.plot(circle10[digits, 0], circle10[digits, 1], color='dodgerblue', lw=3)
for i, (x, y) in enumerate(circle10):
ax.text(x * 1.1, y * 1.1, i, ha='center', va='center', color='crimson')
ax.set_aspect('equal')
ax.margins(x=0.1, y=0.1) # extra margins, because the text isn't taken into account for the default margins
ax.set_title("".join([f'{d}' for d in digits]), size=16)
ax.axis('off')
plt.show()
Similarly, a 17-pointed star could be drawn with:
N = 17
circle = circle_points([0.15], [N])[0]
for i in range(N):
ax.plot([circle[i, 0], circle[(i + 6) % N, 0]],
[circle[i, 1], circle[(i + 6) % N, 1]],
color='dodgerblue', lw=3)
I'm making a program to simulate the retrograde motion of mars visible from Earth.
So it's a plan view of Earth and Mars orbiting the sun
There is also a line going from Earth to Mars.
However, I need it to intersect the point that is Mars and keep going until it intersects the line x = 15
import math
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def _update_plot(i, fig, scat, l):
scat.set_offsets(([math.cos(math.radians(i))*5, math.sin(math.radians(i))*5], [math.cos(math.radians(i/2))*10, math.sin(math.radians(i/2))*10], [0, 0]))
l.set_data(([math.cos(math.radians(i))*5,math.cos(math.radians(i/2))*10],[math.sin(math.radians(i))*5,math.sin(math.radians(i/2))*10]))
return [scat,l]
fig = plt.figure()
x = [0]
y = [0]
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.grid(True, linestyle = '-', color = '0.10')
ax.set_xlim([-11, 11])
ax.set_ylim([-11, 11])
l, = plt.plot([],[], 'r--', zorder=1)
scat = plt.scatter(x, y, c = x, zorder=2)
scat.set_alpha(0.8)
anim = animation.FuncAnimation(fig, _update_plot, fargs = (fig, scat, l),
frames = 720, interval = 10)
plt.show()
I'm not sure I fully understand what you want to do, but I'll assume you want to extend the line that connects the two planets up to x = 15. In this case you can do the following:
Compute the Earth-Mars direction by subtracting their position and normalising the resulting vector. Take one of the planet as the starting point of the line.
Solve the first-order equation that gives you the distance you need to travel to get to the x = 15 axis.
Check whether the result is positive or negative. If it's positive then keep going, if it's negative we chose the wrong planet, as in order for the line to connect the two planes and then keep going towards x = 15 we need to take the other planet. We do so by inverting the direction and re-solving the first-order equation with the new conditions.
Find the y-coordinate of the point at which the line intersects the x = 15 axis
Draw a line from the planet to the intersection point on the x = 15 axis.
Something like this should do the trick:
def _update_plot(i, fig, scat, l):
angle = math.radians(i)
sun_pos = np.array([0., 0.])
earth_pos = np.array([math.cos(angle)*5, math.sin(angle)*5])
mars_pos = np.array([math.cos(angle / 2.) * 10, math.sin(angle / 2.) * 10])
# compute the unit vector that points from Earth to Mars
direction = mars_pos - earth_pos
direction /= np.sqrt(np.dot(direction, direction))
# find out where the line would intersect the x = 15 axis
start_from = earth_pos
alpha = (15 - start_from[0]) / direction[0]
# if alpha comes out to be negative we picked the "wrong" planet
if alpha < 0:
start_from = mars_pos
direction = -direction
alpha = (15 - start_from[0]) / direction[0]
y_line = start_from[1] + alpha * direction[1]
# draw the planets
scat.set_offsets((earth_pos, mars_pos, sun_pos))
# draw the line
l.set_data(([start_from[0], 15], [start_from[1], y_line]))
return [scat,l]
Suppose I have a circle x**2 + y**2 = 20.
Now I want to plot the circle with n_dots number of dots in the circles perimeter in a scatter plot. So I created the code like below:
n_dots = 200
x1 = np.random.uniform(-20, 20, n_dots//2)
y1_1 = (400 - x1**2)**.5
y1_2 = -(400 - x1**2)**.5
plt.figure(figsize=(8, 8))
plt.scatter(x1, y1_1, c = 'blue')
plt.scatter(x1, y1_2, c = 'blue')
plt.show()
But this shows the dots not uniformly distributed all the places in the circle. The output is :
So how to create a circle with dots in scatter plot where all the dots are uniformly distributed in the perimeter of the circle?
A simple way to plot evenly-spaced points along the perimeter of a circle begins with dividing the whole circle into equally small angles where the angles from circle's center to all points are obtained. Then, the coordinates (x,y) of each point can be computed. Here is the code that does the task:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(8, 8))
n_dots = 120 # set number of dots
angs = np.linspace(0, 2*np.pi, n_dots) # angles to the dots
cx, cy = (50, 20) # center of circle
xs, ys = [], [] # for coordinates of points to plot
ra = 20.0 # radius of circle
for ang in angs:
# compute (x,y) for each point
x = cx + ra*np.cos(ang)
y = cy + ra*np.sin(ang)
xs.append(x) # collect x
ys.append(y) # collect y
plt.scatter(xs, ys, c = 'red', s=5) # plot points
plt.show()
The resulting plot:
Alternately, numpy's broadcasting nature can be used and shortened the code:
import matplotlib.pyplot as plt
import numpy as np
fig=plt.figure(figsize=(8, 8))
n_dots = 120 # set number of dots
angs = np.linspace(0, 2*np.pi, n_dots) # angles to the dots
cx, cy = (50, 20) # center of circle
ra = 20.0 # radius of circle
# with numpy's broadcasting feature...
# no need to do loop computation as in above version
xs = cx + ra*np.cos(angs)
ys = cy + ra*np.sin(angs)
plt.scatter(xs, ys, c = 'red', s=5) # plot points
plt.show()
for a very generalized answer that also works in 2D:
import numpy as np
import matplotlib.pyplot as plt
def u_sphere_pts(dim, N):
"""
uniform distribution points on hypersphere
from uniform distribution in n-D (<-1, +1>) hypercube,
clipped by unit 2 norm to get the points inside the insphere,
normalize selected points to lie on surface of unit radius hypersphere
"""
# uniform points in hypercube
u_pts = np.random.uniform(low=-1.0, high=1.0, size=(dim, N))
# n dimensional 2 norm squared
norm2sq = (u_pts**2).sum(axis=0)
# mask of points where 2 norm squared < 1.0
in_mask = np.less(norm2sq, np.ones(N))
# use mask to select points, norms inside unit hypersphere
in_pts = np.compress(in_mask, u_pts, axis=1)
in_norm2 = np.sqrt(np.compress(in_mask, norm2sq)) # only sqrt selected
# return normalized points, equivalently, projected to hypersphere surface
return in_pts/in_norm2
# show some 2D "sphere" points
N = 1000
dim = 2
fig2, ax2 = plt.subplots()
ax2.scatter(*u_sphere_pts(dim, N))
ax2.set_aspect('equal')
plt.show()
# plot histogram of angles
pts = u_sphere_pts(dim, 1000000)
theta = np.arctan2(pts[0,:], pts[1,:])
num_bins = 360
fig1, ax1 = plt.subplots()
n, bins, patches = plt.hist(theta, num_bins, facecolor='blue', alpha=0.5)
plt.show()
similar/related:
https://stackoverflow.com/questions/45580865/python-generate-an-n-dimensional-hypercube-using-rejection-sampling#comment78122144_45580865
Python Uniform distribution of points on 4 dimensional sphere
http://mathworld.wolfram.com/HyperspherePointPicking.html
Sampling uniformly distributed random points inside a spherical volume