Python basemap: adjust legend height to map area - python

When plotting with basemap I can define the region where to plot the map (e.g. by ax1 = fig.add_axes([0.05, 0.05, 0.75, 0.9])) and where to plot the legend (e.g. by ax2 = fig.add_axes([0.85, 0.1, 0.05, 0.8])). Depending on the projection and the geographical region to plot, the map will not cover the area predefined by ax1 (in my case vertical extent is much smaller then 0.9) but my legend's vertical extent is still 0.8.
When I try to get minimum and maximum y figure coordinates by transFigure.inverted to scale the legends vertical extent so that it will have the same vertical extent as the map, I will not get the proper coordinates. They are still 0.05 and 0.95, though they should be larger/smaller due to the plot.
How to get the proper extent of the map in figure coordinates ?
Here is the code:
import matplotlib as mpl
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(6, 6))
ax1 = fig.add_axes([0.05, 0.05, 0.75, 0.9])
# mp
lon0 = 11.35
lat0 = 50.0
lon1 = 15.5
lat1 = 50.0
lon2 = 15.5
lat2 = 52.0
lon3 = 11.35
lat3 = 52.0
lat_c = (lat0 + lat2) / 2.0
lon_c = (lon0 + lon2) / 2.0
map = Basemap(projection='merc', lat_0 = lat_c, lon_0 = lon_c,
resolution = 'h', area_thresh = 0.1,
llcrnrlon=lon0, llcrnrlat=lat0,
urcrnrlon=lon2, urcrnrlat=lat2)
map.drawcountries(zorder=10)
cmap = mpl.cm.get_cmap('jet')
# get min and max value for legend test
min_overall = 0.0
max_overall = 10.0
# plot blue dots for predefined map edges
x0_data, y0_data = map(lon0,lat0) # lower left
map.plot(x0_data, y0_data, 'bo', markersize=24)
x1_data, y1_data = map(lon1,lat1) # lower right
map.plot(x1_data, y1_data, 'bo', markersize=24)
x2_data, y2_data = map(lon2,lat2) # upper right
map.plot(x2_data, y2_data, 'bo', markersize=24)
x3_data, y3_data = map(lon3,lat3) # upper left
map.plot(x3_data, y3_data, 'bo', markersize=24)
# convert data to display coordinates
x0_y0_display = ax1.transData.transform((x0_data,y0_data))
inv_ax1_transData = ax1.transData.inverted()
x0_y0_data_test = inv_ax1_transData.transform(x0_y0_display)
x3_y3_display = ax1.transData.transform((x3_data,y3_data))
# convert display to figure coordinates
inv_fig_transFigure = fig.transFigure.inverted()
x0_y0_figure = inv_fig_transFigure.transform(x0_y0_display)
x3_y3_figure = inv_fig_transFigure.transform(x3_y3_display)
print(x0_y0_figure)
print(x3_y3_figure)
# convert data to display coordinates
x0_y0_display = ax1.transData.transform((x0_data,y0_data))
inv_ax1_transData = ax1.transData.inverted()
x0_y0_data_test = inv_ax1_transData.transform(x0_y0_display)
x3_y3_display = ax1.transData.transform((x3_data,y3_data))
print(x0_y0_figure)
print(x3_y3_figure)
# set colorbar
cmap = mpl.cm.get_cmap('jet')
ax2 = fig.add_axes([0.85, 0.1, 0.05, 0.8])
norm = mpl.colors.Normalize(vmin=min_overall, vmax=max_overall)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap,
norm=norm,
orientation='vertical')
cb1.set_label('Test')
plt.show()

OK, seems the solution seems to be quite simple:
Everything that is needed is stored in the Bbox for the first axis (ax1):
So adding to the code:
bb = ax1.get_position() # get Bbox from ax1
will give the Bbox instance for ax1, containing the edges for what is really needed to plot the map in figure coordinates
values_bb = bb.get_points()
will provide the coordinates for the Bbox edges
ax2 = fig.add_axes([0.85, values_bb[0,1], 0.05, values_bb[1,1]-values_bb[0,1]])
will scale the axis for the legend to the proper vertical extent

Related

