How to make an inset plot with mollweide projection? - python

I want to make a skymap using the Mollweide projection for a main set of axes and for an inset axes. This is easy for the main axes but not for the inset. I've tried a few different things but it doesn't work for the inset. Please help!
Here you can find the latitude and longitude data, and here you can find the sky location probability density data.
First, I make the main plot:
xmin = min(l)
xmax = max(l)
ymin = min(b)
ymax = max(b)
X, Y = np.meshgrid(np.linspace(xmin, xmax, 100), np.linspace(ymin, ymax, 100))
mpl.rcParams["text.usetex"] = True
fig = plt.figure(1)
fig.set_figheight(8)
fig.set_figwidth(8)
ax = plt.axes(projection='mollweide')
ax.grid()
# skypost is the sky location probability-density data accessible above
plt.contour(X, Y, skypost, colors='blue', levels=[5, 50, 95])
which works fine. Next, I define the inset axes and plot the contours, however there seems to be no way that completely works for this. What I want is for the inset to zoom-in on the contours while keeping the mollweide projection. I've tried to do as the example on ligo.skymaps, i.e.,
axesinset = plt.axes(
[0.0, 0.2, 0.25, 0.25],
projection='astro degrees zoom',
center='110d +20d',
radius='10 deg' )
plt.sca(axesinset)
axesinset.contour(X, Y, skypost, colors='blue', levels=[5, 50, 95])
axesinset.grid()
but this doesn't work since the contours don't even appear! I don't understand why they don't appear. I also do not understand why the x-axis of the inset is backwards?
Instead, I've tried just plotting a new mollweide projection in the inset, and restricting the xlim and ylim, but it says these options are not supported for the mollweide projection. Is there a way around this to restrict the axes limits?
Lastly, I've tried just doing a regular inset without the mollweide, which works, however the shape of the contours are distorted relative to the contours on the main mollweide plot which is physically relevant for my case. So this is very sub-optimal.
Any suggestions and advice are greatly appreciated.

To have the axis in the correct way, you can rotate the subplot by using rotate.
Concerning the fact that your contour are not shown, it is probably because you have to add the transform keyword. If you don't specify it, it is plotted in pixel coordinates by default (https://docs.astropy.org/en/stable/visualization/wcsaxes/overlays.html).
The example below shows that the desired point (in blue) is obtained by adding ax.get_transform("world").
The blue and green points are in the lower right corner because of the rotate.
I guess that it should be the same for contour.
ax = plt.subplot(111, projection='geo degrees zoom',
center="0d - 0d", radius='10 deg', rotate='180 deg')
ax.grid()
ax.set_xlabel(r"$\phi \, [deg]$")
ax.set_ylabel(r"$\theta \, [deg]$")
ax.scatter(0,0, color = "blue")
ax.scatter(100,0, color = "green")
ax.scatter(0,0, color = "red", transform = ax.get_transform("world"))

I'm a bit late to the party, but I thought its worth mentioning that I've created a nice inset-map functionality for EOmaps...
It lets you create inset-maps in arbitrary projections and you can add whatever features you want!
from eomaps import Maps
m = Maps(Maps.CRS.Mollweide())
m.add_feature.preset.coastline()
# create a rectangular inset-map that shows a 5 degree rectangle
# centered around a given point
inset = m.new_inset_map(xy=(6, 43), xy_crs=4326,
radius=5, radius_crs=4326,
inset_crs=Maps.CRS.Mollweide(),
shape="rectangles")
inset.add_feature.preset.coastline()
inset.add_feature.preset.ocean()
inset.add_feature.cultural_10m.urban_areas(fc="r", ec="none")
m.apply_layout(
{'0_map': [0.01, 0.17333, 0.98, 0.65333],
'1_map': [0.05, 0.11667, 0.43341, 0.76667]})

Related

Can't draw circle with right proportions Matplotlib Python

