I have plotted curve created by a list with several values. How to find out the x-coordinate that correspond with y-coordinate 0.04400918? This value is not exactly included in the list that describes the curve. Thank you very much.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # 3d graph
from mpl_toolkits.mplot3d import proj3d # 3d graph
import matplotlib.pylab as pl
fig=pl.figure()
ax = Axes3D(fig)
x=[0.02554897, 0.02587839, 0.02623991, 0.02663096, 0.02704882, 0.02749103, 0.02795535, 0.02844018, 0.02894404, 0.02946527, 0.03000235]
y=[0.04739086, 0.0460989, 0.04481555, 0.04354088, 0.04227474, 0.04101689, 0.03976702, 0.03852497, 0.03729052, 0.0360633, 0.03484293]
z=[1.05764017e-18, 1.57788964e-18, 2.00281370e-18, 2.40500994e-18, 2.80239565e-18, 3.19420769e-18, 3.58001701e-18, 3.96024361e-18, 4.33484911e-18, 4.70364652e-18, 5.06672528e-18]
y_point=0.04400918
ax.plot3D(x,y,z)
plt.show()
Here is a specific resolution for your problem.
Some works have already been done for solving line-plane equation. This topic explains how to solve it. Even better, this snippet implements a solution.
For now, we only need to adapt it to our problem.
The first step is to find all the time the line is crossing the plan. To do that, we will iterate over the y dataset and collect all consecutive values when y_point is between them:
lines = []
for i in range(len(y) - 1):
if y[i] >= y_point and y_point >= y[i+1]:
lines.append([[x[i], y[i], z[i]], [x[i+1], y[i+1], z[i+1]]])
Then, for all of these lines, we will solve the intersection equation with the plane. We will use the function provided in sources above.
Finally, we will plot the results
Full code:
# Modules
import numpy as np
import matplotlib.pyplot as plt
# Data
x = [0.02554897, 0.02587839, 0.02623991, 0.02663096, 0.02704882, 0.02749103, 0.02795535, 0.02844018, 0.02894404, 0.02946527, 0.03000235]
y = [0.04739086, 0.0460989, 0.04481555, 0.04354088, 0.04227474, 0.04101689, 0.03976702, 0.03852497, 0.03729052, 0.0360633, 0.03484293]
z = [1.05764017e-18, 1.57788964e-18, 2.00281370e-18, 2.40500994e-18, 2.80239565e-18, 3.19420769e-18, 3.58001701e-18, 3.96024361e-18, 4.33484911e-18, 4.70364652e-18, 5.06672528e-18]
y_point = 0.04400918
# Source: https://rosettacode.org/wiki/Find_the_intersection_of_a_line_with_a_plane#Python
# Resolve intersection
def LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint, epsilon=1e-6):
ndotu = planeNormal.dot(rayDirection)
if abs(ndotu) < epsilon:
raise RuntimeError("no intersection or line is within plane")
w = rayPoint - planePoint
si = -planeNormal.dot(w) / ndotu
Psi = w + si * rayDirection + planePoint
return Psi
# For all line, apply the solving process
def solveAllPoints(lines, y_point):
collision_points = []
for line in lines:
# Define plane
planeNormal = np.array([0, 1, 0]) # Plane normal (e.g. y vector)
planePoint = np.array([0, y_point, 0]) # Any point on the plane
# Define ray
rayDirection = line[1] - line[0] # Line direction
rayPoint = line[0] # Any point of the line
# Append point
collision_points.append(LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint))
return collision_points
# Find all consecutive Y points crossing the plane.
# This function is only working for the given problem (intersection of the line
# with 1 plan defined by a normal vector = [0,1,0])
def getCrossingLines(y_point, x, y, z):
lines = []
for i in range(len(y) - 1):
if y[i] >= y_point and y_point >= y[i+1]:
lines.append([[x[i], y[i], z[i]], [x[i+1], y[i+1], z[i+1]]])
return np.array(lines)
# Get coordinates for drawing our plane
# Related topic: https://stackoverflow.com/questions/53115276/matplotlib-how-to-draw-a-vertical-plane-in-3d-figure
def getXYZPlane(x, y, z):
xs = np.linspace(min(x), max(x), 100)
zs = np.linspace(min(z), max(z), 100)
X, Z = np.meshgrid(xs, zs)
Y = np.array([y_point for _ in X])
return X, Y, Z
# Create plot
plt3d = plt.figure().gca(projection='3d')
ax = plt.gca()
# Draw data line
ax.plot3D(x,y,z)
# Plot plan
X, Y, Z = getXYZPlane(x, y, z)
ax.plot_surface(X, Y, Z)
# Draw crossing points (lines-planes)
lines = getCrossingLines(y_point, x, y , z)
for pt in solveAllPoints(lines, y_point):
ax.scatter(pt[0], pt[1], pt[2], color='green')
plt.show()
Output
Related
Given the region bounded by the curves y=x^2, y=(x-2)^2 and the axis.
I want to plot the 3-D solid rotated about the x-axis.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Define the function to rotate
def f(x):
return x**2
def g(x):
return (x-2)**2
# Define the range of x values to plot
x = np.linspace(0, 1, 100)
x2=np.linspace(1, 2, 100)
# Define the range of angles to rotate over
theta = np.linspace(0, 2*np.pi, 100)
# Create a meshgrid of x and theta values
X, Theta = np.meshgrid(x, theta)
X2, Theta = np.meshgrid(x2, theta)
# Calculate the corresponding cylindrical coordinates
R = X
Y = R*np.sin(Theta)
Z = R*np.cos(Theta)*f(X)
R2 = X2
Y2 = R2*np.sin(Theta)
Z2 = R2*np.cos(Theta)*g(X2)
# Create the 3D plot
fig = plt.figure(figsize = (11,8))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z)
ax.plot_surface(X2, Y2, Z2)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
Output:
As you can see, it works fine for the first curve y = x^2 (blue) but it's not rendering correctly for y=(x-2)^2 (orange). Why is it doing that?
The code and output attached above.
I used a trick to make the plotting process easier.
Instead of rotating around the x-axis, it is much easier rotating around the z-axis using spherical coordinates. matplotlib has intuitive example of utilizing spherical coordinates to draw a ball. Hence, we can swap the axis (e.g. treat the x-axis in the 2D plot as the z-axis in the 3D plot), compute the required spherical coordinates from the given two functions, and then convert back to Cartesian for plotting.
Since we swap the coordinates, eventually we have to rotate the plot and manually assign the axis label.
import matplotlib.pyplot as plt
import numpy as np
from typing import Tuple, Callable
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(projection='3d')
# Define the function to rotate
def f(x):
return x**2
def g(x):
return (x-2)**2
def get_p(phi: np.ndarray, f: Callable, x0: float = 0) -> np.ndarray:
"""Get the distance p
Let the origin be O and a line starting from O with its angle relative to
x-axis being phi intersect with the curve y = f(x) at point Q, the distance
p is the length of the line segment OQ.
:param phi: the angle relative to x-axis
:type phi: np.ndarray
:param f: the curve to be rotated around its x-axis
:type f: Callable
:param x0: starting estimate of x-coord of intersection Q. Use this to
control which intersection is desired. default to 0
:type x0: optional, float
:return: an array of distance, corresponding to each given phi
:rtype: np.ndarray
"""
ks = np.tan(phi)
x = []
for k in ks:
func = lambda x : f(x) - k * x
# we only look for one root
x.append(scipy.optimize.fsolve(func, x0)[0])
x = np.array(x)
y = x * ks
return np.sqrt(x**2 + y**2)
def get_xyz(
theta: np.ndarray, phi: np.ndarray, p: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Produce the Cartesian coordinates from the given spherical coordinates.
For reference, see: https://mathinsight.org/spherical_coordinates#:~:text=In%20summary%2C%20the%20formulas%20for,%CE%B8z%3D%CF%81cos%CF%95.
:param theta: in the 3D coordinate, given its origin O, a point P and its
projection Q on the XY plane, theta is the angle between line segment
OQ and positive x-axis.
:type theta: np.ndarray
:param phi: using the same setup as described above, phi is the angle
between line segment OP and positive z-axis
:type phi: np.ndarray
:param p: using the same setup as described above, p is the length of line
segment OP.
:type p: np.ndarray
:return: the Cartesian coordinates converted from the spherical coordinates
in the form of (x, y, z)
:rtype: Tuple[np.ndarray, np.ndarray, np.ndarray]
"""
return (
np.outer(np.cos(theta), np.sin(phi) * p),
np.outer(np.sin(theta), np.sin(phi) * p),
np.outer(np.ones(np.size(theta)), np.cos(phi) * p),
)
# Make data
theta = np.linspace(0, 2 * np.pi, 100)
phi_intercept = np.pi / 4 # the angle relative to x-axis when the two curves meet
# Plot y = x^2 half
phi2 = np.linspace(0, phi_intercept, 50)
p2 = get_p(phi2, f, x0=1)
ax.plot_surface(*get_xyz(theta, phi2, p2))
# Plot y = (x - 2)^2 half
phi1 = np.linspace(0, phi_intercept, 50)
p1 = get_p(phi1, g, x0=1)
ax.plot_surface(*get_xyz(theta, phi1, p1))
# Set plot properties
ax.set_box_aspect([1,1,1])
# x axis in the 2D plot becomes z here
ax.set_zlim(0, 2)
ax.set_zlabel('X')
# y axis in the 2D plot is still y here
ax.set_ylim(-1, 1)
ax.set_ylabel('Y')
# the new z axis after rotation becomes x here
ax.set_xlim(-1, 1)
ax.set_xlabel('Z')
# rotate the plot
ax.view_init(10, 0, -90)
plt.savefig('demo.png', dpi=100)
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
The figure above is a great artwork showing the wind speed, wind direction and temperature simultaneously. detailedly:
The X axes represent the date
The Y axes shows the wind direction(Southern, western, etc)
The variant widths of the line were stand for the wind speed through timeseries
The variant colors of the line were stand for the atmospheric temperature
This simple figure visualized 3 different attribute without redundancy.
So, I really want to reproduce similar plot in matplotlib.
My attempt now
## Reference 1 http://stackoverflow.com/questions/19390895/matplotlib-plot-with-variable-line-width
## Reference 2 http://stackoverflow.com/questions/17240694/python-how-to-plot-one-line-in-different-colors
def plot_colourline(x,y,c):
c = plt.cm.jet((c-np.min(c))/(np.max(c)-np.min(c)))
lwidths=1+x[:-1]
ax = plt.gca()
for i in np.arange(len(x)-1):
ax.plot([x[i],x[i+1]], [y[i],y[i+1]], c=c[i],linewidth = lwidths[i])# = lwidths[i])
return
x=np.linspace(0,4*math.pi,100)
y=np.cos(x)
lwidths=1+x[:-1]
fig = plt.figure(1, figsize=(5,5))
ax = fig.add_subplot(111)
plot_colourline(x,y,prop)
ax.set_xlim(0,4*math.pi)
ax.set_ylim(-1.1,1.1)
Does someone has a more interested way to achieve this? Any advice would be appreciate!
Using as inspiration another question.
One option would be to use fill_between. But perhaps not in the way it was intended. Instead of using it to create your line, use it to mask everything that is not the line. Under it you can have a pcolormesh or contourf (for example) to map color any way you want.
Look, for instance, at this example:
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d
def windline(x,y,deviation,color):
y1 = y-deviation/2
y2 = y+deviation/2
tol = (y2.max()-y1.min())*0.05
X, Y = np.meshgrid(np.linspace(x.min(), x.max(), 100), np.linspace(y1.min()-tol, y2.max()+tol, 100))
Z = X.copy()
for i in range(Z.shape[0]):
Z[i,:] = c
#plt.pcolormesh(X, Y, Z)
plt.contourf(X, Y, Z, cmap='seismic')
plt.fill_between(x, y2, y2=np.ones(x.shape)*(y2.max()+tol), color='w')
plt.fill_between(x, np.ones(x.shape) * (y1.min() - tol), y2=y1, color='w')
plt.xlim(x.min(), x.max())
plt.ylim(y1.min()-tol, y2.max()+tol)
plt.show()
x = np.arange(100)
yo = np.random.randint(20, 60, 21)
y = interp1d(np.arange(0, 101, 5), yo, kind='cubic')(x)
dv = np.random.randint(2, 10, 21)
d = interp1d(np.arange(0, 101, 5), dv, kind='cubic')(x)
co = np.random.randint(20, 60, 21)
c = interp1d(np.arange(0, 101, 5), co, kind='cubic')(x)
windline(x, y, d, c)
, which results in this:
The function windline accepts as arguments numpy arrays with x, y , a deviation (like a thickness value per x value), and color array for color mapping. I think it can be greatly improved by messing around with other details but the principle, although not perfect, should be solid.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(0,4*np.pi,10000) # x data
y = np.cos(x) # y data
r = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: 1-x/(2*np.pi), 0]) # red
g = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: x/(2*np.pi), lambda x: -x/(2*np.pi)+2]) # green
b = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [0, lambda x: x/(2*np.pi)-1]) # blue
a = np.ones(10000) # alpha
w = x # width
fig, ax = plt.subplots(2)
ax[0].plot(x, r, color='r')
ax[0].plot(x, g, color='g')
ax[0].plot(x, b, color='b')
# mysterious parts
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# mysterious parts
rgba = list(zip(r,g,b,a))
lc = LineCollection(segments, linewidths=w, colors=rgba)
ax[1].add_collection(lc)
ax[1].set_xlim(0,4*np.pi)
ax[1].set_ylim(-1.1,1.1)
fig.show()
I notice this is what I suffered.
The issue is that this script is not able to plot a sphere for example while it is able to plot several cones such as the one in the script.
I have changes the shape and tried finding the lines from which the error comes from using the error message given when plotting a sphere.
import sympy as sy
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
# Plot Figure
nd = 50 # Number of points in graph
ax = plt.axes(projection='3d') # Adds 3d axis to figure
x1 = np.linspace(-15, 15, nd)
y1 = np.linspace(-15, 15, nd)
X, Y = np.meshgrid(x1, y1) # Create 2D grid with x1 and y1
i = 0
a = 0
b = 0
Z = np.array([])
x = sy.Symbol('x')
y = sy.Symbol('y')
z = (x**2+y**2)**0.5 # Function goes here
for i in range(nd): # Iterate over rows
b = 0
xv1 = X[a, :]
yv1 = Y[a, :]
for i in range(nd): # Iterate over elements in one row
xv = xv1[b]
yv = yv1[b]
z1 = z.subs([(x, xv), (y, yv)])
Z = np.append(Z, z1) # Append values to array just a row
b = b + 1
a = a + 1
Z = np.reshape(Z, (nd, nd))
print(Z.dtype)
print(Y.dtype)
print(X.dtype)
Z = Z.astype(float)
Y = Y.astype(float)
X = X.astype(float)
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis', edgecolor='none')
plt.show()
The result with a sphere function is that the script crashes. I would hope this script should be able to graph this kind of 3D shapes.
Is this the error you are getting by any chance?
TypeError: can't convert complex to float
The way this is formulated you are asking for an imaginary number back. If you define this as your sphere equation:
z = (x**2+y**2-1)**0.5
you will end up asking for sqrt(-1) when x=y=0, which will not work. Try parameterizing with spherical coordinates like in this example: Python/matplotlib : plotting a 3d cube, a sphere and a vector?
How do I generate a trapezoidal wave in Python?
I looked into the modules such as SciPy and NumPy, but in vain. Is there a module such as the scipy.signal.gaussian which returns an array of values representing the Gaussian function wave?
I generated this using the trapezoidal kernel of Astropy,
Trapezoid1DKernel(30,slope=1.0)
. I want to implement this in Python without using Astropy.
While the width and the slope are sufficient to define a triangular signal, you would need a third parameter for a trapezoidal signal: the amplitude.
Using those three parameters, you can easily adjust the scipy.signal.sawtooth function to give you a trapeziodal shape by truncating and offsetting the triangular shaped function.
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a[a>amp/2.] = amp/2.
a[a<-amp/2.] = -amp/2.
return a + amp/2. + offs
t = np.linspace(0, 6, 501)
plt.plot(t,trapzoid_signal(t, width=2, slope=2, amp=1.), label="width=2, slope=2, amp=1")
plt.plot(t,trapzoid_signal(t, width=4, slope=1, amp=0.6), label="width=4, slope=1, amp=0.6")
plt.legend( loc=(0.25,1.015))
plt.show()
Note that you may also like to define a phase, depeding on the use case.
In order to define a single pulse, you might want to modify the function a bit and supply an array which ranges over [0,width].
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a += slope*width/4.
a[a>amp] = amp
return a + offs
for w,s,a in zip([2,5], [2,1], [1,0.6]):
t = np.linspace(0, w, 501)
l = "width={}, slope={}, amp={}".format(w,s,a)
plt.plot(t,trapzoid_signal(t, width=w, slope=s, amp=a), label=l)
plt.legend( loc="upper right")
plt.show()
From the SciPy website it looks like this isn't included (they currently have sawtooth and square, but not trapezoid). As a generalised version of the C example the following will do what you want,
import numpy as np
import matplotlib.pyplot as plt
def trapezoidalWave(xin, width=1., slope=1.):
x = xin%(4*width)
if (x <= width):
# Ascending line
return x*slope;
elif (x <= 2.*width):
# Top horizontal line
return width*slope
elif (x <= 3.*width):
# Descending line
return 3.*width*slope - x*slope
elif (x <= 4*width):
# Bottom horizontal line
return 0.
x = np.linspace(0.,20,1000)
for i in x:
plt.plot(i, trapezoidalWave(i), 'k.')
plt.plot(i, trapezoidalWave(i, 1.5, 2.), 'r.')
plt.show()
which looks like,
This can be done more elegantly with Heaviside functions which allow you to use NumPy arrays,
import numpy as np
import matplotlib.pyplot as plt
def H(x):
return 0.5 * (np.sign(x) + 1)
def trapWave(xin, width=1., slope=1.):
x = xin%(4*width)
y = ((H(x)-H(x-width))*x*slope +
(H(x-width)-H(x-2.*width))*width*slope +
(H(x-2.*width)-H(x-3.*width))*(3.*width*slope - x*slope))
return y
x = np.linspace(0.,20,1000)
plt.plot(x, trapWave(x))
plt.plot(x, trapWave(x, 1.5, 2.))
plt.show()
For this example, the Heaviside version is about 20 times faster!
The below example shows how to do that to get points and show scope.
Equation based on reply: Equation for trapezoidal wave equation
import math
import numpy as np
import matplotlib.pyplot as plt
def get_wave_point(x, a, m, l, c):
# Equation from: https://stackoverflow.com/questions/11041498/equation-for-trapezoidal-wave-equation
# a/pi(arcsin(sin((pi/m)x+l))+arccos(cos((pi/m)x+l)))-a/2+c
# a is the amplitude
# m is the period
# l is the horizontal transition
# c is the vertical transition
point = a/math.pi*(math.asin(math.sin((math.pi/m)*x+l))+math.acos(math.cos((math.pi/m)*x+l)))-a/2+c
return point
print('Testing wave')
x = np.linspace(0., 10, 1000)
listofpoints = []
for i in x:
plt.plot(i, get_wave_point(i, 5, 2, 50, 20), 'k.')
listofpoints.append(get_wave_point(i, 5, 2, 50, 20))
print('List of points : {} '.format(listofpoints))
plt.show()
The whole credit goes to #ImportanceOfBeingErnest . I am just revising some edits to his code which just made my day.
from scipy import signal
import matplotlib.pyplot as plt
from matplotlib import style
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a += slope*width/4.
a[a>amp] = amp
return a + offs
for w,s,a in zip([32],[1],[0.0322]):
t = np.linspace(0, w, 34)
plt.plot(t,trapzoid_signal(t, width=w, slope=s, amp=a))
plt.show()
The result:
I'll throw a very late hat into this ring, namely, a function using only numpy that produces a single (symmetric) trapezoid at a desired location, with all the usual parameters. Also posted here
import numpy as np
def trapezoid(x, center=0, slope=1, width=1, height=1, offset=0):
"""
For given array x, returns a (symmetric) trapezoid with plateau at y=h (or -h if
slope is negative), centered at center value of "x".
Note: Negative widths and heights just converted to 0
Parameters
----------
x : array_like
array of x values at which the trapezoid should be evaluated
center : float
x coordinate of the center of the (symmetric) trapezoid
slope : float
slope of the sides of the trapezoid
width : float
width of the plateau of the trapezoid
height : float
(positive) vertical distance between the base and plateau of the trapezoid
offset : array_like
vertical shift (either single value or the same shape as x) to add to y before returning
Returns
-------
y : array_like
y value(s) of trapezoid with above parameters, evaluated at x
"""
# ---------- input checking ----------
if width < 0: width = 0
if height < 0: height = 0
x = np.asarray(x)
slope_negative = slope < 0
slope = np.abs(slope) # Do all calculations with positive slope, invert at end if necessary
# ---------- Calculation ----------
y = np.zeros_like(x)
mask_left = x - center < -width/2.0
mask_right = x - center > width/2.0
y[mask_left] = slope*(x[mask_left] - center + width/2.0)
y[mask_right] = -slope*(x[mask_right] - center - width/2.0)
y += height # Shift plateau up to y=h
y[y < 0] = 0 # cut off below zero (so that trapezoid flattens off at "offset")
if slope_negative: y = -y # invert non-plateau
return y + offset
Which outputs something like
import matplotlib.pyplot as plt
plt.style.use("seaborn-colorblind")
x = np.linspace(-5,5,1000)
for i in range(1,4):
plt.plot(x,trapezoid(x, center=0, slope=1, width=i, height=i, offset = 0), label=f"width = height = {i}\nslope=1")
plt.plot(x,trapezoid(x, center=0, slope=-1, width=2.5, height=1, offset = 0), label=f"width = height = 1.5,\nslope=-1")
plt.ylim((-2.5,3.5))
plt.legend(frameon=False, loc='lower center', ncol=2)
Example output: