Matplotlib: Arrow heads at each vertex in Path - python

I am trying to plot the path taken by some input function (whose vertices are recorded in a numpy array)
I want to add an arrow-head (any direction marker) for every vertex of the form "→→→→" to track the direction of path.
I know the FancyArrowPatch which adds only one arrow-head a the terminal vertex, of the form: "————>". That's NOT what I want. [for reasons that are outside the scope of this question]
Currently, my code looks like this: Note, we can't guess the direction.
def plot_track(self, verts: np.array) -> None:
'''Plot followed track: verts is 2D array: x, y'''
track = Path(verts)
patch = PathPatch(path=track, edgecolor="#FF0000",
fill=False)
self.axs.add_patch(patch)
self.fig.canvas.draw()
self.root.mainloop()

matplotlib.patches.Arrow can be used to draw arrows. A loop needs to visit each vertex and its successor. A similar approach can be used with FancyArrowPatch.
import matplotlib.pyplot as plt
from matplotlib.patches import Arrow
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import PathPatch, Path, Arrow
import numpy as np
def plot_track(verts, ax, **kw_args):
'''Plot followed track: verts is 2D array: x, y'''
for xy0, xy1 in zip(verts[:-1], verts[1:]):
patch = Arrow(*xy0, *(xy1 - xy0), **kw_args)
ax.add_patch(patch)
ax.relim()
ax.autoscale_view()
fig, ax = plt.subplots()
t = np.arange(2, 11, 1)
x = t * np.sin(t)
y = t * np.cos(t)
verts = np.vstack([x, y]).T
plot_track(verts - np.array([7, 0]), ax, color='red', fill=True, width=1)
plot_track(verts + np.array([7, 0]), ax, color='red', fill=False, width=1)
plt.show()

Related

Using fig.transFigure to draw a patch on ylabel