I want to draw Circle on my plot. For this purpose I decided to use patch.Circle class from matplotlib. Cirlce object uses radius argument to set a radius of a circle, but if the axes ratio is not 1 (see my plot), how to draw circle with right proportions?
My code for drawing circle is:
rect = patches.Circle(xy=(9, yaxes),radius= 2, linewidth=3, edgecolor='r', facecolor='red',alpha=0.5)
ax.add_patch(rect)
yaxes is equal 206 in this example (because I wanted to draw it upper left coner).
Here is a picture I got using this code:
But I want something like this:
You could use ax.transData to transform 1,1 vs 0,0 and obtain the deformation in x vs y direction. That ratio can be used to know the horizontal versus the vertical size of the circle.
If you just need to place a circle using coordinates relative to the axes, plt.scatter with transform=ax.transAxes can be used. Note that the size is an "area" measure based on "points" (a "point" is 1/72th of an inch).
The following example code uses the data coordinates to position the "circle" (using an ellipse) and the x-coordinates for the radius. A red circle is placed using axes coordinates.
from matplotlib import pyplot as plt
from matplotlib.patches import Ellipse
import pandas as pd
import numpy as np
# plot some random data
np.random.seed(2021)
df = pd.DataFrame({'y': np.random.normal(10, 100, 50).cumsum() + 2000},
index=np.arange(101, 151))
ax = df.plot(figsize=(12, 5))
# find an "interesting" point
max_ind = df['y'].argmax()
max_x = df.index[max_ind]
max_y = df.iloc[max_ind]['y']
# calculate the aspect ratio
xscale, yscale = ax.transData.transform([1, 1]) - ax.transData.transform([0, 0])
# draw the ellipse to be displayed as circle
radius_x = 4
radius_y = radius_x * xscale / yscale
ax.add_patch(Ellipse((max_x, max_y), radius_x, radius_y, color='purple', alpha=0.4))
# use ax.scatter to draw a red dot at the top left
ax.scatter(0.05, 0.9, marker='o', s=2000, color='red', transform=ax.transAxes)
plt.show()
Some remarks about drawing the ellipse:
this will only work for linear coordinates, not e.g. for logscale or polar coordinates
the code supposes nor the axis limits nor the axis position will change afterwards, as these will distort the aspect ratio
The issue seems to be that your X (passed to xy=) is not always the same as your Y, thus the oval instead of a perfect circle.

Contour Labels not showing up

I am trying to overlay contours on top of a filled contour plot in matplotlib for some atmospheric data. However, my contour labels are not always showing up onscreen. Below is an example:
As you can see, the contour labels are only appearing on the innermost few contours.
Knowing that my contour range is defined earlier as
list(range(950,1052,4))
I have the following code to actually plot:
parallels = np.arange(0.,90,5.)
basem.drawparallels(parallels,labels=[1,0,0,0],fontsize=10)
# draw meridians
meridians = np.arange(180.,360.,5.)
basem.drawmeridians(meridians,labels=[0,0,0,1],fontsize=10)
basem.drawstates()
basem.drawcountries()
if clevs != 0:
cs = basem.contourf(x,y, plotted_var, clevs)
cl = basem.contour(x,y, plotted_var, clevsl, colors='k')
plt.clabel(cl, fmt="%1.0f", fontsize=8)
else:
cs = basem.contourf(x,y, plotted_var, cmap=plt.get_cmap(colorbar),
vmin = vmin, vmax = vmax)
cbar = basem.colorbar(cs, location='bottom', pad = "5%")
cbar.set_label(units)
Additionally, my basemap definition is:
basem = Basemap(width=5800000,height=3000000,
rsphere=(6378137.00,6356752.3142),\
resolution='h',area_thresh=1000.,projection='lcc',\
lat_1=45.,lat_2=55,lat_0=40,lon_0=-102.)
Is this a bug or is there just something I'm missing? I'm attempting to avoid the use of manual if I can.
The input data is a global dataset (GFS weather model). x, y are obtained by:
lons2, lats2 = np.meshgrid(lons, lats)
x,y = basem(lons2, lats2)
where lons, lats are:
lons = [0.0, 0.25, 0.5, 0.75, 1.0, ..., 359.75, 360.0]
lats = [-90, -89.75, ..., 89.75, 90]
I seem to have resolved the issue like so:
What I had to do was bound the data to what is able to be seen on basemap. My input dataset was a global dataset, and when I bound it (seen in the white areas below), the contour labels showed up mostly within the map boundaries. This still seems like a bug- my choice of a display region changes the locations of the contour labels in almost every other meteorological graphics program (like GrADS), but I'm going to mark this answered for now.

