Draw a curve connecting two points instead of a straight line - python

I want to do something like this:
I have the points but don't know how to plot the curves instead of straight lines.
Thank you.

For people interested in this question, I followed Matthew's suggestion and came up with this implementation:
def hanging_line(point1, point2):
import numpy as np
a = (point2[1] - point1[1])/(np.cosh(point2[0]) - np.cosh(point1[0]))
b = point1[1] - a*np.cosh(point1[0])
x = np.linspace(point1[0], point2[0], 100)
y = a*np.cosh(x) + b
return (x,y)
Here is what the result looks like:
import matplotlib.pyplot as plt
point1 = [0,1]
point2 = [1,2]
x,y = hanging_line(point1, point2)
plt.plot(point1[0], point1[1], 'o')
plt.plot(point2[0], point2[1], 'o')
plt.plot(x,y)

You are going to need some expression for the curve you want to plot, then you can make the curve out of many line segments.
Here's a parabola:
x = np.linspace(-1, 1, 100)
y = x*x
plt.plot(x, y)
Here's a sin curve:
x = np.linspace(-2*np.pi, 2*np.pi, 100)
y = np.sin(x)
plt.plot(x, y)
Each of these looks smooth, but is actually made up of many small line segments.
To get a collection of curves like you showed, you are going to need some expression for a curve you want to plot in terms of its two endpoints. The ones in your picture look like catenarys which are (approximately) the shape a hanging chain assumes under the force of gravity:
x = np.linspace(-2*np.pi, 2*np.pi, 100)
y = 2*np.cosh(x/2)
plt.plot(x, y)
You will have to find a way of parameterizing this curve in terms of its two endpoints, which will require you substituting your values of y and x into:
y = a*cosh(x/a) + b
and solving the resulting pair of equations for a and b.

There is a cool (at least for me) way to draw curve lines between two points, using Bezier curves. Just with some simple code you can create lists with dots connecting points and chart them with matplotlib, for example:
def recta(x1, y1, x2, y2):
a = (y1 - y2) / (x1 - x2)
b = y1 - a * x1
return (a, b)
def curva_b(xa, ya, xb, yb, xc, yc):
(x1, y1, x2, y2) = (xa, ya, xb, yb)
(a1, b1) = recta(xa, ya, xb, yb)
(a2, b2) = recta(xb, yb, xc, yc)
puntos = []
for i in range(0, 1000):
if x1 == x2:
continue
else:
(a, b) = recta(x1, y1, x2, y2)
x = i*(x2 - x1)/1000 + x1
y = a*x + b
puntos.append((x,y))
x1 += (xb - xa)/1000
y1 = a1*x1 + b1
x2 += (xc - xb)/1000
y2 = a2*x2 + b2
return puntos
Then, just run the function for some starting, mid and ending points, and use matplotlib:
lista1 = curva_b(1, 2, 2, 1, 3, 2.5)
lista2 = curva_b(1, 2, 2.5, 1.5, 3, 2.5)
lista3 = curva_b(1, 2, 2.5, 2, 3, 2.5)
lista4 = curva_b(1, 2, 1.5, 3, 3, 2.5)
fig, ax = plt.subplots()
ax.scatter(*zip(*lista1), s=1, c='b')
ax.scatter(*zip(*lista2), s=1, c='r')
ax.scatter(*zip(*lista3), s=1, c='g')
ax.scatter(*zip(*lista4), s=1, c='k')
This should be the results:
several Bezier quadratic curves
By extending the code a little more, you can get forms like this:
Bezier quartic curve

Related

Generating a random float below and above a line created by numpy arrays python

