Matplotlib: How to draw edge lines in Arc - patches - python

Given a center and two angles of a rotated ellipse of Arc from matplotlib.patches, I want to plot the two lines starting from the center of the Arc to the ends of the Arc.
Here is a piece of code that does that:
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
fig, ax = plt.subplots(1,1)
r = 2 #Radius
a = 0.2*r #width
b = 0.5*r #height
#center of the ellipse
x = 0.5
y = 0.5
ax.add_patch(Arc((x, y), a, b, angle = 20,
theta1=0, theta2=120,
edgecolor='b', lw=1.1))
#Now look for the ends of the Arc and manually set the limits
ax.plot([x,0.687],[y,0.567], color='r',lw=1.1)
ax.plot([x,0.248],[y,0.711], color='r',lw=1.1)
plt.show()
Which results in
.
Here the red lines were drawn looking carefully at the ends of the arc. However, as Arc does not allow to fill the arc for optimization, I wonder if there is a way to do it automatically for any center and angles.

According to Wikipedia an ellipse in its polar form looks like
Using this you may calculate the end points of the lines.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
fig, ax = plt.subplots(1,1)
r = 2 #Radius
a = 0.2*r #width
b = 0.5*r #height
#center of the ellipse
x = 0.5; y = 0.5
# angle
alpha = 20
ax.add_patch(Arc((x, y), a, b, angle = alpha,
theta1=0, theta2=120,
edgecolor='b', lw=1.1))
def ellipse(x0,y0,a,b,alpha,phi):
r = a*b/np.sqrt((b*np.cos(phi))**2 + (a*np.sin(phi))**2)
return [x0+r*np.cos(phi+alpha), y0+r*np.sin(phi+alpha)]
x1,y1 = ellipse(x, y, a/2., b/2., np.deg2rad(alpha), np.deg2rad(0))
ax.plot([x,x1],[y,y1], color='r',lw=1.1)
x2,y2 = ellipse(x, y, a/2., b/2., np.deg2rad(alpha), np.deg2rad(120))
ax.plot([x,x2],[y,y2], color='r',lw=1.1)
ax.set_aspect("equal")
plt.show()

Related

Assign line a color by its angle in matplotlib

