I'm trying to display Matplotlib patches using the Circle function on a map plot using cartopy geographical projections. Apparently this is supposed to give a smooth, near scale-free circular patch, however the edges are very polygonal. Strangely, CirclePolygon, the polygonal approximation counterpart of Circle, produces a smoother circle, albeit still not as smooth as I would like.
This is pretty much all the code as it pertains to adding the plot and the patches:
fig = plt.figure(figsize=(8,6))
img_extent = [340, 348, -35.5, -31]
ax = fig.add_subplot(1, 1, 1, projection = ccrs.Mollweide(), extent = img_extent)
patch_coords = [[342.5833, -34.5639],[343.4042, -34.3353],[343.8500, -33.8728],
[344.4917, -33.7636],[344.9250, -33.3108],[345.1333, -32.6811],
[344.9233, -32.1583]]
for pair in patch_coords:
ax.add_patch(mpatches.Circle(xy = pair, radius = 0.5,
color = 'r', alpha = 0.3, rasterized = None,
transform = ccrs.Geodetic()))
ax.scatter(ra1, dec1, transform = ccrs.Geodetic(), rasterized = True, s = 1,
marker = ".", c = 'g', label = 'z < 0.025')
ax.scatter(ra2, dec2, transform = ccrs.Geodetic(), rasterized = True, s = 2,
marker = ".", c = 'b', label = '0.25 < z < 0.034')
ax.scatter(ra3, dec3, transform = ccrs.Geodetic(), rasterized = True, s = 0.75,
marker = ".", c = 'grey', label = '0.034 < z < 0.05')
Which produces this
I've tried looking through the available arguments but none seem to fix it. Is there a reason why it comes out like this and is there any way to make it smoother?
I believe plotting Tissot's Indicatrices is more appropriate in your case. An Indicatrix represents a ground circle on a map projection. In many cases, the Indicatrices are rendered as ellipses as map projections do not always preserve shapes. The following is the working code that plots all the ground circles of radius = 55 km on the map projection that you desire. Read the comments in the code for some useful information.
import matplotlib.pyplot as plt
# import matplotlib.patches as mpatches
import cartopy.crs as ccrs
import numpy as np
fig = plt.figure(figsize=(12,8))
img_extent = [340, 348, -35.5, -31]
ax = fig.add_subplot(1, 1, 1, projection = ccrs.Mollweide(), extent = img_extent)
patch_coords = [[342.5833, -34.5639],[343.4042, -34.3353],[343.8500, -33.8728],
[344.4917, -33.7636],[344.9250, -33.3108],[345.1333, -32.6811],
[344.9233, -32.1583]]
for ix,pair in enumerate(patch_coords):
# plot tissot indicatrix at each location
# n_samples = number of points forming indicatrix' perimeter
# rad_km = 55 km. is about the angular distance 0.5 degree
ax.tissot(rad_km=55, lons=np.array(patch_coords)[:,0][ix], \
lats=np.array(patch_coords)[:,1][ix], n_samples=36, \
facecolor='red', edgecolor='black', linewidth=0.15, alpha = 0.3)
gl = ax.gridlines(draw_labels=False, linewidth=1, color='blue', alpha=0.3, linestyle='--')
plt.show()
The resulting plot:
Edit
Since the first version of the code is not optimal.
Code update is offered as follows:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig = plt.figure(figsize=(12,8))
img_extent = [340, 348, -35.5, -31]
ax = fig.add_subplot(1, 1, 1, projection = ccrs.Mollweide(), extent = img_extent)
patch_coords = [[342.5833, -34.5639],[343.4042, -34.3353],[343.8500, -33.8728],
[344.4917, -33.7636],[344.9250, -33.3108],[345.1333, -32.6811],
[344.9233, -32.1583]]
for pair in patch_coords:
# plot tissot indicatrix at each location
# n_samples = number of points forming indicatrix' perimeter
# rad_km = 55 km. is about the angular distance 0.5 degree at equator
ax.tissot(rad_km=55, lons=pair[0], lats=pair[1], n_samples=36, \
facecolor='red', edgecolor='black', linewidth=0.15, alpha = 0.3)
gl = ax.gridlines(draw_labels=False, linewidth=1, color='blue', alpha=0.3, linestyle='--')
plt.show()
I believe that Cartopy does line projections with an arbitrary fixed accuracy, rather than a dynamic line-split calculation.
See e.g. :
https://github.com/SciTools/cartopy/issues/825
https://github.com/SciTools/cartopy/issues/363
I also think work is ongoing right now to address that.
In the meantime, to solve specific problems you can hack the CRS.threshold property,
as explained here : https://github.com/SciTools/cartopy/issues/8
That is, you can make it use finer steps by reprogramming the fixed value.
I think this would also fix this circle-drawing problem, though I'm not 100%
Related
I´m trying to save my 3D trisurface Plot as an interactive HTML figure, so it should be possible to zoom in/ out and change the viewpoint. In the IDE the plot already exists and works so far, but I
can`t save it in the HTML format because of the ValueError:
"The fig parameter must be a dict or Figure.
Received value of type <class 'matplotlib.figure.Figure'>: Figure(1600x900)".
I don´t understand why the "<class 'matplotlib.figure.Figure'>" is not a Figure?
This was my approach: https://plotly.com/python/interactive-html-export/
And I tried it with go.Figure() (Export rotable 3D plots from Python to HTML) already but it didn´t work with the trisurf.
Is there a way to keep my Plot settings (use trisurf as it is) and get the interactive figure in HTML?
Thanks a lot for any answer
#Import libraries
import matplotlib
#matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator
import numpy as np
from mpl_toolkits.mplot3d import axes3d, Axes3D
import pandas as pd
import plotly.express as px
import io
import plotly.io as pio
%matplotlib notebook
E = np.arange(225)
D = np.arange(225)
A = np.arange(225)
E = [10000.0, 10000.0, ...]
D = [500.0, 1000.0, ...]
A = [1.9495, 1.9644, ...]
#Create figure
fig = plt.figure(figsize =(16, 9))
ax = plt.axes(projection ='3d')
# Creating color map
my_cmap = plt.get_cmap('hot')
# Data for three-dimensional scattered points
zdata = A
xdata = D
ydata = E
# Creating plot
trisurf = ax.plot_trisurf(xdata, ydata, zdata,
cmap = my_cmap,
linewidth = 0.2,
antialiased = True,
edgecolor = 'grey')
fig.colorbar(trisurf, ax = ax, shrink = 0.5, aspect = 10)
ax.set_title('AIE_SIM0.003__lowE_10000_upE_460000_stepE_30000_lowD_500.0_upD_8000.0_stepD_500.0')
ax.set_xlabel('Damping Ns/m')
ax.set_ylabel('Stifness N/m')
ax.set_zlabel('Amplification')
A2 = np.arange(225)
A2.fill(20.757)
# Creating color map
my_cmap2 = plt.get_cmap('gray')
# Data for three-dimensional scattered points
zdata2 = A2
xdata = D
ydata = E
# Creating plot
trisurf2 = ax.plot_trisurf(xdata, ydata, zdata2,
cmap = my_cmap2,
linewidth = 0.2,
antialiased = False,
edgecolor = 'none', alpha = 0.2)
fig.colorbar(trisurf2, ax = ax, shrink = 0.5, aspect = 10)
print(type(fig))
#fig.write_html("file.html")
plotly.io.to_html(fig=fig)
fig.savefig('3D_Plot_PNG_lowE_10000_upE_460000_stepE_30000_lowD_500.0_upD_8000.0_stepD_500.0.png')
fig.show()
------------------------------------------------------------------------------------------
Figure 1
printed: <class 'matplotlib.figure.Figure'>
ValueError:
The fig parameter must be a dict or Figure.
Received value of type <class 'matplotlib.figure.Figure'>: Figure(1600x900)
As far as I'm aware, Matplotlib is not able to generate 3D html plot.
Moreover, what you tried above is wrong. That error message is telling you that Plotly's to_html only works with Plotly's Figure. So mixing Plotly and Matplotlib is not going to work. You need to create a Plotly figure.
Also, I don't think that Plotly exposes something similar to Matplotlib's plot_trisurf. However, it exposes go.Mesh that allows us to achieve the same result.
The recipe:
Generate your numerical data.
Create a triangulation. We will use Matplotlib's Triangulation class for this part.
Create the Plotly figure and add the surface.
Export the figure to html.
Here I'm going to post an example to guide you:
import numpy as np
import matplotlib.tri as mtri
import plotly.graph_objects as go
### DATA GENERATION
# Make parameter spaces radii and angles.
n_angles = 36
n_radii = 8
min_radius = 0.25
radii = np.linspace(min_radius, 0.95, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
angles[:, 1::2] += np.pi/n_angles
# Map radius, angle pairs to x, y, z points.
x = (radii*np.cos(angles)).flatten()
y = (radii*np.sin(angles)).flatten()
z = (np.cos(radii)*np.cos(3*angles)).flatten()
### TRIANGULATION
# Create the Triangulation; no triangles so Delaunay triangulation created.
triang = mtri.Triangulation(x, y)
# Mask off unwanted triangles.
xmid = x[triang.triangles].mean(axis=1)
ymid = y[triang.triangles].mean(axis=1)
mask = xmid**2 + ymid**2 < min_radius**2
triangles = triang.triangles[~mask]
### PLOT
fig = go.Figure(data=[
# go.Mesh allows to provide the triangulation
go.Mesh3d(
x=x, y=y, z=z,
colorbar_title='z',
colorscale="aggrnyl",
# Intensity of each vertex, which will be interpolated and color-coded
intensity =z,
# i, j and k give the vertices of triangles
i = triangles[:, 0],
j = triangles[:, 1],
k = triangles[:, 2],
showscale=True
)
])
fig.show()
### EXPORT TO HTML
# Please, execute `help(fig.write_html)` to learn about all the
# available keyword arguments to control the output
fig.write_html("test.html", include_plotlyjs=True, full_html=True)
I am trying to plot a map of a sphere with an orthographic projection of the Northern (0-40N) and Southern (0-40S) hemispheres, and a Mollweide projection of the central latitudes (60N-60S). I get the following plot:
which shows a problem: there is a square bounding box with cut corners around the hemispherical plots. Note that the extent of the colours is the same for all three plots (-90 to 90).
When I plot a hemisphere without limiting its extent, however, I get a round bounding box, as expected from an orthographic projection:
Using plt.xlim(-90,-50) results in a vertical stripe, and plt.ylim(-90,-50) in a horizontal stripe, so that is no solution either.
How can I limit the latitudinal extent of my orthographic projection, whilst maintaining the circular bounding box?
The code to produce above graphs:
import numpy as np
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
# Create dummy data, latitude from -90(S) to 90 (N), lon from -180 to 180
theta, phi = np.meshgrid(np.arange(0,180),np.arange(0,360));
theta = -1*(theta.ravel()-90)
phi = phi.ravel()-180
radii = theta
# Make masks for hemispheres and central
mask_central = np.abs(theta) < 60
mask_north = theta > 40
mask_south = theta < -40
data_crs= ccrs.PlateCarree() # Data CRS
# Grab map projections for various plots
map_proj = ccrs.Mollweide(central_longitude=0)
map_proj_N = ccrs.Orthographic(central_longitude=0, central_latitude=90)
map_proj_S = ccrs.Orthographic(central_longitude=0, central_latitude=-90)
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 2,projection=map_proj)
im1 = ax1.scatter(phi[mask_central],
theta[mask_central],
c = radii[mask_central],
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax1.set_title('Central latitudes')
ax_N = fig.add_subplot(2, 2, 1, projection=map_proj_N)
ax_N.scatter(phi[mask_north],
theta[mask_north],
c = radii[mask_north],
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax_N.set_title('Northern hemisphere')
ax_S = fig.add_subplot(2, 2, 2, projection=map_proj_S)
ax_S.scatter(phi[mask_south],
theta[mask_south],
c = radii[mask_south],
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax_S.set_title('Southern hemisphere')
fig = plt.figure()
ax = fig.add_subplot(111,projection = map_proj_N)
ax.scatter(phi,
theta,
c = radii,
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax.set_title('Northern hemisphere')
plt.show()
(1). In all of your plots, when you use scatter(), the size of the scatter points should be defined with proper s=value, otherwise the default value is used. I use s=0.2 and the resulting plots look better.
(2). For 'Central latitudes' case, you need to specify correct y-limits with set_ylim(). This involves the computation of them. The use of transform_point() is applied here.
(3). For the remaining plots that require elimination of unneeded features, proper circular clip paths can be used. Their perimeters are also used to plot as map boundaries in both cases. Their existence may cause trouble plotting other map features (such as coastlines) as I demonstrate with the code and its output.
# original is modified and extended
import numpy as np
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
import matplotlib.patches as mpatches # need it to create clip-path
# Create dummy data, latitude from -90(S) to 90 (N), lon from -180 to 180
theta, phi = np.meshgrid(np.arange(0,180),np.arange(0,360));
theta = -1*(theta.ravel()-90)
phi = phi.ravel()-180
radii = theta
# Make masks for hemispheres and central
mask_central = np.abs(theta) < 60
mask_north = theta > 40
mask_south = theta < -40
data_crs= ccrs.PlateCarree() # Data CRS
# Grab map projections for various plots
map_proj = ccrs.Mollweide(central_longitude=0)
map_proj_N = ccrs.Orthographic(central_longitude=0, central_latitude=90)
map_proj_S = ccrs.Orthographic(central_longitude=0, central_latitude=-90)
# 'Central latitudes' plot
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 2, projection=map_proj)
# Note: Limits of plot depends on plotting data, but not exact!
im1 = ax1.scatter(phi[mask_central],
theta[mask_central],
s = 0.2,
c = radii[mask_central],
transform=data_crs,
vmin = -90,
vmax = 90,
)
# compute y limits
_, y_btm = map_proj.transform_point(0, -60, ccrs.Geodetic())
_, y_top = map_proj.transform_point(0, 60, ccrs.Geodetic())
# apply y limits
ax1.set_ylim(y_btm, y_top)
ax1.coastlines(color='k', lw=0.35)
ax1.set_title('Central latitudes')
ax_N = fig.add_subplot(2, 2, 1, projection=map_proj_N)
ax_N.scatter(phi[mask_north],
theta[mask_north],
s = 0.1, # not mandatory
c = radii[mask_north],
transform=data_crs,
vmin = -90,
vmax = 90,
)
# use a circular path as map boundary
clip_circle = mpatches.Circle(xy=[0,0], radius=4950000, facecolor='none', edgecolor='k')
ax_N.add_patch(clip_circle)
ax_N.set_boundary(clip_circle.get_path(), transform=None, use_as_clip_path=True)
# with `use_as_clip_path=True` the coastlines do not appear
ax_N.coastlines(color='k', lw=0.75, zorder=13) # not plotted!
ax_N.set_title('Northern hemisphere1')
# 'Southern hemisphere' plot
ax_S = fig.add_subplot(2, 2, 2, projection=map_proj_S)
ax_S.scatter(phi[mask_south],
theta[mask_south],
s = 0.02,
c = radii[mask_south],
transform=data_crs,
vmin = -90,
vmax = 90,
)
clip_circle = mpatches.Circle(xy=[0,0], radius=4950000, facecolor='none', edgecolor='k')
ax_S.add_patch(clip_circle)
# applying the clip-circle as boundary, but not use as clip-path
ax_S.set_boundary(clip_circle.get_path(), transform=None, use_as_clip_path=False)
# with `use_as_clip_path=False` the coastlines is plotted, but goes beyond clip-path
ax_S.coastlines(color='k', lw=0.75, zorder=13)
ax_S.set_title('Southern hemisphere')
# 'Northern hemisphere2' plot, has nice circular limit
fig = plt.figure()
ax = fig.add_subplot(111,projection = map_proj_N)
ax.scatter(phi,
theta,
s = 0.2,
c = radii,
transform=data_crs,
vmin = -90,
vmax = 90,
)
ax.coastlines(color='k', lw=0.5, zorder=13)
ax.set_title('Northern hemisphere2')
ax.set_global()
plt.show()
The output plot:
The usual axes in matplotlib are rectangular. For some projections in cartopy however, it does not make sense to show a rectangle where part of it isn't even defined. Those regions are encircled. This way it is ensured that the axes content always stays within the border.
If you do not want this, but instead use a circular border, even if part of the plot would potentially lie outside the circle, you would define that circle manually:
import numpy as np
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
# Create dummy data, latitude from -90(S) to 90 (N), lon from -180 to 180
theta, phi = np.meshgrid(np.arange(0,180),np.arange(0,360));
theta = -1*(theta.ravel()-90)
phi = phi.ravel()-180
# Make mask for hemisphere
mask_north = theta > 40
data_crs= ccrs.PlateCarree() # Data CRS
# Grab map projections for various plots
map_proj_N = ccrs.Orthographic(central_longitude=0, central_latitude=90)
fig = plt.figure()
ax_N = fig.add_subplot(121, projection=map_proj_N)
ax_N.scatter(phi[mask_north], theta[mask_north],
c = theta[mask_north], transform=data_crs,
vmin = -90, vmax = 90)
ax_N.set_title('Northern hemisphere')
### Remove undesired patch
ax_N.patches[0].remove()
### Create new circle around the axes:
circ = plt.Circle((.5,.5), .5, edgecolor="k", facecolor="none",
transform=ax_N.transAxes, clip_on=False)
ax_N.add_patch(circ)
#### For comparisson, plot the full data in the right subplot:
ax = fig.add_subplot(122,projection = map_proj_N)
ax.scatter(phi, theta, c = theta,
transform=data_crs, vmin = -90, vmax = 90)
ax.set_title('Northern hemisphere')
plt.show()
I am trying to make use the polar plot projection to make a radar chart. I would like to know how to put only one grid line in bold (while the others should remain standard).
For my specific case, I would like to highlight the gridline associated to the ytick "0".
from matplotlib import pyplot as plt
import pandas as pd
import numpy as np
#Variables
sespi = pd.read_csv("country_progress.csv")
labels = sespi.country
progress = sespi.progress
angles=np.linspace(0, 2*np.pi, len(labels), endpoint=False)
#Concatenation to close the plots
progress=np.concatenate((progress,[progress[0]]))
angles=np.concatenate((angles,[angles[0]]))
#Polar plot
fig=plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.plot(angles, progress, '.--', linewidth=1, c="g")
#ax.fill(angles, progress, alpha=0.25)
ax.set_thetagrids(angles * 180/np.pi, labels)
ax.set_yticklabels([-200,-150,-100,-50,0,50,100,150,200])
#ax.set_title()
ax.grid(True)
plt.show()
The gridlines of a plot are Line2D objects. Therefore you can't make it bold. What you can do (as shown, in part, in the other answer) is to increase the linewidth and change the colour but rather than plot a new line you can do this to the specified gridline.
You first need to find the index of the y tick labels which you want to change:
y_tick_labels = [-100,-10,0,10]
ind = y_tick_labels.index(0) # find index of value 0
You can then get a list of the gridlines using gridlines = ax.yaxis.get_gridlines(). Then use the index you found previously on this list to change the properties of the correct gridline.
Using the example from the gallery as a basis, a full example is shown below:
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
ax = plt.subplot(111, projection='polar')
ax.set_rmax(2)
ax.set_rticks([0.5, 1, 1.5, 2]) # less radial ticks
ax.set_rlabel_position(-22.5) # get radial labels away from plotted line
ax.grid(True)
y_tick_labels = [-100, -10, 0, 10]
ax.set_yticklabels(y_tick_labels)
ind = y_tick_labels.index(0) # find index of value 0
gridlines = ax.yaxis.get_gridlines()
gridlines[ind].set_color("k")
gridlines[ind].set_linewidth(2.5)
plt.show()
Which gives:
It is just a trick, but I guess you could just plot a circle and change its linewidth and color to whatever could be bold for you.
For example:
import matplotlib.pyplot as plt
import numpy as np
Yline = 0
Npoints = 300
angles = np.linspace(0,360,Npoints)*np.pi/180
line = 0*angles + Yline
ax = plt.subplot(111, projection='polar')
plt.plot(angles, line, color = 'k', linewidth = 3)
plt.ylim([-1,1])
plt.grid(True)
plt.show()
In this piece of code, I plot a line using plt.plot between any point of the two vectors angles and line. The former is actually all the angles between 0 and 2*np.pi. The latter is constant, and equal to the 'height' you want to plot that line Yline.
I suggest you try to decrease and increase Npoints while having a look to the documentaion of np.linspace() in order to understand your problem with the roundness of the circle.
I am trying to draw the maximum (theoretical) field of view of a satellite along its orbit. I am using Basemap, on which I want to plot different positions along the orbit (with scatter), and I would like to add the whole field of view using the tissot method (or equivalent).
The code below works fine until the latitude reaches about 75 degrees North, on a 300km altitude orbit. Beyond which the code outputs a ValueError:
"ValueError: undefined inverse geodesic (may be an antipodal point)"
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import math
earth_radius = 6371000. # m
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(projection='cyl', resolution='l',
llcrnrlat=-90, urcrnrlat=90,
llcrnrlon=-180, urcrnrlon=180)
# draw the coastlines on the empty map
m.drawcoastlines(color='k')
# define the position of the satellite
position = [300000., 75., 0.] # altitude, latitude, longitude
# radius needed by the tissot method
radius = math.degrees(math.acos(earth_radius / (earth_radius + position[0])))
m.tissot(position[2], position[1], radius, 100, facecolor='tab:blue', alpha=0.3)
m.scatter(position[2], position[1], marker='*', c='tab:red')
plt.show()
To be noted that the code works fine at the south pole (latitude lower than -75). I know it's a known bug, is there a known workaround for this issue?
Thanks for your help!
What you found is some of Basemap's limitations. Let's switch to Cartopy for now. The working code will be different but not much.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import math
earth_radius = 6371000.
position = [300000., 75., 0.] # altitude (m), lat, long
radius = math.degrees(math.acos(earth_radius / (earth_radius + position[0])))
print(radius) # in subtended degrees??
fig = plt.figure(figsize=(12,8))
img_extent = [-180, 180, -90, 90]
# here, cartopy's' `PlateCarree` is equivalent with Basemap's `cyl` you use
ax = fig.add_subplot(1, 1, 1, projection = ccrs.PlateCarree(), extent = img_extent)
# for demo purposes, ...
# let's take 1 subtended degree = 112 km on earth surface (*** you set the value as needed ***)
ax.tissot(rad_km=radius*112, lons=position[2], lats=position[1], n_samples=64, \
facecolor='red', edgecolor='black', linewidth=0.15, alpha = 0.3)
ax.coastlines(linewidth=0.15)
ax.gridlines(draw_labels=False, linewidth=1, color='blue', alpha=0.3, linestyle='--')
plt.show()
With the code above, the resulting plot is:
Now, if we use Orthographic projection, (replace relevant line of code with this)
ax = fig.add_subplot(1, 1, 1, projection = ccrs.Orthographic(central_longitude=0.0, central_latitude=60.0))
you get this plot:
I am having trouble clipping a seaborn plot (a kdeplot, specifically) as I thought would be fairly simple per this example in the matplotlib docs.
For example, the following code:
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
fig = plt.figure()
ax = fig.add_subplot(111, frameon=False, xticks=[], yticks=[])
random_points = np.array([p for p in np.random.random(size=(100, 2)) if 0 < p[0] < 1 and 0 < p[1] < 1])
kde = sns.kdeplot(random_points[:,0], random_points[:,1], ax=ax)
xmin, xmax = kde.get_xlim()
ymin, ymax = kde.get_ylim()
patch = mpl.patches.Circle(((xmin + xmax)/2, (ymin + ymax) / 2), radius=0.4)
ax.add_patch(patch)
kde.set_clip_path(patch)
Results in the following output:
I would like to clip this result so that the KDE contour lines do not appear outside of the circle. I haven't found a way to do it thus far...is this possible?
Serenity's answer works for simple shapes, but breaks down for reasons unknown when the shape contains more than three or so vertices (I had difficulty establishing the exact parameters, even). For sufficiently large shapes the fill flows into where the edge should be, as for example here.
It did get me thinking along the right path, however. While it doesn't seem to be possible to do so simply using matplotlib natives (perhaps there's an error in the code he provided anyway?), it's easy as pie when using the shapely library, which is meant for tasks like this one.
Generating the Shape
In this case you will need shapely's symmetric_difference method. A symmetric difference is the set theoretic name for this cut-out operation.
For this example I've loaded a Manhattan-shaped polygon as a shapely.geometry.Polygon object. I won't covert the initialization process here, it's easy to do, and everything you expect it to be.
We can draw a box around our manhattan using manhattan.envelope, and then apply the difference. This is the following:
unmanhattan = manhattan.envelope.symmetric_difference(manhattan)
Doing which gets us to:
Adding it to the Plot
Ok, but this is a shapely object not a matplotlib Patch, how do we add it to the plot? The descartes library handles this conversion.
unmanhattan_patch = descartes.PolygonPatch(unmanhattan)
This is all we need! Now we do:
unmanhattan_patch = descartes.PolygonPatch(unmanhattan)
ax.add_patch(unmanhattan_patch)
sns.kdeplot(x=points['x_coord'], y=points['y_coord'], ax=ax)
And get:
And with a little bit more work extending this to the rest of the polygons in the view (New York City), we can get the following final result:
I guess your example work only for 'imshow'.
To hide contours lines over the circle you have to plot 'inverse' polygon of desired color.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
import seaborn as sns
# Color plot except polygon
def mask_outside(poly_verts, facecolor = None, ax = None):
from matplotlib.patches import PathPatch
from matplotlib.path import Path
if ax is None: ax = plt.gca()
if facecolor is None: facecolor = plt.gcf().get_facecolor()
# Construct inverse polygon
xlim, ylim = ax.get_xlim(), ax.get_ylim()
bound_verts = [(xlim[0], ylim[0]), (xlim[0], ylim[1]),
(xlim[1], ylim[1]), (xlim[1], ylim[0]), (xlim[0], ylim[0])]
bound_codes = [Path.MOVETO] + (len(bound_verts) - 1) * [Path.LINETO]
poly_codes = [Path.MOVETO] + (len(poly_verts) - 1) * [Path.LINETO]
# Plot it
path = Path(bound_verts + poly_verts, bound_codes + poly_codes)
ax.add_patch(PathPatch(path, facecolor = facecolor, edgecolor = 'None', zorder = 1e+3))
# Your example
fig = plt.figure()
ax = fig.add_subplot(111, frameon=False, xticks=[], yticks=[])
random_points = np.array([p for p in np.random.random(size=(100, 2)) if 0 < p[0] < 1 and 0 < p[1] < 1])
kde = sns.kdeplot(random_points[:,0], random_points[:,1], ax=ax)
xmin, xmax = kde.get_xlim()
ymin, ymax = kde.get_ylim()
patch = mpl.patches.Circle(((xmin + xmax) / 2, (ymin + ymax) / 2), radius=0.4)
mask_outside([tuple(x) for x in patch.get_verts()]) # call before add_patch!
ax.add_patch(patch)
plt.show()