I would like to generate a random float point above and below a line created by numpy arrays.
For example I have these line equations:
x_values = np.linspace(-1, 1, 100)
y1 = 2 * x_values -5
y2= -3 * x_values +2
plt.plot(x_values,y1, '-k')
plt.plot(x_values,y2, '-g')
I have tried this method from Generate random points above and below a line in Python and it works if np.arrange is used like so:
lower, upper = -25, 25
num_points = 1
x1 = [random.randrange(start=1, stop=9) for i in range(num_points)]
x2 = [random.randrange(start=1, stop=9) for i in range(num_points)]
y1 = [random.randrange(start=lower, stop=(2 * x -5) )for x in x1]
y2 = [random.randrange(start=(2 * x -5), stop=upper) for x in x2]
plt.plot(np.arange(10), 2 * np.arange(10) -5)
plt.scatter(x1, y1, c='blue')
plt.scatter(x2, y2, c='red')
However, I wanted to find a way to generate a random point if np.linspace(-1, 1, 100) was used to create the line graph. The difference is involving/allowing float coordinates to be picked to. But unsure how.
Any ideas will be appreciated.
Here is an approach, using functions for the y-values. Random x positions are chosen uniformly over the x-range. For each random x, a value is randomly chosen between its y-ranges.
import numpy as np
import matplotlib.pyplot as plt
x_values = np.linspace(-1, 1, 100)
f1 = lambda x: 2 * x - 5
f2 = lambda x: -3 * x + 2
y1 = f1(x_values)
y2 = f2(x_values)
plt.plot(x_values, y1, '-k')
plt.plot(x_values, y2, '-g')
plt.fill_between (x_values, y1, y2, color='gold', alpha=0.2)
num_points = 20
xs = np.random.uniform(x_values[0], x_values[-1], num_points)
ys = np.random.uniform(f1(xs), f2(xs))
plt.scatter(xs, ys, color='crimson')
plt.show()
PS: Note that the simplicity of the approach chooses x uniform over its length. If you need an even distribution over the area of the trapezium, you need the x less probable at the right, and more at the left. You can visualize this with many more points and using transparency. With the simplistic approach, the right will look denser than the left.
The following code first generates x,y points in a parallelogram, and remaps the points on the wrong side back to its mirror position. The code looks like:
import numpy as np
import matplotlib.pyplot as plt
x0, x1 = -1, 1
x_values = np.linspace(x0, x1, 100)
f1 = lambda x: 2 * x - 5
f2 = lambda x: -3 * x + 2
y1 = f1(x_values)
y2 = f2(x_values)
plt.plot(x_values, y1, '-k')
plt.plot(x_values, y2, '-g')
plt.fill_between(x_values, y1, y2, color='gold', alpha=0.2)
num_points = 100_000
h0 = f2(x0) - f1(x0)
h1 = f2(x1) - f1(x1)
xs1 = np.random.uniform(x0, x1, num_points)
ys1 = np.random.uniform(0, h0 + h1, num_points) + f1(xs1)
xs = np.where(ys1 <= f2(xs1), xs1, x0 + x1 - xs1)
ys = np.where(ys1 <= f2(xs1), ys1, f1(xs) + h0 + h1 + f1(xs1) - ys1)
plt.scatter(xs, ys, color='crimson', alpha=0.2, ec='none', s=1)
plt.show()
Plot comparing the two approaches:
First of all, if you have 2 intersecting lines, there will most likely be a triangle in which you can pick random points. This is dangerously close to Bertrand's paradox, so make sure that your RNG suits its purpose.
If you don't really care about how "skewed" your randomness is, try this:
import numpy as np
left, right = -1, 1
# x_values = np.linspace(left, right, 100)
k1, k2 = 2, -3
b1, b2 = -5, 2
y1 = lambda x: k1*x + b1
y2 = lambda x: k2*x + b2
# If you need a point above the 1st equation, but below the second one.
# Check the limits where you can pick the points under this condition.
nosol = False
if k1==k2:
if b1>=b2:
inters = -100
nosol = True
else:
rand_x = np.random.uniform(left,right)
rand_y = np.random.uniform(y1(rand_x),y2(rand_x))
print(f'Random point is ({round(rand_x,2)}, {round(rand_y,2)})')
else:
inters = (b2-b1)/(k1-k2)
if inters<=left:
if k1>=k2:
nosol=True
elif inters>=right:
if k1<=k2:
nosol=True
if nosol:
print('No solution')
else:
if k1>k2:
right = inters
else:
left = inters
# Pick random X between "left" and "right"
# Pick whatever distribution you like or need
rand_x = np.random.uniform(left,right)
rand_y = np.random.uniform(y1(rand_x),y2(rand_x))
print(f'Random point is ({round(rand_x,2)}, {round(rand_y,2)})')
If your random X needs to belong to a specific number sequence, use some other np.random function: randint, choice...