Matplotlib Rectangle With Color Gradient Fill

I want to draw a rectangle, with a gradient color fill from left to right, at an arbitrary position with arbitrary dimensions in my axes instance (ax1) coordinate system.
My first thought was to create a path patch and somehow set its fill as a color gradient. But according to THIS POST there isn't a way to do that.
Next I tried using a colorbar. I created a second axes instance ax2 using fig.add_axes([left, bottom, width, height]) and added a color bar to that.
ax2 = fig.add_axes([0, 0, width, height/8])
colors = [grad_start_color, grad_end_color]
index = [0.0, 1.0]
cm = LinearSegmentedColormap.from_list('my_colormap', zip(index, colors))
colorbar.ColorbarBase(ax2, cmap=cm, orientation='horizontal')
But the positional parameters passed to fig.add_axes() are in the coordinate system of fig, and don't match up with the coordinate system of ax1.
How can I do this?
I have been asking myself a similar question and spent some time looking for the answer to find in the end that this can quite easily be done by imshow:
from matplotlib import pyplot
pyplot.imshow([[0.,1.], [0.,1.]],
cmap = pyplot.cm.Greens,
interpolation = 'bicubic'
)
It is possible to specify a colormap, what interpolation to use and much more. One additional thing, I find very interesting, is the possibility to specify which part of the colormap to use. This is done by means of vmin and vmax:
pyplot.imshow([[64, 192], [64, 192]],
cmap = pyplot.cm.Greens,
interpolation = 'bicubic',
vmin = 0, vmax = 255
)
Inspired by this example
Additional Note:
I chose X = [[0.,1.], [0.,1.]] to make the gradient change from left to right. By setting the array to something like X = [[0.,0.], [1.,1.]], you get a gradient from top to bottom. In general, it is possible to specify the colour for each corner where in X = [[i00, i01],[i10, i11]], i00, i01, i10 and i11 specify colours for the upper-left, upper-right, lower-left and lower-right corners respectively. Increasing the size of X obviously allows to set colours for more specific points.
did you ever solve this? I wanted the same thing and found the answer using the coordinate mapping from here,
#Map axis to coordinate system
def maptodatacoords(ax, dat_coord):
tr1 = ax.transData.transform(dat_coord)
#create an inverse transversion from display to figure coordinates:
fig = ax.get_figure()
inv = fig.transFigure.inverted()
tr2 = inv.transform(tr1)
#left, bottom, width, height are obtained like this:
datco = [tr2[0,0], tr2[0,1], tr2[1,0]-tr2[0,0],tr2[1,1]-tr2[0,1]]
return datco
#Plot a new axis with a colorbar inside
def crect(ax,x,y,w,h,c,**kwargs):
xa, ya, wa, ha = maptodatacoords(ax, [(x,y),(x+w,y+h)])
fig = ax.get_figure()
axnew = fig.add_axes([xa, ya, wa, ha])
cp = mpl.colorbar.ColorbarBase(axnew, cmap=plt.get_cmap("Reds"),
orientation='vertical',
ticks=[],
**kwargs)
cp.outline.set_linewidth(0.)
plt.sca(ax)
Hopefully this helps anyone in the future who needs similar functionality. I ended up using a grid of patch objects instead.

Matplotlib: Draw a vertical arrow in a log-log plot

