Related
I would like to plot circles which are tangents to the interior of a curve in Python as shown below:
I tried the following approach.
I created an inverted exponential curve to get the curve as shown below.
x = np.arange(0, 2, 0.1)
y = [1/np.exp(i) for i in x]
plt.plot(x, y, marker = "o")
To add a circle as tangent to the curve, I am adding a circle manually as follows:
fig, ax = plt.subplots()
#Plot exponential curve
ax.plot(x, y)
#Add tangential circle manually
pi = np.pi
c1 = plt.Circle((x[1]+ np.sin(pi/4) * 0.1, y[1] + np.sin(pi/4) * 0.1),
radius = 0.1, facecolor = "black", ec = "white")
ax.add_patch(c1)
Here my assumption is that the angle denoted by the shaded part in the figure below (between the normal radius and horizontal x-axis is 45 degrees).
However, I think this is an incorrect assumption and incorrect way to do it.
When I add more circles, they are not exactly tangential to the curve as shown below.
What would be the correct approach to draw the circles as tangents to the curve? Is it possible to do it in Python?
Using trigonometry (there is likely a way to simplify but I'm rusty+tired :p).
fig, ax = plt.subplots()
X = np.arange(0, 5, 0.1)
Y = np.sin(X)
#Plot exponential curve
ax.plot(X, Y)
ax.set_aspect('equal')
#Add tangential circles
for i in np.arange(1,len(X),5):
ax.plot(X[i], Y[i], marker='o', color='r')
pi = np.pi
radius = 0.3
# slope of tangent
dydx = np.gradient(Y)[i]/np.gradient(X)[i]
# slope of perpendicular
slope = 1/abs(dydx)
# angle to horizontal
angle = np.arctan(slope)
c1 = plt.Circle((X[i] - np.sign(dydx)*np.cos(angle) * radius,
Y[i] + np.sin(angle) * radius),
radius = radius, facecolor = "black", ec = "white")
ax.add_patch(c1)
output:
Your function is exp(-x)
It's derivative is -exp(-x), so tangent vector at point x, exp(-x) is (1, -exp(-x)) and normal vector is (exp(-x), 1) (note sign change).
Normalize it
n_len = np.hypot((y)[i], 1)
nx = y[i] / n_len
ny = 1 / n_len
center for radius R is
c1 = plt.Circle((x[i] + R * nx, y[i] + R * ny, ...
For the circle to be tangent, its center must be located along the normal to the curve (this is a straight-line with slope -δx/δy).
You did not specify how the radius varies, so there are two options:
the radius is a function of x or y or s (curvilinear abscissa) that you supply; then place the center at the desired distance from the contact point, on the normal;
the radius is such that it matches the local curvature; then what you want is the osculating circle; there is a formula to compute the radius of curvature.
https://en.wikipedia.org/wiki/Osculating_circle
I want to plot points, which lie in xy plane. The thing is the points are with their given x, y, and z coordinates. I want a plane, not a 3d object. So the planes would be xy, xz, yz. But others can be exemplified from xy plane. What I am asking is xy plane case.
The example points are with their corresponding normals
point A=(10600, 26.662,-0.420), (Normal) N_A = [0.91, 0.36, 0.21]
point B=(9600, -30.532,-6.208), (Normal) N_B = [0.81, 0.50, -0.30]
point C=(1000, 10.5, -3.72), (Normal) N_C = [-0.83, -0.54, 0.11]
Taking x and y values from these x,y, and z coordinates, how to draw the points in xy plane with their normals and perpendicular lines to the normals?
enter image description here
If you want a projection onto the xy plane, you just need to use the first two coordinates of the points:
A_xy = [A[0], A[1]]
Projection on the xz plane is
A_xz = [A[0], A[2]]
and so on. The projections can be plotted as an arrow or points on the plane.
If you have a vector N = [nx, ny] in a plane, then the line perpendicular to this vector and going through the point N is given by:
y(x) = -nx/ny*(x - nx) + ny
which you can plot as a function in variable x. Or, calculate start and end points using this function and plot a straight line between calculated start and end points.
Example:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
fig, ax = plt.subplots()
arrow_scale = 100
A = [10600, 26.662,-0.420]
N_A = np.array([0.91, 0.36, 0.21])*arrow_scale
B = [9600, -30.532,-6.208]
N_B = np.array([0.81, 0.50, -0.30])*arrow_scale
xmin = min(A[0], B[0]) - 2*arrow_scale
xmax = max(A[0], B[0]) + 2*arrow_scale
ymin = min(A[1], B[1]) - 2*arrow_scale
ymax = max(A[1], B[1]) + 2*arrow_scale
arrow_A = mpatches.FancyArrowPatch((A[0], A[1]), (A[0] + N_A[0], A[1] + N_A[1]), mutation_scale=10)
ax.add_patch(arrow_A)
arrow_B = mpatches.FancyArrowPatch((B[0], B[1]), (B[0] + N_B[0], B[1] + N_B[1]), mutation_scale=10)
ax.add_patch(arrow_B)
x = np.linspace(xmin, xmax, 3)
line_A = -N_A[0]/N_A[1]*(x - A[0]) + A[1]
ax.plot(x, line_A)
line_B = -N_B[0]/N_B[1]*(x - B[0]) + B[1]
ax.plot(x, line_B)
ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
ax.set(aspect=1)
plt.show()
I'm trying to generate a right triangle with hypotenuse = 1, interior angle 25, with its base rotated 30 degrees. I've entered the trig formulas correctly to my understanding, and I suspect some kind of rounding error. Because the triangle produced in matplot is slightly off from a right triangle.
import math
import matplotlib.pyplot as plt
from annotation import label
angle_b = math.radians(25) # the interior 25 degree angle of our right triangle
angle_a = math.radians(30) # 30 degree angle between the plotted angle_b triangle base and the x axis.
point_A = (0,0)
point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
point_C = (math.cos(angle_a) * math.cos(angle_b), math.sin(angle_a) * math.cos(angle_b))
# Label our points
label(plt, 'A', point_A)
label(plt, 'B', point_B)
label(plt, 'C', point_C)
# Draw the right triangle between our points.
plt.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')
As you can see, the angle ACB is not a right angle as trigonometry would predict.
#Mad Physicist pointed out a the comment that ABC does appear to be a right triangle. I would like to get ACB to be a right angle, like this example.
Here is with the right triangle formed by angle_a below the previous one:
point_D = (math.cos(angle_a) * math.cos(angle_b), 0)
plt.plot(*zip(point_A, point_C, point_D, point_A), marker='o', color='black')
label(plt, 'D', point_D)
Now solved. I finally got it working thanks to the line ax.set_aspect('equal')
The problem is that by default, matplotlib doesn't use the same distances in the x and in the y direction. Instead, matplotlib tries to fit everything nicely into the given bounds.
These uneven distances distort angles, and also deforms circles.
You can force an equal aspect ratio via ax.set_aspect('equal').
To calculate the positions via the angles, and have the right corner at point B, you need to take into account that the length AC is cos(b) times the length of AC. AC can be chosen to have 1 as length. Alternatively, you could divide both B and C by cos(b) to have a larger triangle, where the length of AB would be 1.
import matplotlib.pyplot as plt
import math
angle_b = math.radians(25) # the interior 25 degree angle of our right triangle
angle_a = math.radians(30) # 30 degree angle between the plotted angle_b triangle and the x axis.
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(14, 5), sharey=True)
for ax in (ax1, ax2):
point_A = (0, 0)
if ax == ax1:
point_B = (math.cos(angle_a + angle_b) * math.cos(angle_b), math.sin(angle_a + angle_b) * math.cos(angle_b))
point_C = (math.cos(angle_a), math.sin(angle_a))
ax.set_title('length AC is 1')
else:
point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
point_C = (math.cos(angle_a) / math.cos(angle_b), math.sin(angle_a) / math.cos(angle_b))
ax.set_title('length AB is 1')
point_M = ((point_A[0] + point_C[0]) / 2, (point_A[1] + point_C[1]) / 2)
# Draw the right triangle between our points.
ax.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')
# draw a circle around the 3 points
ax.add_patch(plt.Circle(point_M, math.sqrt((point_M[0] - point_A[0]) ** 2 + (point_M[1] - point_A[1]) ** 2),
ec='r', ls='--', fc='none'))
ax.set_aspect('equal', 'datalim')
plt.show()
The same calculation works for any angle. Here is how 12 rotations in steps of 30 degrees look together:
The following code shows the effect of ax.set_aspect('equal') for the original points.
import matplotlib.pyplot as plt
import math
angle_b = math.radians(25) # the interior 25 degree angle of our right triangle
angle_a = math.radians(30) # 30 degree angle between the plotted angle_b triangle and the x axis.
point_A = (0, 0)
point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
point_C = (math.cos(angle_a) * math.cos(angle_b), math.sin(angle_a) * math.cos(angle_b))
point_M = ((point_A[0] + point_B[0]) / 2, (point_A[1] + point_B[1]) / 2)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(16, 4), gridspec_kw={'width_ratios': [2, 1]})
for ax in (ax1, ax2):
# Draw the right triangle between our points.
ax.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')
# draw a circle around the 3 points
ax.add_patch(plt.Circle(point_M, math.sqrt((point_M[0] - point_A[0]) ** 2 + (point_M[1] - point_A[1]) ** 2),
ec='r', ls='--', fc='none'))
ax1.set_title('Default aspect ratio, deforms the angles')
ax2.set_aspect('equal') # or plt.axis('equal')
ax2.set_title('Equal aspect ratio')
plt.tight_layout()
plt.show()
You appear to have jumbled your trigonometry. I'm going to suggest moving slowly step-by-step, and using variable names that help you remember what is going on.
Let's start with the original triangle. If interior_angle = np.deg2rad(25) and the hypotenuse has length 1, then the right angle on the x-axis is at (np.cos(interior_angle), 0) and the other interior angle is at (np.cos(interior_angle), np.sin(interior_angle)). Neither of the points in your diagram corresponds.
Now let's express the triangle as a matrix whose columns are the vertices:
interior_angle = np.deg2rad(25)
vertices = np.array([
[0, np.cos(interior_angle), np.cos(interior_angle), 0],
[0, 0, np.sin(interior_angle), 0],
])
The last vertex is a repeat of the origin to make plotting easier.
Now let's look at the rotation. For rotation_angle = np.deg2rad(30), point (x, y) rotates to (np.cos(rotation_angle) * x - np.sin(rotation_angle) * y, np.sin(rotation_angle) * x + np.cos(rotation_angle) * y). This can be expressed as a matrix equation:
rotation_matrix = np.array([
[np.cos(rotation_angle), -np.sin(rotation_angle)],
[np.sin(rotation_angle), np.cos(rotation_angle)]])
p_out = rotation_matrix # p_in
The array vertices is so constructed that it can be directly multiplied by a rotation matrix. You can therefore write
rotation_angle = np.deg2rad(30)
rotation_matrix = np.array([
[np.cos(rotation_angle), -np.sin(rotation_angle)],
[np.sin(rotation_angle), np.cos(rotation_angle)]])
rotated_vertices = rotation_matrix # vertices
The plotted image should make more sense now:
plt.plot(*vertices)
plt.plot(*rotated_vertices)
plt.axis('equal')
plt.show()
Given a linear equation, I want to use the slope to create a circle of values around a given point, defined by the slope of the linear equation if possible
Im currently a bit far away - can only make the radial plot but do not know how to connect this with an input equation. My first thought would be to change the opacity using import matplotlib.animation as animation and looping matplotlib's alpha argument to become gradually more and more opaque. However the alpha doesnt seem to change opacity.
Code:
# lenth of radius
distance = 200
# create radius
radialVals = np.linspace(0,distance)
# 2 pi radians = full circle
azm = np.linspace(0, 2 * np.pi)
r, th = np.meshgrid(radialVals, azm)
z = (r ** 2.0) / 4.0
# creates circle
plt.subplot(projection="polar")
# add color gradient
plt.pcolormesh(th, r, z)
plt.plot(azm, r,alpha=1, ls='', drawstyle = 'steps')
#gridlines
# plt.grid()
plt.show()
Here is one way to solve it, the idea is to create a mesh, calculate the colors with a function then use imshow to visualize the mesh.
from matplotlib import pyplot as plt
import numpy as np
def create_mesh(slope,center,radius,t_x,t_y,ax,xlim,ylim):
"""
slope: the slope of the linear function
center: the center of the circle
raadius: the radius of the circle
t_x: the number of grids in x direction
t_y: the number of grids in y direction
ax: the canvas
xlim,ylim: the lims of the ax
"""
def cart2pol(x,y):
rho = np.sqrt(x**2 + y**2)
phi = np.arctan2(y,x)
return rho,phi
def linear_func(slope):
# initialize a patch and grids
patch = np.empty((t_x,t_y))
patch[:,:] = np.nan
x = np.linspace(xlim[0],xlim[1],t_x)
y = np.linspace(ylim[0],ylim[1],t_y)
x_grid,y_grid = np.meshgrid(x, y)
# centered grid
xc = np.linspace(xlim[0]-center[0],xlim[1]-center[0],t_x)
yc = np.linspace(ylim[0]-center[1],ylim[1]-center[1],t_y)
xc_grid,yc_grid = np.meshgrid(xc, yc)
rho,phi = cart2pol(xc_grid,yc_grid)
linear_values = slope * rho
# threshold controls the size of the gaussian
circle_mask = (x_grid-center[0])**2 + (y_grid-center[1])**2 < radius
patch[circle_mask] = linear_values[circle_mask]
return patch
# modify the patch
patch = linear_func(slope)
extent = xlim[0],xlim[1],ylim[0],ylim[1]
ax.imshow(patch,alpha=.6,interpolation='bilinear',extent=extent,
cmap=plt.cm.YlGn,vmin=v_min,vmax=v_max)
fig,ax = plt.subplots(nrows=1,ncols=2,figsize=(12,6))
slopes = [40,30]
centroids = [[2,2],[4,3]]
radii = [1,4]
for item in ax:item.set_xlim(0,8);item.set_ylim(0,8)
v_max,v_min = max(slopes),0
create_mesh(slopes[0],centroids[0],radii[0],t_x=300,t_y=300,ax=ax[0],xlim=(0,8),ylim=(0,8))
create_mesh(slopes[1],centroids[1],radii[1],t_x=300,t_y=300,ax=ax[1],xlim=(0,8),ylim=(0,8))
plt.show()
The output of this code is
As you can see, the color gradient of the figure on the left is not as sharp as the figure on the right because of the different slopes ([40,30]).
Also note that, these two lines of code
v_max,v_min = max(slopes),0
ax.imshow(patch,alpha=.6,interpolation='bilinear',extent=extent,
cmap=plt.cm.YlGn,vmin=v_min,vmax=v_max)
are added in order to let the two subplots share the same colormap.
I would like to generate N points in a circle C of center (0,0) and of radius R=200. The points follow Poisson distribution. In other words, I would like to generate N homogeneous Poisson point process (HPPP) inside C.
I found this paper Generating Homogeneous Poisson Processes . In Section 2 there is exactly what I want. Specifically, in page 4, Algorithm 3 generates the points HPPP inside C.
I implemented this code in Python as follow:
""" Main Codes """
import matplotlib.pyplot as plt
import numpy as np
lamb = 0.0005 # the rate
pi = np.pi # pi = 3.14...
r = 200 # the radius of the circle C
mean = lamb * pi * r ** 2 # the mean of the Poisson random variable n
n = np.random.poisson(mean) # the Poisson random variable (i.e., the number of points inside C)
u_1 = np.random.uniform(0.0, 1.0, n) # generate n uniformly distributed points
radii = np.zeros(n) # the radial coordinate of the points
for i in range(n):
radii[i] = r * (np.sqrt(u_1[i]))
u_2 = np.random.uniform(0.0, 1.0, n) # generate another n uniformly distributed points
angle = np.zeros(n) # the angular coordinate of the points
for i in range(n):
angle[i] = 2 * pi * u_2[i]
""" Plots """
fig = plt.gcf()
ax = fig.gca()
plt.xlim(-300, 300)
plt.ylim(-300, 300)
circ = plt.Circle((0, 0), radius=200, color='r', linewidth=2, fill=False)
plt.polar(angle, radii, 'bo')
ax.add_artist(circ)
plt.show()
First, I cannot see the points inside the circle. Second, I don't know why the points do not generate inside the circle properly. Is there a problem in my code?
The output is given below: The circle C is in red.
I found the answer. I just convert the polar coordinates to the Cartesian coordinates and then I plot with plt.plot() not with plt.polar().
# Cartesian Coordinates
x = np.zeros(n)
y = np.zeros(n)
for i in range(n):
x[i] = radii[i] * np.cos(angle[i])
y[i] = radii[i] * np.sin(angle[i])
plt.plot(x,y,'bo')
So I get the desired output.
A few years late, but I wrote about this problem a few months ago; see this post.
For future readers, here's my code:
import numpy as np
import scipy.stats
import matplotlib.pyplot as plt
#Simulation window parameters
r=1;
xx0=0; yy0=0; #centre of disk
areaTotal=np.pi*r**2; #area of disk
#Point process parameters
lambda0=100; #intensity (ie mean density) of the Poisson process
#Simulate Poisson point process
numbPoints = scipy.stats.poisson( lambda0*areaTotal ).rvs()#Poisson number of points
theta = 2*np.pi*scipy.stats.uniform.rvs(0,1,((numbPoints,1)))#angular coordinates of Poisson points
rho = r*np.sqrt(scipy.stats.uniform.rvs(0,1,((numbPoints,1))))#radial coordinates of Poisson points
#Convert from polar to Cartesian coordinates
xx = rho * np.cos(theta)
yy = rho * np.sin(theta)
#Shift centre of disk to (xx0,yy0)
xx=xx+xx0; yy=yy+yy0;
#Plotting
plt.scatter(xx,yy, edgecolor='b', facecolor='none', alpha=0.5 )
plt.xlabel("x"); plt.ylabel("y")
plt.axis('equal')
A sample:
A realization of a Poisson point process on a disk