I created a sequence of points that I would like to convert into a Patch.
The goal is then to draw the patch on the left side of the y-label (see in Red in the figure), or draw it in any other part of the figure.
Although it can be accomplished with Gridspec, I would like to do it with a Patch.
import matplotlib.pyplot as plt
import numpy as np
plt.figure()
npoints = 100
td = np.linspace(np.pi*3/4, np.pi*5/4, npoints)
xd = np.cos(td)
yd = np.sin(td)
plt.plot(xd,yd)
EDIT1:
I am now able to make a Patch (just need to move it outside the axis):
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
npoints = 100
td = np.linspace(np.pi*3/4, np.pi*5/4, npoints)
xd = np.cos(td)
yd = np.sin(td)
fig, ax = plt.subplots()
ax.axis([-2, 0, -1, 1])
verts=np.c_[xd,yd]
codes = np.ones(len(xd))*2 # Path.LINETO for all points except the first
codes[0] = 1 #Path.MOVETO only for the first point
path1 = mpath.Path(verts, codes)
patch = mpatches.PathPatch(path1, facecolor='none')
ax.add_patch(patch)
The result:
Now, I only need to move it outside the axis, maybe using a translation or scale.
I'm sure the key to do it is somewhere in this Matplotlib Transforms tutorial, more specifically, I am pretty sure the solution is using fig.transFigure.
EDIT 2: Almost there!
In order to use Figure coordinates (that are between [0,1]) I normalized the points that define the path. And instead of using ax.add_patch() that adds a patch to the axis, I use fig.add_artist() that adds the patch to the figure, over the axis.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
#Normalized Data
def normalize(x):
return (x - min(x)) / (max(x) - min(x))
#plt.figure()
npoints = 100
td = np.linspace(np.pi*3/4, np.pi*5/4, npoints)
xd = np.cos(td)
yd = np.sin(td)
#plt.plot(xd,yd)
xd = normalize(xd)
yd = normalize(yd)
fig, ax = plt.subplots()
ax.axis([-2, 2, -1, 1])
verts=np.c_[xd,yd]
codes = np.ones(len(xd))*2 # Path.LINETO for all points except the first
codes[0] = 1 #Path.MOVETO only for the first point
path1 = mpath.Path(verts, codes)
patch1 = mpatches.PathPatch(path1, facecolor='none')
ax.add_patch(patch1)
patch2 = mpatches.PathPatch(path1, fc='none', ec='red', transform=fig.transFigure)
fig.add_artist(patch2)
And the result so far:
Doing this, I just need to scale and translate the patch, maybe using Affine2D.
EDIT 3: Done!
Finally I was able to do it! I used Try and Error in the scale() and translate() parameters as I did not get what coordinate system they were using. However, it would be great to get the exact y center (0.5 in Figure coordinates).
Here is the complete code:
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
#Normalized Data
def normalize(x):
return (x - min(x)) / (max(x) - min(x))
npoints = 100
td = np.linspace(np.pi*3/4, np.pi*5/4, npoints)
xd = np.cos(td)
yd = np.sin(td)
xd = normalize(xd)
yd = normalize(yd)
fig, ax = plt.subplots()
ax.axis([-2, 2, -1, 1])
verts=np.c_[xd,yd]
codes = np.ones(len(xd))*2 # Path.LINETO for all points except the first
codes[0] = 1 #Path.MOVETO only for the first point
path1 = mpath.Path(verts, codes)
patch1 = mpatches.PathPatch(path1, fc='none', ec='green')
ax.add_patch(patch1) #draw inside axis
patch2 = mpatches.PathPatch(path1, fc='none', ec='C0', transform=fig.transFigure)
fig.add_artist(patch2) #this works! Draw on figure
import matplotlib.transforms as mtransforms
tt = fig.transFigure + mtransforms.Affine2D().scale(0.02, 0.8).translate(10,45)
patch3 = mpatches.PathPatch(path1, fc='none', ec='red', transform=tt)
fig.add_artist(patch3)
And the resulting figure:
As #Pedro pointed out, most of this can be found in the tutorial that he linked. However, here is a short answer.
Basically, it's almost as if you're creating a line plot. Just specify the points you want to pass through, add them to a list and that's it.
In this example I want to pass through some points on the plot, then "lift the pen off of the paper" and continue from another point. So we create two lists - one containing the points I want to use and the second list which describes what I want to do with those points. Path.MOVETO will move your "pen" to the given point without drawing a line, so we use this to set our initial startpoint. Path.LINETO creates a straight line starting from your current pen position towards the next line in the list.
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
# Points you want to "pass through"
pts = [
(0, 0),
(0.2, 0.2),
(0.4, 0.2),
(0.4, 0.4),
(0.4, 0.6),
(0.6, 0.6),
(0.8, 0.8)
]
# What you want to "do" with each point
codes = [
Path.MOVETO, # inital point
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.MOVETO, # pick up the pen
Path.LINETO,
Path.LINETO
]
# Create path object
# https://matplotlib.org/stable/tutorials/advanced/path_tutorial.html
path = Path(pts, codes)
patch = patches.PathPatch(path, lw='2', color='r', fill=False)
# patch the path to the figure
fig, ax = plt.subplots()
ax.add_patch(patch)
plt.show()
Result of code execution:

plot_trisurface with custom color array

