How to return string composed of superscript inside of fraction for matplotlib legend label? [duplicate] - python-3.x

This question already has answers here:
use format with tex fraction expression in matplotlib python
(2 answers)
Closed 4 years ago.
I have coded a routine that plots x and y data. This code also calculates the indices at which the nth derivative d/dx^n (y) changes sign, where n is specified as a function argument. I would like to include this derivative as a legend label in a matplotlib figure.
I can create a label to include this if it is predetermined. As an example, if n was pre-determined to be 2, then:
label = r'$\frac{d^2y}{dx^2}$'
But since n is a function argument, I do not know how to assign it to the fraction. As an example (containing failed attempts), see below:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 10, 10)
y = x
def f(x, y, n):
""" """
fig, ax = plt.subplots()
if n == 1:
label = r'$\frac{dy}{dx} = 0$'
else:
numerator = 'd^{}y'.format(n)
denominator = 'dx^{}'.format(n)
# label = r'$\frac{}{}$'.format(numerator, denominator)
# label = '$\frac{}{}$'.format(numerator, denominator)
# label = '$\frac{numerator}{denominator}$'
label = r'$\frac{numerator}{denominator}$'
ax.scatter(x, y, c='r', marker='.', s=5, label=label)
ax.legend(loc='upper left')
plt.show()
plt.close(fig)
f(x, y, n=1)
f(x, y, n=2)
I am only concerned with the legend label. How can I make it such that I get the desired output of a string fraction, the numerator of which appears as r'$d^ny$' and the denominator of which appears as r'$dx^n$' (where n is a number)?

You can do it using the string formatting syntax %s as following:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 10, 10)
y = x
def f(x, y, n):
""" """
fig, ax = plt.subplots()
if n == 1:
label = r'$\frac{dy}{dx} = 0$'
else:
numerator = 'd^{}y'.format(n)
denominator = 'dx^{}'.format(n)
label = r'$\frac{%s}{%s}$' %(numerator, denominator)
ax.scatter(x, y, c='r', marker='.', s=5, label=label)
ax.legend(loc='upper left', fontsize=18)
plt.show()
plt.close(fig)

Related

Find coordinate on curve

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

Draw curves with triple colors and width by using matplotlib and LineCollection [duplicate]

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.

Colormaps with a colorscale AND one color for unwanted values

I would like to plot a grid of 3 variables (same min, same max, same spacing) in 3D and I would like each point on the grid to have a specific color according to a function f which is a function of these 3 variables except for when the values of the function are superior to a specific threshold for which I assign another color.
The code below as what I have tried so far:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.colors
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
import math
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
%matplotlib notebook
x = np.arange(0.001, 0.175, 0.01)
y = np.arange(0.001, 0.175, 0.01)
z = np.arange(0.001, 0.175, 0.01)
X, Y, Z = np.meshgrid(x, y, z)
def function(X,Y,Z):
'''function of (X,Y,Z) going from 0 to high values'''
return(f)
f=function(X,Y,Z)
#flatten the f array (I think there is a function to flatten an array but I have seen it to late)
fflat=[]
for l in f:
for p in l:
for t in p:
fflat.append(t)
#masking high values with the highest interesting value: maxV
mfflat = ma.masked_greater(fflat, maxV)
mfflat = mfflat.filled(maxV)
#normalizing values and mapping to veridis cmap:
cmap = matplotlib.cm.get_cmap('viridis')
norm = matplotlib.colors.Normalize(vmin=min(mfflat), vmax=maxV) #(vmax=maxV=max(mfflat))
colors = [cmap(norm(value)) for value in mfflat]
#plot
ax.scatter(X, Y, Z, color=colors, s=10, alpha=1)
cax, _ = matplotlib.colorbar.make_axes(ax)
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm)
The problem is that now all the "unwanted high values", i.e. values > maxV have the same colors as my "maximal wanted values", i.e. maxV ...
I would like all my "unwanted values" outside of my veridis colorscale and giving them another unique color.
Thanks for your help !
Cheers
Thanks to ImportanceOfBeingErnest for the answer, I just had to use: cmap.set_over, here is the corrected code:
x = np.arange(0.001, 0.175, 0.01)
y = np.arange(0.001, 0.175, 0.01)
z = np.arange(0.001, 0.175, 0.01)
X, Y, Z = np.meshgrid(x, y, z)
def function(X,Y,Z):
'''function of (X,Y,Z) going from 0 to high values'''
return(f)
f=function(X,Y,Z)
#flatten the f array (I think there is a function to flatten an array but I have seen it to late)
fflat=[]
for l in f:
for p in l:
for t in p:
fflat.append(t)
cmap = plt.cm.get_cmap('viridis')
cmap.set_over(color=(0,0,0), alpha=0.5)
norm = matplotlib.colors.Normalize(vmin=minV, vmax=maxV)
colors = [cmap(norm(value)) for value in fflat]
#plot
ax.scatter(X, Y, Z, color=colors, s=10, alpha=1)
cax, _ = matplotlib.colorbar.make_axes(ax)
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm)
In the meantime I have found a workaround by creating the "colors" list selectively which is of course not as clean as using cmap.set_over:
colors=[]
for value in fflat:
if minV <= value <= maxV:
colors.append(cmap(norm(value)))
else:
colors.append((255/258,255/258,255/258,0))

