art3d.pathpatch_2d_to_3d of paches.arc gives a full circle - python-3.x

I tried to draw an NBA court using matplotlib. I found the code from here but it's a 2D drawing. I modified a few lines and using art3d.pathpatch_2d_to_3d to transform all patches to a 3d plot. Everything works well except for Arc which shows a full circle.
from matplotlib.patches import Circle, Rectangle, Arc
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import art3d
def draw_court(ax=None, color='black', lw=2, outer_lines=False, ax_3d=False):
# If an axes object isn't provided to plot onto, just get current one
if ax is None:
ax = plt.gca()
# Create the various parts of an NBA basketball court
# Create the basketball hoop
# Diameter of a hoop is 18" so it has a radius of 9", which is a value
# 7.5 in our coordinate system
hoop = Circle((0, 0), radius=7.5, linewidth=lw, color=color, fill=False)
# Create backboard
backboard = Rectangle((-30, -7.5), 60, -1, linewidth=lw, color=color)
# The paint
# Create the outer box 0f the paint, width=16ft, height=19ft
outer_box = Rectangle((-80, -47.5), 160, 190, linewidth=lw, color=color,
fill=False)
# Create the inner box of the paint, widt=12ft, height=19ft
inner_box = Rectangle((-60, -47.5), 120, 190, linewidth=lw, color=color,
fill=False)
# Create free throw top arc
top_free_throw = Arc((0, 142.5), 120, 120, theta1=0, theta2=180,
linewidth=lw, color=color, fill=False)
# Create free throw bottom arc
bottom_free_throw = Arc((0, 142.5), 120, 120, theta1=180, theta2=0,
linewidth=lw, color=color, linestyle='dashed')
# Restricted Zone, it is an arc with 4ft radius from center of the hoop
restricted = Arc((0, 0), 80, 80, theta1=0, theta2=180, linewidth=lw,
color=color)
# Three point line
# Create the side 3pt lines, they are 14ft long before they begin to arc
corner_three_a = Rectangle((-220, -47.5), 0, 140, linewidth=lw,
color=color)
corner_three_b = Rectangle((220, -47.5), 0, 140, linewidth=lw, color=color)
# 3pt arc - center of arc will be the hoop, arc is 23'9" away from hoop
# I just played around with the theta values until they lined up with the
# threes
three_arc = Arc((0, 0), 475, 475, theta1=22, theta2=158, linewidth=lw,
color=color)
# Center Court
center_outer_arc = Arc((0, 422.5), 120, 120, theta1=180, theta2=0,
linewidth=lw, color=color)
center_inner_arc = Arc((0, 422.5), 40, 40, theta1=180, theta2=0,
linewidth=lw, color=color)
# List of the court elements to be plotted onto the axes
court_elements = [hoop, backboard, outer_box, inner_box, top_free_throw,
bottom_free_throw, restricted, corner_three_a,
corner_three_b, three_arc, center_outer_arc,
center_inner_arc]
if outer_lines:
# Draw the half court line, baseline and side out bound lines
outer_lines = Rectangle((-250, -47.5), 500, 470, linewidth=lw,
color=color, fill=False)
court_elements.append(outer_lines)
# Add the court elements onto the axes
for element in court_elements:
ax.add_patch(element)
if ax_3d:
art3d.patch_2d_to_3d(element, z=0, zdir="z")
return ax
if __name__ == "__main__":
fig = plt.figure()
ax = fig.add_subplot(121, projection='3d')
ax.set(xlim3d=(-300, 500), xlabel='X')
ax.set(ylim3d=(-300, 500), ylabel='Y')
ax.set(zlim3d=(0, 300), zlabel='Z')
draw_court(lw=1, outer_lines=True, ax_3d=True)
ax2 = fig.add_subplot(122)
ax2.set_xlim(-300, 500)
ax2.set_ylim(-100, 500)
draw_court(outer_lines=True)
plt.show()
Result:
Version:
matplotlib 3.5.3
I found the same problem here which is a known bug. However, I can not find the solution.
Edited:
I found the solution from the discussion here.
I just edited patches.py and added
self._path = Path.arc(self.theta1, self.theta2)
to the Arc class init function. (in my case, line 1913).
Edited result:
However, following the discuss, they said the arc length is incorrect. In my case, the problem is that, there is additional line from one end to another end of the Arc.
Is there another solution for this problem?
Thank you.

