Place Colorbar to One Side of GeoAxes Plot - matplotlib

I am generating a irregular gridded plot with a globe projection and am utilizing both xarray and CartoPy to achieve this. The following minimal code produces the first image below, note that I am leaving out calling specific packages and specifically defined cmap/norm options, as they remain outside the bounds of my question:
file = '/path/to/data/griddeddata.tif'
da = rxr.open_rasterio(file)
da = ((da * 1.8) + 32)
ny, nx = len(da['y']), len(da['x'])
x, y = np.meshgrid(da['x'], da['y'])
fig = plt.figure(figsize=(14,8))
ax = plt.subplot(projection=crs.LambertConformal())
ax.set_extent([-75.500000, -72.000000, 40.500000, 43.000000], crs=crs.LambertConformal())
im = ax.pcolormesh(x, y, da.variable.data[0], cmap=cmap, norm=norm)
plt.gcf().set_size_inches((14, 8))
plt.gca().set_position([0, 0, 1, 1])
When I add the following code plt.colorbar(im, ax=ax, pad=0.01, ticks=[-50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120], aspect=40), I get a colorbar that appears inside the map plot itself, whereas I would like this colorbar to be oriented vertically to the right.
I suspect that this has to do with the sharing of a georeferenced axis (map plot) and an unreferenced colorbar axis, though I am unsure how to correct the issue. What additional steps are recommended to take in order to achieve the desired result? Thanks!