I'm looking for a way to assign color to line plots in matplotlib in a way that's responsive to the line's angle. This is my current code:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
horz = [[0.5,0.6,0.8],[0.1,0.8,0.9],[0.2,0.5,0.9]]
vert = [[0.1,0.2,0.3],[0.05,0.1,0.15],[0.2,0.3,0.35]]
f = plt.figure(figsize=(6,6))
ax = plt.axes()
for column in range(0,len(horz)):
x = np.array(horz[column])
y = np.array(vert[column])
#LINEAR TRENDLINE
z = np.polyfit(horz[column], vert[column], 1)
p = np.poly1d(z)
ax.plot(horz[column],p(horz[column]),"-")
plt.arrow(x=horz[column][-2],y=p(horz[column])[-2],dx=(horz[column][-1]-horz[column][-2]),dy=(p(horz[column])[-1]-p(horz[column])[-2]), shape='full', lw=.01,
length_includes_head=True, head_width=.012, head_length=0.02, head_starts_at_zero=False, overhang = 0.5)
#FIG SETTINGS
plt.xlim([0, 1])
plt.ylim([0.1,0.5])
ax.set_title('Title',
fontsize = 14)
The idea here would be that if the line is at 0 degrees, it would be at one end of a given gradient, and if it were at 90 degrees, at the other end. Additionally, I'd like the line length to be taken as the intensity of the color. So if the line is short, it'd be closer to white, and if the line is long, it'd be closer to the raw color from the gradient.
Managed to solve it myself. Used pretty simple formulas for calculating the lines' slopes and distances and then used these as input for the color mapping and alpha transparency attribute.
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.colors as colors
import numpy as np
%matplotlib inline
#Data
horz = [[0.5,0.6,0.8],[0.1,0.3,0.4],[0.2,0.5,0.9],[0.9,0.95,0.95]]
vert = [[0.1,0.2,0.45],[0.05,0.1,0.15],[0.2,0.3,0.35],[0.1,0.3,0.5]]
#Slope calculation
def slopee(x1,y1,x2,y2):
x = (y2 - y1) / (x2 - x1)
return x
#Color set up
cmap = plt.cm.coolwarm_r
#0 means a horizontal line, 1 means a line at 45 degrees, infinite means a vertical line (2 is vertical enough)
cNorm = colors.Normalize(vmin=0, vmax=2)
scalarMap = cm.ScalarMappable(norm=cNorm,cmap=cmap)
#Fig settings
f = plt.figure(figsize=(6,6))
ax = plt.axes()
for column in range(0,len(horz)):
x = np.array(horz[column])
y = np.array(vert[column])
#LINEAR TRENDLINE
# 1 LINEAR
# >=2 POLINOMIAL
z = np.polyfit(horz[column], vert[column], 1)
p = np.poly1d(z)
#Distance calc formula
def calculateDistance(x1,y1,x2,y2):
dist = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return dist
#Set up max an min distances
maxdist = calculateDistance(0,0,0,0.9)
mindist = calculateDistance(0,0,0,0)
#Calculate line slope
slope = slopee(horz[column][0],p(horz[column])[0],horz[column][-1],p(horz[column])[-1])
#Not interested in any slopes going "down"
if slope >=0:
#Map colors based on slope (0-2)
colorVal = scalarMap.to_rgba(slope)
#Map transparency based on distance
transparency = (calculateDistance(horz[column][0],p(horz[column])[0],horz[column][-1],p(horz[column])[-1])-mindist)/(maxdist-mindist)
#Set up minimun transparency to be 50% instead of 0%
transparency = (0.5*transparency) + 0.5
#The actual arrow plot
plt.arrow(x=horz[column][0],y=p(horz[column])[0],dx=(horz[column][-1]-horz[column][0]),dy=(p(horz[column])[-1]-p(horz[column])[0]), shape='full',length_includes_head=True, head_starts_at_zero=False, lw=.5, head_width=.011, head_length=0.01, overhang = 0.5, color=colorVal,alpha=transparency)
#FIG SETTINGS
plt.xlim([0, 1])
plt.ylim([0,0.5])
ax.set_title('Title',fontsize = 14)
Congrats on solving it yourself. I had put this together before I realized you had posted your answer. Very similar approach:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from math import sqrt
plt.rcParams["figure.figsize"] = (15,15)
# Create a color mapper for degress to color
values = np.linspace(1.0, 90.0, 90)
norm = colors.Normalize(vmin=0.0, vmax=90.0, clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap=cm.coolwarm_r)
horz = [[0.5,0.6,0.8],[0.1,0.3,0.4],[0.2,0.5,0.9],[0.9,0.95,0.95]]
vert = [[0.1,0.2,0.45],[0.05,0.1,0.15],[0.2,0.3,0.35],[0.1,0.3,0.5]]
f = plt.figure(figsize=(15,15))
ax = plt.axes()
# Calculate lengths of each line
lengths = [sqrt((x[-1]-x[0])**2 + (y[-1]-y[0])**2) for x,y in zip(horz, vert)]
for x,y,length in zip(horz,vert, lengths):
alpha = length / sum(lengths)
angle = np.rad2deg(np.arctan2(y[-1] - y[0], x[-1] - x[0]))
color = mapper.to_rgba(angle)
plt.arrow(x[0],y[0],x[-1]-x[0], y[-1]-y[0],shape='full', lw=2,
length_includes_head=True, head_width=.012, head_length=0.02, head_starts_at_zero=False, overhang = 0.5, alpha=alpha, color=color)

Make 3D triangle lines more visible in pyplot

I'd like to make a triangle plot in matplotlib with a mostly-transparent surface. I'm running the example code at https://matplotlib.org/mpl_examples/mplot3d/trisurf3d_demo.py:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
n_radii = 8
n_angles = 36
# Make radii and angles spaces (radius r=0 omitted to eliminate duplication).
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius.
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords.
# (0, 0) is manually added at this stage, so there will be no duplicate
# points in the (x, y) plane.
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Compute z to make the pringle surface.
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, linewidth=0.2, antialiased=True)
plt.show()
I can set
ax.plot_trisurf(x, y, z, linewidth=0.2, alpha = 0.2, antialiased=True)
to set the opacity to 0.2, but then the lines disappear. Furthermore, when I change the linewidth, even without the alpha, I see no change in the thickness of the lines between the points. How can I have a triangle plot where the faces are mostly transparent and the lines are clearly visible?

How to create a circle with uniformly distributed dots in the perimeter of it with scatterplot in python