Finding the x coordinate for the max value of y

I have a function that is split in 3. (0 < x < L1) (L1 < x < a) (a < x L2).
I need to add a notation on the plot for the max value no matter where x is on the (0 < x < L2).
I have:
c1 = np.arange(0,L1+0.1, 0.1)
c2 = np.arange(L1,a+0.1, 0.1)
c3 = np.arange(a,L+0.1, 0.1)
y1 = -q*c1
y2 = -q*c2 + RAV
y3 = -q*c3 + RAV - P
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.fill_between(c1, y1)
ax1.fill_between(c2, y2)
ax1.fill_between(c3, y3)
ax1.set_title('S curve')
Mmax1=np.max(y1)
Mmax2=np.max(y2)
Mmax3=np.max(y3)
Mmax= round(max(Mmax1,Mmax2,Mmax3), 2)
Now I want to take find the x coordinate of the y value Mmax, but I don't know how to use something like x[np.argmax(Mmax)] where x = a.any(c1, c2, c3).
I need the x coordinate so that I can plot it in, where the value occurs
ax2.annotate(text2,
xy=(max_x, Mmax), xycoords='data',
xytext=(0, 30), textcoords='offset points',
arrowprops=dict(arrowstyle="->"))
How can I fix it? Thank you!
Here I would illustrate what may be useful for you,As I don't have you complete code to create your desire function I have defined 3 simple function with following description:
by out_arr=np.maximum.reduce([y1,y2,y3]) we would have maximum in ever x and by result = np.where(out_arr == np.amax(out_arr)) we find out which index have maximum value. then maximum pint would be point=[Max_X,out_arr[Max_X]]
import matplotlib.pyplot as plt
import numpy as np
x= np.arange(0., 6., 1)
y1=x
y2=x**2
y3=x**3
out_arr=np.maximum.reduce([y1,y2,y3])
result = np.where(out_arr == np.amax(out_arr))
Max_X=result[0]
print(Max_X)
point=[Max_X,out_arr[Max_X]]
plt.plot(Max_X,out_arr[Max_X],'ro')
# red dashes, blue squares and green triangles
plt.plot(x, y1, 'r--', x, y2, 'bs', x, y3, 'g^')
plt.show()

Python, matplotlib. Plot a function between two points

I want to plot a function between 2 points using matplotlib.
The similar problem, but for 3d case is without working answer: How to plot a function oriented on a local x axis matplotlib 3d?
I think it should work something like that:
import matplotlib.pyplot as plt
import math
def foo(x, L):
# some polynomial on [0, L]
return x**2 # usually it's more difficult
def magic(plt, foo, point1, point2):
"""
Plot foo from point1 to point2
:param plt: matplotlib.pyplot
:param foo: function
:param point1: tuple (x1, y1) or list [x1, y1]
:param point2: tuple (x2, y2) or list [x2, y2]
:return:
"""
# do magic
# create modified function along new local x' axis using points in initial x,y axis?
# create new axis, rotate and move them?
pass
x1, y1 = 1, 1 # first point coordinates
x2, y2 = 2, 2 # second point coordinates
dx = x1 - x2
dy = y1 - y2
# length, this ratio is always True in my case (see picture below)
L = math.sqrt(dx * dx + dy * dy)
ax, fig = plt.subplots()
magic(plt, foo, (x1,y1), (x2, y2))
plt.show()
Important: The length along a new axis does not change. If there is a function on [0, L] it means it will have the same domain (or 'length') after rotation/moving or representing it along a new axis.
Here is the image of what I'm trying to do
There's no magic involved. Just a translation from (0,0) to (x1,y1) and a rotation by an angle defined by dx and dy. The sine of that angle is dy/L and the cosine dx/L. Using numpy arrays is both handy to write the function is a concise form as well as speeding up the calculations.
In the code below I changed the example function to make it more clear how the function is transformed. Also, the aspect ratio is set to 'equal'. Otherwise the rotated function would look distorted.
import matplotlib.pyplot as plt
import numpy as np
def foo(x):
# some function on [0, L]
return np.sin(x*20)/(x+1)
def function_on_new_axis(foo, point1, point2):
"""
Plot foo from point1 to point2
:param foo: function
:param point1: tuple (x1, y1) or list [x1, y1]
:param point2: tuple (x2, y2) or list [x2, y2]
:return:
"""
# create modified function along new local x' axis using points in initial x,y axis?
# create new axis, rotate and move them?
dx = x2 - x1
dy = y2 - y1
L = np.sqrt(dx * dx + dy * dy)
XI = np.linspace(0, L, 500) # initial coordinate system
YI = foo(XI)
s = dy / L # sine of the rotation angle
c = dx / L # cosine of the rotation angle
XM = c * XI - s * YI + x1 # modified coordinate system
YM = s * XI + c * YI + y1
plt.plot(XI, YI, color='crimson', alpha=0.3)
plt.plot(XM, YM, color='crimson')
plt.plot([x1, x2], [y1, y2], 'go')
x1, y1 = 1, 1 # first point coordinates
x2, y2 = 2, 3 # second point coordinates
fig, ax = plt.subplots()
function_on_new_axis(foo, (x1,y1), (x2, y2))
ax.axis('equal') # equal aspect ratio between x and y axis to prevent that the function would look skewed
# show the axes at (0,0)
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
plt.show()