What is the best way to work with no data involved with 2D matplotlib plots?

I have some 2D matplotlib plots that have an area of no data below a certain value, as shown below (below the ~1.2 km line on the y-axis):
It seems that matplotlib automatically colors the area of no data with the color used for 0 on the colorbar scale. I feel like I've tried everything - masking the data, using set_under, etc. ....but none of it seems to be working. Here is where I'm at currently:
temperatures = ds_object['temperature'].values
time = ds_object['time'].values
inp_conc = ds_object['n_inp_stp'].values
vmin = 0.0
vmax = np.amax(inp_conc)
bad_color = 'lightgray'
cmap = cm.jet
cmap.set_under(color=bad_color)
mesh = display.fig.axes[0].pcolormesh(xrng,temperatures,inp_conc.transpose(),cmap=cmap,
vmin=vmin, vmax=vmax)
fig = display.fig
ax = fig.axes[0]
ax.set_ylabel('Freezing Temperature (degC)')
ax.set_title(f"{datastream} {ds_object['n_inp_stp'].attrs['long_name']} on {date}")
myFmt = mdates.DateFormatter('%H:%M')
ax.xaxis.set_major_formatter(myFmt)
# Give the colorbar it's own axis so the 2D plots line up with 1D
box = ax.get_position()
pad, width = 0.01, 0.01
cax = fig.add_axes([box.xmax + pad, box.ymin, width, box.height])
cbar = plt.colorbar(mesh, cax=cax)
#get n_inp_stp min and max for colorbar
n_inp_stp = ds_object['n_inp_stp'].values
maximum = np.nanmax(n_inp_stp)
minimum = np.nanmin(n_inp_stp)
mesh.set_clim(minimum,maximum) # set colorbar range
cbar.ax.set_ylabel('Number of INP per L of air at STP (count/L)', rotation=270, fontsize=8, labelpad=8)
cbar.ax.tick_params(labelsize=6)
cbar.ax.set_yscale('log')
But for some reason, using the set_color hasn't been working. Does anyone have any idea as to what I'm doing wrong?

How to create a horizontal histogram the other way around?

The gallery of matplotlib has a 2D scatter plot with two adjacent histograms for the marginal distribution of x and y values at the top and right, respectively. I want to modify that to show the histogram of y values on the left (instead of the right) but also oriented towards the scatter plot.
All I managed so far was to merely move it from the right to the left (see below), but not re-orientate it towards the scatter plot. How can I achieve that?
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
# some random data
x = np.random.randn(1000)
y = np.random.randn(1000)
def scatter_hist(x, y, ax, ax_histx, ax_histy):
# no labels
ax_histx.tick_params(axis="x", labelbottom=False)
ax_histy.tick_params(axis="y", labelleft=True,labelright=False)
ax.tick_params(axis="y", left=False,labelleft=False,right=True,labelright=True)
# the scatter plot:
ax.scatter(x, y)
# now determine nice limits by hand:
binwidth = 0.25
xymax = max(np.max(np.abs(x)), np.max(np.abs(y)))
lim = (int(xymax/binwidth) + 1) * binwidth
bins = np.arange(-lim, lim + binwidth, binwidth)
ax_histx.hist(x, bins=bins)
ax_histy.hist(y, bins=bins, orientation='horizontal')
# definitions for the axes
left, width = 0.3, 0.65
bottom, height = 0.1, 0.65
spacing = 0.005
rect_scatter = [left, bottom, width, height]
rect_histx = [left, bottom + height + spacing, width, 0.2]
rect_histy = [left-spacing-0.2, bottom, 0.2, height]
# start with a square Figure
fig = plt.figure(figsize=(8, 8))
ax = fig.add_axes(rect_scatter)
ax_histx = fig.add_axes(rect_histx, sharex=ax)
ax_histy = fig.add_axes(rect_histy, sharey=ax)
# use the previously defined function
scatter_hist(x, y, ax, ax_histx, ax_histy)
plt.show()
and here the result:
This can be achieved by setting the y-axis limit in the opposite direction.
ax_histy.hist(y, bins=bins, orientation='horizontal')
ax_histy.set_xlim(100,0) # add