I basically want to "imshow" the pdf of a three-dimensional Dirichlet distribution on its support. Function simplex below computes regular points on that support, which are stored in the array sim. The array pdf holds a scalar density for each row in sim.
First thing I thought of was to use a triangulation. However, the color argument of plot_trisurface supports only one single color for all triangles. Setting cmap colors the triangles based on the z-coordinate values (See Fig. 1). Also plot_trisurface ignores the facecolors kwarg. What I want, however, is to color the surface based on pdf.
As a workaround I found, that I could interpolated the surface as 3d scatter plot. This generally gives the desired visualization, yet I ist clearly visible that it's a scatter plot; especially on the borders. (See Fig 2.)
Is there a way to plot the projection of the pdf onto the simplex?
import itertools
import matplotlib.pyplot as plt
import numpy as np
from scipy import stats
def simplex(n_vals):
base = np.linspace(0, 1, n_vals, endpoint=False)
coords = np.asarray(list(itertools.product(base, repeat=3)))
return coords[np.isclose(coords.sum(axis=-1), 1.0)]
sim = simplex(20)
pdf = stats.dirichlet([1.1, 1.5, 1.3]).pdf(sim.T)
fig1 = plt.figure()
ax1 = fig1.add_subplot(1, 2, 1, projection='3d', azim=20)
ax2 = fig1.add_subplot(1, 2, 2, projection='3d', azim=20)
ax1.plot_trisurf(x, y, z, color='k')
ax2.plot_trisurf(x, y, z, cmap='Spectral')
fig2 = plt.figure()
ax21 = fig2.add_subplot(projection='3d', azim=20)
ax21.scatter3D(*sim.T, s=50, alpha=.5, c=pdf, cmap='Spectral')
This is a way to do so by coloring each triangle in a triangulation object with the right color. Is this what you were looking for? The only thing is that each patch has a uniform color which make the patches somewhat visible.
# Setup is the same
import itertools
import matplotlib.pyplot as plt
from pylab import get_cmap
from matplotlib.tri import Triangulation, LinearTriInterpolator
import numpy as np
from scipy import stats
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
def simplex(n_vals):
base = np.linspace(0, 1, n_vals, endpoint=False)
coords = np.asarray(list(itertools.product(base, repeat=3)))
return coords[np.isclose(coords.sum(axis=-1), 1.0)]
sim = simplex(20)
pdf = stats.dirichlet([1.1, 1.5, 1.3]).pdf(sim.T)
# For shorter notation we define x, y and z:
x = sim[:, 0]
y = sim[:, 1]
z = sim[:, 2]
# Creating a triangulation object and using it to extract the actual triangles.
# Note if it is necessary that no patch will be vertical (i.e. along the z direction)
tri = Triangulation(x, y)
triangle_vertices = np.array([np.array([[x[T[0]], y[T[0]], z[T[0]]],
[x[T[1]], y[T[1]], z[T[1]]],
[x[T[2]], y[T[2]], z[T[2]]]]) for T in tri.triangles])
# Finding coordinate for the midpoints of each triangle.
# This will be used to extract the color
midpoints = np.average(triangle_vertices, axis = 1)
midx = midpoints[:, 0]
midy = midpoints[:, 1]
# Interpolating the pdf and using it with the selected cmap to produce the color RGB vector for each face.
# Some roundoff and normalization are needed
face_color_function = LinearTriInterpolator(tri, pdf)
face_color_index = face_color_function(midx, midy)
face_color_index[face_color_index < 0] = 0
face_color_index /= np.max(pdf)
cmap = get_cmap('Spectral')
# Creating the patches and plotting
collection = Poly3DCollection(triangle_vertices, facecolors=cmap(face_color_index), edgecolors=None)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.add_collection(collection)
plt.show()
Obviously increasing the resolution would make the plot smoother.
This figure, complete with a colorbar,
was produced by the following script — the function map_colors, defined at the end of the script, could interest the general reader.
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from itertools import product as Π
# the distribution that we want to study
dirichlet = stats.dirichlet([1.1, 1.5, 1.3])
# generate the "mesh"
N = 30 # no. of triangles along an edge
s = np.linspace(0, 1, N+1)
x, y, z = np.array([(x,y,1-x-y) for x,y in Π(s,s) if x+y<1+1E-6]).T
# plot as usual
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d', azim=20)
p3dc = ax.plot_trisurf(x, y, z)
########## change the face colors ####################
mappable = map_colors(p3dc, dirichlet.pdf, 'Spectral')
# ####################################################
# possibly add a colormap
plt.colorbar(mappable, shrink=0.67, aspect=16.7)
# we are done
plt.show()
def map_colors(p3dc, func, cmap='viridis'):
"""
Color a tri-mesh according to a function evaluated in each barycentre.
p3dc: a Poly3DCollection, as returned e.g. by ax.plot_trisurf
func: a single-valued function of 3 arrays: x, y, z
cmap: a colormap NAME, as a string
Returns a ScalarMappable that can be used to instantiate a colorbar.
"""
from matplotlib.cm import ScalarMappable, get_cmap
from matplotlib.colors import Normalize
from numpy import array
# reconstruct the triangles from internal data
x, y, z, _ = p3dc._vec
slices = p3dc._segslices
triangles = array([array((x[s],y[s],z[s])).T for s in slices])
# compute the barycentres for each triangle
xb, yb, zb = triangles.mean(axis=1).T
# compute the function in the barycentres
values = func(xb, yb, zb)
# usual stuff
norm = Normalize()
colors = get_cmap(cmap)(norm(values))
# set the face colors of the Poly3DCollection
p3dc.set_fc(colors)
# if the caller wants a colorbar, they need this
return ScalarMappable(cmap=cmap, norm=norm)