Related

Create line from list of points while ignoring outliers

I have a list of points that almost create a straight line (but they are not perfectly align on that line). I want to create a line that best describes those points.
For example, for points:
points = [(150, 250),(180, 220), (200, 195), (225, 180), (250, 150), (275, 115), (300, 100)]
I want to create line similar to this:
The problem is that sometimes there are points that are very far from that line (outliers). I want to ignore those outliers while creating the line:
How can I create this line?
P.S. this is the code for colab to generate the points:
import numpy as np
import cv2
from google.colab.patches import cv2_imshow
img = np.zeros([400,500,3],dtype=np.uint8)
points = [(150, 250),(180, 225), (200, 200), (225, 100), (250, 150), (275, 115), (300, 100)]
#points = [(150, 250),(180, 220), (200, 195), (225, 180), (250, 150), (275, 115), (300, 100)]
for idx, p in enumerate(points):
img = cv2.circle(img, p, radius=0, color=(0, 0, 255), thickness=10)
text_x, text_y = p
p = round(text_x-20), round(text_y+5)
img = cv2.putText(img=img, text=str(idx), fontFace=cv2.FONT_HERSHEY_SCRIPT_COMPLEX, org=p, fontScale=0.5, color=(0,255,0))
image = cv2.line(img, points[0], points[-1], (255, 0, 255), 1)
cv2_imshow(img)
In my code, I generate the line between first and last element of the list of points, so of course if the last point is outlier, all the line is disrupted:
Thanks for #Christoph Rackwitz's answer, I followed sklearn's doc for RANSAC, and created simple script to calculate the RANSAC (of course that it's need to be polished):
import numpy as np
from matplotlib import pyplot as plt
from sklearn import linear_model, datasets
"""
Add points:
"""
points = [(150, 250),(175, 225), (200, 200), (225, 175), (250, 150), (275, 115), (300, 150)]
Y = []
X = []
for x,y in points:
Y.append(y)
X.append(x)
Y = np.array(Y)
X = np.array(X)
lr = linear_model.LinearRegression()
lr.fit(X.reshape(-1, 1), Y)
# Robustly fit linear model with RANSAC algorithm
ransac = linear_model.RANSACRegressor()
ransac.fit(X.reshape(-1, 1), Y)
inlier_mask = ransac.inlier_mask_
outlier_mask = np.logical_not(inlier_mask)
# Predict data of estimated models
line_X = np.arange(X.min(), X.max())[:, np.newaxis]
line_y = lr.predict(line_X)
line_y_ransac = ransac.predict(line_X)
# Compare estimated coefficients
print("Estimated coefficients (true, linear regression, RANSAC):")
print(coef, lr.coef_, ransac.estimator_.coef_)
lw = 2
plt.gca().invert_yaxis() # Mirror points
plt.scatter(
X[inlier_mask], Y[inlier_mask], color="yellowgreen", marker=".", label="Inliers"
)
plt.scatter(
X[outlier_mask], Y[outlier_mask], color="gold", marker=".", label="Outliers"
)
plt.plot(line_X, line_y, color="navy", linewidth=lw, label="Linear regressor")
plt.plot(
line_X,
line_y_ransac,
color="cornflowerblue",
linewidth=lw,
label="RANSAC regressor",
)
plt.legend(loc="lower right")
plt.xlabel("Input")
plt.ylabel("Response")
plt.show()
And I got the following image (which looks great):

Do not plot gridlines/contourlines/coastlines outside of the canvas with cartopy

