How to optimize the zoom parameter in zoomed_inset_axes? - python

I am creating plots that include zoom inserts. The data is diverse it is impossoble for me to know what the data will be like before the program starts. I want to make the zoom insert zoom in as much as possible, without overlapping with any other element of my plot. Here is an example, where I use a zoom of 2. Ideally, I would like to automatically determine what this number should be:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
fig, ax = plt.subplots()
xin = np.linspace(0, np.random.uniform(.5, 4), 1000)
x_samples = np.random.uniform(0.9, 1.1, (1, 1000)) * np.sqrt(xin[:, np.newaxis])
ax.fill_between(xin, x_samples.min(1), x_samples.max(1))
axins = zoomed_inset_axes(ax, zoom=2, loc='upper left')
axins.fill_between(xin, x_samples.min(1), x_samples.max(1))
axins.set_xlim(.05, .1)
idx = np.logical_and(xin > 0.05, xin < 0.1)
axins.set_ylim(x_samples.min(1)[idx].min(), x_samples.max(1)[idx].max())
axins.set_xticks([])
axins.set_yticks([])
mark_inset(ax, axins, loc1=4, loc2=3, fc="none", ec="0.5")
plt.savefig('hey')
plt.clf()
As you can see, zoom=2 was too low of a value. I can manually set the zoom parameter to a correct value. This is a tedious process. Is there a way to automatically find the zoom parameter that will maximize the insert size while avoiding overlaps with other parts of the plot?

We can face this problem in an iterative way:
Start with the maximum possible zoom (such that the inset occupies the whole height of the plot). As a result, part of the inset will overlap the plot.
Check how much vertical gap exists before the point where the overlapping starts.
Based on the current height of the inset, scale it down to avoid overlapping.
After the rescaling, the width of the inset is also reduced, so we can scale it up again with the free vertical gap that has been left behind.
Go back to 2. until convergence / maximum number of iterations is reached.
In practice the convergence is fast and reached in less than 10 iterations with the given data.
Visually:
Code for insets at the upper-left location.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes, mark_inset
def get_inset_max_zoom(x, y, xlim_inset, ylim_inset, max_iters=10):
""" Zoom that maximizes inset size without overlapping the artists """
# width and height of the inset in non-scaled coordinates.
inset_w = xlim_inset[1] - xlim_inset[0]
inset_h = ylim_inset[1] - ylim_inset[0]
# max y-coordinate of the whole plot.
y_max_plot = y.max()
# start with maximum zoom.
y_gap = y_max_plot - y.min()
zoom = y_gap / inset_h
for i in range(max_iters):
y_affected_max = y[x < zoom * inset_w].max()
# recalculate zoom by adjusting the gap.
y_gap = y_max_plot - y_affected_max
zoom = y_gap / inset_h
return zoom
if __name__ == "__main__":
# Change the seed to show produce different values.
rng = np.random.RandomState(seed=0)
# main plot.
fig, ax = plt.subplots()
xin = np.linspace(0, rng.uniform(.5, 4), 1000)
x_samples = rng.uniform(
0.9, 1.1, (1, 1000)) * np.sqrt(xin[:, np.newaxis])
ax.fill_between(xin, x_samples.min(1), x_samples.max(1))
# get xy pairs.
y = x_samples.ravel()
x = np.repeat(xin, x_samples.shape[1])
# define the limits and location of the zoom inset.
xlim_inset = (.05, .1)
idx = np.logical_and(xin > xlim_inset[0], xin < xlim_inset[1])
ylim_inset = (x_samples.min(1)[idx].min(), x_samples.max(1)[idx].max())
loc = 'upper left'
# get max zoom.
zoom = get_inset_max_zoom(x, y, xlim_inset, ylim_inset, max_iters=5)
# create the inset.
axins = zoomed_inset_axes(ax, zoom=zoom, loc=loc, borderpad=0.5)
axins.set(
xlim=xlim_inset,
ylim=ylim_inset,
xticks=[], yticks=[])
# connect the bboxes.
mark_inset(ax, axins, loc1=4, loc2=3, fc="none", ec="0.5")
# plot within the inset.
axins.fill_between(xin, x_samples.min(1), x_samples.max(1))
Generalizing to the 4 corner locations {upper-left, upper-right, lower-right, lower-left}. For instance, with loc = 'lower right':
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes, mark_inset
def get_inset_max_zoom_given_loc(
x, y, xlim_inset, ylim_inset, loc='upper left', max_iters=10):
""" Zoom that maximizes inset size without overlapping the artists """
# width and height of the inset in non-scaled coordinates.
inset_w = xlim_inset[1] - xlim_inset[0]
inset_h = ylim_inset[1] - ylim_inset[0]
# handy variables.
is_left = 'left' in loc
is_upper = 'upper' in loc
y_min_plot, y_max_plot = y.min(), y.max()
y_xtm_plot = y_max_plot if is_upper else y_min_plot
x_max_plot = x.max()
# start with maximum zoom.
y_gap = y_max_plot - y_min_plot
zoom = y_gap / inset_h
for i in range(max_iters):
# get affected x-coordinate range.
if is_left:
x_affected = x < zoom * inset_w
else:
x_affected = x > x_max_plot - zoom * inset_w
# get affected y-coordinate extremum.
y_affected = y[x_affected]
y_affected_xtm = y_affected.max() if is_upper else y_affected.min()
# recalculate zoom by adjusting the gap.
y_gap = abs(y_xtm_plot - y_affected_xtm)
zoom = y_gap / inset_h
return zoom
if __name__ == "__main__":
# Change the seed to show produce different values.
rng = np.random.RandomState(seed=0)
# main plot.
fig, ax = plt.subplots()
xin = np.linspace(0, rng.uniform(.5, 4), 1000)
x_samples = rng.uniform(
0.9, 1.1, (1, 1000)) * np.sqrt(xin[:, np.newaxis])
ax.fill_between(xin, x_samples.min(1), x_samples.max(1))
# get xy pairs.
y = x_samples.ravel()
x = np.repeat(xin, x_samples.shape[1])
# define the limits and location of the zoom inset.
xlim_inset = (.05, .1)
idx = np.logical_and(xin > xlim_inset[0], xin < xlim_inset[1])
ylim_inset = (x_samples.min(1)[idx].min(), x_samples.max(1)[idx].max())
loc = 'lower right'
# get max zoom.
zoom = get_inset_max_zoom_given_loc(
x, y, xlim_inset, ylim_inset, loc=loc, max_iters=10)
# create the inset.
axins = zoomed_inset_axes(ax, zoom=zoom, loc=loc, borderpad=0.5)
axins.set(
xlim=xlim_inset,
ylim=ylim_inset,
xticks=[], yticks=[])
# connect the bboxes.
mark_inset(ax, axins, loc1=4, loc2=3, fc="none", ec="0.5")
# plot within the inset.
axins.fill_between(xin, x_samples.min(1), x_samples.max(1))