I am not able to draw a simple, vertical arrow in the following log-log plot:
#!/usr/bin/python2
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.yscale('log')
plt.xscale('log')
plt.ylim((1e-20,1e-10))
plt.xlim((1e-12,1))
plt.arrow(0.00006666, 1e-20, 0, 1e-8 - 1e-20, length_includes_head=True)
plt.savefig('test.pdf')
It just doesn't show. From the documentation it appears as if all the arguments, like width, height and so on relate to the scale of the axis. This is very counter-intuitive. I tried using twin() of the axisartist package to define an axis on top of mine with limits (0,1), (0,1) to have more control over the arrow's parameters, but I couldn't figure out how to have a completely independent axis on top of the primary one.
Any ideas?
I was looking for an answer to this question, and found a useful answer! You can specify any "mathtext" character (matplotlib's version of LaTeX) as a marker. Try:
plt.plot(x,y, 'ko', marker=r'$\downarrow$', markersize=20)
This will plot a downward pointing, black arrow at position (x,y) that looks good on any plot (even log-log).
See: matplotlib.org/users/mathtext.html#mathtext-tutorial for more symbols you can use.
Subplots approach
After creating the subplots do the following
Align the positions
Use set_axis_off() to turn the axis off (ticks, labels, etc)
Draw the arrow!
So a few lines gets whats you want!
E.g.
#!/usr/bin/python2
import matplotlib.pyplot as plt
hax = plt.subplot(1,2,1)
plt.yscale('log')
plt.xscale('log')
plt.ylim((1e-20,1e-10))
plt.xlim((1e-12,1))
hax2 = plt.subplot(1,2,2)
plt.arrow(0.1, 1, 0, 1, length_includes_head=True)
hax.set_position([0.1, 0.1, 0.8, 0.8])
hax2.set_position([0.1, 0.1, 0.8, 0.8])
hax2.set_axis_off()
plt.savefig('test.pdf')
Rescale data
Alternatively a possibly easier approach, though the axis labels may be tricky, is to rescale the data.
i.e.
import numpy
# Other import commands and data input
plt.plot(numpy.log10(x), numpy.log10(y)))
Not a great solution, but a decent result if you can handle the tick labels!
I know this thread has been dead for a long time now, but I figure posting my solution might be helpful for anyone else trying to figure out how to draw arrows on log-scale plots efficiently.
As an alternative to what others have already posted, you could use a transformation object to input the arrow coordinates not in the scale of the original axes but in the (linear) scale of the "axes coordinates". What I mean by axes coordinates are those that are normalized to [0,1] (horizontal range) by [0,1] (vertical range), where the point (0,0) would be the bottom-left corner and the point (1,1) would be the top-right, and so on. Then you could simply include an arrow by:
plt.arrow(0.1, 0.1, 0.9, 0.9, transform=plot1.transAxes, length_includes_head=True)
This gives an arrow that spans diagonally over 4/5 of the plot's horizontal and vertical range, from the bottom-left to the top-right (where plot1 is the subplot name).
If you want to do this in general, where exact coordinates (x0,y0) and (x1,y1) in the log-space can be specified for the arrow, this is not too difficult if you write two functions fx(x) and fy(y) that transform from the original coordinates to these "axes" coordinates. I've given an example of how the original code posted by the OP could be modified to implement this below (apologies for not including the images the code produces, I don't have the required reputation yet).
#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
# functions fx and fy take log-scale coordinates to 'axes' coordinates
ax = 1E-12 # [ax,bx] is range of horizontal axis
bx = 1E0
def fx(x):
return (np.log(x) - np.log(ax))/(np.log(bx) - np.log(ax))
ay = 1E-20 # [ay,by] is range of vertical axis
by = 1E-10
def fy(y):
return (np.log(y) - np.log(ay))/(np.log(by) - np.log(ay))
plot1 = plt.subplot(111)
plt.xscale('log')
plt.yscale('log')
plt.xlim(ax, bx)
plt.ylim(ay, by)
# transformed coordinates for arrow from (1E-10,1E-18) to (1E-4,1E-16)
x0 = fx(1E-10)
y0 = fy(1E-18)
x1 = fx(1E-4) - fx(1E-10)
y1 = fy(1E-16) - fy(1E-18)
plt.arrow(
x0, y0, x1, y1, # input transformed arrow coordinates
transform = plot1.transAxes, # tell matplotlib to use axes coordinates
facecolor = 'black',
length_includes_head=True
)
plt.grid(True)
plt.savefig('test.pdf')