I'm plotting a map panel with cartopy in eps format. The resulting plot looks fine but has very broad margins when I insert it into my latex document. When checking the plot with adobe illustrator, it seems like the cartopy plots all the gridlines/contourlines/coastlines, even those outside of the canvas, which are hidden but do take up some spaces in the plot.
I tried to use constrained_layout and tight_layout, but they are incompatible with subplots_adjust which I use for adding the shared colorbar.
The code I use to plot is as follows:
proj2 = ccrs.LambertConformal(central_longitude=0, central_latitude=50)
proj_lonlat = ccrs.PlateCarree()
fig = plt.figure(figsize=(12, 9), constrained_layout=True)
# define a function to plot
def plot_era5_500z_MSLp(f500, fsurf, time, label, ax):
# read data
for i in np.arange(len(f500.time.values)):
if pd.to_datetime(f500.time.values[i]) == pd.to_datetime(time):
print('processing time: ' + time)
lons = f500.longitude.values # 1-d array
lats = f500.latitude.values # 1-d array
gph500 = f500.z.values[i,:,:]/98 # geopotential (m2 s-2) -> geopotential height (dagpm) [time = 72, lat = 241, lon = 561]
pmsl = fsurf.msl.values[i,:,:]/100 # mean sea level pressure Pa -> hPa
# create base map
ax.set_extent([-35, 30, 25, 70]) # x0, x1, y0, y1
gl = ax.gridlines(crs=proj_lonlat, draw_labels=True, xlocs=[-60,-40,-20,0,20,40,60], ylocs=[20,30,40,50,60],
x_inline=False, y_inline=False, color='k', alpha=0.5, linestyle='dotted')
gl.top_labels=False
gl.right_labels=False
gl.xlabel_style = {'size': 14, 'color': 'k'}
gl.ylabel_style = {'size': 14, 'color': 'k'}
gl.rotate_labels = False
ax.add_feature(cfeature.COASTLINE.with_scale('50m'), lw=0.4, alpha=0.9) # add coastline feature
# plot 500hPa geopotential height (zc: z contour)
z_levels = np.arange(500, 580+10, 8)
zc = ax.contour(lons, lats, gph500, transform=proj_lonlat,
levels=z_levels, extent='both', colors='mediumblue', linewidths=0.5)
ax.clabel(zc, inline=True, fontsize=10, fmt='%.0f')
# plot MSL pressure (mslps: MSL p shading; mslpc: MSL p contour)
levels = np.arange(960, 1057, 4)
mslps = ax.contourf(lons, lats, pmsl, levels=levels, cmap='Spectral_r', transform=proj_lonlat)
mslpc = ax.contour(lons, lats, pmsl, levels=levels, colors='k', linewidths=0.5, alpha=0.6, transform=proj_lonlat)
ax.set_title(label + ' ' + time, loc= 'left', pad=0.5, fontsize=14)
return mslps
# fig (a)
ax1 = plt.subplot(2, 2, 1, projection=proj2)
plot_era5_500z_MSLp(f500_2016nov, fsurf_2016nov, '2016-11-20 12:00', '(a)', ax1)
# fig (b)
ax2 = plt.subplot(2, 2, 2, projection=proj2)
plot_era5_500z_MSLp(f500_2016nov, fsurf_2016nov, '2016-11-24 00:00', '(b)', ax2)
# fig (c)
ax3 = plt.subplot(2, 2, 3, projection=proj2)
plot_era5_500z_MSLp(f500_2017feb, fsurf_2017feb, '2017-02-27 18:00', '(c)', ax3)
# fig (4)
ax4 = plt.subplot(2, 2, 4, projection=proj2)
mslps = plot_era5_500z_MSLp(f500_2017mar, fsurf_2017mar, '2017-03-04 06:00', '(d)', ax4) # only return mslps here for plotting the sharred colorbar
fig.subplots_adjust(right=0.8, wspace=0.2, hspace=0.000001)
cbar_ax = fig.add_axes([0.82, 0.2, 0.02, 0.55]) # left border, bottom border, width, height
cbar = fig.colorbar(mslps, cax=cbar_ax)
cbar.set_label(label='Mean sea level pressure (hPa)', size=16)
cbar.ax.tick_params(labelsize=14)
The resulting eps plot looks good, but in adobe illustrator, one can see the excess lines outside of the canvas:
Is there any way I can limit the plotting range of the data, or disable the lines outside of the canvas?

How to fill areas between curves with different scales in a plot?