How to convert values into color information for circle in matplotlib?

I am trying to convert values into color information in Colormap. I have z values and would like to map z value into circle shape. For example, I have a coordinate (xi,yi) and would like to draw a circle centering this coordinate with z value mapped into colormap. However, it is not drawing any circle. Below is my code.
r = 100
color_map = cm.Oranges
norm = Normalize(vmin=1, vmax=100)
rgba = color_map(norm(zi))
CS = plt.Circle((xi, yi), r, color=rgba[0])
You have couple of issues actually. You didn't create any axes and you are using color map incorrectly. So, create your circles and note how color_map is used. Then add it to a created axes object.
import matplotlib.pyplot as plt
r = 100
color_map = plt.get_cmap("Oranges")
circle1 = plt.Circle((0, 0), 0.2, color=color_map(0.66))
circle2 = plt.Circle((0.5, 0.5), 0.2, color=color_map(0.45))
circle3 = plt.Circle((1, 1), 0.2, color=color_map(0.2), clip_on=True)
fig, ax = plt.subplots()
ax.add_artist(circle1)
ax.add_artist(circle2)
ax.add_artist(circle3)

Custom scale for radial contour plot in matplotlib

I have a sample script to generate a polar contour plot in matplotlib:
import os
import math
import numpy as np
import matplotlib.pyplot as plt
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 random
# ------------------------------------ #
def setup_arc_radial_axes(fig, rect, angle_ticks, radius_ticks, min_rad, max_rad):
tr = PolarAxes.PolarTransform()
pi = np.pi
grid_locator1 = FixedLocator([v for v, s in angle_ticks])
tick_formatter1 = DictFormatter(dict(angle_ticks))
grid_locator2 = FixedLocator([a for a, b in radius_ticks])
tick_formatter2 = DictFormatter(dict(radius_ticks))
grid_helper = floating_axes.GridHelperCurveLinear(tr,
extremes=((370.0*(pi/180.0)), (170.0*(pi/180.0)), max_rad, min_rad),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=tick_formatter2,
)
ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper)
fig.add_subplot(ax1)
ax1.grid(True)
# create a parasite axes whose transData in RA, cz
aux_ax = ax1.get_aux_axes(tr)
aux_ax.patch = ax1.patch
ax1.patch.zorder=0.9
#ax1.axis["left"].set_ticklabel_direction("+")
return ax1, aux_ax
# ------------------------------------ #
# write angle values to the plotting array
angles = []
for mic_num in range(38):
angle = float(mic_num)*(180.0/36.0)*(math.pi/180.0)+math.pi
angles.append(angle)
# ------------------------------------ #
### these are merely the ticks that appear on the plot axis
### these don't actually get plotted
angle_ticks = range(0,190,10)
angle_ticks_rads = [a*math.pi/180.0 for a in angle_ticks]
angle_ticks_rads_plus_offset = [a+math.pi for a in angle_ticks_rads]
angle_ticks_for_plot = []
for i in range(len(angle_ticks)):
angle_ticks_for_plot.append((angle_ticks_rads_plus_offset[i],r"$"+str(angle_ticks[i])+"$"))
# ------------------------------------ #
scale = 1.0
aspect = 1.50
height = 8.0
fig = plt.figure(1, figsize=(height*aspect*scale, height*scale))
fig.subplots_adjust(wspace=0.3, left=0.05, right=0.95, top=0.84)
fig.subplots_adjust()
plot_real_min = 30.0
plot_real_max = 100.0
plot_fake_min = 0.0
plot_fake_max = 5000.0
rad_tick_increment = 500.0
radius_ticks = []
for i in range(int(plot_fake_min),int(plot_fake_max)+int(rad_tick_increment),int(rad_tick_increment)):
plot_fake_val = ((i-plot_fake_min)/(plot_fake_max-plot_fake_min))*(plot_real_max-plot_real_min)+plot_real_min
radius_ticks.append((plot_fake_val, r"$"+str(i)+"$"))
ax2, aux_ax2 = setup_arc_radial_axes(fig, 111, angle_ticks_for_plot, radius_ticks, plot_real_min, plot_real_max)
azimuths = np.radians(np.linspace(0, 180, 91))
azimuths_adjusted = [ (x + math.pi) for x in azimuths ]
zeniths = np.arange(0, 5050, 50)
zeniths_adjusted = [((x-plot_fake_min)/(plot_fake_max-plot_fake_min))*(plot_real_max-plot_real_min)+plot_real_min for x in zeniths]
r, theta = np.meshgrid(zeniths_adjusted, azimuths_adjusted)
values = 90.0+5.0*np.random.random((len(azimuths), len(zeniths)))
aux_ax2.contourf(theta, r, values)
cbar = plt.colorbar(aux_ax2.contourf(theta, r, values), orientation='vertical')
cbar.ax.set_ylabel('Contour Value [Unit]', fontsize = 16)
plt.suptitle('Plot Title ', fontsize = 24, weight="bold")
plt.legend(loc=3,prop={'size':20})
plt.xlabel('Angle [deg]', fontsize=20, weight="bold")
plt.ylabel('Frequency [Hz]', fontsize=20, weight="bold")
# plt.show()
plt.savefig('test.png', dpi=100)
plt.close()
This script will generate a plot that looks something like:
My question is how can I plot with an alternate color bar scale? Is it possible to define a custom scale?
Something like a blue-white-red scale where deltas around a central value can easily be shown would be the best, something like:
You can create a custom scale, but matplotlib already has what you want. All you have to do is add an argument to contourf:
aux_ax2.contourf(theta, r, values, cmap = 'bwr')
If you don't like bwr, coolwarm and seismic are also blue to red. If you need to reverse the scale, just add _r to the colormap name. You can find more colormaps here: http://matplotlib.org/examples/color/colormaps_reference.html
I can't run your code, but I think you could solve your problem this way:
from matplotlib import pyplot as plt
import matplotlib as mpl
f = plt.figure(figsize=(5,10))
ax = f.add_axes([0.01, 0.01, 0.4, 0.95])
#here we create custom colors
cmap = mpl.colors.LinearSegmentedColormap.from_list(name='Some Data',colors=['b', 'w','w', 'r'])
cb = mpl.colorbar.ColorbarBase(ax, cmap=cmap, orientation='vertical')
cb.set_label('Some Data')
plt.show()
And if linear way is not what you are looking for here is some other types:
http://matplotlib.org/api/colors_api.html#module-matplotlib.colors