Is it possible to edit the inline labels of a contour plot after the inline label values are generated?

Below is an example code to generate a contour plot with an inline label. I would like to know how I can edit the inline label.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 100)
y = np.linspace(-10, 10, 100)
def z_func(x, y):
""" z = z(x,y) ==> Z = Z(X, Y) """
X, Y = np.meshgrid(x, y)
Z = np.sqrt(X**2 + Y**2)
return X, Y, Z
def get_xyz_contour_plot(X, Y, Z, cmap='plasma', ncontours=6, linecolor='white'):
""" Generates a filled contour plot with inline labels """
plt.contourf(X, Y, Z, cmap=cmap)
contours = plt.contour(X, Y, Z, ncontours, colors=linecolor)
plt.clabel(contours, inline=True, fontsize=8)
plt.show()
X, Y, Z = z_func(x, y)
get_xyz_contour_plot(X, Y, Z)
The code above generates a plot that looks like this. If I wanted to add a negative sign to the inline label, I could just apply a negative sign in the example above. But for my actual purpose, I am making a contour plot of the pvalue that is associated with a chi square value. The code is too long to post here (hence the alternative example above), but I minimize the negative pvalue associated with chi square rather than chi square itself (via scipy). As such, my function produces a negative output and the inline label shows a negative sign.
Is it possible to edit the inline label by removing the negative sign after the inline label values have been generated? As an example, how could I add a negative sign to the inline labels in the code above without changing z_func?
You may specify a fmt to the clabel, which may be a matplotlib formatter instance. You could use a FuncFormatter with a function the just reverses the sign of the value before formatting it.
fmt_func = lambda x,pos: "{:1.3f}".format(-x)
fmt = matplotlib.ticker.FuncFormatter(fmt_func)
plt.clabel(contours, inline=True, fontsize=8, fmt=fmt)
Complete example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker
x = np.linspace(-10, 10, 100)
y = np.linspace(-10, 10, 100)
def z_func(x, y):
""" z = z(x,y) ==> Z = Z(X, Y) """
X, Y = np.meshgrid(x, y)
Z = np.sqrt(X**2 + Y**2)
return X, Y, Z
def get_xyz_contour_plot(X, Y, Z, cmap='plasma', ncontours=6, linecolor='white'):
""" Generates a filled contour plot with inline labels """
plt.contourf(X, Y, Z, cmap=cmap)
contours = plt.contour(X, Y, Z, ncontours, colors=linecolor)
fmt_func = lambda x,pos: "{:1.3f}".format(-x)
fmt = matplotlib.ticker.FuncFormatter(fmt_func)
plt.clabel(contours, inline=True, fontsize=8, fmt=fmt)
plt.show()
X, Y, Z = z_func(x, y)
get_xyz_contour_plot(X, Y, Z)

Trapezoidal wave in Python

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:

Resources