Suppose I have a circle x**2 + y**2 = 20.
Now I want to plot the circle with n_dots number of dots in the circles perimeter in a scatter plot. So I created the code like below:
n_dots = 200
x1 = np.random.uniform(-20, 20, n_dots//2)
y1_1 = (400 - x1**2)**.5
y1_2 = -(400 - x1**2)**.5
plt.figure(figsize=(8, 8))
plt.scatter(x1, y1_1, c = 'blue')
plt.scatter(x1, y1_2, c = 'blue')
plt.show()
But this shows the dots not uniformly distributed all the places in the circle. The output is :
So how to create a circle with dots in scatter plot where all the dots are uniformly distributed in the perimeter of the circle?
A simple way to plot evenly-spaced points along the perimeter of a circle begins with dividing the whole circle into equally small angles where the angles from circle's center to all points are obtained. Then, the coordinates (x,y) of each point can be computed. Here is the code that does the task:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(8, 8))
n_dots = 120 # set number of dots
angs = np.linspace(0, 2*np.pi, n_dots) # angles to the dots
cx, cy = (50, 20) # center of circle
xs, ys = [], [] # for coordinates of points to plot
ra = 20.0 # radius of circle
for ang in angs:
# compute (x,y) for each point
x = cx + ra*np.cos(ang)
y = cy + ra*np.sin(ang)
xs.append(x) # collect x
ys.append(y) # collect y
plt.scatter(xs, ys, c = 'red', s=5) # plot points
plt.show()
The resulting plot:
Alternately, numpy's broadcasting nature can be used and shortened the code:
import matplotlib.pyplot as plt
import numpy as np
fig=plt.figure(figsize=(8, 8))
n_dots = 120 # set number of dots
angs = np.linspace(0, 2*np.pi, n_dots) # angles to the dots
cx, cy = (50, 20) # center of circle
ra = 20.0 # radius of circle
# with numpy's broadcasting feature...
# no need to do loop computation as in above version
xs = cx + ra*np.cos(angs)
ys = cy + ra*np.sin(angs)
plt.scatter(xs, ys, c = 'red', s=5) # plot points
plt.show()
for a very generalized answer that also works in 2D:
import numpy as np
import matplotlib.pyplot as plt
def u_sphere_pts(dim, N):
"""
uniform distribution points on hypersphere
from uniform distribution in n-D (<-1, +1>) hypercube,
clipped by unit 2 norm to get the points inside the insphere,
normalize selected points to lie on surface of unit radius hypersphere
"""
# uniform points in hypercube
u_pts = np.random.uniform(low=-1.0, high=1.0, size=(dim, N))
# n dimensional 2 norm squared
norm2sq = (u_pts**2).sum(axis=0)
# mask of points where 2 norm squared < 1.0
in_mask = np.less(norm2sq, np.ones(N))
# use mask to select points, norms inside unit hypersphere
in_pts = np.compress(in_mask, u_pts, axis=1)
in_norm2 = np.sqrt(np.compress(in_mask, norm2sq)) # only sqrt selected
# return normalized points, equivalently, projected to hypersphere surface
return in_pts/in_norm2
# show some 2D "sphere" points
N = 1000
dim = 2
fig2, ax2 = plt.subplots()
ax2.scatter(*u_sphere_pts(dim, N))
ax2.set_aspect('equal')
plt.show()
# plot histogram of angles
pts = u_sphere_pts(dim, 1000000)
theta = np.arctan2(pts[0,:], pts[1,:])
num_bins = 360
fig1, ax1 = plt.subplots()
n, bins, patches = plt.hist(theta, num_bins, facecolor='blue', alpha=0.5)
plt.show()
similar/related:
https://stackoverflow.com/questions/45580865/python-generate-an-n-dimensional-hypercube-using-rejection-sampling#comment78122144_45580865
Python Uniform distribution of points on 4 dimensional sphere
http://mathworld.wolfram.com/HyperspherePointPicking.html
Sampling uniformly distributed random points inside a spherical volume

plot a circle with some colored points in python

I want to plot a circle with specified center position and radius and plot 100 random points between [0-500] which 80% of them in circle and 20% around with different colors.
To solve this problem I used Plot circle that contains 80% (x, y) points and customized based on my requirements but it's not working.
import numpy as np
import matplotlib.pyplot as plt
n = 100
low = 0
high = 500
x = np.random.random_integers(low, high, n)
y = np.random.random_integers(low, high, n)
x0 = y0 = 250
r = 200
#t = 80 # percent
#r0 = np.percentile(r, t)
plt.plot(x, y, '.')
circle = plt.Circle((x0, y0), r, color='black', fill=False, linestyle='--')
plt.plot(x0, y0, color='black', marker='^')
plt.gca().add_artist(circle)
plt.axis([0, 500, 0, 500])
plt.show()
I found the answer of my question.
we can use the equation of circle to check the point is in the circle or not.
(x-x0)**2+(y-y0)**2 < r**2
center:(x0, y0)
radius:r

Drawing ellipses on matplotlib basemap projections

I am trying to draw ellipses on a basemap projection. To draw a circle like polygon there is the tissot function used to draw Tissot's indicatrix' as illustrates the following example.
from mpl_toolkits.basemap import Basemap
x0, y0 = 35, -50
R = 5
m = Basemap(width=8000000,height=7000000, resolution='l',projection='aea',
lat_1=-40.,lat_2=-60,lon_0=35,lat_0=-50)
m.drawcoastlines()
m.tissot(x0, y0, R, 100, facecolor='g', alpha=0.5)
However, I am interested in plotting an ellipsis in the form (x-x0)**2/a**2 + (y-y0)**2/2 = 1. On the other hand, to draw an ellipsis on a regular Cartesian grid I can use the following sample code:
import pylab
from matplotlib.patches import Ellipse
fig = pylab.figure()
ax = pylab.subplot(1, 1, 1, aspect='equal')
x0, y0 = 35, -50
w, h = 10, 5
e = Ellipse(xy=(x0, y0), width=w, height=h, linewidth=2.0, color='g')
ax.add_artist(e)
e.set_clip_box(ax.bbox)
e.set_alpha(0.7)
pylab.xlim([20, 50])
pylab.ylim([-65, -35])
Is there a way to plot an ellipsis on a basemap projection with the an effect similar to tissot?
After hours analyzing the source code of basemap's tissot function, learning some properties of ellipses and lot's of debugging, I came with a solution to my problem. I've extended the basemap class with a new function called ellipse as follows,
from __future__ import division
import pylab
import numpy
from matplotlib.patches import Polygon
from mpl_toolkits.basemap import pyproj
from mpl_toolkits.basemap import Basemap
class Basemap(Basemap):
def ellipse(self, x0, y0, a, b, n, ax=None, **kwargs):
"""
Draws a polygon centered at ``x0, y0``. The polygon approximates an
ellipse on the surface of the Earth with semi-major-axis ``a`` and
semi-minor axis ``b`` degrees longitude and latitude, made up of
``n`` vertices.
For a description of the properties of ellipsis, please refer to [1].
The polygon is based upon code written do plot Tissot's indicatrix
found on the matplotlib mailing list at [2].
Extra keyword ``ax`` can be used to override the default axis instance.
Other \**kwargs passed on to matplotlib.patches.Polygon
RETURNS
poly : a maptplotlib.patches.Polygon object.
REFERENCES
[1] : http://en.wikipedia.org/wiki/Ellipse
"""
ax = kwargs.pop('ax', None) or self._check_ax()
g = pyproj.Geod(a=self.rmajor, b=self.rminor)
# Gets forward and back azimuths, plus distances between initial
# points (x0, y0)
azf, azb, dist = g.inv([x0, x0], [y0, y0], [x0+a, x0], [y0, y0+b])
tsid = dist[0] * dist[1] # a * b
# Initializes list of segments, calculates \del azimuth, and goes on
# for every vertex
seg = [self(x0+a, y0)]
AZ = numpy.linspace(azf[0], 360. + azf[0], n)
for i, az in enumerate(AZ):
# Skips segments along equator (Geod can't handle equatorial arcs).
if numpy.allclose(0., y0) and (numpy.allclose(90., az) or
numpy.allclose(270., az)):
continue
# In polar coordinates, with the origin at the center of the
# ellipse and with the angular coordinate ``az`` measured from the
# major axis, the ellipse's equation is [1]:
#
# a * b
# r(az) = ------------------------------------------
# ((b * cos(az))**2 + (a * sin(az))**2)**0.5
#
# Azymuth angle in radial coordinates and corrected for reference
# angle.
azr = 2. * numpy.pi / 360. * (az + 90.)
A = dist[0] * numpy.sin(azr)
B = dist[1] * numpy.cos(azr)
r = tsid / (B**2. + A**2.)**0.5
lon, lat, azb = g.fwd(x0, y0, az, r)
x, y = self(lon, lat)
# Add segment if it is in the map projection region.
if x < 1e20 and y < 1e20:
seg.append((x, y))
poly = Polygon(seg, **kwargs)
ax.add_patch(poly)
# Set axes limits to fit map region.
self.set_axes_limits(ax=ax)
return poly
This new function can be used promptly like in this example:
pylab.close('all')
pylab.ion()
m = Basemap(width=12000000, height=8000000, resolution='l', projection='stere',
lat_ts=50, lat_0=50, lon_0=-107.)
m.drawcoastlines()
m.fillcontinents(color='coral',lake_color='aqua')
# draw parallels and meridians.
m.drawparallels(numpy.arange(-80.,81.,20.))
m.drawmeridians(numpy.arange(-180.,181.,20.))
m.drawmapboundary(fill_color='aqua')
# draw ellipses
ax = pylab.gca()
for y in numpy.linspace(m.ymax/20, 19*m.ymax/20, 9):
for x in numpy.linspace(m.xmax/20, 19*m.xmax/20, 12):
lon, lat = m(x, y, inverse=True)
poly = m.ellipse(lon, lat, 3, 1.5, 100, facecolor='green', zorder=10,
alpha=0.5)
pylab.title("Ellipses on stereographic projection")
Which has the following outcome:

Categories

Resources