I have a dataframe with three features: DEPTH, PERMEABILITY and POROSITY. And I would like to plot DEPTH at y axis and PERMEABILITY and POROSITY together at x axis, although these last two features have different scales.
df = pd.DataFrame({'DEPTH(m)': [100, 150, 200, 250, 300, 350, 400, 450, 500, 550],
'PERMEABILITY(mD)': [1000, 800, 900, 600, 200, 250, 400, 300, 100, 200],
'POROSITY(%)': [0.30, 0.25, 0.15, 0.19, 0.15, 0.10, 0.15, 0.19, 0.10, 0.15]})
I already managed to plot them together, but now I need to fill with two different colors the areas between the curves. For example, when PERMEABILITY curve is on the right side of POROSITY, the area between them should be green. If PERMEABILITY is on the left side, the area between curves should be yellow.
f, ax1 = plt.subplots()
ax1.set_xlabel('PERMEABILITY(mD)')
ax1.set_ylabel('DEPTH(m)')
ax1.set_ylim(df['DEPTH(m)'].max(), df['DEPTH(m)'].min())
ax1.plot(df['PERMEABILITY(mD)'], df['DEPTH(m)'], color='red')
ax1.tick_params(axis='x', labelcolor='red')
ax2 = ax1.twiny()
ax2.set_xlabel('POROSITY(%)')
ax2.plot(df['POROSITY(%)'], df['DEPTH(m)'], color='blue')
ax2.tick_params(axis='x', labelcolor='blue')
So the right output should be like this: (Sorry for the Paint image below)
Anyone could help me with this?
You can use the fill_betweenx() function, however you need to convert one of your axis to the scale of the other one, because you use twiny. Below, I converted your POROSITY data to fit to the axis of PERMEABILITY.
Then you can use two conditional fill_betweenx, where the two curves are larger than each other, to assign different colors to those patches. Also, since your data is discrete, you need to set interpolate=True in your fill_betweenx functions.
f, ax1 = plt.subplots()
ax1.set_xlabel('PERMEABILITY(mD)')
ax1.set_ylabel('DEPTH(m)')
ax1.set_ylim(df['DEPTH(m)'].max(), df['DEPTH(m)'].min())
ax1.plot(df['PERMEABILITY(mD)'], df['DEPTH(m)'], color='red')
ax1.tick_params(axis='x', labelcolor='red')
ax2 = ax1.twiny()
ax2.set_xlabel('POROSITY(%)')
ax2.plot(df['POROSITY(%)'], df['DEPTH(m)'], color='blue')
ax2.tick_params(axis='x', labelcolor='blue')
# convert POROSITY axis to PERMEABILITY
# value-min / range -> normalized POROSITY (normp)
# normp*newrange + newmin -> stretched POROSITY to PERMEABILITY
z=df['POROSITY(%)']
x=df['PERMEABILITY(mD)']
nz=((z-np.min(z))/(np.max(z)-np.min(z)))*(np.max(x)-np.min(x))+np.min(x)
# fill between in green where PERMEABILITY is larger
ax1.fill_betweenx(df['DEPTH(m)'],x,nz,where=x>=nz,interpolate=True,color='g')
# fill between in yellow where POROSITY is larger
ax1.fill_betweenx(df['DEPTH(m)'],x,nz,where=x<=nz,interpolate=True,color='y')
plt.show()
The result is as below (I might have used different colors, but I assume that's not a concern).

How to delete the borders and add the name in the bar itself

I would like to delete the lines which are actually shown in the picture and also put the number (their values) in each graph, I mean the value which belong to each one. How can I do it?
The values are from a data set taken from Kaggle.
Here is some code to help you get the requested layout.
The states and the numbers are from Wikipedia.
import matplotlib.pyplot as plt
states = ['Acre', 'Alagoas', 'Amazonas', 'Amapá', 'Bahia', 'Ceará', 'Federal District',
'Espírito Santo', 'Goiás', 'Maranhão', 'Minas Gerais', 'Mato Grosso do Sul',
'Mato Grosso', 'Pará', 'Paraíba', 'Pernambuco', 'Piauí', 'Paraná', 'Rio de Janeiro',
'Rio Grande do Norte', 'Rondônia', 'Roraima', 'Rio Grande do Sul', 'Santa Catarina',
'Sergipe', 'São Paulo', 'Tocantins']
fires = [2918, 73, 7625, 24, 2383, 327, 68, 229, 1786, 5596, 2919, 451, 15476, 10747, 81, 132,
2818, 181, 396, 68, 6441, 4608, 2029, 1107, 62, 1616, 6436]
fires, states = zip(*sorted(zip(fires, states))) #sort both arrays on number of fires
fires = fires[-15:] # limit to the 15 highest numbers
states = states[-15:]
fig, ax = plt.subplots(figsize=(8, 6))
ax.barh(states, fires, color="#08519c")
plt.box(False) # remove the complete box around the plot
plt.xticks([]) # remove all the ticks on the x-axis
ax.yaxis.set_ticks_position('none') # removes the tick marks on the y-axis but leaves the text
for i, v in enumerate(fires):
ax.text(v + 180, i, f'{v:,}'.replace(',', '.'), color='#08519c', fontweight='normal', ha='left', va='center')
plt.subplots_adjust(left=0.22) # more space to read the names
plt.title('Wildfires Brazil 2019', fontsize=20, y=0.98) # title larger and a bit lower
plt.show()
PS: about
for i, v in enumerate(fires):
ax.text(v + 180, i, f'{v:,}'.replace(',', '.'), color='#08519c', fontweight='normal', ha='left', va='center')
This has a v going through each element of fires, one by one. i is the index for which fires[i] == b. ax.text(x, y, 'some text') puts a text on a certain position, where they are measured with the same distances as those marked on the axes (that's why default the axes are shown). When the axes are just text instead of numbers, they are numbered internally 0, 1, 2, 3, ... . So, x=v + 180 is the x-position where number-of-fires v+180 would be. And y=i means just the position of label number i.

Stop axis from expanding matplotlib

I've been using the code from the site bellow to create and use check buttons for my subplot lines:
https://matplotlib.org/gallery/widgets/check_buttons.html
But i can't seem to keep the check button axes (rax) from expanding when i pull on the margins of the figure window, i would like only the plot with lines to expand. I've tried this but it doesn't seem to do the job:
t = np.arange(0.0, 2.0, 0.01)
s0 = np.sin(2*np.pi*t)
s1 = np.sin(4*np.pi*t)
s2 = np.sin(6*np.pi*t)
fig, ax = plt.subplots()
l0, = ax.plot(t, s0, visible=False, lw=2, color='k', label='2 Hz')
l1, = ax.plot(t, s1, lw=2, color='r', label='4 Hz')
l2, = ax.plot(t, s2, lw=2, color='g', label='6 Hz')
plt.subplots_adjust(left=0.2)
lines = [l0, l1, l2]
rax = plt.axes([0.05, 0.4, 0.1, 0.15])
rax.autoscale(enable=FALSE, tight=TRUE) #this is the part i don't want expanding
labels = [str(line.get_label()) for line in lines]
visibility = [line.get_visible() for line in lines]
check = CheckButtons(rax, labels, visibility)
def func(label):
index = labels.index(label)
lines[index].set_visible(not lines[index].get_visible())
plt.draw()
check.on_clicked(func)
plt.show()
Is there a way the do this?
Thanks!
The question can be translated into how to position an axes in figure coordinates with a fixed width and height in absolute (pixel) coordinates. This can be done via setting the axes locator to a
mpl_toolkits.axes_grid1.inset_locator.AnchoredSizeLocator via ax.set_axes_locator.
import matplotlib.pyplot as plt
import matplotlib.transforms as mtrans
from mpl_toolkits.axes_grid1.inset_locator import AnchoredSizeLocator
fig, ax = plt.subplots()
# Create axes, which is positionned in figure coordinates,
# with width and height fixed in inches.
# axes extent in figure coordinates (width & height ignored)
axes_extent = [0.03, 0.5, 0, 0]
# add axes to figure
rax = fig.add_axes(axes_extent)
# create locator: Position at (0.03, 0.5) in figure coordinates,
# 0.7 inches wide and tall, pinned at left center of bbox.
axes_locator = AnchoredSizeLocator(mtrans.Bbox.from_bounds(*axes_extent),
.7, .7, loc="center left",
bbox_transform=fig.transFigure,
borderpad=0)
rax.set_axes_locator(axes_locator)
Now, when the figure size changes, the axes will stay at the same relative position without changing its width and height.

Resources