Issue with polar plot

I am trying to plot some values on a polar plot. The code is as follows:
def plot_values(data):
bridge = CvBridge()
fig = figure(figsize=(4,4))
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
w,h = fig.canvas.get_width_height()
plot([0,0],[0,1], color=(0.5,0.5,0.5), linewidth = 3)
plot([1*pi/2,1*pi/2],[0,1], color=(0.5,0.5,0.5), linewidth = 3)
plot([2*pi/2,2*pi/2],[0,1], color=(0.5,0.5,0.5), linewidth = 3)
plot([3*pi/2,3*pi/2],[0,1], color=(0.5,0.5,0.5), linewidth = 3)
HAS= len(HAS_names)*4
radii=np.repeat(data,HAS)
width = 2*np.pi/HAS
for i in range(0, len(HAS_names)):
radii[i] = AUC_value[HAS_names[i]]/HAS_frames[i] #assignment done through lists
theta = np.arange(0.0, 2*np.pi, 2*np.pi/HAS)
bars = ax.bar(theta, radii, width=width, bottom=0.0)
xT=PLT.xticks()[0]
xL=['','HAS','','HAD','','TAS','','TAD']
PLT.xticks(xT, xL)
for r,bar in zip(radii, bars):
bar.set_facecolor( cm.jet(r/10.))
bar.set_alpha(0.5)
fig.canvas.draw()
canvas = PLT.get_current_fig_manager().canvas
canvas.draw()
I get the output as follows:
I am trying to plot the values occurring in the quadrant labeled HAS and rest on the region need to blank. I tried removing radii=np.repeat(data,HAS) and declare it as global variable within the function itself, but I needed to assign some value to radii initially before the for loop.
Any help is appreciated.

Categories

Resources