How to draw multiple triangles with different sizes and directions based on data using plotting tools like Matplotlib

I want to draw the graph like the picture below. Its x-axis is the order of the data points, e.g. from 1 to 7. The y-axis is the scale from 0 to 25. If I want to draw a triangle, for example, with its data (1,22,20), then '1' gives the order among all data points(different triangles), the triangle should be drew in most left; "22,20" gives the "bottom-tip" of the triangle along the y-axis.
Does anyone know how to draw such triangle with multiple number in a graph using matplotlib python package?
Read this post and this post about drawing polygons with matplotlib.
EDIT1: Just saw #Poolka's answer. This was also my way to go, but notice that in one of the above links, it is stated, that adding single polygons (p = pat.Polygon([[x1, y1], [x2, y2], [x3, y3]); ax.add_patch(p)) to the figure can become very slow, and therefore collections are preferred.
EDIT 2: Also see TheImportanceOfBeingErnest's answer for a more elaborated version of this concept.
Together with this snippet of code, it should get you going:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as pat # Patches like pat.Polygon()
from matplotlib.collections import PolyCollection # Collections of patches
test = ((1, 22, 20),
(2, 21, 19.5),
(3, 18, 20)) # Test data
triangles = []
fig, ax = plt.subplots(1, 1)
for t in test:
xmid = t[0] # Middle x-coord
xleft = t[0] - 0.5
xright = t[0] + 0.5 # Use fixed width of 0.5
y1 = t[1] # y-coords
y2 = t[2]
coordinates = [[xleft, y1], [xright, y1], [xmid, y2]]
print(coordinates)
triangles.append(coordinates) # Append to collection
z = np.random.random(len(triangles))
collec = PolyCollection(triangles, array=z, cmap=matplotlib.cm.viridis)
ax.add_collection(collec) # Plot polygon collection
ax.autoscale_view()
plt.show()
Consider the following simple example:
import matplotlib.pyplot as plt
# data
data = [[1, 22, 20], [3, 20, 25]]
plt.figure()
for val in data:
# coordinates
dy = val[1] - val[2]
dx = abs(dy) / 2
x0 = val[0]
y0 = val[1]
# drawing
triangle = plt.Polygon([[x0, y0], [x0 - dx, y0 + dy], [x0 + dx, y0 + dy]])
plt.gca().add_patch(triangle)
# misc
plt.grid()
plt.axis('square')
# these 2 lines are needed because patches in matplotlib do not adjust axes limits automatically, another approach is to add some data to the figure with plot, scatter, etc.
plt.xlim([-20, 20])
plt.ylim([0, 40])
Result is:
Using a PolyCollection (as shown in #cripcate's answer) is advantageous in this case. A more condensed version using a single numpy array could look like this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
def triangle_collection(d, ax=None, width=0.4, **kwargs):
ax = ax or plt.gca()
verts = np.c_[d[:,0]-width/2, d[:,1], d[:,0]+width/2,
d[:,1], d[:,0], d[:,2]].reshape(len(d),3,2)
c = PolyCollection(verts, **kwargs)
ax.add_collection(c)
ax.autoscale()
return c
data = np.array([(1,22,20), (2,21,19.5), (3,18,20),
(4,17,19), (5,15,17), (6,11,8.5), (7,14,12)])
fig, ax = plt.subplots()
fig.subplots_adjust(left=0.3, right=0.7)
triangle_collection(data, facecolors=plt.cm.tab10(np.arange(len(data))))
plt.show()

Python/Matplotlib: 2d random walk with kde joint density contour in a 3d plot

I'm struggling with creating a quite complex 3d figure in python, specifically using iPython notebook. I can partition the content of the graph into two sections:
The (x,y) plane: Here a two-dimensional random walk is bobbing around, let's call it G(). I would like to plot part of this trajectory on the (x,y) plane. Say, 10% of all the data points of G(). As G() bobs around, it visits some (x,y) pairs more frequently than others. I would like to estimate this density of G() using a kernel estimation approach and draw it as contour lines on the (x,y) plane.
The (z) plane: Here, I would like to draw a mesh or (transparent) surface plot of the information theoretical surprise of a bivariate normal. Surprise is simply -log(p(i)) or the negative (base 2) logarithm of outcome i. Given the bivariate normal, each (x,y) pair has some probability p(x,y) and the surprise of this is simply -log(p(x,y)).
Essentially these two graphs are independent. Assume the interval of the random walk G() is [xmin,xmax],[ymin,ymax] and of size N. The bivariate normal in the z-plane should be drawn from the same interval, such that for each (x,y) pair in the random walk, I can draw a (dashed) line from some subset of the random walk n < N to the bivariate normal. Assume that G(10) = (5,5) then I would like to draw a dashed line from (5,5) up the Z-axes, until it hits the bivariate normal.
So far, I've managed to plot G() in a 3-d space, and estimate the density f(X,Y) using scipy.stats.gaussian_kde. In another (2d) graph, I have the sort of contour lines I want. What I don't have, is the contour lines in the 3d-plot using the estimated KDE density. I also don't have the bivariate normal plot, or the projection of a few random points from the random walk, to the surface of the bivariate normal. I've added a hand drawn figure, which might ease intuition (ignore the label on the z-axis and the fact that there is no mesh.. difficult to draw!)
Any input, even just partial, such as how to draw the contour lines in the (x,y) plane of the 3d graph, or a mesh of a bivariate normal would be much appreciated.
Thanks!
import matplotlib as mpl
import matplotlib.pyplot as plt
import random
import numpy as np
import seaborn as sns
import scipy
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
def randomwalk():
mpl.rcParams['legend.fontsize'] = 10
xyz = []
cur = [0, 0]
for _ in range(400):
axis = random.randrange(0, 2)
cur[axis] += random.choice([-1, 1])
xyz.append(cur[:])
x, y = zip(*xyz)
data = np.vstack([x,y])
kde = scipy.stats.gaussian_kde(data)
density = kde(data)
fig1 = plt.figure()
ax = fig1.gca(projection='3d')
ax.plot(x, y, label='Random walk')
sns.kdeplot(data[0,:], data[1,:], 0)
ax.scatter(x[-1], y[-1], c='b', marker='o') # End point
ax.legend()
fig2 = plt.figure()
sns.kdeplot(data[0,:], data[1,:])
Calling randomwalk() initialises and plots this:
Edit #1:
Made some progress, actually the only thing I need is to restrict the height of the dashed vertical lines to the bivariate. Any ideas?
import matplotlib as mpl
import matplotlib.pyplot as plt
import random
import numpy as np
import seaborn as sns
import scipy
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.mlab import bivariate_normal
%matplotlib inline
# Data for random walk
def randomwalk():
mpl.rcParams['legend.fontsize'] = 10
xyz = []
cur = [0, 0]
for _ in range(40):
axis = random.randrange(0, 2)
cur[axis] += random.choice([-1, 1])
xyz.append(cur[:])
# Get density
x, y = zip(*xyz)
data = np.vstack([x,y])
kde = scipy.stats.gaussian_kde(data)
density = kde(data)
# Data for bivariate gaussian
a = np.linspace(-7.5, 7.5, 20)
b = a
X,Y = np.meshgrid(a, b)
Z = bivariate_normal(X, Y)
surprise_Z = -np.log(Z)
# Get random points from walker and plot up z-axis to the gaussian
M = data[:,np.random.choice(20,5)].T
# Plot figure
fig = plt.figure(figsize=(10, 7))
ax = fig.gca(projection='3d')
ax.plot(x, y, 'grey', label='Random walk') # Walker
ax.scatter(x[-1], y[-1], c='k', marker='o') # End point
ax.legend()
surf = ax.plot_surface(X, Y, surprise_Z, rstride=1, cstride=1,
cmap = plt.cm.gist_heat_r, alpha=0.1, linewidth=0.1)
#fig.colorbar(surf, shrink=0.5, aspect=7, cmap=plt.cm.gray_r)
for i in range(5):
ax.plot([M[i,0], M[i,0]],[M[i,1], M[i,1]], [0,10],'k--',alpha=0.8, linewidth=0.5)
ax.set_zlim(0, 50)
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
Final code,
import matplotlib as mpl
import matplotlib.pyplot as plt
import random
import numpy as np
import seaborn as sns
import scipy
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.mlab import bivariate_normal
%matplotlib inline
# Data for random walk
def randomwalk():
mpl.rcParams['legend.fontsize'] = 10
xyz = []
cur = [0, 0]
for _ in range(50):
axis = random.randrange(0, 2)
cur[axis] += random.choice([-1, 1])
xyz.append(cur[:])
# Get density
x, y = zip(*xyz)
data = np.vstack([x,y])
kde = scipy.stats.gaussian_kde(data)
density = kde(data)
# Data for bivariate gaussian
a = np.linspace(-7.5, 7.5, 100)
b = a
X,Y = np.meshgrid(a, b)
Z = bivariate_normal(X, Y)
surprise_Z = -np.log(Z)
# Get random points from walker and plot up z-axis to the gaussian
M = data[:,np.random.choice(50,10)].T
# Plot figure
fig = plt.figure(figsize=(10, 7))
ax = fig.gca(projection='3d')
ax.plot(x, y, 'grey', label='Random walk') # Walker
ax.legend()
surf = ax.plot_surface(X, Y, surprise_Z, rstride=1, cstride=1,
cmap = plt.cm.gist_heat_r, alpha=0.1, linewidth=0.1)
#fig.colorbar(surf, shrink=0.5, aspect=7, cmap=plt.cm.gray_r)
for i in range(10):
x = [M[i,0], M[i,0]]
y = [M[i,1], M[i,1]]
z = [0,-np.log(bivariate_normal(M[i,0],M[i,1]))]
ax.plot(x,y,z,'k--',alpha=0.8, linewidth=0.5)
ax.scatter(x, y, z, c='k', marker='o')

How to draw a filled arc in matplotlib

In matplotlib, I would like draw an filled arc which looks like this:
The following code results in an unfilled line arc:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fg, ax = plt.subplots(1, 1)
pac = mpatches.Arc([0, -2.5], 5, 5, angle=0, theta1=45, theta2=135)
ax.add_patch(pac)
ax.axis([-2, 2, -2, 2])
ax.set_aspect("equal")
fg.canvas.draw()
The documentation says that filled arcs are not possible.
What would be the best way to draw one?
#jeanrjc's solution almost gets you there, but it adds a completely unnecessary white triangle, which will hide other objects as well (see figure below, version 1).
This is a simpler approach, which only adds a polygon of the arc:
Basically we create a series of points (points) along the edge of the circle (from theta1 to theta2). This is already enough, as we can set the close flag in the Polygon constructor which will add the line from the last to the first point (creating a closed arc).
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
def arc_patch(center, radius, theta1, theta2, ax=None, resolution=50, **kwargs):
# make sure ax is not empty
if ax is None:
ax = plt.gca()
# generate the points
theta = np.linspace(np.radians(theta1), np.radians(theta2), resolution)
points = np.vstack((radius*np.cos(theta) + center[0],
radius*np.sin(theta) + center[1]))
# build the polygon and add it to the axes
poly = mpatches.Polygon(points.T, closed=True, **kwargs)
ax.add_patch(poly)
return poly
And then we apply it:
fig, ax = plt.subplots(1,2)
# #jeanrjc solution, which might hide other objects in your plot
ax[0].plot([-1,1],[1,-1], 'r', zorder = -10)
filled_arc((0.,0.3), 1, 90, 180, ax[0], 'blue')
ax[0].set_title('version 1')
# simpler approach, which really is just the arc
ax[1].plot([-1,1],[1,-1], 'r', zorder = -10)
arc_patch((0.,0.3), 1, 90, 180, ax=ax[1], fill=True, color='blue')
ax[1].set_title('version 2')
# axis settings
for a in ax:
a.set_aspect('equal')
a.set_xlim(-1.5, 1.5)
a.set_ylim(-1.5, 1.5)
plt.show()
Result (version 2):
You can use fill_between to achieve this
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
fg, ax = plt.subplots(1, 1)
r=2.
yoff=-1
x=np.arange(-1.,1.05,0.05)
y=np.sqrt(r-x**2)+yoff
ax.fill_between(x,y,0)
ax.axis([-2, 2, -2, 2])
ax.set_aspect("equal")
fg.canvas.draw()
Play around with r and yoff to move the arc
EDIT:
OK, so you want to be able to plot arbitrary angles? You just need to find the equation of the chord, rather than using a flat line like above. Here's a function to do just that:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
fg, ax = plt.subplots(1, 1)
col='rgbkmcyk'
def filled_arc(center,r,theta1,theta2):
# Range of angles
phi=np.linspace(theta1,theta2,100)
# x values
x=center[0]+r*np.sin(np.radians(phi))
# y values. need to correct for negative values in range theta=90--270
yy = np.sqrt(r-x**2)
yy = [-yy[i] if phi[i] > 90 and phi[i] < 270 else yy[i] for i in range(len(yy))]
y = center[1] + np.array(yy)
# Equation of the chord
m=(y[-1]-y[0])/(x[-1]-x[0])
c=y[0]-m*x[0]
y2=m*x+c
# Plot the filled arc
ax.fill_between(x,y,y2,color=col[theta1/45])
# Lets plot a whole range of arcs
for i in [0,45,90,135,180,225,270,315]:
filled_arc([0,0],1,i,i+45)
ax.axis([-2, 2, -2, 2])
ax.set_aspect("equal")
fg.savefig('filled_arc.png')
And here's the output:
Here's a simpler workaround. Use the hatch argument in your mpatches.Arc command. If you repeat symbols with the hatch argument it increases the density of the patterning. I find that if you use 6 dashes, '-', or 6 dots, '.' (others probably also work), then it solidly fills in the arc as desired. When I run this
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
plt.axes()
pac = mpatches.Arc([0, -2.5], 5, 5, 45, theta1=45, theta2=135, hatch = '......')
plt.gca().add_patch(pac)
pac.set_color('cyan')
plt.axis('equal')
plt.show()
I get this:
Arc filled with dense dot hatch and rotated 45 degrees just for show
You can draw a wedge, and then hide part of it with a triangle:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
def filled_arc(center, radius, theta1, theta2, ax, color):
circ = mpatches.Wedge(center, radius, theta1, theta2, fill=True, color=color)
pt1 = (radius * (np.cos(theta1*np.pi/180.)) + center[0],
radius * (np.sin(theta1*np.pi/180.)) + center[1])
pt2 = (radius * (np.cos(theta2*np.pi/180.)) + center[0],
radius * (np.sin(theta2*np.pi/180.)) + center[1])
pt3 = center
pol = mpatches.Polygon([pt1, pt2, pt3], color=ax.get_axis_bgcolor(),
ec=ax.get_axis_bgcolor(), lw=2 )
ax.add_patch(circ)
ax.add_patch(pol)
and then you can call it:
fig, ax = plt.subplots(1,2)
filled_arc((0,0), 1, 45, 135, ax[0], "blue")
filled_arc((0,0), 1, 0, 40, ax[1], "blue")
and you get:
or:
fig, ax = plt.subplots(1, 1)
for i in range(0,360,45):
filled_arc((0,0), 1, i, i+45, ax, plt.cm.jet(i))
and you get:
HTH
The command ax.get_axis_bgcolor() needs to be replaced by ax.get_fc() for newer matplotlib.

Categories