Python: point on a line closest to third point

I have a line/vector between two XY points (p1 and p2) and a third XY point (p3) that is outside the line. According to this post I know how to get the distance of that point to the line. But what I'm actually looking for is a point (p4) on that line that is in a minimum distance (d) to the third point (p3). I found this post, but I feel it's not the correct solution. Maybe there's something included in Numpy or Python?
According to #allo I tried the following. You can download my code as Python file or Jupyter Notebook (both Python3).
points = [[1, 1], [3, 1], [2.5, 2], [2.5, 1]]
import matplotlib.pyplot as plt
%matplotlib inline
fig, ax = plt.subplots()
fig.set_size_inches(6,6)
x, y = zip(*points[:2])
l1, = ax.plot(x,y, color='blue')
scatter1 = ax.scatter(x=x,y=y, color='blue', marker='x', s=80, alpha=1.0)
x, y = zip(*points[2:])
l2, = ax.plot(x,y, color='red')
scatter2 = ax.scatter(x=x,y=y, color='red', marker='x', s=80, alpha=1.0)
p1 = Vector2D(*points[0])
p2 = Vector2D(*points[1])
p3 = Vector2D(*points[2])
p1p2 = p2.sub_vector(p1)
p1p3 = p3.sub_vector(p1)
angle_p1p2_p1p3 = p1p2.get_angle_radians(p1p3)
length_p1p3 = p1p3.get_length()
length_p1p2 = p1p2.get_length()
p4 = p1.add_vector(p1p2.multiply(p1p3.get_length()/p1p2.get_length()).multiply(math.cos(p1p2.get_angle_radians(p1p3))))
#p4 = p1 + p1p2 * length(p1p3)/length(p1p2)*cos(angle(p1p2, p1p3))
p4 = p1.add_vector(p1p2.multiply(length_p1p3/length_p1p2*math.cos(angle_p1p2_p1p3)))
p4
Which results in p4 = (1.8062257748298551, 1.0) but should obviously be (2.5, 1.0).
Analytical Geometry
Let's start with the assigned line, we define the line in terms of two points on it (x1, y1) and (x2, y2).
With dx = x2-x1 and dy = y2-y1 we can formally write every point on the line as (x12, y12) = (x1, y1) + a*(dx, dy) where a is a real number.
Using an analogous notation a point on the line passing in (x3, y3) and perpendicular to the assigned one is (x34, y34) = (x3, y3) + b*(-dy, +dx).
To find the intersection we have to impose (x12, y12) = (x34, y34) or
(x1, y1) + a*(dx, dy) = (x3, y3) + b*(-dy, +dx).
Writing separately the equations for x and y
y1 + a dy - y3 - b dx = 0
x1 + a dx + b dy - x3 = 0
it is a linear system in a and b whose solution is
a = (dy y3 - dy y1 + dx x3 - dx x1) / (dy^2 + dx^2)
b = (dy x3 - dy x1 - dx y3 + dx y1) / (dy^2 + dx^2)
The coordinates of the closest point to (x3, y3) lying on the line
are (x1+a*dx, y1+a*dy) — you need to compute only the coefficient a.
Numerically speaking, the determinant of the linear system is dx**2+dy**2 so you have problems only when the two initial points are extremely close to each other with respect to their distance w/r to the third point.
Python Implementation
We use a 2-uple of floats to represent a 2D point and we define a function whose arguments are 3 2-uples representing the points that define the line (p1, p2) and the point (p3) that determines the position of p4 on said line.
In [16]: def p4(p1, p2, p3):
...: x1, y1 = p1
...: x2, y2 = p2
...: x3, y3 = p3
...: dx, dy = x2-x1, y2-y1
...: det = dx*dx + dy*dy
...: a = (dy*(y3-y1)+dx*(x3-x1))/det
...: return x1+a*dx, y1+a*dy
To test the implementation I am using the three points used by the OP
to demonstrate their issues with this problem:
In [17]: p4((1.0, 1.0), (3.0, 1.0), (2.5, 2))
Out[17]: (2.5, 1.0)
It seems that the result of p4(...) coincides with the OP expectation.
A Matplotlib Example
import matplotlib.pyplot as plt
def p(p1, p2, p3):
(x1, y1), (x2, y2), (x3, y3) = p1, p2, p3
dx, dy = x2-x1, y2-y1
det = dx*dx + dy*dy
a = (dy*(y3-y1)+dx*(x3-x1))/det
return x1+a*dx, y1+a*dy
p1, p2, p3 = (2, 4), (7, 3), (1, 1)
p4 = p(p1, p2, p3)
fig, ax = plt.subplots()
# if we are after right angles, anything else would be wrong
ax.set_aspect(1)
plt.plot(*zip(p1, p2, p4, p3), marker='*')
Shapely's distance() function returns the minimum distance:
>>> from shapely.geometry import LineString as shLs
>>> from shapely.geometry import Point as shPt
>>> l = shLs([ (1,1), (3,1)])
>>> p = shPt(2,2)
>>> dist = p.distance(l)
1.0
>>> l.interpolate(dist).wkt
'POINT (2 1)'
What you want to do is a vector projection.
The edge p1p3 is rotated onto the edge p1p2 and you need to find the correct length of the segment p1p4. Then you can just use p1+FACTOR*p1p2 / length(p1p2). The needed factor is given by the cosine of the angle between p1p2 and p1p3. Then you get
p4 = p1 + p1p2 * length(p1p3)/length(p1p2)*cos(angle(p1p2, p1p3))
Here the two edge cases as example:
The cosinus is 0 if p1p3 is orthogonal to p1p2, so p4 lies on p1.
The cosinus is 1 when p1p3 lies on p1p2, so p1p2 is just scaled by length(p1p3)/length(p1p2) to get p1p4.
You further can replace the cosinus by the dot product dot(p1p2 / length(p1p2), p1p3 / length(p1p3).
You can find more details and nice illustrations in the wikibook about linear algebra.
Here an full example derived from your python code. I used numpy instead of Vector2D here.
points = [[1, 1], [3, 1], [2.5, 2], [2.5, 1]]
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
fig, ax = plt.subplots()
fig.set_size_inches(6,6)
x, y = zip(*points[:2])
l1, = ax.plot(x,y, color='blue')
scatter1 = ax.scatter(x=x,y=y, color='blue', marker='x', s=80, alpha=1.0)
x, y = zip(*points[2:])
l2, = ax.plot(x,y, color='red')
scatter2 = ax.scatter(x=x,y=y, color='red', marker='x', s=80, alpha=1.0)
p1 = np.array(points[0])
p2 = np.array(points[1])
p3 = np.array(points[2])
p1p2 = p2 - p1
p1p3 = p3 - p1
p4 = p1 + p1p2 / np.linalg.norm(p1p2) * np.linalg.norm(p1p3) * ((p1p2/np.linalg.norm(p1p2)).T * (p1p3/np.linalg.norm(p1p3)))
p1, p2, p3, p4, p1p2, p1p3
We can shorten the p4 line a bit like this, using the linearity of the scalar product:
p4 = p1 + p1p2 / np.linalg.norm(p1p2) * ((p1p2/np.linalg.norm(p1p2)).T * (p1p3))
p4 = p1 + p1p2 / np.linalg.norm(p1p2)**2 * (p1p2.T * (p1p3))

Generate random points above and below a line in Python

I would like to generate random points on an x,y scatter plot that are either above or below a given line. For example, if the line is y=x I would like to generate a list of points in the top left of the plot (above the line) and a list of points in the bottom right of the plot (below the line). Here's is an example where the points are above or below y=5:
import random
import matplotlib.pyplot as plt
num_points = 10
x1 = [random.randrange(start=1, stop=9) for i in range(num_points)]
x2 = [random.randrange(start=1, stop=9) for i in range(num_points)]
y1 = [random.randrange(start=1, stop=5) for i in range(num_points)]
y2 = [random.randrange(start=6, stop=9) for i in range(num_points)]
plt.scatter(x1, y1, c='blue')
plt.scatter(x2, y2, c='red')
plt.show()
However, I generated the x and y points independently, which limits me to equations where y = c (where c is a constant). How can I expand this to any y=mx+b?
You can change the stop and start limits for y1 and y2 to be the line you want. You will need to decide where the plane ends (set lower and upper).
Note this only works for integers. You can use truncated multivariate distributions if you want something more sophisticated.
m, b = 1, 0
lower, upper = -25, 25
x1 = [random.randrange(start=1, stop=9) for i in range(num_points)]
x2 = [random.randrange(start=1, stop=9) for i in range(num_points)]
y1 = [random.randrange(start=lower, stop=m*x+b) for x in x1]
y2 = [random.randrange(start=m*x+b, stop=upper) for x in x2]
plt.plot(np.arange(10), m*np.arange(10)+b)
plt.scatter(x1, y1, c='blue')
plt.scatter(x2, y2, c='red')
You may as well have my answer too.
This way puts Gaussian noise above the line, and below. I have deliberately set the mean of the noise to 20 so that it would stand out from the line, which is y = 10*x + 5. You would probably make the mean zero.
>>> import random
>>> def y(x, m, b):
... return m*x + b
...
>>> import numpy as np
>>> X = np.linspace(0, 10, 100)
>>> y_above = [y(x, 10, 5) + abs(random.gauss(20,5)) for x in X]
>>> y_below = [y(x, 10, 5) - abs(random.gauss(20,5)) for x in X]
>>> import matplotlib.pyplot as plt
>>> plt.scatter(X, y_below, c='g')
>>> plt.scatter(X, y_above, c='r')
>>> plt.show()
Here's the plot.
There are many approaches possible, but if your only requirement is that they are above and below the y = mx + b line, then you can simply plug the random x values into the equation and then add or subtract a random y value.
import random
import matplotlib.pyplot as plt
slope = 1
intercept = 0
def ymxb(slope, intercept, x):
return slope * x + intercept
num_points = 10
x1 = [random.randrange(start=1, stop=9) for i in range(num_points)]
x2 = [random.randrange(start=1, stop=9) for i in range(num_points)]
y1 = [ymxb(slope, intercept, x) - random.randrange(start=1, stop=9) for x in x1]
y2 = [ymxb(slope, intercept, x) + random.randrange(start=1, stop=9) for x in x2]
plt.scatter(x1, y1, c='blue')
plt.scatter(x2, y2, c='red')
plt.show()
That looks like this:
Side of (x, y) is defined by the sign of y - mx - b. You can read it here, for example.
import random
import matplotlib.pyplot as plt
num_points = 50
x = [random.randrange(start=1, stop=9) for i in range(num_points)]
y = [random.randrange(start=1, stop=9) for i in range(num_points)]
m = 5
b = -3
colors = ['blue' if y[i] - m * x[i] - b > 0 else 'red' for i in range(num_points) ]
plt.plot([0, 10], [b, 10 * m + b], c='green')
plt.xlim((0, 10))
plt.ylim((0, 10))
plt.scatter(x, y, c=colors)
plt.show()

Categories