How to do a scatter plot with empty circles in Python?

In Python, with Matplotlib, how can a scatter plot with empty circles be plotted? The goal is to draw empty circles around some of the colored disks already plotted by scatter(), so as to highlight them, ideally without having to redraw the colored circles.
I tried facecolors=None, to no avail.
From the documentation for scatter:
Optional kwargs control the Collection properties; in particular:
edgecolors:
The string ‘none’ to plot faces with no outlines
facecolors:
The string ‘none’ to plot unfilled outlines
Try the following:
import matplotlib.pyplot as plt
import numpy as np
x = np.random.randn(60)
y = np.random.randn(60)
plt.scatter(x, y, s=80, facecolors='none', edgecolors='r')
plt.show()
Note: For other types of plots see this post on the use of markeredgecolor and markerfacecolor.
Would these work?
plt.scatter(np.random.randn(100), np.random.randn(100), facecolors='none')
or using plot()
plt.plot(np.random.randn(100), np.random.randn(100), 'o', mfc='none')
Here's another way: this adds a circle to the current axes, plot or image or whatever :
from matplotlib.patches import Circle # $matplotlib/patches.py
def circle( xy, radius, color="lightsteelblue", facecolor="none", alpha=1, ax=None ):
""" add a circle to ax= or current axes
"""
# from .../pylab_examples/ellipse_demo.py
e = Circle( xy=xy, radius=radius )
if ax is None:
ax = pl.gca() # ax = subplot( 1,1,1 )
ax.add_artist(e)
e.set_clip_box(ax.bbox)
e.set_edgecolor( color )
e.set_facecolor( facecolor ) # "none" not None
e.set_alpha( alpha )
(The circles in the picture get squashed to ellipses because imshow aspect="auto" ).
In matplotlib 2.0 there is a parameter called fillstyle
which allows better control on the way markers are filled.
In my case I have used it with errorbars but it works for markers in general
http://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.errorbar.html
fillstyle accepts the following values: [‘full’ | ‘left’ | ‘right’ | ‘bottom’ | ‘top’ | ‘none’]
There are two important things to keep in mind when using fillstyle,
1) If mfc is set to any kind of value it will take priority, hence, if you did set fillstyle to 'none' it would not take effect.
So avoid using mfc in conjuntion with fillstyle
2) You might want to control the marker edge width (using markeredgewidth or mew) because if the marker is relatively small and the edge width is thick, the markers will look like filled even though they are not.
Following is an example using errorbars:
myplot.errorbar(x=myXval, y=myYval, yerr=myYerrVal, fmt='o', fillstyle='none', ecolor='blue', mec='blue')
Basend on the example of Gary Kerr and as proposed here one may create empty circles related to specified values with following code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.markers import MarkerStyle
x = np.random.randn(60)
y = np.random.randn(60)
z = np.random.randn(60)
g=plt.scatter(x, y, s=80, c=z)
g.set_facecolor('none')
plt.colorbar()
plt.show()
So I assume you want to highlight some points that fit a certain criteria. You can use Prelude's command to do a second scatter plot of the hightlighted points with an empty circle and a first call to plot all the points. Make sure the s paramter is sufficiently small for the larger empty circles to enclose the smaller filled ones.
The other option is to not use scatter and draw the patches individually using the circle/ellipse command. These are in matplotlib.patches, here is some sample code on how to draw circles rectangles etc.

Categories