Related

Display matplotlib legend element as 2D line of colormap

I wish to modify the 2D line in my legend to plot as line segments (or another method like patches) that will display the range of my colormap (here viridis_r) instead of a singular color. While the third variable (radius) is included in the colorbar, having it displayed in the legend as well will be informative when I add more complications to the plot. Thanks!
fig, ax = plt.subplots()
radii = [1,2,3,4,5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(radii)
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
ax.plot(x, y, color=cmap(norm(radius)))
radius_2Dline = plt.Line2D((0, 1), (0, 0), color='k', linewidth=2)
ax.legend([radius_2Dline],['Radius'], loc='best')
ax.set_aspect( 1 )
fig.colorbar(m).set_label('Radius', size=15)
plt.show()
The following approach uses the "tuple legend handler". That handler puts a list of legend handles (in this case the circles drawn via ax.plot). Setting ndivide=None will draw one short line for each element in the list. The padding can be set to 0 to avoid gaps between these short lines. The default handlelength might be too small to properly see these special handles; therefore, the example code below increases it a bit.
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerTuple
import numpy as np
fig, ax = plt.subplots()
radii = [1, 2, 3, 4, 5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
lines = [] # list of lines to be used for the legend
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
line, = ax.plot(x, y, color=cmap(norm(radius)))
lines.append(line)
ax.legend(handles=[tuple(lines)], labels=['Radius'],
handlelength=3, handler_map={tuple: HandlerTuple(ndivide=None, pad=0)})
ax.set_aspect('equal')
plt.tight_layout()
plt.show()
I am not sure if this is your goal but here is a stab at it. Following this answer, you can make a 'fake' legend with a colormap.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, ax = plt.subplots()
radii = [1, 2, 3, 4, 5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(radii)
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
ax.plot(x, y, color=cmap(norm(radius)))
# Set box that will act as a 'fake' legend, 25% width of the
# x-axis, 15% of y-axis
cbbox = inset_axes(ax, width="25%", height="15%", loc=2)
cbbox.tick_params(
axis = 'both',
left = False,
top = False,
right = False,
bottom = False,
labelleft = False,
labeltop = False,
labelright = False,
labelbottom = False
)
# Semi-transparent like the usual ax.legend()
cbbox.set_facecolor([1, 1, 1, 0.7])
# Colorbar inside the fake legend box, occupying 85% of the
# box width and %5 box height
cbaxes = inset_axes(cbbox, width="85%", height="5%", loc=2)
cbar = fig.colorbar(m, cax=cbaxes, orientation='horizontal',
ticks=[1, 3, 5])
cbar.set_label('Radius', size=9)
cbar.ax.tick_params(labelsize=9)
ax.set_aspect(1)
plt.show()
I was unsuccessful in creating an actual ax.legend() from a LineCollection or a multicolored line - it only plotted one color - so my solution was this 'fake' legend approach. Hope this helps, cheers.

Loop to create subplot /Python

i have a little problem to create a subplot loop.
The following code show my result for one plot.... So it starts with a dayloop than with a hour loop (8 timesteps).
If i run the code i get a nice QUiver plot with the colorbar.
for dd in range(1,15):
day=str(dd)
readfile=fns[files_indizes[dd]]
if dd < 10:
nc_u_comp = NetCDFFile(ROOT+u_comp1+'0'+day+comp)
nc_v_comp = NetCDFFile(ROOT+v_comp1+'0'+day+comp)
else:
nc_u_comp = NetCDFFile(ROOT+u_comp1+day+comp)
nc_v_comp = NetCDFFile(ROOT+v_comp1+day+comp)
time = nc_u_comp.variables['time'][:]
index=readfile.find(comp)
index=index+len(comp)
date=readfile[index-14:index-6]
plt.clf()
for tt in range(0,len(time)):
if tt < 10:
h =str(0)+str(tt)
else:
h=str(tt)
varU=nc_u_comp.variables['u10'][tt,:,:]
varV=nc_v_comp.variables['v10'][tt,:,:]
lat = nc_u_comp.variables['latitude'][:]
lon = nc_u_comp.variables['longitude'][:]
plt.rcParams["figure.figsize"] = [10,10]
#plane projection of the world
#map with box size (defintion on the top)
box = sgeom.box(minx=llcrnrlon, maxx=urcrnrlon, miny=llcrnrlat, maxy=urcrnrlat)
x0, y0, x1, y1 = box.bounds
#Map plot. The middel of the map is central_longitude
#proj = ccrs.PlateCarree(central_longitude=0)
proj=ccrs.PlateCarree()
#Change middelpoint of the map
box_proj = ccrs.PlateCarree(central_longitude=0)
ax2 = plt.axes(projection=proj)
ax2.set_extent([x0, x1, y0, y1], box_proj)
ax2.add_feature(cartopy.feature.BORDERS, linestyle='-', alpha=.5)
ax2.coastlines(resolution='50m')
#Definition of the scale_bar
gl = ax2.gridlines(ccrs.PlateCarree(), \
linestyle='--', alpha=1, linewidth=0.5, draw_labels=True)
gl.xlabels_top = False
gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
magnitude = (varU ** 2 + varV ** 2) ** 0.5
strm =plt.streamplot(lon , lat , varU, varV, linewidth=2, density=2, color=magnitude)
cbar= plt.colorbar()
cbar.set_label('$m/s$')
name='Wind in 10 m '+ date + h+' UTC'
ax2.set_aspect('auto')
plt.title(name, y=1)
Now i want to create an 2x4 Subplot array with a colorbar allocate to the complete Subplot array.
I find some infromation in the internet, but it doesn't run with my code. Maybe someone can help me?
This shows how to plot an array of simple Cartopy maps in 4 rows 2 columns. Also shows how to plot a colorbar to accompany the maps array. Hope it helps.
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib as mpl
# create figure with figsize big enough to accomodate all maps, labels, etc.
fig = plt.figure(figsize=(8, 10), tight_layout=False)
# define plot array's arrangement
columns = 2
rows = 4
# set projection to use
projex = ccrs.PlateCarree()
# set the colormap and norm for
# the colorbar to use
cmap1 = mpl.cm.magma
norm1 = mpl.colors.Normalize(vmin=0, vmax=100)
def plotmymap(axs):
# your plot specs of each map should replace this
img = np.random.randint(100, size=(15, 30)) # 2d array of random values (1-100)
# render image on current axis
plims = plt.imshow(img, extent=[-180,180,-90,90], alpha=0.5, cmap=cmap1, norm=norm1)
axs.set_global()
axs.coastlines()
# add title to the map
axs.set_title("Map_"+str(i))
return plims # for use by colorbar
for i in range(1, columns*rows +1):
# add a subplot into the array of plots
ax = fig.add_subplot(rows, columns, i, projection=projex)
plims = plotmymap(ax) # a simple maps is created on subplot
# add a subplot for vertical colorbar
bottom, top = 0.1, 0.9
left, right = 0.1, 0.8
fig.subplots_adjust(top=top, bottom=bottom, left=left, right=right, hspace=0.15, wspace=0.25)
cbar_ax = fig.add_axes([0.85, bottom, 0.05, top-bottom])
fig.colorbar(plims, cax=cbar_ax) # plot colorbar
plt.show() # this plot all the maps
The resulting plots:

Aligning maps made using basemap

Is there a way to align python basemaps like this figure below?
Here's some sample basemap code to produce a map:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8, 4.5))
plt.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.00)
m = Basemap(projection='robin',lon_0=0,resolution='c')
m.fillcontinents(color='gray',lake_color='white')
m.drawcoastlines()
plt.savefig('world.png',dpi=75)
I am not an expert with Matplotlib, but I found a way to get a similar result by using the data files included in the source folder of basemap. They can be combined into a meshgrid to plot some data, in the example below we plot the altitude at every point.
One of the tricks I used is to set matplotlib to an orthogonal projection so that there is no distortion in the vertical spacing of the maps.
I have put the parameters at the beginning of the code as you may find it useful to adjust.
One thing I couldn't get my head around is the shadow under the maps.
from mpl_toolkits.mplot3d import proj3d
from mpl_toolkits.basemap import Basemap
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import numpy as np
import matplotlib.pyplot as plt
# Parameters
n_maps = 5 # Number of maps
z_spacing = 4. # Spacing of maps along z
z_reduction = 1E-8 # Reduction factor for Z data, makes the map look flat
view_angles = (14., -100.) # Set view port angles
colbar_bottom = 0.2 # Space at the bottom of colorbar column
colbar_spacing = .132 # Space between colorbars
colbar_height = 0.1 # Height of colorbars
# Set orthogonal projection
def orthogonal_proj(zfront, zback):
a = (zfront+zback)/(zfront-zback)
b = -2*(zfront*zback)/(zfront-zback)
return np.array([[1,0,0,0],
[0,1,0,0],
[0,0,a,b],
[0,0,-0.0001,zback]])
proj3d.persp_transformation = orthogonal_proj
fig = plt.figure(figsize=[30, 10*n_maps])
ax = fig.gca(projection='3d')
etopo = np.loadtxt('etopo20data.gz')
lons = np.loadtxt('etopo20lons.gz')
lats = np.loadtxt('etopo20lats.gz')
# Create Basemap instance for Robinson projection.
m = Basemap(projection='robin', lon_0=0.5*(lons[0]+lons[-1]))
# Compute map projection coordinates for lat/lon grid.
X, Y = m(*np.meshgrid(lons,lats))
# Exclude the oceans
Z = etopo.clip(-1)
# Set the colormap
cmap = plt.cm.get_cmap("terrain")
cmap.set_under("grey")
for i in range(n_maps):
c = ax.contourf(X, Y, z_spacing*i + z_reduction*Z, 30, cmap=cmap, vmin=z_spacing*i, extend='neither')
cax = inset_axes(ax,
width="5%",
height="100%",
loc=3,
bbox_to_anchor=(.85, colbar_spacing*i+colbar_bottom, .2, colbar_height),
bbox_transform=ax.transAxes,
borderpad=0
)
cb = fig.colorbar(c, cax=cax)
cb.set_label("Altitude")
# Reset the ticks of the color bar to match initial data
cb.set_ticks([z_spacing * i + j/10. * z_reduction * Z.max() for j in range(11)])
cb.set_ticklabels([str(int(j/10. * Z.max())) for j in range(11)])
ax.set_axis_off()
ax.view_init(*view_angles)
ax.set_xlim3d(X.min(), X.max())
ax.set_ylim3d(Y.min(), Y.max())
ax.set_zlim3d(-1E-2, (n_maps-1)*z_spacing)
plt.savefig('world.png',dpi=75)
Edit:
If you want shadows and don't mind the extra compute time you can change the beginning of the for loop with something along the lines of:
shadow_Z = np.empty(Z.shape)
for i in range(n_maps):
c = ax.contourf(X, Y, z_spacing*i + z_reduction*Z, 30, cmap=cmaps[i], vmin=z_spacing*i, extend='neither')
for j in range(10):
shadow_Z.fill(z_spacing*i - 1E-2 * j)
s = ax.contourf((X - X.mean()) * (1 + 8E-3 * j) + X.mean() + 2E5,
(Y - Y.mean()) * (1 + 8E-3 * j) + Y.mean() - 2E5,
shadow_Z, colors='black', alpha=0.1 - j * 1E-2)

Scaling the second axe on a histogram with Matplotlib

I want to have a second axe on my histogram, with the pourcentage corresponding to each bin, like if I used normed=True. I tried to use twins, but the scale is not correct.
x = np.random.randn(10000)
plt.hist(x)
ax2 = plt.twinx()
plt.show()
Bonus point if you can make it work with log scaled x :)
plt.hist returns the bins and the number of data in each bucket. You may use these to compute the area under the histogram, and using that you may find the normalized height of each bar. twinx axis can be aligned accordingly:
xs = np.random.randn(10000)
ax1 = plt.subplot(111)
cnt, bins, patches = ax1.hist(xs)
# area under the istogram
area = np.dot(cnt, np.diff(bins))
ax2 = ax1.twinx()
ax2.grid('off')
# align the twinx axis
ax2.set_yticks(ax1.get_yticks() / area)
lb, ub = ax1.get_ylim()
ax2.set_ylim(lb / area, ub / area)
# display the y-axis in percentage
from matplotlib.ticker import FuncFormatter
frmt = FuncFormatter(lambda x, pos: '{:>4.1f}%'.format(x*100))
ax2.yaxis.set_major_formatter(frmt)

Matplotlib polar plot radial axis offset

I was wondering, is it possible to offset the start of the radial axis or move it outside of the graph.
This is what I'm hoping to achieve:
And this is what I have for now.
I have read the documentation and different topics on SO, but I couldn't find anything helpful. Does that mean that it is not even possible if it is not mentioned anywhere.
Thank you in advance.
EDIT (added snippet of a code used to create the plot):
ax = fig.add_subplot(111, projection='polar')
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.plot(X,lines[li]*yScalingFactor,label=linelabels[li],color=color,linestyle=ls)
To offset the start of the radial axis:
EDIT: As of Matplotlib 2.2.3 there's a new Axes method called set_rorigin which does exactly that. You call it with the theoretical radial coordinate of the origin. So if you call ax.set_ylim(0, 2) and ax.set_rorigin(-1), the radius of the center circle will be a third of the radius of the plot.
A quick and dirty workaround for Matplotlib < 2.2.3 is to set the lower radial axis limit to a negative value and hide the inner part of the plot behind a circle:
import numpy as np
import matplotlib.pyplot as plt
CIRCLE_RES = 36 # resolution of circle inside
def offset_radial_axis(ax):
x_circle = np.linspace(0, 2*np.pi, CIRCLE_RES)
y_circle = np.zeros_like(x_circle)
ax.fill(x_circle, y_circle, fc='white', ec='black', zorder=2) # circle
ax.set_rmin(-1) # needs to be after ax.fill. No idea why.
ax.set_rticks([tick for tick in ax.get_yticks() if tick >= 0])
# or set the ticks manually (simple)
# or define a custom TickLocator (very flexible)
# or leave out this line if the ticks are fully behind the circle
To add a scale outside the plot:
You can add an extra axes object in the upper half of the other axes and use its yaxis:
X_OFFSET = 0 # to control how far the scale is from the plot (axes coordinates)
def add_scale(ax):
# add extra axes for the scale
rect = ax.get_position()
rect = (rect.xmin-X_OFFSET, rect.ymin+rect.height/2, # x, y
rect.width, rect.height/2) # width, height
scale_ax = ax.figure.add_axes(rect)
# hide most elements of the new axes
for loc in ['right', 'top', 'bottom']:
scale_ax.spines[loc].set_visible(False)
scale_ax.tick_params(bottom=False, labelbottom=False)
scale_ax.patch.set_visible(False) # hide white background
# adjust the scale
scale_ax.spines['left'].set_bounds(*ax.get_ylim())
# scale_ax.spines['left'].set_bounds(0, ax.get_rmax()) # mpl < 2.2.3
scale_ax.set_yticks(ax.get_yticks())
scale_ax.set_ylim(ax.get_rorigin(), ax.get_rmax())
# scale_ax.set_ylim(ax.get_ylim()) # Matplotlib < 2.2.3
Putting it all together:
(The example is taken from the Matplotlib polar plot demo)
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
ax = plt.subplot(111, projection='polar')
ax.plot(theta, r)
ax.grid(True)
ax.set_rorigin(-1)
# offset_radial_axis(ax) # Matplotlib < 2.2.3
add_scale(ax)
ax.set_title("A line plot on an offset polar axis", va='bottom')
plt.show()
I am not sure if the polar plot can be adjusted like that. But here is a work-around, based on the last example given here: Floating Axes.
I have included explanatory comments in the code, if you copy/paste it, it should run as-is:
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib.projections import PolarAxes
from mpl_toolkits.axisartist.grid_finder import FixedLocator, \
MaxNLocator, DictFormatter
import numpy as np
import matplotlib.pyplot as plt
# generate 100 random data points
# order the theta coordinates
# theta between 0 and 2*pi
theta = np.random.rand(100)*2.*np.pi
theta = np.sort(theta)
# "radius" between 0 and a max value of 40,000
# as roughly in your example
# normalize the r coordinates and offset by 1 (will be clear later)
MAX_R = 40000.
radius = np.random.rand(100)*MAX_R
radius = radius/np.max(radius) + 1.
# initialize figure:
fig = plt.figure()
# set up polar axis
tr = PolarAxes.PolarTransform()
# define angle ticks around the circumference:
angle_ticks = [(0, r"$0$"),
(.25*np.pi, r"$\frac{1}{4}\pi$"),
(.5*np.pi, r"$\frac{1}{2}\pi$"),
(.75*np.pi, r"$\frac{3}{4}\pi$"),
(1.*np.pi, r"$\pi$"),
(1.25*np.pi, r"$\frac{5}{4}\pi$"),
(1.5*np.pi, r"$\frac{3}{2}\pi$"),
(1.75*np.pi, r"$\frac{7}{4}\pi$")]
# set up ticks and spacing around the circle
grid_locator1 = FixedLocator([v for v, s in angle_ticks])
tick_formatter1 = DictFormatter(dict(angle_ticks))
# set up grid spacing along the 'radius'
radius_ticks = [(1., '0.0'),
(1.5, '%i' % (MAX_R/2.)),
(2.0, '%i' % (MAX_R))]
grid_locator2 = FixedLocator([v for v, s in radius_ticks])
tick_formatter2 = DictFormatter(dict(radius_ticks))
# set up axis:
# tr: the polar axis setup
# extremes: theta max, theta min, r max, r min
# the grid for the theta axis
# the grid for the r axis
# the tick formatting for the theta axis
# the tick formatting for the r axis
grid_helper = floating_axes.GridHelperCurveLinear(tr,
extremes=(2.*np.pi, 0, 2, 1),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=tick_formatter2)
ax1 = floating_axes.FloatingSubplot(fig, 111, grid_helper=grid_helper)
fig.add_subplot(ax1)
# create a parasite axes whose transData in RA, cz
aux_ax = ax1.get_aux_axes(tr)
aux_ax.patch = ax1.patch # for aux_ax to have a clip path as in ax
ax1.patch.zorder=0.9 # but this has a side effect that the patch is
# drawn twice, and possibly over some other
# artists. So, we decrease the zorder a bit to
# prevent this.
# plot your data:
aux_ax.plot(theta, radius)
plt.show()
This will generate the following plot:
You'd have to tweak the axis labels to meet your demands.
I scaled the data because otherwise the same issue as with your plot would have occurred - the inner, empty circle would have been scaled to a dot. You might try the scaling with your polar plot and just put custom labels on the radial axis to achieve a similar effect.

Categories