I would suggest you create additonal axis besides the plot for the colorbar.
The following code can be adjusted to your need.
Define the position
cbar_pos = [0.90, 0.30, 0.03, 0.45] #axis for colorbar left, bottom, width, height
Create the axis
cbar_ax = fig.add_axes(cbar_pos)
cbar_ax.get_xaxis().set_visible(False)
cbar_ax.yaxis.set_ticks_position('right')
cbar_ax.set_yticklabels([])
cbar_ax.tick_params(size=0)`
Pass the cbar_ax into your colorbar function
plt.colorbar(im, cax=cbar_ax, pad=0.01, ticks=[-50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120], aspect=40)

Related

Setting independent colorbar scale to y-values of plot using matplotlib and proplot

I have a series of histograms that I plot over the top of each other using a for loop:
import matplotlib as plt
import proplot as pplt
cmap = colormap
fig = pplt.figure(figsize=(12, 10), dpi=300)
jj = [ 4, 3, 2, 1, 0]
for j in jj:
plt.fill_between(p[:,j], s[:, j], y2=0, alpha = 0.6, color = colormap[:,4-j], edgecolor=[0,0,0], linewidth=1.5)
The colormap in question is a manually specified list of RGB triplets (from Fabio Crameri's 'lajolla' map):
0.64566 0.823453 0.895061 0.924676 0.957142
0.277907 0.386042 0.526882 0.657688 0.803006
0.259453 0.301045 0.317257 0.331596 0.408285
Each color corresponds to data recorded under different conditions. I want the colorbar to have manually specified ticks corresponding to this variable (e.g. c = 30, 35, 40, 45, 50), but I can't seem to configure the colormap to not just pull the indices of the cmap matrix (0, 1, 2, 3, 4) as the values of the mapped variable. Trying to set the ticks outside of this range just result in them not being shown.
cbar = fig.colorbar(np.transpose(cmap))
cbar.set_ticks([30, 35, 40, 45, 50])
cbar.set_ticklabels([30, 35, 40, 45, 50])
Any idea how I can resolve this?
Tried shifting indices of colormap but this doesn't seem to work.
Trying to get the colorbar with ticks corresponding to the '30, 35, 40, 45, 50' values quoted above.

Is it allowed to draw a matplotlib.patches.Rectangle on a blank figure with matplotlib?

This code adds an rectangle on the Lenna image.
imgurl = 'https://upload.wikimedia.org/wikipedia/en/thumb/7/7d/Lenna_%28test_image%29.png/330px-Lenna_%28test_image%29.png'
f = urllib.request.urlopen(imgurl)
img = plt.imread(f)
fig,ax = plt.subplots(1)
ax.imshow(img)
rect = patches.Rectangle((50, 50), 50, 30, linewidth=1, edgecolor='b', facecolor='w')
ax.add_patch(rect)
plt.show()
while this code cannot do the job on a blank figure
fig,ax = plt.subplots(1)
rect = patches.Rectangle((50, 50), 50, 30, linewidth=1, edgecolor='b', facecolor='r')
ax.add_patch(rect)
plt.show()
why is that?
Running your second part of code I get:
Please notice the x and y axis scale. They each go up to 1.0, but your rectangle patch has coordinates of (50,50). Let's expand the axis limits:
fig,ax = plt.subplots(1)
rect = mpl.patches.Rectangle((50,50), 50, 30, linewidth=1, edgecolor='b', facecolor='r')
ax.add_patch(rect)
ax.set_xlim(left = 0, right = 150)
ax.set_ylim(bottom = 0, top = 150)
plt.show()
And you get:
As you can see, it is plotting the rectangle onto an empty axis, it's just that you couldn't see it the first time around, due to rectangle being out of the view of the axis.

matplotlib asymmetric errorbar showing wrong information

I am trying to plot a grouped barplot with asymmetrical errobars. When the error bars a symmetrical, it's producing the correct chart. However, for the asymmetric version, the length of the error bar is wrong.
Here is a minimally reproducible code:
# test with code from documentation
men_means, men_std = (20, 35, 30, 35, 27), (2, 3, 4, 1, 2)
women_means, women_std = (25, 32, 34, 20, 25), (3, 5, 2, 3, 3)
# dummy dataframe similar to what I will be using
avg = [20, 35, 30, 35, 27]
men_std_l = [19,33,28,34,25]
men_std_u = [22,37,31,39,29]
df = pd.DataFrame({'avg' :avg, 'low':men_std_l, 'high':men_std_u})
ind = np.arange(df.shape[0]) # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind - width/2, df['avg'], width, yerr=[df['low'].values,df['high'].values], label='Men')
rects2 = ax.bar(ind + width/2, women_means, width, yerr=women_std,label='Women')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Scores')
ax.set_title('error bar is wrong for asymmetrical, correct otherwise')
ax.legend()
fig.tight_layout()
plt.show()
I have tried the solutions from Asymmetrical errorbar with pandas (getting ValueError: In safezip, len(args[0])=5 but len(args1)=1) and plotting asymmetric errorbars using matplotlib (getting TypeError: Cannot cast array data from dtype('< U1') to dtype('float64') according to the rule 'safe')
Any help is much appreciated.
Answering my own question as I could not understand from the documentation what those lower and upper bounds of errors were. In the hindsight, it should have been clearer if I were not so used to with ggplot in r.
The matplotlib version of asymmetrical errorbar requires the the values to add and subtract from the height of the bars. It does not want the user to provide the upper and lower values, rather the numbers that should be added and subtracted. Therefore, I needed the following:
xel = df['avg'].values - df['low'].values
xeh = df['high'].values - df['avg'].values

Correlate a Radial Distance to a 2D mgrid on python

I have two 1D arrays. One containing temperature and the other radial distance (for each respective temperature). I want to produce a heat map type plot using this information.
Here is where I'm running into issues:
1. If I create a 2d numpy grid, how do I correlate a radial distance to each one? Say the radial distance is 5 units, how to I find all grid squares that are 5 units from the center?
2. Then how to I correlate to each temperature its respective set of grid points. So say the temperate is 20 degrees at radial distance 5, how do I express this as it is 20 degrees at the following set of x,y grid squares?
Thanks for any assistance.
meshgrid is your friend here. First set up the grid plus x and y coordinate grids (you will have two 5 by 5 arrays):
import numpy as np
x, y = np.meshgrid(np.arange(-2, 3), np.arange(-2, 3))
heatmap = 0 * x # easy way to get shape right
Now, fake some data:
r = np.array((0, 0.5, 1.5, 2.5)) # Your radial distance
T = np.array((100, 90, 70, 40)) # Your temperature at distance
Overlay the data from the inside outward, starting from middle (assuming r is monotonically increasing):
r2 = r**2
xy2 = x**2 + y**2
for ii in range(r.size):
heatmap[np.where(xy2 >= r2[ii])] = T[ii]
That's it. Here's the resulting heatmap:
array([[ 40, 70, 70, 70, 40],
[ 70, 90, 90, 90, 70],
[ 70, 90, 100, 90, 70],
[ 70, 90, 90, 90, 70],
[ 40, 70, 70, 70, 40]])

matplotlib + wxpython not sizing correctly with legend

I have a matplotlib figure embedded in a wxpython frame with a few sizers. Everything works fine until I include a legend but then the sizers don't seem to be working with the legend.
Even when I resize the window by dragging at the corner, the main figure changes size, but only the edge of the legend is ever shown.
That is, note that the legend is not visible in the wxFrame.
import wx
import matplotlib as mpl
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
from random import shuffle
class PlotFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, title="Plot", size=(-1, -1))
self.main_panel = wx.Panel(self, -1)
self.plot_panel = PlotPanel(self.main_panel)
s0 = wx.BoxSizer(wx.VERTICAL)
s0.Add(self.main_panel, 1, wx.EXPAND)
self.SetSizer(s0)
self.s0 = s0
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.main_sizer.Add(self.plot_panel, 1, wx.EXPAND)
self.main_panel.SetSizer(self.main_sizer)
class PlotPanel(wx.Panel):
def __init__(self, parent, id = -1, dpi = None, **kwargs):
wx.Panel.__init__(self, parent, id=id, **kwargs)
self.figure = mpl.figure.Figure(dpi=dpi, figsize=(2,2))
self.canvas = Canvas(self, -1, self.figure)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas,1,wx.EXPAND)
self.SetSizer(sizer)
sizer.SetMinSize((600, 500))
self.sizer = sizer
def test(plot_panel):
axes = plot_panel.figure.gca()
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
return legend
if __name__=="__main__":
app = wx.PySimpleApp()
frame = PlotFrame()
legend = test(frame.plot_panel)
frame.Fit()
print "legend frame pre show: ", legend.get_frame()
frame.Show(True)
print "legend frame post show:", legend.get_frame()
frame.Fit()
app.MainLoop()
Edit:
For a solution to be useful to me, I would like it to look good when the figure is automatically drawn by the program, so adjustment parameters can be hard coded in the program, or, for example, on a window resize event, but not adjusted by hand for each plot. The main things that I expect to change here are: 1) the lengths of the labels (from, say, 1 to 25 characters), 2) the windows size (usually by the user dragging around the corner, and 3) the number of points and lines. (Also, if it matters, eventually, I'll want to have dates on the bottom axis.)
I've put the legend outside of the axes so that it won't cover any data points, and I'd prefer that it stay to the right of the axes.
I'm using Python 2.6.6, wxPython 2.8.12.1, and matplotlib 1.1.0 and am stuck with these for now.
It is re-sizing correctly, you just didn't tell it to do what you want it to do.
The problem is this line:
axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
Pretty sure the bbox_to_anchor kwarg is over-ridding the loc kwarg and you are pegging the bottom left of the legend to (1.05, 0.5) in axes units. If the axes expands to fill your window, the left edge of the legend will always be 5% of the width axes to the right of the right edge of you axes, hence always out of view.
You either need to put your legend someplace else or shrink your axes (in figure fraction).
option 1 move the legend:
axes.legend(bbox_to_anchor=(0.5, 0.5)) #find a better place this is in the center
option 2 move the axes + resize the figure:
axes.set_position([.1, .1, .5, .8]) # units are in figure fraction
set_position
fig = figure()
axes = fig.add_subplot(111)
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
# adjust the figure size (in inches)
fig.set_size_inches(fig.get_size_inches() * np.array([1.5, 1]), forward=True)
# and the axes size (in figure fraction)
# to (more-or-less) preserve the aspect ratio of the original axes
# and show the legend
pos = np.array(axes.get_position().bounds)
pos[2] = .66
axes.set_position(pos)
option 3: automate option 2
fig = figure() # use plt to set this up for demo purposes
axes = fig.add_subplot(111) # add a subplot
# control paramters
left_pad = .05
right_pad = .05
# plot data
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
# set axes labels
axes.set_xlabel('test x')
axes.set_ylabel('test y')
# make the legend
legend = axes.legend(loc='center left', bbox_to_anchor=(1 + left_pad, 0.5))
# function to 'squeeze' the legend into place
def squeeze_legend(event):
fig.tight_layout()
right_frac = 1 - legend.get_window_extent().width / fig.get_window_extent().width - left_pad - right_pad
fig.subplots_adjust(right=right_frac)
fig.canvas.draw()
# call it so the first draw is right
squeeze_legend()
# use the resize event call-back to make sure it works even if the window is re-sized
fig.canvas.mpl_connect('resize_event', squeeze_legend)